| 1 | # Remote sessions |
| 2 | |
| 3 | <a href="../README.md">README</a> |
| 4 | · |
| 5 | <a href="./REMOTE_SESSIONS.zh-CN.md">简体中文</a> |
| 6 | · |
| 7 | <a href="./GUIDE.md">General guide</a> |
| 8 | |
| 9 | The remote module (Remote SSH) runs Reasonix on a remote host and reaches it |
| 10 | over your own SSH connection — VS Code Remote-SSH style. This document |
| 11 | describes the whole system: what runs where, host configuration, the CLI, the |
| 12 | remote serve process, the session lifecycle, the desktop surface, credential |
| 13 | modes, and troubleshooting. |
| 14 | |
| 15 | The screenshots in this guide use the Simplified Chinese desktop UI; the |
| 16 | controls and states are the same in other locales. |
| 17 | |
| 18 | ## Contents |
| 19 | |
| 20 | - [What the remote module does](#what-the-remote-module-does) |
| 21 | - [What runs where](#what-runs-where) |
| 22 | - [Hosts and configuration](#hosts-and-configuration) |
| 23 | - [Connecting from the CLI](#connecting-from-the-cli) |
| 24 | - [The remote serve process](#the-remote-serve-process) |
| 25 | - [Remote session lifecycle](#remote-session-lifecycle) |
| 26 | - [Desktop remote work](#desktop-remote-work) |
| 27 | - [Credentials and model access](#credentials-and-model-access) |
| 28 | - [Connection behavior and failures](#connection-behavior-and-failures) |
| 29 | - [Troubleshooting](#troubleshooting) |
| 30 | - [Command reference](#command-reference) |
| 31 | |
| 32 | ## What the remote module does |
| 33 | |
| 34 | Reasonix bootstraps a persistent headless `reasonix serve` on the remote host, |
| 35 | forwards a local loopback port to it over the SSH tunnel, and then opens the |
| 36 | serve web client or an in-app remote session tab through that tunnel. The |
| 37 | agent, its tools, and its files all live on the remote host at full fidelity; |
| 38 | nothing runs through a lossy file proxy. |
| 39 | |
| 40 | - V1 remote hosts must be Linux or macOS. The local CLI and desktop also run |
| 41 | on Windows, but V1 Windows authentication does not support the OpenSSH |
| 42 | named-pipe agent; use an identity file or password instead. |
| 43 | - There is no local background daemon: the CLI's `connect` is a foreground |
| 44 | supervisor, and the desktop holds its own tunnel. |
| 45 | - Disconnecting the local side never touches the remote serve — it keeps |
| 46 | running and the next connection reuses it. |
| 47 | |
| 48 | ## What runs where |
| 49 | |
| 50 | ``` |
| 51 | Local side Remote host |
| 52 | ────────── ────────── |
| 53 | reasonix remote … (CLI) ~/.reasonix/remote/ |
| 54 | desktop app / separate web window serve-<slug>.{json,token,port,pid,log} |
| 55 | │ │ |
| 56 | ▼ ▼ |
| 57 | supervised SSH connection ─── SSH tunnel ─── headless reasonix serve |
| 58 | (keepalive, backoff reconnect, binds remote 127.0.0.1:0, HTTP + SSE |
| 59 | TOFU host keys, SFTP) agent / tools / files all remote |
| 60 | │ |
| 61 | ▼ local loopback -L forward |
| 62 | serve web UI in a browser, or the in-app remote session tab |
| 63 | ``` |
| 64 | |
| 65 | - **Local frontends**: the `reasonix remote …` CLI; the desktop app (Electron); |
| 66 | and serve's own web client (opened in a browser or hosted by the separate |
| 67 | web-window child process). |
| 68 | - **Transport kernel**: one supervised SSH connection — dial, host-key |
| 69 | verification, attaching port forwards, keepalive, and backoff reconnect |
| 70 | after a drop. The CLI and the desktop share the same kernel; interactive |
| 71 | moments (TOFU confirmation, password/passphrase prompts) surface through |
| 72 | callbacks to whichever frontend is driving. |
| 73 | - **Remote side**: a headless `reasonix serve` bound only to the remote |
| 74 | loopback address; port, auth token, and pid are handed over through files, |
| 75 | never exposed on the remote network. |
| 76 | - **Data plane**: sessions, tool execution, and file operations all happen on |
| 77 | the remote host; the local side only forwards and renders. Remote file |
| 78 | browsing and editing go over SFTP, not through serve. |
| 79 | |
| 80 | ## Hosts and configuration |
| 81 | |
| 82 | Hosts live in the user-global `[remote]` section of `config.toml`. Like |
| 83 | `[secrets]`, a project `reasonix.toml` cannot inject or override remote hosts |
| 84 | — a cloned repo can never steer where Reasonix opens SSH connections. |
| 85 | |
| 86 | ```toml |
| 87 | [remote] |
| 88 | [[remote.hosts]] |
| 89 | name = "gpu-box" |
| 90 | host = "203.0.113.7" |
| 91 | user = "dev" |
| 92 | identity_file = "~/.ssh/id_ed25519" |
| 93 | workspace = "~/projects/app" |
| 94 | serve_install = "auto" # auto | npm | upload | never |
| 95 | credential_mode = "remote" # remote | local-proxy |
| 96 | |
| 97 | [[remote.hosts.forwards]] |
| 98 | type = "local" # local (-L) | remote (-R) |
| 99 | bind = "127.0.0.1:5432" |
| 100 | target = "127.0.0.1:5432" |
| 101 | ``` |
| 102 | |
| 103 | ### Host fields |
| 104 | |
| 105 | | Field | Meaning | |
| 106 | | --- | --- | |
| 107 | | `name` | Host name; CLI subcommands refer to it | |
| 108 | | `host` / `port` / `user` | Address and login user; port defaults to 22, user to the current user | |
| 109 | | `identity_file` | Path to a private key. Only the path is stored; key material is never stored | |
| 110 | | `passphrase_env` / `password_env` | Env var names holding the passphrase/password; values live in Reasonix's global `.env` | |
| 111 | | `proxy_jump` | Jump chain, OpenSSH `ProxyJump` syntax | |
| 112 | | `workspace` | Default remote workspace | |
| 113 | | `serve_install` | Remote CLI install strategy: `auto` \| `npm` \| `upload` \| `never` | |
| 114 | | `credential_mode` | `remote` (key on the remote host) \| `local-proxy` (desktop holds the key); default `remote` | |
| 115 | | `use_ssh_config` | Layer unset fields from `~/.ssh/config` | |
| 116 | |
| 117 | `[[remote.hosts.forwards]]` persists port forwards with the host. `type` |
| 118 | selects `local` (`-L`) or `remote` (`-R`). For `-L`, `bind` listens locally |
| 119 | and `target` is dialed from the remote host; for `-R`, `bind` listens on the |
| 120 | remote host and `target` is dialed locally. |
| 121 | |
| 122 | `[[remote.projects]]` pins remote workspaces into the desktop project tree: |
| 123 | `host_id` + `workspace` + `title`. |
| 124 | |
| 125 | ### Credential slots |
| 126 | |
| 127 | When the desktop host form receives a plaintext password or key passphrase, |
| 128 | Reasonix stores it in a generated `REASONIX_REMOTE_<hash>_PASSWORD` / |
| 129 | `REASONIX_REMOTE_<hash>_KEY_PASSPHRASE` slot in the global `.env` (atomic |
| 130 | write with rollback on failure) and writes only the slot name to |
| 131 | `config.toml`. Leaving the plaintext field empty preserves the current |
| 132 | reference and does not create a slot. Deleting or clearing the host |
| 133 | garbage-collects unused generated slots; env var names you configured |
| 134 | yourself are never deleted. |
| 135 | |
| 136 | ### Host resolution precedence |
| 137 | |
| 138 | 1. Fields set explicitly in `[remote]`; |
| 139 | 2. the local `ssh -G` resolution (authoritative; covers `Include`, wildcard |
| 140 | `Host`, `Match` (including `Match exec`), repeated `IdentityFile`, |
| 141 | `ProxyJump`, and `IdentitiesOnly`); |
| 142 | 3. the built-in `~/.ssh/config` parser; |
| 143 | 4. defaults (port 22, current user). |
| 144 | |
| 145 | `reasonix remote import` stores the original alias with |
| 146 | `use_ssh_config = true` instead of copying a snapshot that goes stale. |
| 147 | |
| 148 | ## Connecting from the CLI |
| 149 | |
| 150 | ### Host management |
| 151 | |
| 152 | ```bash |
| 153 | reasonix remote add gpu-box dev@203.0.113.7 --workspace '~/projects/app' |
| 154 | reasonix remote import --all # import aliases from ~/.ssh/config |
| 155 | reasonix remote test gpu-box # dial + auth + host-key check |
| 156 | reasonix remote list # list configured hosts |
| 157 | reasonix remote remove gpu-box |
| 158 | ``` |
| 159 | |
| 160 | ### connect: the foreground supervisor |
| 161 | |
| 162 | `connect` behaves like `ssh -N` plus the serve bootstrap: it establishes and |
| 163 | holds the SSH connection, bootstraps the remote serve, forwards the serve |
| 164 | port to a local loopback port, and attaches the configured forwards. If the |
| 165 | link drops it auto-reconnects with exponential backoff and re-attaches the |
| 166 | forwards. Ctrl-C disconnects the local side only — the remote serve keeps |
| 167 | running, and the next `connect` reuses it. |
| 168 | |
| 169 | ```bash |
| 170 | reasonix remote connect gpu-box --open # bootstrap serve, tunnel, open the URL |
| 171 | reasonix remote open gpu-box # same as connect --open |
| 172 | reasonix remote connect gpu-box --local-port 18787 --no-serve |
| 173 | ``` |
| 174 | |
| 175 | `--no-serve` (alias `--forward-only`) establishes forwards only and does not |
| 176 | bootstrap serve. |
| 177 | |
| 178 | For a host with `credential_mode = local-proxy`, use the desktop to bootstrap |
| 179 | and open the workspace. CLI `remote connect` does not create the |
| 180 | desktop-owned reverse credential channel; use `--no-serve` only when you need |
| 181 | the configured forwards without a remote session. |
| 182 | |
| 183 | ### Remote serve operations |
| 184 | |
| 185 | ```bash |
| 186 | reasonix remote serve start gpu-box |
| 187 | reasonix remote serve status gpu-box |
| 188 | reasonix remote serve logs gpu-box -n 100 |
| 189 | reasonix remote serve stop gpu-box |
| 190 | ``` |
| 191 | |
| 192 | `serve start` refuses hosts with `credential_mode = local-proxy`. The desktop |
| 193 | is required to bootstrap the serve and provide its reverse credential channel. |
| 194 | |
| 195 | ### Port forwards and remote files |
| 196 | |
| 197 | ```bash |
| 198 | reasonix remote forward add gpu-box -L 127.0.0.1:5432:127.0.0.1:5432 |
| 199 | reasonix remote forward ls gpu-box |
| 200 | reasonix remote forward rm gpu-box 127.0.0.1:5432 |
| 201 | reasonix remote fs ls gpu-box:'~/projects/app' |
| 202 | reasonix remote fs get gpu-box:'~/projects/app/main.go' ./main.go |
| 203 | reasonix remote fs put ./patch.diff gpu-box:'~/projects/app/patch.diff' |
| 204 | ``` |
| 205 | |
| 206 | The `fs` subcommands go over SFTP and do not need serve to be running. |
| 207 | |
| 208 | ## The remote serve process |
| 209 | |
| 210 | One serve per workspace: remote state files are named by workspace slug and |
| 211 | never interfere with each other. |
| 212 | |
| 213 | **Bootstrap flow** (run automatically by `connect` or when the desktop opens |
| 214 | a remote project): |
| 215 | |
| 216 | 1. Try to reuse a running serve — it counts as alive only if the pid and the |
| 217 | launch arguments match exactly, which defeats pid-reuse misjudgment. |
| 218 | 2. Probe the remote platform and binary (see the install ladder). |
| 219 | 3. Generate a fresh auth token: written to `.token.next` first, then renamed |
| 220 | atomically, so no reader ever sees a half-written token. |
| 221 | 4. Launch `reasonix serve` detached via `setsid`/`nohup`: bound to |
| 222 | `127.0.0.1:0`, token passed through `--token-file` (never in argv, never |
| 223 | visible in `ps`), port and pid written to `.port` / `.pid` files. |
| 224 | 5. Poll the port file, then write the state JSON and establish the local |
| 225 | forward. |
| 226 | |
| 227 | **Binary install ladder** (tried in order when |
| 228 | `serve_install = "auto"`): |
| 229 | |
| 230 | 1. an existing Reasonix binary on the remote host; |
| 231 | 2. `npm` global install; |
| 232 | 3. uploading the local same-platform binary to the remote |
| 233 | `~/.reasonix/remote/bin/`; |
| 234 | 4. downloading from the official release. |
| 235 | |
| 236 | Whether a binary is usable is decided by a capability probe, not a version |
| 237 | number: an older binary missing any required serve capability is treated as |
| 238 | missing and upgraded. `serve_install = "never"` forbids all installation. |
| 239 | |
| 240 | **Remote state files** (remote `~/.reasonix/remote/`): `serve-<slug>.json` |
| 241 | (pid, bound loopback address, workspace), `serve-<slug>.token` (0600), |
| 242 | `serve-<slug>.port`, `serve-<slug>.pid`, `serve-<slug>.log`. |
| 243 | |
| 244 | **Access URL**: `http://127.0.0.1:<local-port>/#token=<token>`. The token |
| 245 | lives in the URL fragment, so it never reaches server logs with a request; |
| 246 | older serve builds fall back to the `?token=` query parameter. |
| 247 | |
| 248 | **Stopping**: `serve stop` signals only the process whose pid and launch |
| 249 | arguments match exactly; it never kills an unrelated process. |
| 250 | |
| 251 | **Concurrent bootstraps**: clients bootstrapping the same workspace at the |
| 252 | same time are serialized by a remote file lock; the lock expires after 60 |
| 253 | seconds of inactivity. |
| 254 | |
| 255 | ## Remote session lifecycle |
| 256 | |
| 257 | - One serve carries one **foreground session**. Switching to another session |
| 258 | leaves a busy turn running detached in the background until it finishes; it |
| 259 | is never interrupted. |
| 260 | - A session has a single writer (a lease): while another process holds it, |
| 261 | resuming that session is refused and the UI reports "session in use". |
| 262 | - **Handoff**: a local window on the serve host may take over the foreground |
| 263 | session. Serve then degrades to a read-only mirror that forwards the local |
| 264 | writer's frames in real time; 30 seconds without a writer heartbeat |
| 265 | reclaims the session automatically, and an explicit reclaim is always |
| 266 | possible. The desktop remote tab enters spectator mode and shows a reclaim |
| 267 | banner. |
| 268 | - **Final-format identities**: sessions-v4 sessions hand over through their |
| 269 | writer lock rather than the legacy lease. `/takeover` in the CLI (or a |
| 270 | local window's startup resume) asks every resident serve to release the |
| 271 | identity, then the CLI mirrors its frames while the desktop tab watches |
| 272 | read-only. **Take back** (`/reclaim`) returns ownership; the CLI stays |
| 273 | alive on the reclaimed conversation and can `/takeover` the same session |
| 274 | back directly or `/resume` elsewhere. Serve discovery ignores state files |
| 275 | whose recorded process is gone. |
| 276 | - **History before activation**: the identity-addressed history endpoints |
| 277 | (`/session-history/*` and `/session/open`) answer for any session stored |
| 278 | on the serve, not just the bound foreground — persisted history is a cold |
| 279 | read that needs no runtime. A desktop remote tab therefore renders the |
| 280 | stored transcript while `POST /resume` is still activating the session; |
| 281 | the live Follow v2 stream replaces the baseline once the runtime is ready. |
| 282 | The live `/transcript/*` protocol still requires the owning runtime and |
| 283 | keeps answering 409 otherwise. |
| 284 | - The desktop project tree lists the workspace's remote sessions. Selecting a |
| 285 | row resumes that exact session in the shared transcript and composer |
| 286 | surface; a running turn keeps executing remotely with its state shown in |
| 287 | the tree. The desktop holds the SSH tunnel and never mixes local |
| 288 | conversation sessions into the remote tab. |
| 289 | - **Forking a completed turn**: `GET /fork-targets` lists the foreground |
| 290 | session's turns with the reason each one is or is not forkable, and |
| 291 | `POST /fork-session` creates an independent child session from one of them |
| 292 | without switching the foreground session, moving the lease, or interrupting a |
| 293 | running turn. Desktop uses this path when the server advertises |
| 294 | `session-fork-targets-v1`; without that capability the tab reports the server |
| 295 | as unsupported rather than falling back to `/fork`, which switches the parent. |
| 296 | Both requests carry an expected-session header. The read returns the |
| 297 | authoritative `source`; creation submits `sourceSessionId`, stable `turnId`, |
| 298 | the atomic commit's `boundarySequence`, and a required `operationId`. |
| 299 | Refusals use structured `code`, `reason`, and `message` JSON fields. |
| 300 | |
| 301 | The following screenshots show both ends of a handoff. First, the Reasonix |
| 302 | window running locally on the remote host confirms taking over an idle |
| 303 | session: |
| 304 | |
| 305 |  |
| 306 | |
| 307 | After the takeover, the remote-session tab on the connecting desktop becomes |
| 308 | a read-only spectator. It continues receiving the live transcript and offers |
| 309 | a **Take back** action: |
| 310 | |
| 311 |  |
| 312 | |
| 313 | ## Desktop remote work |
| 314 | |
| 315 | - **Settings -> Remote SSH**: manage hosts — add/edit/remove, scan-import |
| 316 | from `~/.ssh/config`, connect/disconnect, view status. |
| 317 | - **Add a remote project**: in the project tree's add-project menu choose |
| 318 | **Remote connection**. The three-step wizard saves or reuses an SSH host, |
| 319 | connects and verifies that the remote OS is supported, then lets you browse |
| 320 | and choose a workspace before opening an in-app remote session tab. The |
| 321 | key-file button uses the native file picker so the saved identity is always |
| 322 | an absolute desktop path. |
| 323 | - **Remote explorer**: the status-bar chip or the host row's **Remote |
| 324 | explorer** button — browse and edit remote files over SFTP, manage port |
| 325 | forwards, start/open the remote workspace. |
| 326 | - **Remote session tab**: the same transcript/composer surface as local |
| 327 | sessions, with model switching, reasoning effort, plan mode, compaction, |
| 328 | fork, skills, background jobs, and the other commands; the tab survives a |
| 329 | brief SSH outage while the desktop reconnects in the background. |
| 330 | - **Model catalog**: in `remote` credential mode it comes straight from the |
| 331 | remote `/models`; in `local-proxy` mode the desktop-configured catalog is |
| 332 | shown, filtered to the current provider kind. |
| 333 | - **Dialogs**: TOFU fingerprint confirmation, askpass password/passphrase |
| 334 | entry, structured connection errors (naming the `known_hosts` file and |
| 335 | line), and the takeover reclaim banner. |
| 336 | - **Web window**: a separate child process hosts the serve web UI; the login |
| 337 | ticket is written to a one-shot 0600 file (valid for 2 minutes) instead of |
| 338 | argv, one instance per host. |
| 339 | |
| 340 | ### Desktop walkthrough |
| 341 | |
| 342 | The project-tree add menu places **Remote connection** beside creating a new |
| 343 | project and opening an existing folder: |
| 344 | |
| 345 |  |
| 346 | |
| 347 | The remote connection wizard shows its three stages on the left: connection |
| 348 | configuration, connecting, and choosing a directory. Once SSH is ready, you |
| 349 | can jump to a path, show hidden directories, and choose the workspace to open |
| 350 | in the current window: |
| 351 | |
| 352 |  |
| 353 | |
| 354 | After opening, the remote project and its sessions appear in the project tree; |
| 355 | the session keeps the complete transcript, composer, mode and model selectors, |
| 356 | status bar, and session metrics: |
| 357 | |
| 358 |  |
| 359 | |
| 360 | ## Credentials and model access |
| 361 | |
| 362 | | | `remote` | `local-proxy` | |
| 363 | | --- | --- | --- | |
| 364 | | API key location | the remote host's Reasonix config | the desktop machine | |
| 365 | | Model-call path | remote serve → provider | remote serve → reverse tunnel → desktop key holder → provider | |
| 366 | | Model list source | remote `/models` | desktop-configured catalog (filtered by provider kind) | |
| 367 | | CLI | fully supported | `remote serve start` refuses; `remote connect` cannot provide the desktop-owned credential channel. Use the desktop (`--no-serve` remains valid for ordinary forwards) | |
| 368 | |
| 369 | Functional behavior of `local-proxy` mode: |
| 370 | |
| 371 | - The desktop injects a managed `[[providers]]` block into the remote |
| 372 | `config.toml`, pointing at the reverse tunnel address with a scoped token; |
| 373 | Reasonix maintains that block — do not edit it by hand. |
| 374 | - The credential watchdog polls the reverse tunnel every 3 seconds: a missing |
| 375 | forward, a failed probe, or port drift triggers a full heal plus a provider |
| 376 | reload. The tunnel secret necessarily rotates after every SSH reconnect |
| 377 | (even when the port is unchanged), so a reconnect is always followed by one |
| 378 | unconditional heal. |
| 379 | - The channel recovers by itself after a brief SSH outage; no manual action |
| 380 | is needed. |
| 381 | |
| 382 | Typed passwords and key passphrases are cached in memory, so reconnects |
| 383 | never re-prompt; a desktop restart requires entering them again. |
| 384 | |
| 385 | ## Connection behavior and failures |
| 386 | |
| 387 | - **Keepalive**: probed every 30 seconds; 3 consecutive misses (10-second |
| 388 | timeout each) declare the link dead, tear it down, and redial. |
| 389 | - **Reconnect backoff**: full-jitter exponential — starting at 1 s, doubling |
| 390 | per attempt, capped at 60 s. A transient failure on the first connect is |
| 391 | reported immediately, never retried silently. |
| 392 | - **Terminal failures**: authentication failures and host-key errors are not |
| 393 | retried; the desktop marks the remote workspace unavailable until a human |
| 394 | intervenes. A brief network outage keeps the UI available while the desktop |
| 395 | reconnects and re-attaches its forwards in the background. |
| 396 | - **Host keys**: verified against your OpenSSH `~/.ssh/known_hosts` |
| 397 | (read-only) plus the Reasonix-managed `~/.reasonix/remote/known_hosts`. A |
| 398 | first-seen key prompts for trust-on-first-use and is recorded in the |
| 399 | managed file; a key that contradicts a recorded one is a hard error naming |
| 400 | the offending file and line, never auto-accepted. |
| 401 | - **Auth order**: SSH agent → `identity_file` → password / kbd-interactive. |
| 402 | - **Jump hosts**: every `ProxyJump` hop verifies its own host key and |
| 403 | authenticates with its own credentials; the target host's password is never |
| 404 | sent to an upstream hop. |
| 405 | - **Forward semantics**: `-L` listeners survive reconnects (connections are |
| 406 | refused while detached); `-R` listeners are recreated on every reconnect; |
| 407 | when serve moves ports, the local forward is switched atomically to the new |
| 408 | address. `remote forward add` warns for a non-loopback bind; a hand-edited |
| 409 | TOML rule is applied as written without that warning, so review its exposure |
| 410 | explicitly. |
| 411 | - **SFTP**: handles rotate with each reconnect; remote file operations fail |
| 412 | during an outage and work again once reconnected. |
| 413 | |
| 414 | ## Troubleshooting |
| 415 | |
| 416 | | Symptom | Cause and remedy | |
| 417 | | --- | --- | |
| 418 | | Host-key conflict; the error names a `known_hosts` line | The remote was reinstalled or its address changed. Verify the line by hand, remove that entry from the named file, and reconnect. Never auto-accepted | |
| 419 | | serve will not start | `serve_install = "never"` with no remote binary, or npm unavailable — switch to `upload` or the release download. Check `remote serve logs` | |
| 420 | | Suspected incompatible older serve | A failed capability probe upgrades automatically; if needed, `remote serve stop` then reconnect to force a fresh bootstrap | |
| 421 | | `connect` stuck bootstrapping | Concurrent bootstraps are serialized by a remote file lock that expires after at most 60 seconds; retry shortly | |
| 422 | | Session reports "in use" | Another process holds the session's lease (another window or serve). Exit from that side or wait for the holder to release | |
| 423 | | Remote tab switched to spectator mode | A local window on the serve host took over the session; it auto-reclaims after 30 s without a heartbeat, or use the reclaim banner | |
| 424 | | `local-proxy` model calls failing | The watchdog heals automatically; confirm the desktop is online and SSH is connected. Never hand-edit the managed remote provider block | |
| 425 | | Authentication failure keeps coming back | Auth failure is terminal and never retried. Check the `.env` slots and key passphrase, or switch to the SSH agent | |
| 426 | | Windows local side | The CLI and desktop are supported, but V1 cannot use the OpenSSH named-pipe agent; configure an identity file or password. Remote hosts must still be Linux/macOS | |
| 427 | |
| 428 | ## Command reference |
| 429 | |
| 430 | | Command | Purpose | |
| 431 | | --- | --- | |
| 432 | | `remote add <name> [user@]host[:port]` | Add a host. Flags: `--identity`, `--jump`, `--workspace`, `--use-ssh-config`, `--serve-install`, `--credential-mode`, `--passphrase-env`, `--password-env` | |
| 433 | | `remote list` | List configured hosts | |
| 434 | | `remote remove <name>` | Remove a host | |
| 435 | | `remote import [alias...]` / `--all` | Import aliases from `~/.ssh/config` | |
| 436 | | `remote test <name\|user@host>` | Dial + auth + host-key check | |
| 437 | | `remote connect <name>` | Foreground supervised connection: bootstrap serve, tunnel, forwards, held until Ctrl-C. Flags: `--workspace`, `--local-port`, `--no-serve`, `--open` | |
| 438 | | `remote open <name>` | `connect --open` | |
| 439 | | `remote status [<name>]` | Without a name, list configured hosts; with a name, print that host's configured target and workspace | |
| 440 | | `remote forward add <host> (-L\|-R) <spec>` | Add a port forward | |
| 441 | | `remote forward rm <host> <bind>` | Remove a forward | |
| 442 | | `remote forward ls <host>` | List forwards | |
| 443 | | `remote serve start\|stop\|status\|logs <name>` | Remote serve lifecycle; `--workspace` selects the workspace, `logs -n` caps lines | |
| 444 | | `remote fs ls <name>:<path>` | List a remote directory | |
| 445 | | `remote fs get <name>:<remote> [local]` | Download a remote file | |
| 446 | | `remote fs put <local> <name>:<remote>` | Upload a file to the remote | |
| 447 | |
| 448 | See also: [Configuration paths](./CONFIG_PATHS.md) (where `config.toml` and |
| 449 | `.env` live and how they prioritize) and the [main guide](./GUIDE.md). |
| 450 |