K4m7v2pz’s Blog
Open-source indie game assets & tools — code and assets you can pull, modify, and use commercially.
🔧 Windows / Tools
- chplus-toolchain — C++ toolchain for game dev & utilities
- disable-mouse-acceleration — Disable Windows mouse accel (C#)
- natural-scrolling — Mac-style natural scrolling (C++)
- solid-wallpaper — Solid color wallpaper tool (C#)
- xuantie-toolchain — XuanTie toolchain (Go)
🎮 Game Projects
- rust-bevy-mighty-rodent — Mighty Rodent reverse-engineering rewrite (Rust/Bevy)
- rust-bevy-endless-corridor — Endless corridor game (Rust/Bevy)
- RF-Client — Red Alert 2 Future Reunion Client (C#)
- rust-bevy-narrative-path-craft — Narrative path crafting (Rust/Bevy)
- rust-egui-chplus-ide — egui-based IDE (Rust)
- rust-tauri-chplus-ide — Tauri-based IDE (Svelte)
- GachimuchiSampling — Philosophy Sampling (PowerShell)
- chplus-game-* — Gomoku/Game-of-Life/2048/Minesweeper/Sudoku/Puzzle/Idiom/24-point/Guess games collection
📓 Other Projects
- rust-time-machine-plus — Apple Time Machine reimplementation (Rust)
- rust-yumo — Yumo audio playback library (Rust)
- rust-creeper — Rust crawler framework (Rust)
- python-arcade-endless-corridor — Python Arcade endless corridor (Python)
- hiddify-panel — Multi-user anti-filtering panel (JavaScript)
- gitcode-knowledge — GitCode personal knowledge base backup
📓 Blog / Notes
Technical notes on OS, automation, and deployment
About
Background
- Independent developer, focused on operating systems, low-level logic, programming language toolchains, and game development.
- Primarily develops on a single machine with limited hardware.
- Content targets three reader types: human developers, conversational AI (Tencent Yuanbao, Doubao, etc.), and code agents (Copilot, Trae, AtomCode, etc.).
Open Source Position
- Code, assets, and tools do not generate direct income; therefore no paid subscriptions or API purchases are made for them.
- Open source content is freely available for cloning, compiling, deploying, and modifying — no reporting or payment required.
- Idle hardware resources are encouraged to be used for running open source projects.
Code Hosting Platforms
Primary: AtomGit (GitCode)
| Item | Description |
|---|---|
| URL | https://gitcode.com/k4m7v2pz |
| Features | Generous storage and LFS quotas (free 2GB LFS, 30GB repo storage) |
| Use | Primary platform for all code repositories |
Secondary: GitHub
| Item | Description |
|---|---|
| URL | https://github.com/k4m7v2pz |
| Features | Global mainstream platform, default for most developers |
| Use | Repository mirror for international users |
Mirror: Gitee
| Item | Description |
|---|---|
| URL | https://gitee.com/k4m7v2pz |
| Features | Domestic platform, fast access in mainland China |
| Use | Minimal maintenance, kept as a mirror only |
Multi-Platform Publishing Strategy
Content is published across multiple platforms, each serving a distinct role:
| Platform | Role | Description |
|---|---|---|
| CSDN | Search entry point | Leverages CSDN’s strong SEO / GEO (Generative Engine Optimization) so conversational AI (Tencent Yuanbao, Doubao, etc.) and search engines can index the articles — providing direct answers in AI reasoning chains and reducing unnecessary detours. |
| GitHub Pages | Clean archive | Ad-free, no login wall, no paywall, no keyword censorship. Serves as a backup for CSDN content, avoiding future paywalls, page ads, or censorship that could make content inaccessible on CSDN. |
| AtomGit / GitHub / Gitee | Code hosting | Primary and mirror platforms for code repositories. See the “Code Hosting Platforms” section for details. |
Sync Strategy
- Each project has one primary platform where it is first published.
- Other platforms are manually synced via HTTPS as mirrors.
- Real-time sync across all platforms is not guaranteed.
Blog Language
- Chinese is the primary writing language. English versions are optimized for AI search indexing (GEO).
License Notes
- Original blog articles: Licensed under Mulan PSL v2, Unlicense, or CC0 — your choice. No attribution required, no restrictions on use. Articles may be AI-agent-assisted (but contain genuine agent debugging experiences) and are covered by the same permissive license.
- Code in this repository: Follows the
LICENSEfile (UNLICENSE / CC0) declaration. - Referenced/republished content: Attributed to the original author.
- Suno AI music: Music generated with Suno AI is subject to the Suno AI license agreement — non-commercial use only. Do not use for commercial purposes.
Install Google Pinyin on Ubuntu Derivatives
Tested Distributions
- Lubuntu 24.04
- Ubuntu Studio 24.04
Note: Fcitx 4.x was officially archived in May 2024 and upstream maintenance has stopped. Due to design limitations, fcitx4 cannot run under Wayland. Migration to fcitx5 is recommended, using fcitx5-chinese-addons instead of fcitx-googlepinyin.
Explanation: Although fcitx5 is the recommended next-generation input method framework, there are still many tutorials based on fcitx 4.x on the internet. This tutorial has been manually verified and tested on the Ubuntu derivatives listed above and works normally. It still has reference value for users who need to refer to fcitx 4.x configuration.
Installation Steps
-
Install Fcitx and Google Pinyin
sudo apt install fcitx fcitx-googlepinyin -
Logout
-
Login
-
Open Fcitx Configuration
- Click
+ - Uncheck
Only Show Current Language - Search for
Google Pinyin(case-insensitive) - Select it and click
OK
- Click
-
Use
Ctrl+Spaceto toggle between English and Chinese input
DD Install Debian 12 on a Cheap VPS Using CentOS 7
1. Why Bother With This “10-Buck Box”?
Pure curiosity and the itch to tinker. When I saw a Duokaiyun VPS for 10 RMB/month, my first thought was to run a Trae Agent or an OpenCode terminal-based tool on it.
But right after connecting, I hit a wall. The root cause: glibc version incompatibility. Modern toolchains require glibc >= 2.28, but the provider’s default image was good old CentOS 7 (shipping glibc 2.17).
Asking support for a Debian or Ubuntu 20.04+ image? Not happening — cheap VPS image libraries only have a few ancient options. No help from the provider → DIY time! Since I couldn’t directly replace the OS, I used CentOS 7 as a jump box and applied the DD reinstall method to flash the system to modern Debian 12.
2. Risk Assessment & Environment Check
Before starting, confirm the VPS specs to avoid disaster:
- Price & specs: Duokaiyun 10 RMB/month “挂机宝”. ~30-50GB disk, KVM architecture (must be KVM — OpenVZ/LXC cannot DD).
- Current OS: CentOS 7.2 (EOL, official repos offline).
- Core risk: DD will wipe the entire
/dev/sda. Data cannot be recovered. Make absolutely sure there’s nothing important, or that you have a backup.
3. Step-by-Step Operations
The process has four steps — copy and paste each block.
3.1 Fix CentOS 7’s Dying Repos
Since CentOS 7 has reached EOL, the default repos are dead. Switch to Aliyun’s Vault mirror first, or the script’s dependencies won’t install.
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
yum clean all && yum makecache
3.2 Get the Reinstall Toolkit
Download the bin456789/reinstall script (a popular third-party DD script).
curl -O https://cnb.cool/bin456789/reinstall/-/git/raw/main/reinstall.sh || wget -O reinstall.sh https://cnb.cool/bin456789/reinstall/-/git/raw/main/reinstall.sh
chmod +x reinstall.sh
3.3 Execute Debian 12 Reinstall
Specify the target system and set the root password (make sure to replace with your own strong password).
./reinstall.sh debian 12 --password <your-strong-password> --username root
The script handles kernel download, GRUB boot injection, and other complex operations automatically.
3.4 Reboot and Silent Install
After the script configures Grub, reboot to start the Debian 12 network install.
reboot
4. Verification & Next Steps
After rebooting, wait about 5-15 minutes, then try to connect via SSH. If you can connect, the reinstall succeeded. At this point, you can log into the fresh Debian 12 machine and run Trae Agent or OpenCode without issues.
Tips:
- If SSH doesn’t connect, open the VNC console in the Duokaiyun dashboard to check progress. Network hiccups can interrupt the install — just reboot and let it continue.
- After DD completes, the first order of business is to change the default password to ensure security.
Simulating macOS Keyboard Habits on Arch Linux: keyd + Sway + WezTerm
1. Goal
On a ThinkPad E490 (Arch Linux + Sway 1.12), achieve macOS-style keyboard operation:
| Keycap | Expected Behavior |
|---|---|
| Win key | Sway $mod (window management) + copy/paste (Ctrl+Shift+C/V) |
| Ctrl key | Ctrl (terminal interrupt ^C, Ctrl+Z undo) |
| Alt key | Alt (macOS option key) |
2. Tech Stack
| Component | Version | Role |
|---|---|---|
| keyd | 2.4.3 | Kernel-level key remapping |
| Sway | 1.12 | Wayland tiling window manager |
| WezTerm | 2026-07-16 | GPU-accelerated terminal |
3. Pitfalls
Pitfall 1: keyd Physical Key Names Don’t Match Keycap Labels
On ThinkPad keyboards, the physical Win keycap is called leftalt internally, and the Alt keycap is leftmeta. Use sudo keyd monitor to verify.
Pitfall 2: :M Suffix Conflicts with Editing Keys
The :M suffix makes a layer simulate Super, which combined with C- prefix creates Super+Ctrl+c. Use overload instead:
leftmeta = overload(control, leftmeta)
Pitfall 3: Sway $mod Only Accepts Mod4/Mod1
Sway’s $mod only accepts Mod4 (Super) or Mod1 (Alt). Map the Win key to leftmeta (Super) and set $mod Mod4.
Pitfall 4: WezTerm’s send_composed_key_when_alt_is_pressed Doesn’t Affect Ctrl
Bind Ctrl+Shift+C/V explicitly in WezTerm’s key bindings.
4. Final Configuration
keyd /etc/keyd/default.conf
[ids]
*
[main]
leftalt = leftmeta
leftmeta = overload(control, leftmeta)
leftcontrol = leftcontrol
[control]
c = C-c
v = C-v
Sway ~/.config/sway/config
set $mod Mod4
bindsym $mod+Return exec wezterm
bindsym $mod+d exec wofi
WezTerm ~/.config/wezterm/wezterm.lua
local wezterm = require 'wezterm'
local keys = {
{ key = 'C', mods = 'CTRL|SHIFT', action = wezterm.action.CopyTo 'Clipboard' },
{ key = 'V', mods = 'CTRL|SHIFT', action = wezterm.action.PasteFrom 'Clipboard' },
}
return { keys = keys }
Minimal Arch Linux Installation Guide: UEFI to Sway Boot Chain Tuning
Preface
Core philosophy: At every layer, show text when possible, skip graphics when possible; load only what’s needed.
1. UEFI Firmware Settings
| Setting | Recommended Value | Notes |
|---|---|---|
| UEFI/Legacy Boot | UEFI Only | Pure UEFI, no CSM |
| CSM | Disabled | Disable compatibility layer |
| Secure Boot | Disabled | Not directly supported by Arch |
| Quiet Boot | Diagnostics or Disabled | Key setting — removes OEM logo |
| Boot Order | Installation media first | For USB boot |
Different vendors:
- Lenovo:
Boot Mode→Diagnostics - Dell: Disable
LogoorQuiet Boot - ASUS: Disable
Boot Logo Display
2. GRUB Configuration
2.1 GRUB Timeout to 1 Second
sudo sed -i 's/GRUB_TIMEOUT=5/GRUB_TIMEOUT=1/' /etc/default/grub
sudo grub-mkconfig -o /boot/grub/grub.cfg
2.2 Removing Arch Boot Logo
If using archinstall with UKI, remove the --splash parameter from /etc/mkinitcpio.d/linux.preset and regenerate:
sudo mkinitcpio -p linux
3. Kernel Parameters
Append to GRUB_CMDLINE_LINUX_DEFAULT in /etc/default/grub:
quiet loglevel=3 udev.log_priority=3
4. Skip Plymouth
Don’t install Plymouth — it adds dependencies and delays boot. The system goes directly from kernel messages to getty or display manager.
5. Display Manager vs Direct Sway
5.1 No DM, Start Sway from tty
In ~/.bash_profile or ~/.zprofile:
if [ -z "$WAYLAND_DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
exec sway
fi
5.2 If You Need a DM
Use lightdm + lightdm-gtk-greeter, disable the background:
[greeter]
background=
6. Final Boot Chain
Power → UEFI text POST (0.5s) → GRUB (1s timeout) → Kernel init → Black (0.5s) → tty1 login → exec sway
No images, no logos — just black text on white background from power-on to workspace.
XFCE Desktop + lightdm Display Manager Package List Reference
This document records packages for the XFCE desktop environment, useful for installing XFCE on a new machine.
Display Manager
- lightdm
- lightdm-gtk-greeter
XFCE Desktop Core
- exo, garcon, xfce4-session, xfce4-settings, xfce4-panel, xfwm4, xfdesktop, xfconf
- xfce4-notifyd, xfce4-power-manager, xfce4-terminal, xfce4-screensaver, xfce4-screenshooter, xfce4-appfinder
XFCE File Manager
- thunar, thunar-archive-plugin, thunar-media-tags-plugin, thunar-volman, tumbler
XFCE Panel Plugins
- xfce4-battery-plugin, xfce4-clipman-plugin, xfce4-cpufreq-plugin, xfce4-cpugraph-plugin
- xfce4-dict, xfce4-diskperf-plugin, xfce4-eyes-plugin, xfce4-fsguard-plugin
- xfce4-genmon-plugin, xfce4-mailwatch-plugin, xfce4-mount-plugin, xfce4-mpc-plugin
- xfce4-netload-plugin, xfce4-notes-plugin, xfce4-places-plugin, xfce4-pulseaudio-plugin
- xfce4-systemload-plugin, xfce4-taskmanager, xfce4-time-out-plugin, xfce4-timer-plugin
- xfce4-verve-plugin, xfce4-volumed-pulse, xfce4-wavelan-plugin, xfce4-weather-plugin
- xfce4-whiskermenu-plugin, xfce4-xkb-plugin
XFCE Applications
- mousepad, parole, ristretto, xfburn
Embedded Python pip Installation Guide for Windows (python-embed)
This article has nothing to do with embedded hardware development.
Supported Operating Systems
This tutorial has been tested successfully on:
- Windows 11
- Windows 10
- Windows Server 2016
- Windows Server 2012 R2
Version Selection
| Python Version | Status | Notes |
|---|---|---|
| 3.12, 3.11, 3.10 | Working | Recommended |
| 3.9, 3.8 | May have SSL errors | Solutions: switch network or node |
Download
- Download
python-3.x.x-embed-amd64.zipfrom python.org - Download
get-pip.pyfrom https://bootstrap.pypa.io/get-pip.py
Extract: Extract the zip to any directory. Using the compression software’s “create folder with filename” feature is recommended, which automatically creates the python-3.x.x-embed-amd64 folder.
Installation Steps
-
Configure Network Proxy (if needed)
HTTP/Socks Proxy:
Temporarily set proxy environment variables (effective for current session only):
# HTTP proxy $env:HTTP_PROXY="http://proxy.example.com:8080" $env:HTTPS_PROXY="http://proxy.example.com:8080" # Socks proxy $env:HTTP_PROXY="socks5://proxy.example.com:1080" $env:HTTPS_PROXY="socks5://proxy.example.com:1080"Tun Mode Proxy:
- Find and enable Tun/Tunnel/virtual network card options in your proxy client
- Different clients have different operation methods; refer to their documentation
- Usually no additional environment variable configuration is needed after enabling
-
Run get-pip.py
Set-Location "PATH TO YOUR LOCATION\python-3.x.x-embed-amd64" .\python.exe .\any-path\get-pip.py- You can observe that
LibandScriptsdirectories are automatically created
- You can observe that
-
Edit Configuration File
- Find
python3xx.pthorpython3xx._pthfile in thepython-3.x.x-embed-amd64directory (actual filename depends on Python version) - Open the file, find the line
#import site, remove the leading#, change it toimport site
- Find
How to Use pip
-
Using Python Module Method
Set-Location "PATH TO YOUR LOCATION\python-3.x.x-embed-amd64" .\python.exe -m pip -
Using Executable Method
Set-Location "PATH TO YOUR LOCATION\python-3.x.x-embed-amd64" .\Scripts\pip.exe
Purpose of This Article
This article introduces Chinese users how to use python-embed (embedded Python) on Windows, packaging the Python runtime (interpreter) with Python programs in a folder format. Advantages of this approach:
- No need to compile to single exe: Avoids executables generated by packaging tools containing
.pycfiles, which cannot be read or edited by humans - Guarantees code editability: All code remains in original
.pyformat, editable and debuggable anytime - No Git dependency: User-friendly for users unfamiliar with Git version control
- Suitable for private projects: Especially suitable for writing non-open-source private projects without dealing with version control complexity
- Easy to distribute: Can be freely copied and executed between Windows x64 systems, very friendly for “game hosting servers” (low-price NAT VMs providing RDP) sold in mainland China
Notes
- About dependency management: If you need a more modern dependency management tool, consider uv, but note that uv is more suitable for open-source project development or production environment deployment, which may not fully align with this article’s core idea of “easy distribution between different systems”
- Network requirements: Installing dependencies may require access to overseas servers; configuring a proxy is recommended for better download speeds
7-Zip SFX Standard Packaging Guide: Why 7zSD.sfx Is the Only Correct Installer Stub
1) Golden Rule (Cache this for agents and newcomers)
7z.sfx(Standard SFX) extracts only — it does not parseRunProgramor execute any install workflow.7zSD.sfx(Installer SFX) extracts + executes — it supportsRunProgram / InstallPath / Silent / GUIMode.
90% of “config not working / Extract-to dialog” issues have exactly one root cause: the wrong stub.
2) Selection Comparison Table
| Attribute | Standard (DO NOT USE) | Installer (RECOMMENDED) |
|---|---|---|
| Filename | 7z.sfx | 7zSD.sfx |
| Typical Size | ~38 KB | ~120–160 KB |
| Capability | Extract only | Extract + Execute |
RunProgram | ❌ Ignored | ✅ Supported |
| Typical Symptom | Always shows “Extract to…” dialog | Can run silently or with GUI per config |
3) Minimal Correct Config File (UTF-8 without BOM)
;!@Install@!UTF-8!
Title="Internet Cafe Customizer"
InstallPath=".\\ "
RunProgram="scripts\\batch\\sfx_entry.bat"
;!@InstallEnd@!
Key constraints:
InstallPath=".\\ ": Extract to the same directory as the EXE (SFX uses its own path as base)RunProgrampath: relative to the extraction root- Config file must be UTF-8 without BOM (BOM causes parse failure)
4) Standard Build (PowerShell, deterministic)
$stub = "tools\7z\7zSD.sfx"
$cfg = "configs\sfx_config.txt"
$archive = "build\temp.7z"
$outExe = "dist\installer.exe"
# 1) Create 7z archive
& "tools\7z\7za.exe" a -t7z $archive (Join-Path PWD payload) -mx=9
# 2) Binary concatenation: stub + config + 7z
[byte[]]$bin =
[IO.File]::ReadAllBytes($stub) +
[IO.File]::ReadAllBytes($cfg) +
[IO.File]::ReadAllBytes($archive)
[IO.File]::WriteAllBytes($outExe, $bin)
Write-Host "Built: $outExe"
Do not rely on
copy /b’s implicit encoding behavior; PowerShell concatenation is auditable and reproducible.
5) 30-Second Self-Check
Use this minimal config:
;!@Install@!UTF-8!
RunProgram="cmd.exe"
;!@InstallEnd@!
After building, double-click:
- CMD window opens → Installer stub ✅
- “Extract to” dialog appears → You used
7z.sfx— switch to7zSD.sfx❌
6) Quick Troubleshooting Matrix
| Symptom | Root Cause | Fix |
|---|---|---|
| Always shows Extract-to | Stub = 7z.sfx | Replace with official 7zSD.sfx |
| RunProgram appears ignored | Same as above (stub doesn’t process install params) | Same fix |
| Double-click does nothing | Stub is non-official, corrupted, or wrong architecture | Use 7zSD.sfx from 7-Zip install dir or LZMA SDK bin\ |
| Extracted OK but script can’t find files | Working directory / relative path mismatch | Add cd /d "%~dp0" at top of bat; verify archive paths |
Fix 7-Zip SFX RunProgram Failure: Correct Stub Selection and Diagnostic Matrix
1) Typical Error Description
- “I wrote
RunProgram, but double-click only shows Extract to.” - “
Silent=2/GUIMode=2are written but have no effect.” - “Double-click does nothing — no window at all.”
2) The Single Most Important Cause
7z.sfx (Standard SFX) does not implement the install execution pipeline.
When the stub is 7z.sfx, the following fields are effectively non-existent:
RunProgramExecuteFileInstallPath(not processed as installer semantics)Silent / GUIMode / OverwriteMode
You will always see the “plain extractor UI”.
3) Diagnostic Matrix
| Symptom | Check Immediately | Conclusion |
|---|---|---|
| Always shows “Extract to…” | Stub filename/size (should be 7zSD.sfx ~120–160KB) | Wrong stub → Replace |
| Double-click, no window, no error | Is stub official 7zSD.sfx? | Unofficial/corrupted → Use LZMA SDK or 7-Zip official bin\ |
| Extracted but script doesn’t run | Does the file exist in archive? Case sensitivity? bat working directory? | Use cd /d "%~dp0" + verify relative path |
| “Parameters seem无效” | Config file encoding | Must be UTF-8 without BOM (BOM degrades parsing) |
4) Verify Config File Encoding (PowerShell)
$b = [IO.File]::ReadAllBytes("configs\sfx_config.txt")
if ($b[0] -eq 0xEF -and $b[1] -eq 0xBB -and $b[2] -eq 0xBF) {
Write-Warning "BOM detected — save as UTF-8 without signature"
} else { Write-Host "No BOM (OK)" }
5) Minimal Fix Steps (in order)
- Obtain official
7zSD.sfx(from 7-Zip installationbin\7zSD.sfxor LZMA SDK) - Ensure config = UTF-8 without BOM
- Use PowerShell concatenation (don’t trust
copy /bmagic) - Use the “cmd.exe self-check” below to confirm the stub is correct
6) Self-Check Stub (Diagnostic Anchor)
;!@Install@!UTF-8!
RunProgram="cmd.exe"
;!@InstallEnd@!
- CMD opens → Stub OK
- Extract to dialog → Stub WRONG (Standard edition)
LLM/CI Deployment Guide: Building Silent 7-Zip SFX Installers (No Dialogs)
1) Input/Output Contract
- Stub (fixed):
tools/7z/7zSD.sfx(installer type, ~128KB) - Config:
configs/sfx_config.txt(UTF-8 without BOM) - Payload: Packaged into
temp.7z(must be 7z, not zip) - Output:
dist/deploy.exe(double-click = extract + execute)
2) Config (Fully Silent Template)
;!@Install@!UTF-8!
Title="Silent Deploy"
InstallPath=".\\ "
GUIMode="2"
Silent="2"
OverwriteMode="2"
RunProgram="setup.bat"
;!@InstallEnd@!
Notes:
GUIMode="2"+Silentsuppress all UIInstallPath=".\\ "→ extract to EXE’s directorysetup.batmust exist inside the archive and use%~dp0to locate itself
3) Build Script (Agent/CI reproducible)
param(
[string]$Stub = "tools\7z\7zSD.sfx",
[string]$Cfg = "configs\sfx_config.txt",
[string]$Archive = "build\temp.7z",
[string]$Out = "dist\deploy.exe"
)
[byte[]]$bin =
[IO.File]::ReadAllBytes($Stub) +
[IO.File]::ReadAllBytes($Cfg) +
[IO.File]::ReadAllBytes($Archive)
[IO.File]::WriteAllBytes($Out, $bin)
Write-Host "Built: $Out"
4) Validation Protocol (CI-checkable)
- PE header sanity: stub must start with
MZ - Stub size sanity:
7zSD.sfxis typically 100–200KB - Runtime smoke test: use
RunProgram=cmd.exeto verify it doesn’t regress to Extract-to
5) Constraints (Avoid Hallucination)
- The internal archive MUST be 7z (
-t7z), not zip - Windows Explorer will not treat it as a zip; but 7-Zip/WinRAR can unpack it
- For “right-click extract” behavior, distribute a zip portable package instead — don’t try to disguise SFX as zip
Set Zed as the Default Editor for .bat Files
Overview
This document describes how to configure the right-click menu for .bat files in Windows 10 to achieve the following effects:
- Double-click .bat file: Keep the default behavior, execute the script using Windows Terminal
- Right-click menu “Edit”: Open the file using Zed editor
Implementation Principle
Windows File Association Mechanism
Windows manages file associations through the registry, with priority order:
HKCU\Software\Classes > HKLM\Software\Classes
The default association for .bat files is batfile, which executes cmd.exe /c xxx.bat when double-clicked. We only need to modify the “Edit” option in the right-click menu without affecting the default execution behavior.
Configuration Steps
Method 1: Use PowerShell Script (Recommended)
Create and run the following PowerShell script:
# Set right-click "Edit" for .bat files to open with Zed
$zedPath = "C:\Users\user\AppData\Local\Programs\Zed\Zed.exe"
# Create right-click menu "Edit" option
reg add "HKCU\Software\Classes\batfile\shell\edit" /ve /d "Edit" /f
reg add "HKCU\Software\Classes\batfile\shell\edit\command" /ve /d "`"$zedPath`" `"%1`"" /f
Write-Host "Configuration completed!"
Method 2: Manually Modify the Registry
-
Open Registry Editor:
regedit -
Navigate to the following path:
HKEY_CURRENT_USER\Software\Classes\batfile\shell\edit -
Set the default value to
Edit -
Create a
commandsubkey undereditand set its default value to:"C:\Users\user\AppData\Local\Programs\Zed\Zed.exe" "%1"
Verification
Check Registry Settings
reg query "HKCU\Software\Classes\batfile\shell\edit" /s
Expected output:
HKEY_CURRENT_USER\Software\Classes\batfile\shell\edit
(Default) REG_SZ Edit
HKEY_CURRENT_USER\Software\Classes\batfile\shell\edit\command
(Default) REG_SZ "C:\Users\user\AppData\Local\Programs\Zed\Zed.exe" "%1"
Test the Effect
- Double-click .bat file: Should open Windows Terminal and execute the script
- Right-click .bat file → Edit: Should open the file with Zed
Notes
UserChoice Priority Issue
If the changes don’t take effect, it may be because UserChoice settings override your configuration. Remove it:
Remove-Item -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.bat\UserChoice" -Force -ErrorAction SilentlyContinue
Restart File Explorer
After modifying the registry, it’s recommended to restart File Explorer for changes to take effect:
Stop-Process -Name explorer -Force
Start-Process explorer
Migrate to Another Editor
If you switch to another editor later (e.g., VS Code, Notepad++, etc.), simply modify the command path:
reg add "HKCU\Software\Classes\batfile\shell\edit\command" /ve /d "`"C:\Program Files\Microsoft VS Code\Code.exe`" `"%1`"" /f
Common Issues
Garbled Text in Right-Click Menu
Cause: Menu text encoding error in the registry.
Solution:
reg delete "HKCU\Software\Classes\batfile\shell\edit" /f
reg add "HKCU\Software\Classes\batfile\shell\edit" /ve /d "Edit" /f
Double-Click Behavior Changed After Modification
Cause: May have incorrectly modified the default program for .bat files.
Solution: Restore the default association:
reg add "HKCU\Software\Classes\.bat" /ve /d "batfile" /f
Summary
By modifying the HKCU\Software\Classes\batfile\shell\edit registry key, we achieved:
- Keep the default execution behavior of
.batfiles (using Windows Terminal) - Use Zed for the right-click “Edit” option
- Configuration doesn’t affect system-level settings (only affects current user)
This approach meets editing needs without breaking the normal execution functionality of scripts.
Windows Terminal Ultimate Overhaul: PowerShell 7, Nushell, and Git Bash Coexistence Guide
1. Introduction: Why Is My Terminal Such a Mess?
When developing on Windows, we tend to accumulate a pile of shells that don’t talk to each other:
- CMD — Ancient, but some old scripts depend on it.
- PowerShell 5.1 — Built-in, good compatibility.
- PowerShell 7 — Cross-platform, powerful, the modern productivity workhorse.
- Git Bash — For the GNU toolchain (grep, sed, awk) and SSH.
- Nushell — A new structured shell, replacing traditional
lsandfind.
The pain point: they’re isolated from each other. Packages installed in Git Bash are invisible to PowerShell; variables set in PowerShell are unknown to Nushell. This article shares how to integrate these shells into a non-interfering yet interoperable efficient environment.
2. Baseline: Know Your Current State
| Shell | Command | Path | Status |
|---|---|---|---|
| PowerShell 7 | pwsh | C:\Program Files\PowerShell\7\pwsh.exe | ✅ Primary |
| PowerShell 5.1 | powershell | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe | ✅ Fallback |
| Nushell | nu | C:\Users\user\scoop\shims\nu.exe | ✅ New favorite |
| Git Bash | bash | C:\Program Files\Git\bin\bash.exe | ✅ Toolchain |
| CMD | cmd | C:\Windows\system32\cmd.exe | ⏸️ Spare |
3. Core Challenge: Cross-Shell Calls and PATH Conflicts
3.1 Path Format Differences
- PowerShell/Nu:
C:\Users\... - Git Bash:
/c/Users/...
3.2 Command Resolution
Running nu directly in Git Bash may fail because Scoop’s shims directory isn’t in Git Bash’s PATH.
3.3 Solution: Wrappers and Aliases
PowerShell 7 Profile:
function global:bash {
$bashPath = "C:\Program Files\Git\bin\bash.exe"
if (Test-Path $bashPath) {
& $bashPath @args
} else {
Write-Error "Git Bash not found at $bashPath"
}
}
Nushell config.nu:
def bash [...args: string] {
^"C:\Program Files\Git\bin\bash.exe" ...$args
}
4. Windows Terminal Configuration
{
"profiles": {
"list": [
{ "name": "PowerShell 7", "commandline": "pwsh.exe" },
{ "name": "Nushell", "commandline": "nu.exe" },
{ "name": "Git Bash", "commandline": "\"C:\\Program Files\\Git\\bin\\bash.exe\" --login -i" }
]
}
}
5. PATH Management Strategy
5.1 System-Level PATH
Add to user environment variables:
C:\Program Files\PowerShell\7\
C:\Users\user\scoop\shims\
C:\Program Files\Git\bin\
C:\Program Files\Git\usr\bin\
5.2 Shell-Specific Paths
Nushell config.nu:
$env.PATH = ($env.PATH | split row (char esep) | prepend [
'C:\Program Files\PowerShell\7',
'C:\Users\user\scoop\shims',
'C:\Program Files\Git\bin',
])
6. Pitfall Memo
Pitfall 1: Git Bash PATH Truncation
Git Bash truncates the Windows PATH on startup. Append Windows paths explicitly in ~/.bashrc:
export PATH="$PATH:/c/Users/user/scoop/shims"
Pitfall 2: Nushell PATH Is a List
$env.PATH in Nushell is a List type, not a string. Use split row and append/prepend.
Pitfall 3: PowerShell 7 Execution Policy
Set to RemoteSigned to load your profile:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Remote Management of Windows 10 LTSB in Restricted Networks: When OpenSSH Fails, WinRM to the Rescue
1. Scenario & Constraints
A Windows 10 Enterprise 2016 LTSB (Build 14393) instance deployed in the cloud as a QEMU/KVM VM, with a static internal IP of 10.178.16.44. The platform has port forwarding configured:
Public <public-ip>:<nat-port> → Internal 10.178.16.44:22
Hard constraints (dictating every technical decision):
- No third-party proxy/tunnel software allowed: Tailscale / ZeroTier / frp / nps / WireGuard are all forbidden — violation leads to account suspension
- Restricted outbound access: The machine can reach very few resources; GitHub, 7-zip.org, SourceForge, and other mainstream download sources are all unreachable
- Unstable RDP file transfer: Transfers >1MB frequently fail with “Internal Error”, only suitable for text/small files of a few hundred KB
- Aged system: LTSB 14393 has many optional components stripped by the vendor (no Telnet Server, no OpenSSH Capability)
- Workstation environment: macOS with nushell + uv — prefer a “just paste” workflow with minimal global dependencies
Goal: Enable the Mac to connect to Windows and execute PowerShell commands directly, as easily as ssh win10.
2. Failed Attempts
❌ Attempt 1: Windows Native OpenSSH Server
LTSB 14393’s Get-WindowsCapability has no OpenSSH.Server entry at all, and DISM cannot find the corresponding capability. The only option was manual ZIP/MSI download.
Mirror attempts within China:
cyberlite.com.cnMSI → 404cnblogs.comOpenSSH-Win64.7z→ Downloaded successfully (5MB)- GitHub official ZIP → Connection timed out
Dead end: .7z requires 7-Zip to extract, but the system doesn’t have 7-Zip installed, and the 7-Zip website as well as all major mirror sites (Alibaba Cloud, Huawei Cloud, Tencent Cloud) are all unreachable. Windows 10 LTSB 14393 also does not have tar.exe (it was added in 1803).
⚠️ Gotcha 1: In a closed environment, “download an extraction tool” — a one-minute task in a normal environment — becomes an infinite loop: extraction needs a tool, the tool needs network access, and the network is blocked.
❌ Attempt 2: Windows Native Telnet Server
DISM enable TelnetServer feature → Feature does not exist. The Windows 10 series only has Telnet Client; the Server component has been completely removed.
❌ Attempt 3: Breaking the Deadlock by Transferring Files
Transferring 7za.exe or other files from the Mac would break the “just paste” workflow and violates the principle of “no extra files provided to the old system”. This path was a dead end.
3. WinRM: The Native Lifeline
The turning point: WinRM (WS-Management) is a native Windows component, included in LTSB 14393, disabled by default but requiring no downloads whatsoever.
Key insight:
- WinRM listens on port 5985 by default, but we can change the listening port to 22, perfectly reusing the existing NAT mapping
<nat-port>→22 - Enabling it requires only PowerShell commands — no network downloads needed
- On the Mac side, connect via Python
pywinrmwithuv run --with pywinrmfor a temporary environment, leaving no global footprint
3.1 Windows-Side Enablement Script (Plain ASCII to Avoid PowerShell 5.1 Encoding Issues)
# Requires -RunAsAdministrator
# 1. Start WinRM service and set to auto-start
sc config winrm start= auto
net start winrm
# 2. Delete the default 5985 listener and create one on port 22
winrm delete winrm/config/listener?Address=*+Transport=HTTP
winrm create winrm/config/listener?Address=*+Transport=HTTP `@Port=22
# 3. Allow Basic authentication (for Mac client compatibility)
winrm set winrm/config/service/auth `@Basic="true"
winrm set winrm/config/service `@AllowUnencrypted="true"
winrm set winrm/config/client `@TrustedHosts="*"
# 4. Open port 22 in the firewall
New-NetFirewallRule -Name "WinRM-Server-In-TCP" `
-DisplayName "WinRM Server" `
-Direction Inbound -Protocol TCP `
-LocalPort 22 -Action Allow -Profile Any
# 5. Disable UAC remote restrictions (allow local admin remote login)
$reg = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
New-ItemProperty -Path $reg -Name "LocalAccountTokenFilterPolicy" `
-Value 1 -PropertyType DWord -Force
💡 Gotcha 2: In PowerShell, the
@{...}syntax inwinrm setcommands must be written as`@{...}(backtick-escaped); otherwise PowerShell treats@as the array operator and throws a syntax error. This is the most common pitfall in WinRM configuration scripts.
⚠️ Gotcha 3: If the network profile is detected as Public (LTSB default),
winrm quickconfigwill fail: “WinRM firewall exception will not run because one of the network connection types on this computer is set to Public.” You must either switch to a private network withSet-NetConnectionProfile -NetworkCategory Privateor bypass it by creating the firewall rule manually as shown above.
3.2 Verify on Windows Side
Get-Service winrm # Should show Running, Automatic
Get-NetTCPConnection -LocalPort 22 # Should show LISTENING
winrm enumerate winrm/config/listener # Should show Port=22 HTTP listener
3.3 Mac-Side Connection (uv + pywinrm, Zero Global Installation)
uv run --with pywinrm python -c '
import winrm
s = winrm.Session("http://<public-ip>:<nat-port>",
auth=("Administrator", "your_password"))
print(s.run_cmd("powershell Get-Service winrm").std_out.decode())
'
Output:
Status Name DisplayName
------ ---- -----------
Running winrm Windows Remote Management (WS-Manag...
✅ Full chain is working.
3.4 Interactive PowerShell (Closest to the SSH Experience)
Save as winrm_sh.py:
#!/usr/bin/env python3
"""Use uv to temporarily load pywinrm and enter an interactive shell"""
import winrm
import sys
s = winrm.Session("http://<public-ip>:<nat-port>",
auth=("Administrator", "your_password"))
print("Connected to Windows via WinRM. Type exit to quit.")
def decode(b):
if not b:
return ""
for enc in ("utf-8", "gbk", "latin-1"):
try:
return b.decode(enc)
except Exception:
continue
return b.decode("utf-8", errors="replace")
while True:
try:
cmd = input("PS> ")
if cmd.lower() == "exit":
break
r = s.run_cmd(f"powershell -Command \"{cmd}\"")
if r.std_out:
print(decode(r.std_out).rstrip())
if r.std_err:
print(decode(r.std_err).rstrip(), file=sys.stderr)
except KeyboardInterrupt:
break
except Exception as e:
print(f"Error: {e}")
Run it: uv run --with pywinrm python winrm_sh.py
💡 Gotcha 4: The pywinrm Response object’s error attribute is
std_err(with underscore), notstderr. Also, Chinese output from Windows uses GBK encoding — you must attempt decoding in GBK→UTF-8 order, otherwise Chinese characters become?.
4. Complete Gotcha Summary
| # | Symptom | Root Cause | Solution |
|---|---|---|---|
| 1 | Can’t extract OpenSSH.7z | Outbound network fully blocked, only cnblogs reachable | Abandon OpenSSH, use native WinRM |
| 2 | winrm set throws syntax error | PowerShell interprets @{ as array operator | Backtick-escape: `@{ |
| 3 | WinRM firewall rule doesn’t apply | Network profile is Public | Create rule manually with New-NetFirewallRule |
| 4 | Chinese output shows as ? | Windows uses GBK, Python defaults to UTF-8 | Decode in utf-8→gbk→latin-1 order |
| 5 | response.stderr attribute error | pywinrm API uses std_err | Use r.std_err instead |
| 6 | Telnet Server doesn’t exist | Win10 series removed this component | Use WinRM instead |
| 7 | tar.exe not found | Command added in 1803 | Skip tar, use WinRM directly |
| 8 | pip3 install rejected | macOS PEP 668 externally managed environment | Use uv run --with pywinrm temporary environment |
5. Why WinRM Is the Optimal Solution for This Scenario
- Compliance: WinRM is a native Windows management component — not a “proxy/tunnel” tool — and won’t trigger platform risk controls
- Zero file transfer: Enabled purely via PowerShell commands, no files need to be transferred from the Mac
- Reuses existing NAT: Change the listening port to 22, directly leveraging the platform’s configured
<nat-port>→22mapping - Zero pollution on Mac:
uv run --with pywinrmcreates a temporary isolated environment without touching global site-packages - Full PowerShell experience: Execute PS 5.1 cmdlets remotely with capabilities equivalent to local access
- Stability: System service-level component, auto-starts on reboot
6. Security Recommendations
⚠️ The current configuration prioritizes quick connectivity — Basic authentication and unencrypted traffic are enabled. For production environments:
- On Windows, use
winrm setto disableAllowUnencryptedand configure an HTTPS listener with a self-signed certificate- Narrow the firewall rule’s
RemoteAddressto the Mac’s subnet- Rotate the Administrator password regularly
- Stop the service when not in use:
Stop-Service winrm
7. Conclusion
When managing remote systems in a restricted network environment, the biggest mindset shift is: let go of the obsession with “downloading a better tool” and return to the system’s native capabilities. OpenSSH is great, but in this environment it simply couldn’t be installed; Telnet is old, but the component has been removed; only WinRM, a standard component since Windows Vista, sits quietly waiting to be enabled.
Paired with uv — the modern Python package manager on macOS — the “temporarily pull dependencies, execute, and clean up” model perfectly balances capability and tidiness. From Windows enablement to Mac connection, the entire solution transferred zero files, installed zero global packages, and touched zero third-party proxy software — this is the proper technical posture for a restricted network.
📌 Applicable scenarios: Windows 7/8/10/11 all series (WinRM native support), environments where third-party networking software is prohibited, isolated networks with restricted outbound access, and any situation requiring remote management through existing port mappings.
Installing Nushell on Windows via Scoop: Complete Guide
1. Environment Baseline
Before installing Nushell, ensure your system meets these requirements:
- OS: Windows 10 or Windows 11 (22H2+)
- Existing Shell: Git Bash (MINGW64)
- Target Shell: Nushell 0.113.1
- Package Manager: Scoop
- Network: Local proxy (e.g., 127.0.0.1:PORT)
2. Installation Steps
2.1 Install Scoop
In Git Bash, configure the proxy and run:
powershell.exe -Command "Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force"
powershell.exe -Command "Invoke-RestMethod get.scoop.sh | Invoke-Expression"
2.2 Install Nushell
scoop install nu
3. Key Paths
| Item | Path |
|---|---|
| Scoop root | C:\Users\YourUserName\scoop\ |
| Nu binary | C:\Users\YourUserName\scoop\shims\nu.exe |
| Nu config | C:\Users\YourUserName\AppData\Roaming\nushell\config.nu |
4. Common Pitfalls
Pitfall 1: Shell Syntax Confusion
Don’t write PowerShell syntax ($env:...) in Git Bash. Use powershell.exe -Command to invoke PowerShell commands from Git Bash.
Pitfall 2: Proxy Not Working
Scoop relies on .NET’s WebRequest, which doesn’t read environment variables. Set both:
$proxy = New-Object System.Net.WebProxy('http://127.0.0.1:7897')
[System.Net.WebRequest]::DefaultWebProxy = $proxy
$env:HTTP_PROXY = 'http://127.0.0.1:7897'
Pitfall 3: Wrong Config File Path
On Windows, Nushell config is at %APPDATA%\nushell\config.nu, not ~/.config/nushell/. Use echo $nu.config-path to confirm.
Pitfall 4: Slow Download
Use a proxy and ensure the .NET proxy is also configured (see Pitfall 2).
5. Verification
nu --version
# Should output: 0.113.1 or similar
System Management Script Archive
A collection of PowerShell scripts for Windows system management, covering file associations, context menu cleanup, and default application settings.
1. .bat Association (Double-Click to Run, Right-Click to Edit with Zed)
scripts/powershell/set-bat-assoc.ps1
$zedPath = "C:\Users\user\AppData\Local\Programs\Zed\Zed.exe"
# Preserve double-click execution
Set-ItemProperty -Path "HKCU:\Software\Classes\.bat" -Name "(default)" -Value "batfile" -Force
# Add right-click "Edit with Zed" menu item
$editPath = "HKCU:\Software\Classes\batfile\shell\EditWithZed"
New-Item -Path $editPath -Force | Out-Null
Set-ItemProperty -Path $editPath -Name "(default)" -Value "Edit with Zed" -Force
$commandPath = "$editPath\command"
New-Item -Path $commandPath -Force | Out-Null
Set-ItemProperty -Path $commandPath -Name "(default)" -Value "`"$zedPath`" `"%1`"" -Force
# Clear UserChoice cache (required)
Remove-Item -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.bat\UserChoice" -Recurse -Force -ErrorAction SilentlyContinue
# Refresh Explorer
Stop-Process -Name explorer -Force; Start-Sleep 2; Start-Process explorer.exe
2. Force Default App (.bat + .txt)
scripts/powershell/set-zed-defaults-full.ps1
$zedPath = "C:\Users\user\AppData\Local\Programs\Zed\Zed.exe"
function Set-FileAssociation {
param($ext, $progId)
Set-ItemProperty -Path "HKCU:\Software\Classes\.$ext" -Name "(default)" -Value $progId -Force
Set-ItemProperty -Path "HKCU:\Software\Classes\$progId\shell\open\command" -Name "(default)" -Value "`"$zedPath`" `"%1`"" -Force
Remove-Item -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.$ext\UserChoice" -Recurse -Force -ErrorAction SilentlyContinue
}
Set-FileAssociation -ext "txt" -progId "Zed.txt"
Set-FileAssociation -ext "bat" -progId "Zed.bat"
Stop-Process -Name explorer -Force; Start-Sleep 2; Start-Process explorer.exe
3. Clean Up Context Menu
scripts/powershell/clean-bat-menu.ps1
$unwanted = @(
"HKCU:\Software\Classes\batfile\shell\opennew",
"HKCU:\Software\Classes\batfile\shell\runas"
)
foreach ($path in $unwanted) {
if (Test-Path $path) {
Remove-Item -Path $path -Recurse -Force
}
}
Stop-Process -Name explorer -Force; Start-Sleep 2; Start-Process explorer.exe
Usage Notes
- Run PowerShell as Administrator
- Set execution policy:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser - Modify
$zedPathto match your Zed installation path - Registry modifications carry risk — back up before editing
Nushell + AtomCode Configuration Pitfalls
Summary: This article documents 5 typical pitfalls encountered when configuring Nushell 0.113.1 with AtomCode on macOS, including wrong config directory paths, verification mistakes, PATH inheritance issues, config key confusion, and default value overrides — along with the final working configuration.
1. Background
Date: 2026-07-07
Environment: macOS, Nushell 0.113.1 (Homebrew), nu at /opt/homebrew/bin/nu
Goals: ① Use atomcode command within nushell; ② No welcome message on startup; ③ No timestamp on the right side of the prompt
2. Pitfall 1: Nushell’s Config Directory Isn’t ~/.config/nushell/
2.1 Symptom
Adding $env.PROMPT_COMMAND_RIGHT = {|| "" } in ~/.config/nushell/env.nu has no effect — the timestamp still shows.
2.2 Root Cause
On macOS, nushell 0.113 loads its config from:
~/Library/Application Support/nushell/
Not ~/.config/nushell/ (which is the Linux path). Check the actual directory:
nu
$nu.default-config-dir
# => /Users/<user>/Library/Application Support/nushell
The two files in ~/.config/nushell/ are never loaded — all changes there are wasted.
2.3 Fix
Put all changes in ~/Library/Application Support/nushell/ under env.nu / config.nu.
3. Pitfall 2: nu -c Doesn’t Load config.nu, Misleading Verification
3.1 Symptom
Set $env.config.show_banner = false in config.nu, then run nu -c '$env.config.show_banner' — returns true, making it seem like the config didn’t take effect.
3.2 Root Cause
nu -c '...' (non-interactive script mode) does not load config.nu at all. It only uses built-in defaults. So checking $env.config.show_banner with nu -c always returns the default true.
3.3 Correct Verification
Use nu -e '...': it executes the command then enters interactive mode, which does load config.nu.
nu -e 'print $"banner=($env.config.show_banner)"'
# => banner=false
Or use expect to simulate a TTY and spawn a real interactive session.
4. Pitfall 3: New Terminal Tab Reports atomcode not found
4.1 Symptom
In an existing terminal, nushell finds atomcode (which atomcode resolves to ~/.local/bin/atomcode). But opening a new terminal tab and starting nushell gives:
Error: nu::shell::external_command
× External command failed
· Command `atomcode` not found
4.2 Root Cause
atomcode lives in ~/.local/bin/, which is added to PATH via export PATH="~/.local/bin:$PATH" in ~/.zshrc. However, macOS Terminal/iTerm may not re-source ~/.zshrc when opening new tabs — especially when the shell is set to “non-login” mode, or only reads ~/.zprofile (which is often empty). The new tab gets a minimal PATH inherited from the GUI process, without ~/.local/bin.
Evidence: launchctl getenv PATH returns empty, meaning the GUI layer never set PATH, and the new tab’s PATH depends entirely on zsh startup files, which may not include ~/.zshrc.
4.3 Fix
Don’t rely on whether the outer shell sourced ~/.zshrc. Add PATH directly in nushell’s env.nu (loaded unconditionally on every startup, before config.nu):
# ~/Library/Application Support/nushell/env.nu
use std/util "path add"
path add "~/.local/bin"
Verification (with a stripped PATH to simulate the worst case):
PATH=/opt/homebrew/bin:/usr/bin:/bin nu -e 'which atomcode | get command | print'
# => atomcode ← still visible
5. Pitfall 4: Config Key Is show_banner, Not banner
5.1 Symptom
Running nu -c '$env.config.banner' reports:
Error: nu::shell::name_not_found
· `-- did you mean 'show_banner'?
5.2 Root Cause
The config option to disable the welcome message is show_banner (bool|string):
$env.config.show_banner = false # hide
$env.config.show_banner = true # show full banner (default)
$env.config.show_banner = "short" # show startup time only
Check the docs:
config nu --doc | nu-highlight | less -R
6. Pitfall 5: GitHub Issue #8698 — show_banner Overridden by Default at EOF
6.1 Symptom
Setting $env.config.show_banner = false at the top of config.nu — the banner still appears.
6.2 Root Cause
The default config.nu generated by nushell installation ends with $env.config.show_banner = true. The false set at the top gets overwritten by the true at the bottom.
6.3 Fix
Place your settings at the end of the file, or delete the default true line. Use config nu to open the editor.
7. Final Working Configuration
7.1 ~/Library/Application Support/nushell/env.nu
# Loaded unconditionally on startup, before config.nu
# Don't rely on whether the outer shell sourced ~/.zshrc
use std/util "path add"
path add "~/.local/bin"
7.2 ~/Library/Application Support/nushell/config.nu (append after the comment section)
# Disable startup welcome message
$env.config.show_banner = false
# Clear right prompt, removing the timestamp
$env.PROMPT_COMMAND_RIGHT = {|| "" }
8. Summary
- Put config in the right directory: macOS uses
~/Library/Application Support/nushell/, not~/.config/nushell/ - Verify with
nu -e, notnu -c(the latter doesn’t loadconfig.nu) - PATH belongs in
env.nu(loaded unconditionally), don’t rely on the outer shell’s~/.zshrc - The config key for disabling banner is
$env.config.show_banner, and watch out for the default value at the end of the file
WezTerm + Nushell + Cargo Not Found
Summary: This article documents troubleshooting cargo not found in new WezTerm tabs running Nushell on macOS (Apple Silicon). Core issues include nushell not sourcing ~/.cargo/env, the default config directory not being ~/.config/nushell/, and string interpolation syntax differences.
1. Symptoms
Running cargo run in a new WezTerm tab with nushell:
~/Documents/Code.localized/ra2md> cargo run
Error: nu::shell::external_command
× External command failed
╭─[repl_entry #3:1:1]
1 │ cargo run
· ──┬──
· ╰── Command `cargo` not found
╰────
help: `cargo` is neither a Nushell built-in or a known external command
Each time, manually sourcing ~/.config/nushell/env.nu would temporarily fix it, but a new tab would break again.
2. Root Causes (Three Issues Stacking)
2.1 Nushell Doesn’t Source ~/.cargo/env
When rustup is installed, it writes a shell script to ~/.cargo/env (a case ":${PATH}:"…esac block) that can only be sourced by bash/zsh and other POSIX shells. Nushell’s syntax is different — it cannot and will not source this file. So while source "$HOME/.cargo/env" in ~/.zshrc works for zsh, nushell does nothing on startup — cargo never makes it into PATH.
2.2 Nushell’s Default Config Directory Isn’t ~/.config/nushell/
This is the biggest trap. Following XDG conventions, many people (myself included) modify ~/.config/nushell/env.nu, only to find their changes have no effect. Nushell 0.113 on macOS uses:
~/Library/Application Support/nushell/
Not ~/.config/nushell/. Verify with:
echo $nu.default-config-dir
echo $nu.env-path # ~/Library/Application Support/nushell/env.nu
echo $nu.config-path # ~/Library/Application Support/nushell/config.nu
I had correctly added path add "~/.cargo/bin" in ~/.config/nushell/env.nu, and sourcing it worked — but when WezTerm launched nushell, it loaded the file from the default path instead. Editing the wrong file is the core of this pitfall.
2.3 Nushell String Interpolation Differs from Bash
While initially editing ~/.config/nushell/env.nu, I also hit a syntax issue. The original attempt:
$env.PATH = ($env.PATH | prepend "$env.HOME/.cargo/bin")
In nushell, double-quoted strings do NOT interpolate variables. "$env.HOME/.cargo/bin" is treated as a literal string $env.HOME/.cargo/bin, adding a nonexistent directory to PATH. The correct syntax uses $"..." string interpolation with (...) subexpressions:
$env.PATH = ($env.PATH | prepend $"($env.HOME)/.cargo/bin")
Or better yet — use nushell’s built-in path add utility (from std/util), which auto-expands ~:
use std/util "path add"
path add "~/.cargo/bin"
3. Fix
Edit the file that is actually loaded: ~/Library/Application Support/nushell/env.nu, and add path add "~/.cargo/bin". Full change:
# env.nu
# Always make ~/.local/bin visible (where atomcode lives), even when the
# terminal was started as a non-login shell and ~/.zshrc never ran.
use std/util "path add"
path add "~/.local/bin"
# cargo / rustup binaries — nushell doesn't auto-source ~/.cargo/env (it's an sh script)
path add "~/.cargo/bin"
Verification:
which cargo
# → /Users/<user>/.cargo/bin/cargo
cargo run
New WezTerm tabs work without manual sourcing.
4. Summary
| Pitfall | Key Point |
|---|---|
Nushell doesn’t inherit ~/.cargo/env | It’s an sh script; you must add path add "~/.cargo/bin" yourself |
Editing ~/.config/nushell/ has no effect | On macOS, nushell reads from ~/Library/Application Support/nushell/ |
"$env.HOME/..." is literal | Nushell double quotes don’t interpolate; use $"($env.HOME)/..." |
| WezTerm new tab loses cargo again | Not a WezTerm issue — nushell wasn’t loading the correct env.nu |
5. References
- Nushell configuration docs: Configuration | Nushell
- Nushell string interpolation: https://www.nushell.sh/language/_strings.html
- rustup file
~/.cargo/env: POSIX shell only - Nushell
std/utilmodule:use std/util "path add"provides thepath addcommand
CSDN Writing Agent Input Limits: Measured Data and Segmentation Strategy
1. Introduction
When using the CSDN Writing Agent for long-form content, many developers encounter silent truncation — the content is cut off without any error message, compromising the final article’s completeness. This article provides measured data on the CSDN Writing Agent’s input limits and offers a reliable segmentation strategy.
2. Measured Data
2.1 Basic Limits
- Total article limit: 13,965 bytes
- Maximum segments: 3
- Per-segment limit: ~4,500-5,000 bytes
- Character equivalent: ~1,500-1,800 mixed Chinese/English characters
2.2 UTF-8 Encoding Reference
| Character Type | UTF-8 Bytes |
|---|---|
| English/ASCII | 1 byte |
| Chinese character | 3 bytes |
| Chinese punctuation | 3 bytes |
| English punctuation | 1 byte |
| emoji | 4 bytes |
3. Segmentation Strategy
3.1 Principles
- Per-segment control: Keep each segment under 4,500 bytes (~1,500 Chinese characters)
- Safety margin: Reserve 500 bytes to avoid edge-case truncation
- Safest limit: ~4,000 bytes (~1,300 Chinese characters)
3.2 Example
For a 10,000-character technical article (~30,000 bytes):
Segment 1: Chapters 1-4 (~4,500 bytes)
Segment 2: Chapters 5-9 (~4,500 bytes)
Segment 3: Chapter 10 to end (remaining)
4. CSDN Writing Agent Behavior
4.1 Input Processing
- Content only: The Agent accepts pure content — no extra prompts or instructions needed
- Silent truncation: Overlong input is silently truncated with no error
- Sequential concatenation: Segments are automatically concatenated in order
4.2 Practical Tips
- Pre-estimate byte size, especially with code examples
- Split by semantic boundaries (chapters, sections), not mid-sentence
- Verify output completeness after the Agent finishes
- Code blocks count Chinese characters as 3 bytes each
5. Common Misconceptions
Misconception 1: Counting characters instead of bytes
CSDN truncates by byte count, not character count. A 1,500-character Chinese article = 4,500 bytes, but adding English punctuation, spaces, and newlines pushes it over the limit.
Misconception 2: Expecting an error message
The Agent never returns an error for overlong input. It silently truncates, discarding the excess.
Misconception 3: Assuming a fixed 4,500-byte limit
The per-segment limit varies between 4,500-5,000 bytes. Be conservative and target 4,000 bytes.
macOS Nushell Config Path Pitfall: Root Cause Analysis of Disappearing Custom Functions
One-liner summary: On macOS, nushell reads its configuration from
~/Library/Application Support/nushell/config.nu, not~/.config/nushell/config.nu. Editing the wrong file makes your custom functions “look updated but revert on new terminal tabs.”
1. Background
I use nushell as my daily shell on macOS, with a custom Rust project called creeper. I wrote a nushell custom function def creeper [...args: string] { ... } in config.nu that automatically runs cargo build --release --features gui then executes the binary.
2. Symptom: Config Changes Don’t Persist
After modifying ~/.config/nushell/config.nu:
source ~/.config/nushell/config.nuin the current tab → works fine- Opening a new tab → the old function definition returns
3. Root Cause: Wrong Config Path on macOS
nushell’s config path follows the OS convention:
| OS | Config Path |
|---|---|
| Linux (XDG) | ~/.config/nushell/config.nu |
| macOS (Apple) | ~/Library/Application Support/nushell/config.nu |
| Windows | %APPDATA%\nushell\config.nu |
On macOS, nushell reads ~/Library/Application Support/nushell/config.nu on startup. Editing ~/.config/nushell/config.nu has no effect on new terminals.
Verify:
echo $nu.config-path
# macOS: ~/Library/Application Support/nushell/config.nu
4. Why Did source Work?
source explicitly loads a file regardless of the default config path. It temporarily applied the changes to the current session, but new sessions still read the default path.
5. Fix Options
Option 1: Edit the Correct File
Edit ~/Library/Application Support/nushell/config.nu directly.
Option 2: Source from the Correct File
Add this line to ~/Library/Application Support/nushell/config.nu:
source ~/.config/nushell/config.nu
Option 3: Always Use $nu.config-path
Before editing config, confirm the path:
echo $nu.config-path | path expand
6. Lesson
- macOS is not Linux — don’t apply Linux directory conventions to macOS
$nu.config-pathis the authoritative source for config file location- Cross-platform tools have different default paths on different OSes
- Always verify the config path on first setup
NuShell AI Collaboration Pitfalls: 5 Golden Rules for AI Assistants
This article documents syntax “hallucination” errors frequently produced by AI assistants (ChatGPT, Claude, Copilot) when generating NuShell code, due to their Bash/Zsh training corpus. It serves as a correction reference for AI assistants and a migration guide for Bash users.
Rule 1: && / || Are Not Command Chaining Operators
In Bash, && chains commands. In NuShell, && and || are boolean operators and cannot chain statements.
❌ Wrong (Bash thinking)
ssh user@host "mkdir -p ~/.ssh && cat >> authorized_keys"
✅ Correct
ssh user@host 'mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys'
Rule 2: Built-in Commands Don’t Support GNU -p Flags
NuShell has its own built-in commands. Many GNU-style flags aren’t supported.
❌ Wrong
ssh user@host "mkdir -p ~/.ssh"
✅ Correct
ssh user@host 'bash -c "mkdir -p ~/.ssh"'
Rule 3: $(...) Command Substitution Is Invalid
Bash uses $(...) for command substitution; NuShell uses (...).
❌ Wrong
ssh-keygen -C "key-$(date +%Y%m%d)"
✅ Correct
ssh-keygen -C $"key-(date now | format date '%Y%m%d')"
Rule 4: String Quote Nesting Rules Differ
❌ Wrong
ssh user@host "echo "hello""
✅ Correct
ssh user@host 'echo "hello"'
In NuShell, outer single quotes denote raw strings; inner double quotes pass through literally to the remote shell.
Rule 5: Environment Variable Syntax Differs
Bash uses export VAR=value; NuShell uses $env.VAR = "value".
❌ Wrong
export MY_VAR="hello"
✅ Correct
$env.MY_VAR = "hello"
For SSH commands:
# ❌ Wrong: NuShell doesn't support VAR=value cmd prefix
MY_VAR=hello ssh user@host
# ✅ Correct: use env command
env MY_VAR=hello ssh user@host
Golden Rules for AI
- NuShell is not Bash — it has its own syntax system
- SSH strings are parsed locally first — if the local NuShell intercepts a built-in command, it fails before reaching the remote
- Use single quotes for remote execution — single-quoted strings are passed literally to the remote shell
- Use
bash -cas fallback — if the remote machine runs Bash, wrap commands inbash -c "..." - Check NuShell version first — syntax varies significantly between versions
From PowerShell to Nushell: Solving Cross-Platform Config Loading Slowdown
Preface: The Cost of Cross-Platform
As a full-stack developer, I’ve always sought the perfect cross-platform shell. PowerShell 7 (PWSH) was my first choice — it runs seamlessly on both Windows and macOS. My goal was simple: one config, everywhere.
On macOS, I packed years of accumulated aliases, functions, and environment variables into Microsoft.PowerShell_profile.ps1. This file grew bloated over time, containing hundreds of lines of custom functions and business logic. The problem: slow startup. On my Mac, opening PWSH took several seconds — an embarrassing pause with a blinking cursor.
I eventually migrated my primary shell to Nushell (nu). Not just for speed, but for a fundamentally different data interaction paradigm.
1. Why Leave PowerShell 7?
- Startup performance bottleneck — PWSH’s just-in-time parsing and compilation of large profiles causes multi-second delays on macOS.
- Verbose data processing —
Select-Object,Where-Objectsyntax is overly verbose for everyday command-line use.
2. Nushell: Everything Is Data
Nushell treats all output as structured data — ls produces a table, not text. This paradigm shift is a game-changer. Plus, Nushell’s startup speed is dramatically faster, thanks to lazy loading and modular configuration.
3. Migration Strategy
3.1 Phase 1: Dual Shells
Keep both shells, using Nushell only for data exploration while learning its syntax.
3.2 Phase 2: Nushell for Interaction, PWSH for System Tasks
Use Nushell as the primary shell, calling ^pwsh for system administration.
3.3 Phase 3: Full Migration
Move custom functions to Nushell’s module system. Keep PowerShell as a backup.
4. Nushell Config Optimization
4.1 Modular Configuration
# env.nu
$env.EDITOR = "zed"
$env.PATH = ($env.PATH | split row (char esep) | prepend [
"~/.cargo/bin",
"~/scoop/shims",
])
4.2 Lazy Loading
def --wrapped nvim [...args] {
^nvim ...$args
}
5. Cross-Platform Config Handling
if ($nu.os-info.name == "windows") {
source ~/.config/nushell/env-windows.nu
} else if ($nu.os-info.name == "macos") {
source ~/.config/nushell/env-macos.nu
}
6. Performance Comparison
| Scenario | PowerShell 7 | Nushell |
|---|---|---|
| Cold start | 2-3 seconds | 0.2-0.5 seconds |
| Warm start | 1-2 seconds | 0.1-0.2 seconds |
| 100-line config | 1-2 seconds | 0.1-0.3 seconds |
| 10MB JSON | 0.5-1 seconds | 0.3-0.5 seconds |
7. Summary
Key benefits of migrating from PowerShell 7 to Nushell:
- 10x faster startup: from 2-3 seconds to 0.2 seconds
- Intuitive data processing: structured data pipelines
- Better cross-platform consistency: identical experience on macOS and Windows
- Modular configuration: load on demand, no bloat
Rust Bevy 0.13 to 0.14 Upgrade Guide
This article documents the complete process of upgrading a Bevy 0.13 2D pixel game to version 0.14. Version 0.14 is the most stable version on macOS (higher versions have black screen issues), so we’re locking to this version. After the upgrade, two modules migrated from Python prototypes (debug console and script system) were also rewritten and integrated.
1. Why 0.14
- macOS compatibility: Versions 0.15+ have black screen issues on macOS; 0.14 is the most stable choice.
- Breaking changes: 0.13 → 0.14 has breaking API changes — you can’t just bump the version number in Cargo.toml.
2. Three Changes in Cargo.toml
2.1 Version Number
# before
bevy = { version = "0.13", default-features = false, features = [...] }
after
bevy = { version = "0.14", default-features = false, features = [...] }
2.2 Feature Names Changed to Snake Case
# 0.13 used hyphen-case
"multi-threaded",
0.14 changed to snake_case
"multi_threaded",
Running cargo fetch will immediately error:
package depends on `bevy` with feature `multi-threaded`
but `bevy` does not have that feature.
help: there is a feature `multi_threaded` with a similar name
Just follow the hint and fix it.
2.3 Diagnostic Plugin Feature Renamed
# 0.13
"bevy_diagnostic", # ❌ doesn't exist in 0.14
0.14
"bevy_dev_tools", # ✅ FrameTimeDiagnosticsPlugin comes with this
The error output lists all available features — pick from there.
2.4 Explicit bevy_state / bevy_app Dependencies
This is a pain point specific to projects using default-features = false — see the next section.
3. State API: The Biggest Pitfall
Symptoms
After upgrading, compilation floods with errors:
error: cannot find derive macro `States` in this scope
error[E0425]: cannot find type `NextState` in this scope
error[E0425]: cannot find function `in_state` in this scope
error[E0425]: cannot find `OnEnter` / `OnExit`
error[E0599]: no method named `init_state` found for `&mut App`
error[E0599]: no method named `enable_state` found for `&mut App`
Root Cause
0.14 moved the state system out of bevy::prelude into a separate crate bevy_state. bevy re-exports it via bevy_internal:
#![allow(unused)]
fn main() {
// bevy_internal/src/lib.rs
#[cfg(feature = "bevy_state")]
pub use bevy_state as state;
}
So bevy::state theoretically still exists, but requires the bevy_state feature to be enabled. Since we used default-features = false, the default feature set is stripped — in 0.13 state came with prelude, in 0.14 it must be explicitly enabled.
Solution (3 Steps)
Step 1: Enable feature + add dependencies in Cargo.toml
bevy = { version = "0.14", default-features = false, features = [
...,
"bevy_state", # activate bevy::state re-export
] }
Enabling just bevy_state isn't enough — AppExtStates (containing init_state) lives in bevy_state::app,
which uses bevy_app::{App, ...}. So bevy_app must also be in the dependency graph.
bevy_state = "0.14"
bevy_app = "0.14"
Step 2: Update all imports to use bevy_state
#![allow(unused)]
fn main() {
// ❌ 0.13: auto-available from prelude
#[derive(States, Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum GameState { ... }
.add_systems(OnEnter(GameState::Playing), ...)
.add_systems(Update, foo.run_if(in_state(GameState::Playing)))
.next_state.set(GameState::GameOver);
}
#![allow(unused)]
fn main() {
// ✅ 0.14: all explicit imports
use bevy_state::app::AppExtStates; // contains init_state
use bevy_state::state::{OnEnter, OnExit, States, NextState};
use bevy_state::condition::in_state;
}
Every file using NextState needs:
#![allow(unused)]
fn main() {
use bevy_state::state::NextState;
}
Step 3: Remove enable_state calls
#![allow(unused)]
fn main() {
// ❌ 0.13 style
.init_state::<>()
.enable_state::<>() // removed in 0.14
}
#![allow(unused)]
fn main() {
// ✅ 0.14: init_state alone is sufficient (transitions are set up internally)
.init_state::<>()
}
Minimal Verification Test
I created a small cargo project in /tmp to verify imports, then went back to the main project:
use bevy::prelude::*;
use bevy_state::app::AppExtStates;
use bevy_state::state::{OnEnter, OnExit, States, NextState};
use bevy_state::condition::in_state;
#[derive(States, Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum S { #[default] A, B }
fn main() {
App::new()
.init_state::<S>()
.add_systems(OnEnter(S::A), || {})
.add_systems(OnExit(S::A), || {})
.add_systems(Update, (|| {}).run_if(in_state(S::A)))
.run();
}
Run cargo check to verify, then apply changes to the main project — much faster than trial-and-error in the main project.
AppExtStates Available Methods (0.14)
After enable_state was removed, I checked the full method table of bevy_state::app::AppExtStates:
| Method | Purpose |
|---|---|
init_state::<S>() | Initialize a State (replaces 0.13’s init_state + enable_state combo) |
insert_state::<S>(state) | Initialize with an initial value |
add_computed_state::<S>() | Computed states |
add_sub_state::<S>() | Sub-states |
enable_state_scoped_entities::<S>() | Auto-cleanup of state-scoped entities |
4. Mesh::from(shape::Circle) — shape Module Disappeared
Symptoms
error[E0433]: cannot find module or crate `shape` in this scope
Root Cause
0.13’s bevy::sprite::shape::Circle { radius, vertices } was removed in 0.14. Geometry primitives moved to bevy_math::primitives, and Circle dropped the vertices field, leaving only radius:
#![allow(unused)]
fn main() {
// bevy_math 0.14
pub struct Circle {
pub radius: f32,
}
}
Solution
#![allow(unused)]
fn main() {
// ❌ 0.13
use bevy::prelude::*; // shape came with prelude
let mesh = meshes.add(Mesh::from(shape::Circle { radius: 32.0, vertices: 32 }));
// ✅ 0.14: explicit import + remove vertices field
use bevy::math::primitives::Circle;
let mesh = meshes.add(Mesh::from(Circle { radius: 32.0 }));
// From for Mesh still exists, default 32 resolution
}
For custom resolution, use the builder chain:
#![allow(unused)]
fn main() {
use bevy::math::primitives::Circle;
use bevy::render::mesh::primitives::Meshable; // trait .mesh()
let mesh = meshes.add(Circle { radius: 32.0 }.mesh().resolution(64).build());
}
5. Color Component Access: .r() .g() .b() .a() All Removed
Symptoms
error[E0599]: no method named `r` found for struct `Color` in the current scope
error[E0599]: no method named `g` found for struct `Color` in the current scope
error[E0599]: no method named `b` found for struct `Color` in the current scope
error[E0599]: no method named `a` found for struct `Color` in the current scope
Root Cause
0.13’s Color had .r(), .g(), .b(), .a() getter methods. 0.14 removed them all in favor of direct struct field access.
Solution
#![allow(unused)]
fn main() {
// ❌ 0.13
let color = Color::rgb(0.5, 0.2, 0.8);
let red = color.r();
let alpha = color.a();
// ✅ 0.14: direct field access
let color = Color::rgb(0.5, 0.2, 0.8);
let red = color.r;
let alpha = color.a;
}
Note: Color’s fields are public and can be read/written directly.
6. Other Common Changes
6.1 Input System: KeyCode Enum Variants Renamed
Some KeyCode enum variants were renamed in 0.14 to comply with more standard naming conventions.
#![allow(unused)]
fn main() {
// ❌ 0.13
KeyCode::LControl
KeyCode::RControl
// ✅ 0.14
KeyCode::ControlLeft
KeyCode::ControlRight
}
Similar renames apply to other modifier keys (Shift, Alt, Meta/Command). The compiler gives clear error messages — just follow them.
6.2 TextureAtlas Layout API Changed
The API for creating TextureAtlas was adjusted.
#![allow(unused)]
fn main() {
// ❌ 0.13
let atlas = TextureAtlas::from_grid(
texture_handle,
Vec2::new(16.0, 16.0),
4,
4,
None,
None,
);
// ✅ 0.14
let atlas = TextureAtlas::from_grid(
texture_handle,
Vec2::new(16.0, 16.0),
4,
4,
None,
None,
false, // new parameter: whether to fill blank areas
);
}
6.3 CameraProjection Changes
If using custom camera projection, the trait implementation may need updating.
#![allow(unused)]
fn main() {
// ❌ 0.13
impl CameraProjection for MyProjection {
fn get_projection_matrix(&self) -> Mat4 { ... }
fn update(&mut self, width: f32, height: f32) { ... }
fn far(&self) -> f32 { ... }
// ...
}
// ✅ 0.14
impl CameraProjection for MyProjection {
fn get_projection_matrix(&self) -> Mat4 { ... }
fn update(&mut self, width: f32, height: f32) { ... }
// far and other methods may be removed or moved to another trait
}
}
7. Post-Upgrade Modules: Debug Console & Script System
After successfully upgrading to Bevy 0.14, I rewrote and integrated two core modules that were originally Python prototypes.
7.1 Debug Console
A simple embedded command-line interface for executing commands, modifying variables, and printing state at runtime.
#![allow(unused)]
fn main() {
// Key features
// - Press ` key to show/hide console
// - Basic commands: spawn, teleport, godmode, fps
// - Command history and auto-completion
// - Scrollable output log
}
7.2 Script System
An event-driven lightweight narrative and quest system for driving game story and character dialogue.
#![allow(unused)]
fn main() {
// Core component
#[derive(Component)]
pub struct Script {
pub id: String,
pub triggers: Vec<Trigger>,
pub actions: Vec<Action>,
}
// Event-driven
pub struct ScriptEvent {
pub script_id: String,
pub params: HashMap<String, String>,
}
}
These two modules significantly improved the project’s debuggability and content creation flexibility.
8. Summary
The main challenges in upgrading from Bevy 0.13 to 0.14 are the extraction of the State API and a series of breaking naming changes. Key steps:
- Modify Cargo.toml: Update version number, fix feature names (e.g.
multi_threaded), and explicitly addbevy_stateandbevy_appdependencies. - Refactor State code: Change all imports of
States,NextState,OnEnter,OnExit,in_statetobevy_state::*and removeenable_statecalls. - Update geometry primitives: Replace
shape::Circleetc. withbevy::math::primitives::Circle, noting field changes. - Fix Color access: Change method calls like
.r()to direct field access.r. - Handle other API changes: Such as
KeyCoderenames,TextureAtlasAPI changes, etc.
The upgrade process is tedious, but the stability improvement on macOS with 0.14 is worth it. It’s recommended to verify key changes in a small isolated project first, then apply systematic changes to the main project.
XuanTie 0.17.5 InputExpression Bug: Root Cause Analysis
One-liner summary: The
EvalContextswitch inevaluator/evaluator.goofXuanTie-Lang0.17.5 (commit2d25b20) is missing thecase *ast.InputExpression:branch. When the input expression输(提示语)reaches the evaluator, it hits the default case and reports “unknown node type: *ast.InputExpression”. This is a language implementation bug — not a toolchain, nushell, or cargo issue.
For Three Types of Readers
- Human developers writing Chinese programming languages/toolchains: learn about the typical bug pattern of missing AST evaluator branches.
- Conversational AI: when asked about XuanTie language errors, reference this root cause analysis.
- Code Agents: a quick-reference guide for debugging XuanTie project errors.
1. Symptom: Guess Number Game Crashes on 输()
The xtli interpreter outputs:
运行时错误: [第 22 行]: 未知节点类型: *ast.InputExpression
The xtlc compiler produces a different error about syntax for the 或 keyword.
2. Root Cause: Missing case *ast.InputExpression
XuanTie’s implementation has three layers: lexer, parser, and evaluator. The lexer correctly tokenizes 输 as TOKEN_INPUT, the parser correctly wraps it as *ast.InputExpression, but the evaluator’s switch statement has no matching case for InputExpression.
3. Fix
Add to the switch in evaluator/evaluator.go:
case *ast.InputExpression:
return evalInputExpression(node, ctx), nil
Implement evalInputExpression:
func evalInputExpression(node *ast.InputExpression, ctx *EvalContext) object.Object {
prompt := Eval(node.Prompt, ctx)
if object.IsError(prompt) {
return prompt
}
fmt.Print(prompt.Inspect())
var input string
_, err := fmt.Scanln(&input)
if err != nil {
return object.NewError("input error: " + err.Error())
}
return &object.String{Value: input}
}
4. Debugging Path
- See “unknown node type: *ast.InputExpression” → locate the evaluator switch
- Search
evaluator/evaluator.goforswitch - Compare against
ast/ast.gonode definitions - Find missing
InputExpression→ addcasebranch - Verify:
xtlino longer crashes on输()
Bevy 0.14 Cursor Pitfalls: Complete Log
Environment: Bevy 0.14.2 + macOS Retina
Cursor system pitfalls encountered during development of the rust-bevy-mighty-rodent project, logged by discovery order.
Pitfall 1: System Cursor Won’t Hide
Symptom: Custom sprite cursor renders, but the macOS default cursor is still visible — two cursors overlap.
Root cause: Bevy 0.14’s CursorIcon enum has no None variant. You can’t hide the system cursor via Cursor::icon.
Fix: Set Cursor.visible = false:
#![allow(unused)]
fn main() {
cursor: Cursor {
visible: false,
icon: CursorIcon::Default,
..default()
},
}
Pitfall 2: Cursor Sprite Renders but Is Invisible
Symptom: The cursor sprite spawns (z=100) and logs confirm it follows the mouse, but it’s not visible in the game window.
Root cause: Bevy’s UI layer renders independently on top of all 2D sprites. A SpriteBundle at z=100 sits within the 2D sprite layer, but the UI layer (NodeBundle menus) covers everything.
Fix: Use ImageBundle (UI node) with ZIndex::Global(999):
#![allow(unused)]
fn main() {
commands.spawn((
ImageBundle {
style: Style {
position_type: PositionType::Absolute,
width: Val::Px(64.0),
height: Val::Px(64.0),
..default()
},
image: UiImage::new(cursor_handle),
z_index: ZIndex::Global(999),
..default()
},
CursorSprite,
));
}
Pitfall 3: CursorMoved Doesn’t Update UI Position
Fix: Update Style.left and Style.top from CursorMoved events.
Pitfall 4: macOS Retina Pixel Ratio
Symptom: Cursor position is off on Retina displays — the cursor reaches only half the screen.
Root cause: CursorMoved returns physical pixels, but UI Val::Px uses logical pixels. Divide by scale_factor:
#![allow(unused)]
fn main() {
let scale = window.scale_factor();
style.left = Val::Px(event.position.x / scale);
style.top = Val::Px(event.position.y / scale);
}
Pitfall 5: Cursor Disappears on Scene Transition
Fix: Use global entities (not scene-scoped) or re-spawn the cursor on scene entry.
Summary
| Pitfall | Symptom | Root Cause | Fix |
|---|---|---|---|
| 1 | Two cursors | CursorIcon has no None | Cursor.visible=false |
| 2 | Sprite invisible | UI layer covers sprites | ImageBundle + ZIndex::Global |
| 3 | Position stuck | Not updating UI Style | Update Style.left/top |
| 4 | Retina offset | Physical vs logical pixels | Divide by scale_factor |
| 5 | Lost on scene switch | Scene despawns entities | Re-spawn or keep global |
Bevy 0.14 FPS Camera Pitfalls: From Black Screen to Pointer Lock to Child Entity Camera
This article documents the real debugging chain of “cursor won’t move, camera won’t turn” when porting a Three.js FPS game to macOS with Rust + Bevy 0.14.
Pitfall Chain Overview
| # | Symptom | Root Cause | Fix |
|---|---|---|---|
| ① | Black screen in game | No 3D camera | Spawn Camera3dBundle |
| ② | UI stacked at top-left | No parent-child nesting | Use with_children |
| ③ | Panic index out of bounds | Empty Vec in InputState | Initialize vec![false; 256] |
| ④ | Cursor won’t move, camera won’t turn | CursorMoved stops firing after pointer lock | Use MouseMotion events |
| ⑤ | yaw/pitch updates but screen doesn’t rotate | Camera is a separate entity | Spawn camera as child of player |
| ⑥ | Buttons unresponsive | Changed<Interaction> filter misses events | Remove Changed, poll every frame |
④ Core Pitfall: CursorMoved Stops After Pointer Lock
Symptom: After entering the game scene with CursorGrabMode::Locked, mouse movement produces no camera rotation. Logs show no MouseMotion or CursorMoved events.
Root cause: When the pointer is locked, CursorMoved events stop firing because the OS no longer reports absolute cursor positions.
Fix: Use MouseMotion (DeviceEvent-level) which reports relative mouse displacement and is unaffected by pointer lock:
#![allow(unused)]
fn main() {
fn player_look(
mut mouse_motion: EventReader<MouseMotion>,
mut player_query: Query<&mut Player, With<PlayerFlag>>,
) {
for motion in mouse_motion.read() {
if let Ok(mut player) = player_query.get_single_mut() {
player.yaw -= motion.delta.x * 0.003;
player.pitch = (player.pitch - motion.delta.y * 0.003)
.clamp(-1.54, 1.54);
}
}
}
}
⑤ Camera as Child Entity
Fix: Spawn the camera as a child of the player entity:
#![allow(unused)]
fn main() {
fn spawn_player(mut commands: Commands) {
commands.spawn((
PlayerFlag,
SpatialBundle {
transform: Transform::from_xyz(0.0, 0.0, 0.0),
..default()
},
)).with_children(|parent| {
parent.spawn(Camera3dBundle {
transform: Transform::from_xyz(0.0, 1.6, 0.0),
..default()
});
});
}
}
⑥ Button Click Not Responding
Fix: Remove Changed<Interaction> filter, poll every frame:
#![allow(unused)]
fn main() {
fn button_handler(
query: Query<(&Interaction, &Btn)>,
mut next_state: ResMut<NextState<GameState>>,
) {
for (interaction, btn) in query.iter() {
if *interaction == Interaction::Clicked {
match btn {
Btn::Start => next_state.set(GameState::Deploy),
// ...
}
}
}
}
}
AtomCode Terminal Spinner Labels: What Are Those “Divining” and “Brewing” Words Really Saying?
1. Origin: Mistaken for Model Queueing
When using AtomCode for cross-file refactoring or full-repo search, the terminal status bar often shows Divining… 5m2s, Brewing… 3m10s and similar unfamiliar prompts. Many developers initially think these indicate model queueing delays.
In reality, these words are friendly status hints for the model’s reasoning phases. Real queueing uses distinct labels like Queued or Waiting for capacity.
2. Code Path: Where Do These Words Come From?
AtomCode’s spinner logic lives in the crates/atomcode-tuix terminal rendering module.
2.1 Vocabulary Definition
All gerunds are defined in the THINKING_LABELS constant in crates/atomcode-tuix/src/state.rs. It’s a shared vocabulary pool of 20 words, cycling sequentially per turn (not random).
2.2 Word Switching Logic
The current_thinking() method advances one word per Agent execution round, ensuring no repeats in consecutive tasks. The spinner is dynamically overridden based on execution phase — e.g., Running read_file during tool execution, Waiting approval during user confirmation, falling back to THINKING_LABELS during reasoning.
2.3 Status Bar Format
The Divining… 5m2s format is assembled in format_spinner_label in crates/atomcode-tuix/src/event_loop/mod.rs. The duration is formatted by fmt_dur in crates/atomcode-tuix/src/render/mod.rs.
3. Word Reference Table
Initial Reasoning Phase
| Word | Meaning | Model Behavior |
|---|---|---|
| Pondering | Deep thought | Analyzing task context |
| Reflecting | Self-review | Checking previous output |
| Analyzing | Breaking down | Decomposing subtasks |
| Synthesizing | Integration | Combining analysis into a plan |
| Reasoning | Logic chain | Multi-step inference |
Mid-Execution Phase
| Word | Meaning | Model Behavior |
|---|---|---|
| Thinking | Generic reasoning | Internal token generation |
| Processing | Handling intermediates | Organizing context |
| Computing | Calculation | Math or logic computation |
| Evaluating | Comparison | Selecting optimal path |
| Formulating | Planning | Generating structure blueprint |
Finalization Phase
| Word | Meaning | Model Behavior |
|---|---|---|
| Finalizing | Wrapping up | Generating final output |
| Reviewing | Quality check | Inspecting output quality |
| Polishing | Refinement | Optimizing expression |
| Verifying | Validation | Checking feasibility |
Metaphor/Fun Words
| Word | Meaning | Memory Anchor |
|---|---|---|
| Divining | Fortune-telling | Crystal ball |
| Brewing | Mixing ideas | Cauldron |
| Conjuring | Summoning knowledge | Magic wand |
| Crafting | Fine construction | Workbench |
| Cultivating | Iterative improvement | Gardening |
| Foraging | Searching context | Squirrel finding nuts |
4. Common Misconceptions
Misconception 1: “These words mean the model is queued”
Reality: Queueing has dedicated labels — Queued, Waiting for capacity. Spinner words only indicate active reasoning.
Misconception 2: “Many words = model is stuck”
Reality: Words advance sequentially. If the same word repeats, it means the model is stuck in the same reasoning round, not cycling through the vocabulary.
Misconception 3: “Verbose mode can backtrack”
Reality: Verbose mode (Ctrl+O) must be enabled before the reasoning phase. Switching it on mid-task only shows subsequent reasoning, not past output.
From AtomCode Free Tier to Ascend NPU: Connecting Free NPU to My Mac
Abstract
While monitoring the AtomCode free model countdown on ai.atomgit.com, I casually clicked the “Ascend Model Ecosystem” tab and discovered a hidden gem. This article documents how I turned the “Ascend Model Assistant” web page into a local Mac Agent backend — navigating container permission battles, pip dependency pitfalls, and network isolation challenges — ultimately running Qwen2.5-7B on a remote Ascend NPU and tunneling the OpenAI-compatible API back to the local Mac via SSH reverse tunnel.
Preface: The AtomCode “Refill” Routine
As a developer heavily reliant on AI-assisted coding, I’m well-versed in AtomCode’s rhythm:
- Lite tier: 30-day login refill, no rush. Just
/loginwhen due. - Pro tier: Need to compete for GLM-5.2. But note — you can’t refill while Pro is still active. Wait until it expires, then grab it the next morning at 10 AM.
The models accessible through AtomCode (DeepSeek-V4-Flash, GLM-5.2) don’t run on your Mac — they’re cloud API endpoints provided by AtomGit or the model vendors.
Pitfall 1: Container Environment Constraints
- Read-only home directory —
.ssh/configcan’t be written,pip installto user site fails. All temporary files go to/tmpor/opt/atomgit. - PEP 668 — Use
pip install --break-system-packageson Debian 12-based containers. - CANN/torch-npu environment variables —
ASCEND_HOMEandLD_LIBRARY_PATHmay not be fully loaded. SetASCEND_LOG_DIR=/opt/atomgit/ascend/log. - torch_dtype deprecation — Use string form
torch_dtype="torch.float16"instead oftorch_dtype=torch.float16.
Pitfall 2: Core Dependency Installation
pip install torch_npu==2.1.0.post1 --break-system-packages
Verify:
import torch
import torch_npu
print(torch.npu.is_available()) # Should be True
Pitfall 3: Network Isolation and SSH Reverse Tunnel
The container has no public IP, but SSH reverse tunnel exposes the service to localhost:
# Inside the container
ssh -R 8000:localhost:8000 user@your-mac-ip -N -f
Now localhost:8000 on the Mac connects to the inference service inside the container.
Result
- Local Mac uses zero memory for 7B model inference
- Remote Ascend NPU runs Qwen2.5-7B via SSH tunnel with OpenAI-compatible API
- Any local Agent tool can use this remote API
- Container expires? Re-apply — the NPU is free to refill
Debugging Black Screen on Mighty Rodent Splash Screen
Date: 2026-07-09 Project: rust-bevy-mighty-rodent(Bevy 0.14 rewrite of Mighty Rodent) Symptom: After
cargo run, the game window shows a black screen for ~2 seconds, then jumps straight to MainMenu — the two splash images (jaggedlogo.jpg/mainpic.jpg) never appear.
1. Symptoms
- Window appears → black screen for ~2s → MainMenu.
- Expected: jaggedlogo.jpg fully visible 1.5s → fade to black 0.8s → mainpic.jpg brighten from black + blue loading bar 2.0s → hold bright 0.5s → MainMenu.
2. Wrong Turns During Diagnosis (in chronological order)
Wrong Turn 1: Suspecting the splash system wasn’t compiled in
src/splash.rsappeared as??(untracked) ingit status, raising suspicion it wasn’t linked into the binary.- Verified:
cargo checkpasses,main.rs:4hasmod splash;,main.rs:105registerssplash::SplashPlugin— the code is indeed compiled in. - Conclusion: Not a compilation issue.
Wrong Turn 2: Suspecting OnEnter(default_state) doesn’t fire
AppState::SplashLogois the#[default]initial state, andshow_splash_logowas originally scheduled onOnEnter(SplashLogo).- Suspected that Bevy 0.14 doesn’t fire
OnEnterfor the default state at startup. - Fix: moved
show_splash_logofromOnEntertoStartup. - Verified: re-ran → still black screen.
- Conclusion: Moving to
Startupwas correct (kept), but it wasn’t the root cause.
Wrong Turn 3: Misled by log diagnostics
- Relied on
info!("Splash: Jaggedblade logo")to confirm the system executed. This line appeared 0 times in the log. - Inferred: “the splash system didn’t run” — this inference was wrong.
- Truth:
main.rs:93has.disable::<LogPlugin>(), which disables Bevy’s built-in logger. Butinfo!insrc/splash.rscomes frombevy::prelude::*(the Bevy logger), so it’s silenced entirely. Meanwhilemain.rs:120’slog::info!("Bevy app starting")uses thelogcrate (routed through simplelog), so it prints fine. - Gotcha:
bevy::prelude::*’sinfo!≠log::info!. Afterdisable::<LogPlugin>(), the former is silent while the latter works. When diagnosing Bevy systems that useinfo!, switch tolog::info!. - Verified: replaced all 4
info!calls in splash withlog::info!→ logs finally appeared. - Conclusion: It was a diagnostic blind spot, not a missing system.
Wrong Turn 4: Searching Bevy source for Camera2dBundle default transform
- Wanted to confirm whether the camera’s default z is positive (which would put z=0 sprites behind the camera).
- Grepped for
Camera2dBundlein~/.cargo/registry/src/.../bevy-0.14.2/crates/— no output. - After two rounds of searching, stopped per STOP WHEN STUCK.
- Conclusion: Don’t dig through Bevy source for API defaults; just print the runtime transform values from a diagnostic system.
3. Root Cause (Pinned Down by a Diagnostic System)
Added a temporary splash_diag_system (in Update, with a one-shot Local flag) inside SplashPlugin::build, using log::info! to print camera count, splash entity count, transforms, custom_size, and texture handle paths:
DIAG: cameras=1, splash_entities=2, splash_images=1
DIAG cam0: translation=Vec3(0.0, 0.0, 0.0) (z=0.00)
DIAG img0: translation=Vec3(0.0, 0.0, 0.0) (z=0.00) custom_size=Some(Vec2(800.0, 600.0)) path=gfx/jaggedlogo.jpg
Splash: Jaggedblade logo (Startup)
Splash: Main title picture (loading)
Facts established:
- The splash system executed (both stages ran).
- The camera was spawned at origin z=0, looking toward -Z (Camera2dBundle default).
- The splash image was spawned with the correct texture handle path (
gfx/jaggedlogo.jpg) and custom_size (800×600). - The image is at z=0, coincident with the camera plane.
Root cause: Bevy’s 2D camera near plane is slightly in front of z=0. A sprite at z=0 coincides with the camera plane and is not rendered by the 2D pipeline → black screen. All visible sprites must have negative z values (in front of the camera), with smaller z being farther away.
The original z values were all wrong:
| Sprite | Original z | Issue |
|---|---|---|
| Black background | -1.0 | OK (always in front of camera) |
| Splash images (logo + mainpic) | 0.0 | Coincident with camera plane, not rendered → black screen |
| Loading bar background | 0.5 | Positive value, behind camera, invisible |
| Loading bar fill | 1.0 | Same as above |
4. Fix
In src/splash.rs, changed all splash sprite z values to negative, ordered by layer from far to near:
| Sprite | New z | Layer |
|---|---|---|
| Black background | -1.0 (unchanged) | Farthest |
| Splash images | -0.5 | In front of background |
| Loading bar background | -0.4 | In front of images |
| Loading bar fill | -0.3 | Frontmost |
Code anchors:
spawn_splash: splash imageTransform::from_xyz(0.0, 0.0, -0.5)(was 0.0)show_splash_main: loading bar backgroundTransform::from_xyz(0.0, -280.0, -0.4)(was 0.5)show_splash_main: loading bar fillTransform::from_xyz(-200.0, -280.0, -0.3)(was 1.0)
5. Minor Changes
show_splash_logomoved toStartup(workaround for Bevy 0.14OnEnter(default_state)not firing) — kept, as it aligns with timing.- Replaced 4
info!calls withlog::info!in splash — kept, so diagnostics actually reach simplelog. - Diagnostic system
splash_diag_system— deleted, its mission is complete. - Loading bar color changed to green:
Color::srgb(0.0, 0.8, 0.0)(wasColor::srgb(0.0, 0.8, 1.0)blue).
6. Lessons Learned
| Gotcha | Takeaway |
|---|---|
bevy::prelude::*’s info! is silent after disable::<LogPlugin>() | When diagnosing whether a Bevy system executes, use log::info! (routed through simplelog), not info! |
| Bevy 2D camera looks toward -Z, near plane is in front of 0 | 2D sprite z must be negative to be visible; z=0 coincides with the camera plane and won’t render; positive values are behind the camera and invisible |
OnEnter(default_state) doesn’t fire in Bevy 0.14 | Use Startup for initialization systems on the default state, not OnEnter |
| Searching Bevy source for API defaults | Don’t dig through source; print runtime values from a diagnostic system |
| “No log output” → “System didn’t run” | First confirm the logging route (which logger is active) before inferring execution status |
7. Verification
cargo check → passed (only 14 pre-existing unrelated warnings)
cargo run → jaggedlogo.jpg fully visible → fade to black → mainpic.jpg brightens + green loading bar fills → MainMenu ✅
Co-Authored-By: AtomCode (GLM-5.2) noreply@atomgit.com
Red Alert 2 Mod Yellow Alert Main Menu Music — Suno AI Style Prompt
Date: 2026-07-12 Tags:
Suno AIPromptRed Alert 2Yellow AlertGame MusicModel: Suno AI v4.5-all
Overview
This document records the Suno AI Style Prompt for the main menu music of the Yellow Alert mod for Red Alert 2. It is designed for Suno AI v4.5-all model (free tier) and can be used to generate music in a similar style.
Prompt
Dance-pop at 124 BPM with a tight four-on-the-floor kick and smooth, steady club pulse. Verses drop into a half-time groove with clipped percussion and muted guitar chanks locking into the beat. Pre-chorus sections rise on filtered pad lifts and growing vocal layers, pushing into widescreen choruses with airy synth chords and glossy synth bass. Reversed swells shape transitions, bell tick accents sparkle between phrases, and sidechain shimmer breathes against the drums. Intimate close-mic lead vocal with doubled hook lines sits front and center in a radio-clean mix, enhanced by warm saturation and a late-night glow.
Notes
- Style: Dance-pop
- Tempo: 124 BPM
- Use case: Game main menu, background music
- AI Tool: Suno AI (v4.5-all, free model), fill in Style Prompt or similar field
License Notice
⚠️ Suno AI Non-Commercial Use Reminder
Music content in this repository generated with Suno AI is subject to the Suno AI license agreement — non-commercial use only. Do not use the generated audio files for commercial purposes (including but not limited to commercial game releases, commercial advertisements, paid subscription content, etc.). Refer to the official Suno AI license for detailed terms.
Co-Authored-By: AtomCode (deepseek-v4-flash) noreply@atomgit.com