macos-rebuild-dev
GitHub用于本地开发环境,通过脚本终止正在运行的 termio-dev 应用及其隧道,使用 SwiftPM 重新构建隔离的 dev bundle,并重新启动前台应用,确保不干扰日常使用的 release 版本。
Trigger Scenarios
Install
npx skills add termio-sh/termio --skill macos-rebuild-dev -g -y
SKILL.md
Frontmatter
{
"name": "macos-rebuild-dev",
"description": "Kill the running termio dev app, rebuild it with SwiftPM, and relaunch it as a foreground app. Invoke when the user says 'rebuild', 'rebuild app', 'restart app', 'relaunch', '重新编译', or '重启 app'."
}
Rebuild App (local development)
Kill the running dev build, rebuild it as the isolated termio-dev.app bundle,
and relaunch — so every rebuild reflects the current Dock icon and, crucially, does
not disturb an installed release build you use daily.
The dev channel
termio ships a TERMIO_CHANNEL=dev build that is a fully separate app from the
release one, so both can run at the same time:
| release | dev (this skill) | |
|---|---|---|
| Bundle | termio.app (usually /Applications) |
termio-dev.app (repo root) |
| Bundle id | sh.termio.app |
sh.termio.app.dev |
| State + sockets | ~/Library/Application Support/termio |
…/termio-dev |
| User config | ~/.termio |
~/.termio-dev |
| Daemon socket | $TMPDIR/termiod-<uid> |
…-dev |
| Daemon launchd job | sh.termio.termiod |
sh.termio.termiod.dev |
| Companion port | 8787 | 8788 |
| CLI on PATH | termio |
termio-dev |
| Sparkle auto-update | on | no update feed (dev never self-updates) |
All of this falls out of the .dev bundle-id suffix via Sources/termio/Companion/AppChannel.swift
(paths + port) and scripts/build-app.sh (id, name, SUFeedURL deletion, CLI rebind).
Sparkle.framework itself is embedded on both channels — the binary links it either
way — so a dev bundle still carries it; it just has nothing to check.
termio is a plain SwiftPM executable (Package.swift → executableTarget named
termio); swift build alone produces a bare binary with no Dock icon. So this
skill builds the real bundle via scripts/build-app.sh. For a dev build the script
auto-picks a real codesigning identity from your own keychain (any "Apple
Development" / "Developer ID Application" cert — a free Apple ID gives you one) so the
dev app can post macOS notifications; usernoted rejects an ad-hoc signature outright,
so an unsigned dev build can never banner. A contributor with no signing cert
falls back to ad-hoc automatically — the build still succeeds, they just don't get
notifications. Set SIGN_IDENTITY=… to force a specific one. The app runs
unsandboxed with .exec PTYs — see CLAUDE.md. The icon is packaging/icon-static.svg,
rasterized to packaging/AppIcon.png by scripts/render-icon.sh (needs headless
Chrome). Note: dev and release currently share the same icon art and differ only by
name ("termio dev") — a tinted dev icon is a possible follow-up.
Instructions
When invoked, execute these steps sequentially:
-
Kill only the running dev app + its tunnel. Both channels' inner binary is named
termio(CFBundleExecutable), sopkill -x termiowould kill the release app too. Match the dev bundle path instead. SIGKILL skips the app'swillTerminatecleanup, so also reap the companion tunnel it spawned on the dev port (8788):pkill -9 -f "termio-dev.app/Contents/MacOS/termio" || true pkill -9 -f "cloudflared tunnel --url http://127.0.0.1:8788" || true pkill -9 -f "tunelo port 8788" || true -
Build the dev bundle from the committed
packaging/AppIcon.png— do not re-render the icon (see "Icons" below). Show the tail of the output; if the build fails, show the error and stop — do NOT relaunch:TERMIO_CHANNEL=dev ./scripts/build-app.sh 2>&1 | tail -12 -
Re-register the dev bundle with LaunchServices, then refresh the Dock cache. macOS resolves an app's icon by its bundle id (
sh.termio.app.dev);lsregister -fforces our path to win andkillall Dockdrops the cached icon:LSREG="/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister" "$LSREG" -f "$PWD/termio-dev.app" touch ./termio-dev.app killall Dock 2>/dev/null || true -
Relaunch via
open(NOTnohupon the inner binary — that bypasses LaunchServices).open's--stdout/--stderrstill capture logs:open ./termio-dev.app --stdout /tmp/termio-dev.log --stderr /tmp/termio-dev.log echo "launched ./termio-dev.app (logs: /tmp/termio-dev.log)" -
Upgrade the running dev daemon.
build-app.shalready rebuilt the daemon intoResources/termiod, but a running dev daemon keeps its old image: a dev app's launch-time reconcile deliberately refuses to stage (its bundle stamp is a placeholder, and that guard is what keeps a dev build from ever deploying itself over a release daemon). Without this step, a termiod-side change gets "verified" against the previous daemon. The deploy is idempotent — same version reportscurrentand does nothing, so it costs nothing on Swift-only rebuilds — and an execve handoff keeps the pid and carries every live session.TERMIOD_SOCKmust be unset first: a shell inside a termio session carries it pointed at the release daemon, and it overrides the channel.unset TERMIOD_SOCK TERMIO_CHANNEL=dev ./termio-dev.app/Contents/Resources/termiod deploy --json 2>&1 | tail -3A daemon that isn't running needs nothing: the first pane starts it from the new bundle.
-
Report the result: whether the build succeeded, the app relaunched, and the daemon version the deploy reported. If the window doesn't appear, check
/tmp/termio-dev.log.
Verifying a termiod-side change
Sessions carried across the handoff keep the environment and shell their old daemon spawned them with — a change to session spawning (env, injection, PTY setup) is invisible in them by design. Open a new session to see it.
Icons
scripts/render-icon.sh rasterizes packaging/icon-static.svg into
packaging/AppIcon.png (and the iOS asset) through headless Chrome — and its
output is not reproducible: rendering the same SVG twice writes two different
files, neither matching the committed one. Running it on every rebuild therefore
dirtied the tree with two unreviewable binary diffs, cost a Chrome launch, and
risked overwriting hand-touched artwork. So it is not part of this loop.
Re-render only when the SVG actually changed, and commit the PNGs as their own change:
./scripts/render-icon.sh && git add packaging/AppIcon.png \
ios/Sources/Assets.xcassets/AppIcon.appiconset/AppIcon.png
Notes
- This builds release configuration (via
build-app.sh) into the dev channel bundle, so it's a few seconds slower than a bareswift build. For quick code-only iteration without a bundle, runswift buildand launch"$(swift build --show-bin-path)/termio"directly — but that has no Dock icon and, running unbundled, usesAppChannel.suffix == ""(i.e. the release state dir and port 8787), so it is not isolated. Use the bundle when you need isolation. - A concurrent SwiftPM process holding the
.buildlock can make a build emit spurious errors mid-write; if that happens, just rerun. - Do NOT modify
Package.swiftor sources during a rebuild. - After rebuilding a UI change, pair this with the
app-screenshot-debugskill to actually see the result.
Version History
-
b49f823
Current 2026-09-08 19:03
新增将 dev daemon (termiod) 纳入重建循环的步骤,确保守护进程随应用同步更新,解决版本不一致问题。
-
83cf92e
2026-09-02 21:19
修正文档描述,明确dev通道是禁用Sparkle更新而非移除框架,澄清二进制仍链接Sparkle.framework的事实。
- 2dfed32 2026-08-27 09:02


