# Guany's website > Website with projects, docs, and uses > Author: Guany > Source: https://guany.me > License: MIT > Updated: 2026-09-12 > Full text of all content below ## Docs --- # docs Guany's Documentation --- # macos MacOS ## Configure Proxy ```sh export https_proxy="http://127.0.0.1:7890" export http_proxy="http://127.0.0.1:7890" export all_proxy="socks5://127.0.0.1:7890" ``` ## Show Hidden Files ```sh defaults write com.apple.finder AppleShowAllFiles -bool true; killall Finder ``` ## Reset Launchpad ```sh rm -rf /private$(getconf DARWIN_USER_DIR)com.apple.dock.launchpad; killall Dock ``` ## Reset Dock ```sh defaults delete com.apple.dock killall Dock ``` ## Reset Notification Center ```sh defaults delete com.apple.notificationcenterui killall NotificationCenter ``` --- # linux Linux ## Configure Proxy ```sh export https_proxy="http://127.0.0.1:7890" export http_proxy="http://127.0.0.1:7890" export all_proxy="socks5://127.0.0.1:7891" ``` --- # ubuntu Ubuntu ## Configure Alibaba Cloud Mirror The commands below assume you are already running in a root shell. ### Back Up Configuration File Ubuntu 24.04 LTS uses the deb822-style `/etc/apt/sources.list.d/ubuntu.sources` by default. Ubuntu 22.04 LTS and older releases usually still use `/etc/apt/sources.list`. Ubuntu 24.04 LTS: ```sh cp -a /etc/apt/sources.list.d/ubuntu.sources /etc/apt/sources.list.d/ubuntu.sources.bak ``` Ubuntu 22.04 LTS and older: ```sh cp -a /etc/apt/sources.list /etc/apt/sources.list.bak ``` ### Alibaba Cloud Public Mirror Ubuntu 24.04 LTS: ```sh cat > /etc/apt/sources.list.d/ubuntu.sources <<'EOF' Types: deb URIs: https://mirrors.aliyun.com/ubuntu Suites: noble noble-updates noble-backports Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg Types: deb URIs: https://mirrors.aliyun.com/ubuntu Suites: noble-security Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg EOF apt update ``` Ubuntu 22.04 LTS and older: ```sh sed -i "s@http://.*archive.ubuntu.com@https://mirrors.aliyun.com@g" /etc/apt/sources.list sed -i "s@http://.*security.ubuntu.com@https://mirrors.aliyun.com@g" /etc/apt/sources.list apt update ``` ### Alibaba Cloud ECS VPC Mirror Ubuntu 24.04 LTS: ```sh cat > /etc/apt/sources.list.d/ubuntu.sources <<'EOF' Types: deb URIs: http://mirrors.cloud.aliyuncs.com/ubuntu Suites: noble noble-updates noble-backports Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg Types: deb URIs: http://mirrors.cloud.aliyuncs.com/ubuntu Suites: noble-security Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg EOF apt update ``` Ubuntu 22.04 LTS and older: ```sh sed -i "s@http://.*archive.ubuntu.com@http://mirrors.cloud.aliyuncs.com@g" /etc/apt/sources.list sed -i "s@http://.*security.ubuntu.com@http://mirrors.cloud.aliyuncs.com@g" /etc/apt/sources.list apt update ``` --- # windows Windows ## Configure Proxy ```powershell [System.Environment]::SetEnvironmentVariable("http_proxy", "http://127.0.0.1:7890", "User") [System.Environment]::SetEnvironmentVariable("https_proxy", "http://127.0.0.1:7890", "User") ``` ## Environment: Don't Use SetEnvironmentVariable on PATH Environment variables live in two registry scopes: ``` HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment machine HKCU\Environment user ``` They merge at logon, and **PATH is the special case: machine first, user appended** (every other variable has the user scope override the machine one). The value type decides whether `%VAR%` expands at all: | Type | Behaviour | | ---------------------------------------------- | ----------------------------------------------------------------- | | `REG_EXPAND_SZ` (`ExpandString` in PowerShell) | `%USERPROFILE%\bin` expands | | `REG_SZ` (`String`) | `%USERPROFILE%` is treated as a **literal directory name** — dead | **`[System.Environment]::SetEnvironmentVariable` writes `REG_SZ`.** One call on PATH bakes every `%VAR%` reference into an absolute path — that is exactly why "editing an environment variable expanded everything". The GUI editor in System Properties does the same. Specify the type explicitly instead: ```powershell # ❌ writes REG_SZ [Environment]::SetEnvironmentVariable("Path", $v, "User") # ✅ Set-ItemProperty -Path "HKCU:\Environment" -Name Path -Value $v -Type ExpandString ``` Read the **unexpanded** value (otherwise what you see is already resolved): ```powershell (Get-Item "HKCU:\Environment").GetValue("Path", "", "DoNotExpandEnvironmentNames") ``` Broadcast afterwards so running processes pick it up — new processes only, already-open terminals will not change: ```powershell $sig = @' [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)] public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult); '@ $t = Add-Type -MemberDefinition $sig -Name Win32 -Namespace Env -PassThru [UIntPtr]$r = [UIntPtr]::Zero $t::SendMessageTimeout([IntPtr]0xffff, 0x1A, [UIntPtr]::Zero, "Environment", 2, 5000, [ref]$r) ``` ### Use variables where variables belong The stock machine PATH is written with variables; a machine whose PATH has been through the GUI editor ends up with hardcoded absolute paths. Restoring them means a JDK or CUDA upgrade only touches one variable: ``` %SystemRoot%\system32 %SystemRoot%\System32\Wbem %SystemRoot%\System32\WindowsPowerShell\v1.0\ %JAVA_HOME%\bin %CUDA_PATH%\bin ``` Same for the user scope — everything under `%USERPROFILE%\…`. **Do not convert `Program Files` to `%ProgramFiles%`**, though: inside a 32-bit process it expands to `Program Files (x86)`, which causes bugs that are miserable to track down. Stock Windows only uses variables for system directories. Use `%CUDA_PATH%` rather than `%CUDA_PATH_V12_9%`: the former points at the active version, the latter is a version-pinned alias and defeats the purpose. Always compare the **expanded** value before and after, and roll back on any mismatch: ```powershell $before = [Environment]::GetEnvironmentVariable("Path","Machine") # …edit… $after = [Environment]::GetEnvironmentVariable("Path","Machine") if ($before.TrimEnd(";") -ne $after.TrimEnd(";")) { "mismatch, rolling back" } ``` ### Installers will revert it Fixing it once does not make it stick. `[Environment]::SetEnvironmentVariable` is the most convenient API in .NET, so third-party installers reach for it whenever they touch the user PATH — and even a harmless "read it, confirm we are already listed, write it back unchanged" is enough to drop the type to `REG_SZ` and bake every `%VAR%` into a literal. Checking the **user** scope is usually enough; machine-scope installers rarely touch it: ```powershell $uk = "HKCU:\Environment" (Get-Item $uk).GetValueKind("Path") # expect ExpandString (Get-Item $uk).GetValue("Path","","DoNotExpandEnvironmentNames") # expect %USERPROFILE% ``` To find the culprit, line the registry key's last-write time up against recently installed program directories — they are usually a minute or two apart: ```powershell Get-ChildItem "$env:LOCALAPPDATA\Programs" -Directory | Sort-Object LastWriteTime -Descending | Select-Object -First 5 LastWriteTime, Name ``` Rather than repairing it by hand every time, drop a self-heal into `$PROFILE` so a new shell fixes it. Only the representation changes — the expanded value stays the same, which makes it idempotent — and the usual cost is a single registry read: ```powershell $__uk = "HKCU:\Environment" $__raw = (Get-Item $__uk -ErrorAction SilentlyContinue).GetValue("Path", "", "DoNotExpandEnvironmentNames") if ($__raw) { $__kind = (Get-Item $__uk).GetValueKind("Path") if ($__kind -ne "ExpandString" -or $__raw -like "*$env:USERPROFILE\*") { $__fixed = (($__raw -split ";") | Where-Object { $_ } | ForEach-Object { if ($_.StartsWith("$env:USERPROFILE\", [StringComparison]::OrdinalIgnoreCase)) { "%USERPROFILE%\" + $_.Substring($env:USERPROFILE.Length + 1) } else { $_ } }) -join ";" if ([Environment]::ExpandEnvironmentVariables($__fixed) -eq [Environment]::ExpandEnvironmentVariables($__raw).TrimEnd(";")) { Set-ItemProperty -Path $__uk -Name Path -Value $__fixed -Type ExpandString } } } ``` Do not drop the "only write if the expanded value matches" guard — it is what stops the rewrite from touching an entry whose literal home-directory path was deliberate. ## "Untrusted mount point" in SSH Sessions On Windows 11 24H2 and later, running certain commands after logging in over SSH fails with: ``` Cannot traverse the path because it contains an untrusted mount point. Program 'fnm.exe' failed to run: An error occurred trying to start process 'C:\Users\\AppData\Local\Microsoft\WinGet\Links\fnm.exe' ``` This generation of Windows **tightened how SSH sessions traverse reparse points** (symbolic links and junctions). A terminal opened locally is unaffected, so the problem only shows up over ssh. ### Confirming it is this The phrase "untrusted mount point" is the whole diagnosis — no need to look elsewhere. The decisive variable is the **OS version**, not configuration: | | Windows 10 22H2 · 19045 | Windows 11 25H2 · 26200 | | ----------------------------------------- | ----------------------- | ----------------------- | | SSH logon token | NETWORK | NETWORK | | `fsutil behavior query SymlinkEvaluation` | L2L/L2R on, R2L/R2R off | identical | | Developer Mode | on | on | | WinGet symlinks traversable | **yes** | **no** | Three settings identical, only the OS differs — so nothing is misconfigured. **Two dead ends worth skipping**: `fsutil behavior set SymlinkEvaluation R2L:1` does nothing here (L2L/R2L describe whether the link and its target are local or remote paths; with both on C: it is L2L, which is already enabled). The logon token type is not the cause either — the Win10 box hands out a NETWORK token too and works fine. ### Three fixes **1. Junction blocked → rebuild it with the native API.** Junctions made by `mklink /J` traverse fine; ones written by a language runtime's own reparse-data code may not: ```powershell cmd /c rmdir "" # no /s — removes the link, not the target cmd /c mklink /J "" "" ``` **2. WinGet shim blocked → put the real package directory ahead on PATH.** Everything under `WinGet\Links` is a symlink; point at `WinGet\Packages\` instead: ```powershell $base = "$env:LOCALAPPDATA\Microsoft\WinGet\Packages" $real = "$base\Schniz.fnm_Microsoft.Winget.Source_8wekyb3d8bbwe" # Read the User scope, never $env:Path — that is Machine + User already merged, # and writing it back copies the machine PATH into the user one, growing it every time $u = [Environment]::GetEnvironmentVariable("Path", "User") [Environment]::SetEnvironmentVariable("Path", "$real;$u", "User") ``` **3. Do installs and upgrades locally or over RDP.** pnpm builds `node_modules` out of a large number of junctions, and in an SSH session it cannot read back the links it just created, so the install is bound to fail. Running already-installed software is unaffected. Switching to password authentication is not worth it — it does yield a full token, but at the cost of passwordless login. --- # wsl WSL ## Configure Proxy ```sh export hostip=$(cat /etc/resolv.conf | grep -oP '(?<=nameserver\ ).*') export http_proxy="http://$hostip:7890" export https_proxy="http://$hostip:7890" ``` --- # Surge Network proxy and rule engine for macOS / iOS. Use `surge-cli` for status checks and policy tweaks; agents can use the bundled Skill for scripted operations. ## surge-cli Resolve the executable in this order: 1. `surge-cli` on `PATH` 2. `/Applications/Surge.app/Contents/Applications/surge-cli` Prefer `--raw` for JSON output. Add `--remote password@host:port` when targeting a remote Surge instance. ## Common Commands Inspect runtime environment: ```sh surge-cli --raw environment ``` Dump policy and profile snapshots: ```sh surge-cli --raw dump policy surge-cli --raw dump profile ``` Apply runtime changes (dump first, then verify with `environment`): ```sh surge-cli --raw set ProxyMode=2 surge-cli --raw set ProxyGroupSelection.Proxy=HK surge-cli --raw set AutoPolicyGroupOverride.Streaming= ``` ## Agent Skill Surge ships an Agent Skill inside the app bundle: `/Applications/Surge.app/Contents/Resources/Skills/surge` On this machine, personal skills follow the same layout as [Claude Code](./claude-code): **`~/.agents/skills` holds content**, and **`~/.claude/skills` symlinks into it**. Link to the bundle so the skill updates when Surge is upgraded. ```sh ln -sfn "/Applications/Surge.app/Contents/Resources/Skills/surge" "$HOME/.agents/skills/surge" ln -sfn "../../.agents/skills/surge" "$HOME/.claude/skills/surge" ``` Verify: ```sh test -f "$HOME/.claude/skills/surge/SKILL.md" && echo "Surge skill OK" ``` Notes: - You do not need to edit `~/.agents/.skill-lock.json`; that file tracks skills installed via `npx skills add` from GitHub only. - Cursor loads the same personal skills from `~/.claude/skills`. If you use `~/.cursor/skills`, link the same way: `ln -sfn "../../.agents/skills/surge" "$HOME/.cursor/skills/surge"`. ## Proxy Port Other local tools ([macOS proxy env](./macos), [WSL proxy env](./wsl)) often use **7890** for HTTP/SOCKS; keep that aligned with Surge’s HTTP/SOCKS5 listener. --- # zsh Zsh ## Installation ```sh sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" ``` ```sh cd ~/.oh-my-zsh/plugins ``` ```sh gcl https://github.com/zsh-users/zsh-autosuggestions.git ``` ```sh gcl https://github.com/zsh-users/zsh-syntax-highlighting.git ``` ```sh cd ~ ``` ```sh curl -sS https://starship.rs/install.sh | sh ``` ## Usage ```sh plugins=( command-not-found zsh-autosuggestions zsh-syntax-highlighting ) ``` The `git` plugin is left out — it defines 197 aliases in one go where only a handful ever get used, so those are written by hand instead, see the git page. Same for `zsh-z`: the `i` function covers the jumping. ```sh eval "$(starship init zsh)" ``` ```sh i() { cd ~/i/$1 } ``` ## Aliases `la` is the only ls alias kept identical across machines. Git ones live on the git page. ```sh alias la='ls -lAh' # long format + hidden entries ``` Ubuntu's `.bashrc` ships `la='ls -A'` (hidden only, short format), which means something else. Edit it **in place** — comment out the distro default and put the new value right below: ```sh # some more ls aliases alias ll='ls -alF' # alias la='ls -A' alias la='ls -lAh' alias l='ls -CF' ``` Appending at the end of the file works too (the last definition wins), but editing in place is what makes the change visible — otherwise the next reader assumes the distro default is still in effect. **`ll` and `l` are deliberately left as each distro ships them.** oh-my-zsh gives `ll='ls -lh'` and `l='ls -lah'`; Ubuntu gives `ll='ls -alF'` and `l='ls -CF'` — genuinely inconsistent, but with `la` being the only one actually typed, aligning them buys nothing. Cross-machine consistency exists so that switching machines never surprises you, and that only happens on commands you reach for; aligning unused aliases helps no one and the change itself is noise. oh-my-zsh defines `la` already, so there is nothing to add where it is installed. ## Load Order ``` ① ~/.zshenv every zsh, including scripts, cron and LaunchAgents ② /etc/zprofile ← macOS runs path_helper here ③ ~/.zprofile login shells ④ /etc/zshrc ⑤ ~/.zshrc interactive only ``` ### path_helper reorders PATH macOS ships this in `/etc/zprofile`: ```sh if [ -x /usr/libexec/path_helper ]; then eval `/usr/libexec/path_helper -s` fi ``` It moves everything from `/etc/paths` and `/etc/paths.d/*` **to the front**, pushing the user directories set in `~/.zshenv` behind `/usr/bin`: ``` ~/.zshenv only: ~/.local/bin ~/.cargo/bin /opt/homebrew/bin … after .zprofile: /opt/homebrew/bin /usr/local/bin /usr/bin … ~/.local/bin ``` **So on macOS, relying on `~/.zshenv` for PATH priority does not hold.** `.zshenv` only guarantees that scripts can find things; the actual priority has to be re-established after `path_helper` runs. ### Non-interactive login shells degrade silently If the priority lives only in `.zshrc`, the same command resolves differently depending on the shell — because `.zshrc` is never read non-interactively: ```sh zsh -lic 'command -v python3; command -v tar' # interactive: uv's python, GNU tar zsh -lc 'command -v python3; command -v tar' # non-interactive: homebrew python, bsdtar ``` `ssh host 'command'`, LaunchAgents and CI all take the latter path. GNU tar and bsdtar differ on `--wildcards` and `--transform`, so a command that works interactively can fail once it lands in a script. **The fix is to put the priority block in `~/.zprofile`** — it runs after `path_helper`, and both interactive and non-interactive login shells read it: ```sh typeset -U path fpath path=( "$HOME/.local/bin" "$HOME/.local/share/mise/shims" # language runtime fallback "${HOMEBREW_PREFIX:-/opt/homebrew}/opt/gnu-tar/libexec/gnubin" $path ) ``` To cover `zsh -c` scripts as well, add the same entries to `~/.zshenv` (`typeset -U` dedupes). The mise shims belong here for the same reason: `mise activate` lives in `.zshrc` and never runs non-interactively. Shims resolve the version for the current directory themselves, so per-project switching still works without activation. See the mise page. ## Command Shadowing When the same executable name exists in several PATH directories, only the first one wins. To list every duplicate: ```sh echo $PATH | tr ':' '\n' | while read -r d; do find "$d" -maxdepth 1 -type f -perm -u+x 2>/dev/null | while read -r f; do echo "$(basename "$f")|$d" done done | sort -t'|' -k1,1 | awk -F'|' '$1==p{print $1" <- "$2} {p=$1}' ``` Count **executable files only** — directory symlinks (such as gnu-tar's `gnuman`) also carry the execute bit and produce false positives. ### uv's python and pip must be linked together If `~/.local/bin` holds only `python`/`python3`, then `pip3` falls through to Homebrew, and packages installed by `pip3 install` are invisible to `python3`: ```sh python3 -m pip --version # ~/.local/share/uv/python/.../site-packages/pip pip3 --version # /opt/homebrew/lib/python3.14/site-packages/pip ← different ``` uv's python directory already ships pip; just add the links: ```sh base="$HOME/.local/share/uv/python/cpython-3.14-macos-aarch64-none/bin" for f in pip pip3 pip3.14; do ln -s "$base/$f" ~/.local/bin/$f; done ``` ### Upstreams compete for the same command name Cursor's CLI binary is literally called `agent` and installs into `~/.local/bin`; the Grok installer creates both `grok` and `agent` in `~/.grok/bin` as symlinks to the same binary. Both claim `agent`, and whichever comes first on PATH wins. Prefer keeping the one that **has only that name** — Cursor's `agent` is gone if shadowed, while Grok's `agent` is merely an alias for `grok` and costs nothing to lose. ## Keep Secrets Out of the Environment `export`ing a private key from `.zshrc` makes it readable by **every child process** — npm postinstall scripts, CLI crash reporters and agent env dumps all carry it along. Inject it on demand instead, so the key only exists for the duration of the wrapped command: ```sh tauri-sign() { local k="$HOME/.tauri/tauri.key" p="$HOME/.tauri/tauri.pass" [ -r "$k" ] || { print -u2 "tauri-sign: missing $k"; return 1 } [ -r "$p" ] || { print -u2 "tauri-sign: missing $p"; return 1 } [ $# -gt 0 ] || { print -u2 "usage: tauri-sign [args...]"; return 2 } TAURI_SIGNING_PRIVATE_KEY="$(<"$k")" \ TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$(<"$p")" \ "$@" } ``` ```sh tauri-sign nr build env | grep -c '^TAURI_SIGNING' # 0 the rest of the time ``` Keep the key files at `chmod 600` and the directory at `chmod 700`. ## config [⚙︎ Guany config](https://github.com/guanyme/config) --- # powershell PowerShell ## Installation ```powershell winget install --id Microsoft.PowerShell ``` ```powershell winget install --id Starship.Starship ``` ```powershell winget install gerardog.gsudo ``` ## Usage ```powershell Set-PSReadlineKeyHandler -Key Tab -Function MenuComplete ``` ```powershell # la — matching the Unix convention: long format + hidden entries. # It has to be a function — a PowerShell alias cannot carry a fixed argument # (-Force here). And aliases outrank functions, so the existing Set-Alias la # has to go first Remove-Item Alias:la -Force -ErrorAction Ignore function la { Get-ChildItem -Force @args } ``` `-Force` is the counterpart to Unix `-A`: it makes `la` list hidden and system entries. No `ll` is defined — PowerShell never had one, and cross-platform alignment only covers the command actually typed. ```powershell function i { param ( [string]$DirectoryName ) Set-Location -Path "$HOME\i\$DirectoryName" } ``` Git no longer goes through the `posh-git` / `git-aliases` modules — the functions are defined by hand instead, see the git page. ## Speeding Up Startup Nearly all of the profile's cost is spawning subprocesses. Measured medians of `pwsh -Command "exit"`: | | Time | | ---------------------------- | ------ | | `pwsh -NoProfile` (baseline) | 151 ms | | Before | 630 ms | | After | 524 ms | ### starship gets launched twice `starship init powershell` prints exactly one line: ```powershell Invoke-Expression (& 'C:\Program Files\starship\bin\starship.exe' init powershell --print-full-init | Out-String) ``` So running it launches **starship a second time**. Ask for the full script directly and cache it to a file, which removes that whole round trip: ```powershell $__cacheDir = "$HOME\.cache\pwsh" if (-not (Test-Path $__cacheDir)) { New-Item -ItemType Directory $__cacheDir -Force | Out-Null } $__f = "$__cacheDir\starship.ps1" $__src = (Get-Command starship -ErrorAction SilentlyContinue).Source if ($__src -and ((-not (Test-Path $__f)) -or (Get-Item $__src).LastWriteTime -gt (Get-Item $__f).LastWriteTime)) { starship init powershell --print-full-init | Out-String | Set-Content $__f -Encoding utf8 } if (Test-Path $__f) { . $__f } ``` Regeneration keys off the binary's `LastWriteTime`, so a `winget upgrade` refreshes the cache on its own — no manual clearing. **The dot-source has to sit at the top level of the profile.** Wrap this in a function and `. $__f` only applies inside that function's scope: the prompt never reaches global scope, and the symptom is "the cache ran but the prompt didn't change". mise's completion script is cached the same way, but it **must come after `mise activate`** — the completions shell out to `usage` at runtime, and `usage` is itself a mise-managed tool that is not on PATH before activation, so every new shell would print `usage CLI not found`. See the mise page. ### The part that cannot be reduced The single `Set-PSReadlineKeyHandler` line costs about 183 ms, which is really the **first load of the PSReadLine module**, not the key binding. An interactive session loads that module anyway, so moving or deferring the line just pushes the cost to the first keystroke — it does not feel faster. `mise activate` cannot be cached either: it has to run every time to resolve versions for the current session and install the directory-change hook. ## powershell-profile [⚙︎ Guany Powershell profile](https://github.com/guanyme/powershell-profile/) --- # Ghostty Ghostty terminal emulator ## Settings Config file location: `~/Library/Application Support/com.mitchellh.ghostty/config.ghostty` ```ini font-family = "FiraCode Nerd Font" font-family-bold = "FiraCode Nerd Font" font-family-italic = "FiraCode Nerd Font" font-family-bold-italic = "FiraCode Nerd Font" font-size = 16 font-feature = calt font-feature = liga theme = light:vitesse-light,dark:vitesse-dark custom-shader = ~/.config/ghostty/shaders/cursor_warp.glsl custom-shader = ~/.config/ghostty/shaders/ripple_cursor.glsl custom-shader-animation = always ``` ## Theme Install [vitesse-ghostty-theme](https://github.com/hamlim/vitesse-ghostty-theme): ```sh git clone https://github.com/hamlim/vitesse-ghostty-theme.git ~/.config/ghostty/themes ``` ## Shader Install [ghostty-cursor-shaders](https://github.com/sahaj-b/ghostty-cursor-shaders): ```sh git clone https://github.com/sahaj-b/ghostty-cursor-shaders.git ~/.config/ghostty/shaders ``` ## Reset Window Size Delete the window position cache from plist, then restart Ghostty: ```sh defaults delete com.mitchellh.ghostty NSWindowLastPosition ``` ## SSH Terminfo Fix `'xterm-ghostty': unknown terminal type.` on remote servers: ```sh infocmp xterm-ghostty | ssh user@host 'tic -x -' ``` --- # warp Warp ## Theme [warp-theme-vitesse](https://github.com/HiDeoo/warp-theme-vitesse) **macOS** ```sh mkdir -p $HOME/.warp/themes git clone https://github.com/HiDeoo/warp-theme-vitesse.git /tmp/warp-theme-vitesse cp /tmp/warp-theme-vitesse/*.yaml $HOME/.warp/themes/ ``` **Windows** ```powershell New-Item -Path "$env:APPDATA\warp\Warp\data\themes" -ItemType Directory -Force git clone https://github.com/HiDeoo/warp-theme-vitesse.git "$env:TEMP\warp-theme-vitesse" Copy-Item "$env:TEMP\warp-theme-vitesse\*.yaml" "$env:APPDATA\warp\Warp\data\themes\" ``` ## Reset Command History **macOS** ```sh rm -r "$HOME/Library/Group Containers/2BBY89MBSN.dev.warp/Library/Application Support/dev.warp.Warp-Stable/warp.sqlite" ``` **Windows** ```powershell Remove-Item "$env:LOCALAPPDATA\warp\Warp\data\warp.sqlite" ``` --- # windows-terminal Windows Terminal ## profiles.defaults ```json { "bellStyle": "none", "colorScheme": "Vitesse Dark", "font": { "face": "FiraCode Nerd Font" } } ``` ## profiles.list ```json [ { "commandline": "C:\\Program Files\\PowerShell\\7\\pwsh.exe -NoLogo" } ] ``` ## schemes ```json { "background": "#121212", "black": "#393A34", "blue": "#6394BF", "brightBlack": "#777777", "brightBlue": "#6394BF", "brightCyan": "#5EAAB5", "brightGreen": "#4D9375", "brightPurple": "#D9739F", "brightRed": "#CB7676", "brightWhite": "#FFFFFF", "brightYellow": "#E6CC77", "cursorColor": "#CDC9BD", "cyan": "#5EAAB5", "foreground": "#CDCABE", "green": "#4D9375", "name": "Vitesse Dark", "purple": "#D9739F", "red": "#CB7676", "selectionBackground": "#252525", "white": "#CDCABE", "yellow": "#E6CC77" } ``` --- # Herdr A terminal multiplexer built for coding agents. It organises terminals into workspaces, tabs and panes, recognises the agent running inside a pane, and exposes the live session through the `herdr` CLI — that last part is the real difference from tmux: an agent can open its own pane, dispatch a command, and read the output back. Website: [herdr.dev](https://herdr.dev) ## Install macOS / Linux: ```sh curl -fsSL https://herdr.dev/install.sh | sh ``` Windows: ```powershell powershell -ExecutionPolicy Bypass -c "irm https://herdr.dev/install.ps1 | iex" ``` The binary lands in `~/.local/bin/herdr`. Note that **a non-login shell usually does not have that directory on `PATH`** — `command -v herdr` over ssh will come back empty even when it is installed. Don't read that as "not installed": ```sh ssh myhost 'command -v herdr' # may be empty ssh myhost 'export PATH=$PATH:~/.local/bin; herdr --version' # this is the real check ``` Updates and channels: ```sh herdr update herdr channel show # stable / preview herdr channel set preview ``` ## Config `~/.config/herdr/config.toml`: ```toml onboarding = false [ui] agent_panel_sort = "priority" [theme] name = "terminal" auto_switch = false [ui.toast] delivery = "system" ``` The same directory holds `session.json` (persisted layout), `herdr.sock` (API socket) and `herdr-server.log`. ### Point it at pwsh on Windows A pane's shell defaults to `$SHELL`, which on Windows lands on the built-in **Windows PowerShell 5.1** rather than PowerShell 7. To get 7 inside panes you have to say so: ```toml [terminal] default_shell = "pwsh.exe" ``` The documented behaviour is "when unset or empty, Herdr uses `$SHELL`, then `/bin/sh` on Unix and PowerShell on Windows" — and that Windows fallback is the **built-in 5.1**. The value is an executable name or path, not a shell command line. Run `herdr server reload-config` afterwards, or just open a new pane. To check what you're actually in: ```powershell $PSVersionTable.PSVersion # 5.1.x means it's the old one ``` Install 7 first if needed: `winget install --id Microsoft.PowerShell`. Note also that 5.1 and 7 have separate `$PROFILE` files (`WindowsPowerShell\` vs `PowerShell\`), so anything configured in the old one does not carry over. ### The other two [terminal] options `shell_mode` — `"auto"` (default) / `"login"` / `"non_login"`, controlling whether a new pane's shell starts as a login shell. The documentation spells out the reason: **`"auto"` starts login shells on macOS so login-only PATH setup runs in new panes** — things like `/usr/libexec/path_helper` and Homebrew's shell initialisation. Worth remembering: on macOS `path_helper` reorders the system paths to the front, so "the PATH inside a pane differs from the one in my terminal" usually comes down to `shell_mode`. `new_cwd` — `"follow"` (default) / `"home"` / `"current"` / a fixed path such as `"~/Projects"`. `"follow"` inherits the source pane or workspace; with no source, Herdr starts in `$HOME`. Validate with herdr's own checker rather than by eye: ```sh herdr config check # only "config: ok" counts ``` ## CLI Running bare `herdr` launches or attaches the TUI, so **don't use it to explore commands**. Print a command group instead: ```sh herdr --help herdr pane # prints the pane command group herdr tab herdr workspace herdr agent ``` Most commands return JSON. Read pane / tab / workspace ids out of the response rather than guessing them. ### Running a command in a pane ```sh # split to the right without stealing focus herdr pane split --current --direction right --cwd "$PWD" --no-focus # → .result.pane.pane_id # dispatch, await, collect herdr pane run "pnpm build" herdr pane wait-output --regex "" --source visible --timeout 60000 herdr pane read --source visible --lines 40 ``` Herdr injects the caller's context into every managed pane: ```sh printf '%s\n' "$HERDR_WORKSPACE_ID" "$HERDR_TAB_ID" "$HERDR_PANE_ID" ``` `HERDR_ENV=1` means you are currently inside a herdr pane. ## Three gotchas found the hard way ### The pane is an interactive TTY, so pagers kick in `git log`, `git diff`, `systemctl status` and friends drop into `less` and sit there. The trailing `&& echo DONE` never runs, so `wait-output` just times out. ```sh herdr pane run "git --no-pager log --oneline -3 && echo DONE" # or prefix with PAGER=cat ``` ### Completion markers must be unique per invocation `wait-output` **searches the existing snapshot immediately**, so a fixed marker matches leftover output from the previous command and reports a hit straight away. That is worse than a timeout: a timeout at least raises an error, a false positive convinces you the command finished. ```sh TAG="DONE_$$_$RANDOM" herdr pane run "pnpm test && echo ${TAG}_OK || echo ${TAG}_FAIL" herdr pane wait-output --regex "${TAG}_(OK|FAIL)" --source visible --timeout 120000 ``` There is also no exit code coming back from a pane — success and failure have to be printed by the command itself. ### Read output with `--source visible` `recent` / `recent-unwrapped` frequently come back with zero bytes. Don't use them to decide whether a command produced output: ``` --source visible 99 bytes --source recent 0 bytes --source recent-unwrapped 0 bytes ``` ## Running it under systemd (servers) Herdr's `session.json` restores **the layout, cwds and pane labels — but not the commands that were running in those panes**; what comes back is a clean shell. So autostart needs two layers: one to bring up the server, one to launch the services into their panes. `/etc/systemd/system/herdr.service`: ```ini [Unit] Description=Herdr headless server After=network-online.target Wants=network-online.target [Service] Type=simple User=root Environment=HOME=/root Environment=PATH=/root/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin Environment=TERM=xterm-256color # Must be set explicitly: systemd does not take the login shell from passwd and # falls back to bash, so panes end up without zsh and without the starship prompt # configured in .zshrc Environment=SHELL=/usr/bin/zsh Environment=LANG=en_US.UTF-8 ExecStart=/root/.local/bin/herdr server ExecStop=/root/.local/bin/herdr server stop Restart=on-failure RestartSec=3 TimeoutStopSec=60 KillMode=mixed [Install] WantedBy=multi-user.target ``` `herdr server` is described upstream as the headless server; it needs no TTY. Then a oneshot unit that launches the services once the server is up. **Locate panes by label, not by pane id** — ids change across restarts, labels don't: ```sh find_pane() { # usage: find_pane