
Native desktop app for macOS, Linux and Windows that wraps AgentVM — a lightweight WASM-based Alpine Linux VM with the pi coding agent installed — and gives you:
Ctrl+B c),.pi directory so pi's model config, credentials and
sessions live in the workspace and survive reboots,.pi-box/config startup file for power users (extra env
vars or a custom tmux config),vi inside a new tmux window,open / xdg-open / Explorer depending on OS),apk add,
/etc, /root, /tmp, caches) survive restarts.Startup note: booting to a shell is fast because the VM is snapshotted with Wizer, but launching
piitself (a Node/V8 process inside the emulated RISC-V VM) is currently slow — expect roughly a minute before the splash screen hands over to pi. We setPI_OFFLINE=1at startup so pi skips its slow network downloads (fd/ripgrep, catalog refresh), which keeps this as fast as possible.
Latest release · v0.2.0
macOS / Windows note: these builds are unsigned and not notarized, and may still have issues (Gatekeeper prompts or odd errors). If a binary misbehaves, running from source is usually smoother:
npm install npm run devOn macOS, if Gatekeeper blocks the app on first launch, you can also remove the quarantine attribute:
xattr -dr com.apple.quarantine /Applications/pi-box.app(adjust the path if you moved the app elsewhere).
Built on deepclause-agentvm 0.3.0:
apk add, /etc, /root, /tmp, and pi's caches survive restarts). The
overlay image lives at <workspace>/.agentvm/upper.img.| Option | Verdict |
|---|---|
| Electron | ✅ chosen |
| Tauri | ❌ AgentVM is a Node.js library (worker_threads, node:wasi, SharedArrayBuffer); Tauri would need a sidecar Node process |
| Neutralino / others | ❌ less mature, weaker Node integration |
AgentVM runs in a Node worker thread and needs real Node built-ins
(node:wasi, worker_threads, net, dgram, dns). Electron's main process
is Node, so we can instantiate AgentVM directly with zero IPC plumbing to an
external runtime.
On top of Electron:
┌─────────────────────────── Electron ───────────────────────────┐
│ │
│ main process (Node) preload renderer (React)│
│ ┌──────────────────┐ contextBridge/ipc ┌───────────────┐ │
│ │ WorkspaceStore │◄──────────────────────►│ Sidebar │ │
│ │ workspaces.json │ │ (tree view) │ │
│ ├──────────────────┤ ├───────────────┤ │
│ │ VmManager │ pibox:output │ TerminalView │ │
│ │ AgentVM worker │◄──────────────────────►│ (xterm.js) │ │
│ └──────────────────┘ pibox:termInput └───────────────┘ │
└────────────────────────────────────────────────────────────────┘
VmManager owns a single AgentVM instance in interactive/raw mode.
onStdout/onStderr are forwarded to the renderer as decoded strings;
keystrokes from xterm are written back with vm.writeToStdin(). Once the VM
console shell is up, VmManager injects a startup script that sources the
workspace's .pi-box/config (power-user env/tmux overrides), starts a tiny
resize daemon (applies the host-written .pi/tty-size via TIOCSWINSZ)
and then launches tmux with pi in the first window. tmux handles
terminal multiplexing natively, so extra shells are just new tmux windows
(Ctrl+B c), and window resizes propagate to pi through tmux.WorkspaceStore persists workspaces to <userData>/workspaces.json,
builds the file tree lazily per directory, and seeds each workspace with
.pi/settings.json, .pi/sessions/, .pi/tmux.conf and
.pi/tty-resize-daemon.py./workspace inside the VM. Switching
workspaces restarts the VM with the new mount (simple by design).persistentRoot enabled, AgentVM mounts an ext4-overlay root backed
by <workspace>/.agentvm/upper.img before the startup script runs, so
guest-root changes persist across restarts.AgentVM with the last used workspace mounted at
/workspace (status: “Booting AgentVM…”). With persistence enabled,
AgentVM mounts the ext4 overlay root first.pi
(status: “Starting pi…”).pi-box-app/
├── package.json
├── electron.vite.config.ts
├── tsconfig*.json
├── scripts/
│ └── ev.mjs # dev/preview launcher (sets NO_SANDBOX on Linux)
└── src/
├── shared/types.ts # types shared across processes
├── main/
│ ├── index.ts # app bootstrap, window, VM wiring
│ ├── vm.ts # VmManager (AgentVM lifecycle)
│ ├── workspaces.ts # WorkspaceStore (persistence + tree)
│ ├── ipc.ts # IPC handlers
│ └── agentvm.d.ts # type declarations for deepclause-agentvm
├── preload/
│ ├── index.ts # contextBridge API (window.pibox)
│ └── index.d.ts
└── renderer/
├── index.html
├── public/logo.png # DeepClause logo (loading screen)
└── src/
├── App.tsx
├── styles.css
└── components/
├── LoadingScreen.tsx
├── NetworkSettings.tsx
├── WorkspaceSidebar.tsx
├── FileTree.tsx
├── icons.tsx
└── TerminalView.tsx
Requirements: Node ≥ 22, npm.
cd pi-box-app
npm install
npm run dev
deepclause-agentvmis an exact npm pin (currently0.3.0), so the app uses the published package (including its ~322 MBagentvm-alpine-python.wasmimage).
On Linux containers/CI the Chromium SUID sandbox helper is often not configured
(chrome-sandbox is not root:root mode 4755). npm run dev and npm run start
now set NO_SANDBOX=1 only on Linux via scripts/ev.mjs, so Electron runs
with --no-sandbox there. macOS and Windows are unaffected.
If you prefer to run the raw commands, you can pass the flag manually:
npx electron-vite dev --noSandbox
npm run dev # start with HMR
npm run build # production build into out/
npm run start # preview the production build
npm run typecheck # type-check main + renderer
npm run dist:dir # package an unpacked build locally (fast check)
npm run dist:linux # build AppImage + deb
npm run dist:mac # build dmg + zip (macOS only)
npm run dist:win # build NSIS + portable (Windows only)
Release binaries are built by GitHub Actions on every published release — see
.github/workflows/release.yml and docs/release-pipeline-proposal.md.
.pi (config + sessions), workspace list + lazy
file tree, default workspace, add/remove/switch workspaces, open folder
in OS file manager, last workspace restored on launch, extra shell
sessions via tmux windows.vi is done; open/rename/delete in the
tree are still pending..pi/settings.json, pick provider/model,
manage sessions from the sidebar.SIGWINCH propagation for its panes.chmod in WASI
preview1: mode changes (fs.chmod/fs.fchmod) return EPROTO and creation
mode bits are dropped. pi's auth save path is unaffected, but downloaded
helper binaries (fd/ripgrep) and extension temp folders rely on chmod.PI_OFFLINE=1, so it skips its startup network downloads; fd
and ripgrep are therefore not downloaded by default (pi shows an offline
warning) until offline mode is disabled.<workspace>/.agentvm/upper.img); the first boot of a workspace is slower
because the guest formats it with mkfs.ext4.24 commits
TypeScript
80.7%
CSS
17.7%
JavaScript
1.1%

