Commands, package names, and image names on this page come from the open-source project that Mibyan Desktop is built on, and can differ from the Mibyan Desktop installer. For the supported Mibyan install and update path, see Install and update.
cd in and mibyan just works. The two TypeScript surfaces do not: ui-tui/ and apps/desktop/ each need a populated node_modules, and a fresh npm ci per worktree is slow and duplicates gigabytes across every branch you have checked out.
htui and hgui are two shell helpers that close that gap. Each launches its surface from the current worktree while borrowing node_modules from one canonical checkout — so a throwaway branch costs a symlink, not an install.
They’re developer conveniences, not shipped commands. Drop them in ~/.zshrc; adapt paths to taste.
The deps-sharing model
One checkout is the deps checkout — the one place you actually runnpm install. Every other worktree links against it, and only re-installs locally when its lockfile diverges (a branch that bumps a dependency must not silently run against stale packages).
Two env vars name the canonical checkout:
Neither is read by Mibyan itself — they’re private to these helpers. The variables Mibyan does read are covered in Environment Variables.
htui — TUI from the worktree
The Ink TUI has a dev path already: mibyan --tui --dev runs the TypeScript sources via tsx instead of the prebuilt bundle. htui is a one-liner over it that also points the run at the current worktree’s ui-tui/:
--dev compiles from source, so it links ui-tui/node_modules from mibyan_MAIN_CHECKOUT when the root lockfile matches and installs locally otherwise (see _mibyan_root / linking helpers).
hgui — desktop app from the worktree
The desktop app needs dependencies at both the repo root and apps/desktop/, a Vite server, and a Python backend. The stock npm run dev pins Vite to 5174; Electron also defaults to CDP port 9222 and takes a single-instance lock on its user-data directory. Changing only the Vite port is not enough to run two desktops.
This zsh example gives each launch an explicit slot (HGUI_SLOT, default 0). Use a different slot in each terminal. It uses the shared helpers below and requires lsof:
mibyan_MAIN_CHECKOUT and sourcing the helpers:
0 uses ports 5174/9222; slot 1 uses 5175/9223. Slots are caller-assigned, not atomically reserved: always use distinct slots for simultaneous starts. Busy ports are rejected, never evicted. Use separate checkouts for separate builds because launches in the same checkout still share build outputs.
Each slot starts with fresh desktop preferences and remembers them on later launches. This example does not copy browser storage, saved navigation, or backend ownership from a running app.
Quit the app normally or press Ctrl-C in its launching terminal.
concurrently -k manages its own child commands, and Electron owns its backend shutdown. Do not add a global killport, pkill electron, or a sweep of all serve/dashboard --port 0 processes: those can terminate another instance. Remove the old _mibyan_gui_cleanup trap if replacing an earlier version of this helper.
Shared helpers
Both functions resolve the enclosing checkout and link deps the same way:Why link only when locks matchA symlink to a divergent
node_modules is worse than no install — the worktree would build against packages its own lockfile never declared. Byte-comparing package-lock.json is the cheap, exact guard: same lock ⇒ safe to borrow; different lock ⇒ npm ci locally. Vite realpaths symlinks before enforcing server.fs.allow, which is why apps/desktop/vite.config.ts whitelists the real node_modules location.See also
- Git Worktrees — the isolation model these helpers build on
- TUI —
mibyan --tui --devand themibyan_TUI_DIRprebuild path - Desktop App — building from source and the backend resolution ladder
apps/desktop/README.md— dev server, sandbox script, and packaging- Environment Variables — every
mibyan_*variable Mibyan reads

