@@ -50,23 +50,157 @@ Wiring **deferred** to a follow-up (small UX work, no architectural risk):
5050- Workspace header badge for non-local workspaces.
5151- Workspace list filter dropdown.
5252
53- ## ` codemux-remote ` slim binary (2c)
53+ ## ` codemux-remote ` binary
5454
55- ` src-tauri/src/bin/codemux_remote.rs ` . New ` [[bin]] ` target in ` Cargo.toml ` . Same ` codemux_lib ` crate, no UI deps.
55+ ` src-tauri/src/bin/codemux_remote.rs ` . ` [[bin]] ` target in ` src-tauri/ Cargo.toml` . Same ` codemux_lib ` crate, no UI deps.
5656
5757CLI:
5858```
5959codemux-remote version
60- → {"name":"codemux-remote","version":"0.3.1 ","protocol_version":1}
60+ → {"name":"codemux-remote","version":"<v> ","protocol_version":1}
6161
6262codemux-remote pty-daemon --socket /tmp/codemux-ptyd-<rand>.sock
63- → binds the socket, runs the daemon server, never returns
63+ → binds the Unix socket the laptop's "Push workspace" tunnel uses,
64+ runs the PTY daemon server, never returns.
6465
6566codemux-remote scheduler
6667 → runs the Automations reconcile + pull + tick + execute loop on an
67- always-on host; never returns
68+ always-on host; never returns.
69+
70+ codemux-remote serve [--port <n>] [--state-dir <path>]
71+ → runs the headless Codemux daemon: axum HTTP server on 127.0.0.1
72+ with bearer-token auth (manifest at <state-dir>/manifest.json,
73+ mode 0600). Tracks workspaces in <state-dir>/codemux.db. Survives
74+ SIGTERM/SIGINT cleanly (manifest is removed on shutdown). v1
75+ listens loopback-only; the desktop tunnels in over SSH.
76+
77+ codemux-remote serve status [--state-dir <path>]
78+ → prints {endpoint, pid, started_at, host_id, alive} as JSON.
79+ Exit 0 if alive, exit 1 otherwise.
80+
81+ codemux-remote serve stop [--state-dir <path>]
82+ → SIGTERM the running daemon (pid read from manifest).
83+
84+ codemux-remote mcp [--state-dir <path>]
85+ → stdio JSON-RPC MCP server. Reads <state-dir>/manifest.json for the
86+ daemon's endpoint + secret, then bridges agent CLI tool calls to
87+ the daemon's HTTP API. Configure your CLI agent with:
88+ {"command": "codemux-remote", "args": ["mcp"]}
6889```
6990
91+ ### ` serve ` mode overview
92+
93+ The ` serve ` subcommand is the headless equivalent of running the
94+ desktop Codemux app on this host — an MCP-aware agent on this machine
95+ can drive Codemux locally without any UI. Module layout:
96+
97+ - ` src-tauri/src/remote/manifest.rs ` — manifest read/write, pid liveness.
98+ - ` src-tauri/src/remote/auth.rs ` — bearer-token axum middleware,
99+ attaches ` Identity::Local ` to the request.
100+ - ` src-tauri/src/remote/identity.rs ` — ` Identity::Local | Cloud{…} ` .
101+ v1 only ever produces ` Local ` ; the ` Cloud ` variant is here so a
102+ future optional relay layer can forward verified identity without
103+ changing handler signatures.
104+ - ` src-tauri/src/remote/workspace.rs ` — SQLite workspace registry
105+ with nullable ` owner_id ` column for future relay use.
106+ - ` src-tauri/src/remote/pty.rs ` — minimal portable-pty wrapper with
107+ per-terminal ring buffer.
108+ - ` src-tauri/src/remote/server.rs ` — axum routes (` /health ` ,
109+ ` /tools/list ` , ` /tools/call ` ).
110+ - ` src-tauri/src/remote/mcp.rs ` — stdio MCP server.
111+ - ` src-tauri/src/remote/tools/mod.rs ` — 11-tool catalog.
112+
113+ Headless tool surface (advertised via ` tools/list ` ):
114+
115+ | Tool | Purpose |
116+ | ---| ---|
117+ | ` workspace_create ` | Register a new workspace. |
118+ | ` workspace_list ` | All workspaces, newest first. |
119+ | ` workspace_info ` | One workspace by id. |
120+ | ` workspace_update ` | Mutate name/branch/notes. |
121+ | ` workspace_close ` | Remove from registry. |
122+ | ` terminal_spawn ` | Spawn a shell PTY. |
123+ | ` terminal_write ` | Write bytes to PTY stdin. |
124+ | ` terminal_read ` | Read accumulated PTY output. |
125+ | ` terminal_list ` | List open PTYs. |
126+ | ` terminal_close ` | SIGHUP the shell. |
127+ | ` app_status ` | Daemon version, uptime, counts. |
128+
129+ Deliberately * not* in the headless surface: ` pane_* ` , ` browser_* `
130+ (no UI on a headless host).
131+
132+ ### Process supervision
133+
134+ Sample systemd user unit at ` scripts/codemux-remote.service.example ` .
135+ Install:
136+
137+ ```
138+ cp scripts/codemux-remote.service.example ~/.config/systemd/user/codemux-remote.service
139+ loginctl enable-linger $USER
140+ systemctl --user daemon-reload
141+ systemctl --user enable --now codemux-remote.service
142+ ```
143+
144+ ` Restart=on-failure ` , ` StandardError=journal ` , ` loginctl enable-linger `
145+ so the daemon survives ssh logouts and reboots.
146+
147+ ### Designing for a future optional cloud relay
148+
149+ ` docs/plans/mcp-on-remote.md ` details the four design choices baked
150+ into v1 so that a paid-tier cloud relay (team collaboration, "control
151+ from phone without SSH") can be added later as a purely additive
152+ feature, not a rewrite: HTTP transport + bearer-token, ` Identity `
153+ passthrough, nullable ` owner_id ` , no Better-Auth coupling in the
154+ daemon. None of those four costs anything today.
155+
156+ ### Auto-provisioning on push (the "it just works" flow)
157+
158+ The desktop's push flow does the following automatically, so the user
159+ never has to know about manifests, secrets, or systemd:
160+
161+ 1 . ** Binary install.** ` ssh::bootstrap::bootstrap_remote ` scp's the
162+ matching ` codemux-remote-<target> ` to ` ~/.local/bin/codemux-remote `
163+ and chmods it. Skipped if ` version ` already returns the right value.
164+ 2 . ** ` serve ` systemd unit.** ` ssh::bootstrap::provision_serve ` writes
165+ ` ~/.config/systemd/user/codemux-remote.service ` , runs
166+ ` loginctl enable-linger ` so it survives logout, and
167+ ` systemctl --user enable --now codemux-remote ` . The daemon binds an
168+ ephemeral loopback port and writes its manifest under
169+ ` ~/.local/share/codemux-remote/ ` .
170+ 3 . ** ` .mcp.json ` in the pushed workspace.** ` ssh::push::push_workspace `
171+ drops a ` .mcp.json ` into the rsynced workspace directory pointing
172+ ` codemux ` at ` codemux-remote mcp ` . Any CLI agent (Claude Code,
173+ Codex, Gemini) launched in that directory on the host auto-discovers
174+ Codemux as an MCP server with zero further config.
175+ 4 . ** Workspace registration.** After rsync,
176+ ` ssh::bootstrap::register_workspace_on_remote ` runs `codemux-remote
177+ workspace register --path ... --name ... --branch ...` on the host
178+ via SSH. That command talks to the local daemon over loopback HTTP
179+ and inserts the workspace into its registry, so it shows in
180+ ` workspace_list ` from any MCP-aware agent on the host.
181+
182+ Net effect: user clicks "Push workspace to host" once, gets back a
183+ working MCP control plane on the remote without ever having to know
184+ the words "manifest" or "systemd."
185+
186+ ### Controlling the daemon from your phone (Tailscale)
187+
188+ v1's transport is SSH-only — there is no cloud relay. The cleanest
189+ way to drive your home/VPS Codemux daemon from a phone today is
190+ ** Tailscale** :
191+
192+ 1 . Install Tailscale on both the host running ` codemux-remote serve `
193+ and your phone (Tailscale has iOS and Android apps).
194+ 2 . Both devices join the same tailnet.
195+ 3 . From any agent app on the phone that can SSH (or any web shell
196+ pointed at the host's tailnet name), ` ssh <host> ` works without
197+ any port forwarding or DDNS.
198+
199+ This costs nothing, works today, and is what we recommend until the
200+ optional cloud relay (paid tier) ships. The relay would replace the
201+ SSH-from-phone requirement, not the Tailscale-or-similar mesh — some
202+ users will always prefer mesh VPN over a hosted relay for privacy.
203+
70204The ` scheduler ` subcommand was added by the Automations feature, not by
712052c. Host bootstrap provisions it — it writes the scheduler token + host
72206identity and registers a systemd user service so it survives reboots.
0 commit comments