Native desktop app for macOS, Linux and Windows that wraps AgentVM — a lightweight WASM-based Alpine Linux VM with the pi coding agent installed — and gives you:
Ctrl+B c),.pi directory so pi's model config, credentials and
sessions live in the workspace and survive reboots,.pi-box/config startup file for power users (extra env
vars or a custom tmux config),vi inside a new tmux window,open / xdg-open / Explorer depending on OS),apk add,
/etc, /root, /tmp, caches) survive restarts.Startup note: booting to a shell is fast because the VM is snapshotted with Wizer, but launching
piitself (a Node/V8 process inside the emulated RISC-V VM) is currently slow — expect roughly a minute before the splash screen hands over to pi. We setPI_OFFLINE=1at startup so pi skips its slow network downloads (fd/ripgrep, catalog refresh), which keeps this as fast as possible.
Latest release · v0.2.0
macOS / Windows note: these builds are unsigned and not notarized, and may still have issues (Gatekeeper prompts or odd errors). If a binary misbehaves, running from source is usually smoother:
npm install npm run devOn macOS, if Gatekeeper blocks the app on first launch, you can also remove the quarantine attribute:
xattr -dr com.apple.quarantine /Applications/pi-box.app(adjust the path if you moved the app elsewhere).
Built on deepclause-agentvm 0.3.0:
apk add, /etc, /root, /tmp, and pi's caches survive restarts). The
overlay image lives at <workspace>/.agentvm/upper.img.| Option | Verdict |
|---|---|
| Electron | ✅ chosen |
| Tauri | ❌ AgentVM is a Node.js library (worker_threads, node:wasi, SharedArrayBuffer); Tauri would need a sidecar Node process |
| Neutralino / others | ❌ less mature, weaker Node integration |
AgentVM runs in a Node worker thread and needs real Node built-ins
(node:wasi, worker_threads, net, dgram, dns). Electron's main process
is Node, so we can instantiate AgentVM directly with zero IPC plumbing to an
external runtime.
On top of Electron:
┌─────────────────────────── Electron ───────────────────────────┐
│ │
│ main process (Node) preload renderer (React)│
│ ┌──────────────────┐ contextBridge/ipc ┌───────────────┐ │
│ │ WorkspaceStore │◄──────────────────────►│ Sidebar │ │
│ │ workspaces.json │ │ (tree view) │ │
│ ├──────────────────┤ ├───────────────┤ │
│ │ VmManager │ pibox:output │ TerminalView │ │
│ │ AgentVM worker │◄──────────────────────►│ (xterm.js) │ │
│ └──────────────────┘ pibox:termInput └───────────────┘ │
└────────────────────────────────────────────────────────────────┘
VmManager owns a single AgentVM instance in interactive/raw mode.
onStdout/onStderr are forwarded to the renderer as decoded strings;
keystrokes from xterm are written back with vm.writeToStdin(). Once the VM
console shell is up, VmManager injects a startup script that sources the
workspace's .pi-box/config (power-user env/tmux overrides), starts a tiny
resize daemon (applies the host-written .pi/tty-size via TIOCSWINSZ)
and then launches tmux with pi in the first window. tmux handles
terminal multiplexing natively, so extra shells are just new tmux windows
(Ctrl+B c), and window resizes propagate to pi through tmux.WorkspaceStore persists workspaces to <userData>/workspaces.json,
builds the file tree lazily per directory, and seeds each workspace with
.pi/settings.json, .pi/sessions/, .pi/tmux.conf and
.pi/tty-resize-daemon.py./workspace inside the VM. Switching
workspaces restarts the VM with the new mount (simple by design).persistentRoot enabled, AgentVM mounts an ext4-overlay root backed
by <workspace>/.agentvm/upper.img before the startup script runs, so
guest-root changes persist across restarts.AgentVM with the last used workspace mounted at
/workspace (status: “Booting AgentVM…”). With persistence enabled,
AgentVM mounts the ext4 overlay root first.pi
(status: “Starting pi…”).pi-box-app/
├── package.json
├── electron.vite.config.ts
├── tsconfig*.json
├── scripts/
│ └── ev.mjs # dev/preview launcher (sets NO_SANDBOX on Linux)
└── src/
├── shared/types.ts # types shared across processes
├── main/
│ ├── index.ts # app bootstrap, window, VM wiring
│ ├── vm.ts # VmManager (AgentVM lifecycle)
│ ├── workspaces.ts # WorkspaceStore (persistence + tree)
│ ├── ipc.ts # IPC handlers
│ └── agentvm.d.ts # type declarations for deepclause-agentvm
├── preload/
│ ├── index.ts # contextBridge API (window.pibox)
│ └── index.d.ts
└── renderer/
├── index.html
├── public/logo.png # DeepClause logo (loading screen)
└── src/
├── App.tsx
├── styles.css
└── components/
├── LoadingScreen.tsx
├── NetworkSettings.tsx
├── WorkspaceSidebar.tsx
├── FileTree.tsx
├── icons.tsx
└── TerminalView.tsx
Requirements: Node ≥ 22, npm.
cd pi-box-app
npm install
npm run dev
deepclause-agentvmis an exact npm pin (currently0.3.0), so the app uses the published package (including its ~322 MBagentvm-alpine-python.wasmimage).
On Linux containers/CI the Chromium SUID sandbox helper is often not configured
(chrome-sandbox is not root:root mode 4755). npm run dev and npm run start
now set NO_SANDBOX=1 only on Linux via scripts/ev.mjs, so Electron runs
with --no-sandbox there. macOS and Windows are unaffected.
If you prefer to run the raw commands, you can pass the flag manually:
npx electron-vite dev --noSandbox
npm run dev # start with HMR
npm run build # production build into out/
npm run start # preview the production build
npm run typecheck # type-check main + renderer
npm run dist:dir # package an unpacked build locally (fast check)
npm run dist:linux # build AppImage + deb
npm run dist:mac # build dmg + zip (macOS only)
npm run dist:win # build NSIS + portable (Windows only)
Release binaries are built by GitHub Actions on every published release — see
.github/workflows/release.yml and docs/release-pipeline-proposal.md.
.pi (config + sessions), workspace list + lazy
file tree, default workspace, add/remove/switch workspaces, open folder
in OS file manager, last workspace restored on launch, extra shell
sessions via tmux windows.vi is done; open/rename/delete in the
tree are still pending..pi/settings.json, pick provider/model,
manage sessions from the sidebar.SIGWINCH propagation for its panes.chmod in WASI
preview1: mode changes (fs.chmod/fs.fchmod) return EPROTO and creation
mode bits are dropped. pi's auth save path is unaffected, but downloaded
helper binaries (fd/ripgrep) and extension temp folders rely on chmod.PI_OFFLINE=1, so it skips its startup network downloads; fd
and ripgrep are therefore not downloaded by default (pi shows an offline
warning) until offline mode is disabled.<workspace>/.agentvm/upper.img); the first boot of a workspace is slower
because the guest formats it with mkfs.ext4.24 commits
TypeScript
80.7%
CSS
17.7%
JavaScript
1.1%