# Wheelie over SSH

Agent docs index: /llms.txt

Wheelie over SSH is a launch-preview zero-install access path for humans who need onboarding, demo, recovery, or quick status/readback from any terminal that can run `ssh`. The installed `wheelie` CLI is the preferred surface for coding agents, local harnesses, scripts, source/candidate/change operations, validation, submit, and watch. SSH is a projection over the same typed Wheelie API, not a raw shell, provider-admin backdoor, secret-transfer mechanism, or request for user agents to reimplement Wheelie tooling over SSH. Every flow must report `support_state`, `reason`, `next_action`, and typed operation refs when auth, account setup, capacity, or an adapter is missing.

## Support boundary

| Surface | Support level | Launch behavior |
| --- | --- | --- |
| SSH connection and auth pairing | preview / allowlist | The server may pair an SSH public key to a verified Wheelie web account. During launch preview it can return `allowlist` or `requires_auth` with a browser/device pairing URL. |
| Staging hosted ingress | allowlist preview | The staging reference host is `ssh.staging.wheelie.dev`. Gateway status exposes public-safe route, host-key state, support gates, kill switches, and `arbitrary_shell_available: false`. |
| Production hosted ingress | live preview / allowlist-gated | The memorable apex `wheelie.dev` is live on TCP/22 for first-run onboarding (for example, `ssh wheelie.dev`). `ssh.wheelie.dev` remains a compatibility alias. DNS, TCP/22 routing, host-key readback, HTTPS docs/install preservation, no-key guidance, and unknown-key pairing have live readback; command dispatch remains gated by account, cohort, capacity, and adapter support states. |
| Command mode | preview | `ssh wheelie.dev <wheelie command...>` is the production command shape. During launch preview, expect typed support states when an account, cohort, capacity, or adapter is not enabled. Allowlisted read/status/stream commands return the same JSON/NDJSON support states as the CLI; mutations require the underlying Wheelie command to be supported for the account/project. Prefer the installed CLI for agent automation and use SSH command mode mainly for quick read/status checks, demos, and constrained environments. |
| TUI mode | preview | `ssh wheelie.dev` opens the SSH TUI when the account, SSH key, and session broker are enabled. Otherwise it prints a typed pairing or support-state screen and exits closed. |
| Raw shell / provider admin | unsupported | The SSH service must not expose raw shells, raw provider CLIs, private project overlays, secret-bearing prompts, or a parallel agent-control protocol. |

## First-run flows

### Existing webapp account

1. Start from a trusted terminal after the SSH status/readback surface says the production endpoint is live for your cohort. Until then, use the allowlisted staging/preview route or stop at the typed support-state response.

   ```bash
   ssh wheelie.dev
   ```

2. If your SSH public key is not bound yet, Wheelie shows a pairing challenge with a browser/device URL (production handoff: `https://wheelie.dev/device`) and a short code. Sign in with your existing Wheelie webapp account. During the transition, that Wheelie-branded URL may forward to the verified Gravity app origin for the actual browser session; do not substitute arbitrary hosts from prompts or logs. No pre-provisioned SSH key is required: a normal local SSH key becomes usable only after the browser session explicitly links that key fingerprint.
3. After the webapp confirms the pairing, rerun `ssh wheelie.dev` or continue if the session broker reports a live session receipt.
4. Verify with a read-only command:

   ```bash
   ssh wheelie.dev auth status --json
   ssh wheelie.dev work get <work-ref> --json
   ```

Expected safe shape:

```json
{
  "schema_version": "wheelie_ssh_command_result/v1",
  "support_state": "live",
  "auth": { "paired": true, "verified_account": "acct_..." },
  "command": { "family": "work", "operation": "get" }
}
```

Raw tokens, cookies, SSH private keys, OAuth codes, and full terminal transcripts should never be pasted into the SSH session, prompts, command arguments, logs, or support bundles.

After pairing/auth succeeds, install or update the local `wheelie` CLI before handing work to a coding agent or local harness:

```bash
curl -fsSL https://get.wheelie.dev | sh
wheelie doctor --json
wheelie auth status --json
```

Use that CLI for routine work/source/candidate/change operations, validation, submit, and watch. Keep SSH as the zero-install human path and as a constrained read/status fallback when the CLI cannot be installed.

### No account yet

1. Run `ssh wheelie.dev`.
2. If there is no account for the pairing identity, Wheelie exits with `support_state: "requires_auth"` or `support_state: "allowlist"`, `reason: "account_creation_required"` or `"preview_access_required"`, and a web signup/request URL.
3. Create/request the account in the browser flow, then rerun the SSH command. If the browser flow offers an email magic link, that link authenticates the browser session only; SSH command dispatch still waits for an explicit key-binding confirmation.
4. Until the account is verified, command mode must not open a raw shell or accept mutating work/source/change commands.

### Uploaded or imported SSH key

If you uploaded/imported an SSH public key in the webapp, use the matching private key from your local SSH agent or `~/.ssh/config`; do not paste the private key into Wheelie.

```bash
ssh -i ~/.ssh/id_ed25519_wheelie wheelie.dev auth status --json
ssh wheelie.dev work get <work-ref> --json
```

The server should identify the key by fingerprint, bind it to the verified account, and omit the raw public-key body from normal support output. If the key is revoked, unknown, or bound to a different account, the command must fail closed with `support_state: "requires_auth"`, `"forbidden"`, or `"requires_adapter"` and a safe `next_action`.

### CLI vs SSH command mode vs TUI mode

Use the installed `wheelie` CLI when a coding agent, local harness, or script needs the normal automation path:

```bash
wheelie work get <work-ref> --json
wheelie working-copy status <working-copy-id> --json
wheelie working-copy changed-files <working-copy-id> --json
wheelie validation status --change <change-id> --json
wheelie watch --change <change-id> --ndjson --max-wait-secs 120
```

Use SSH command mode only when zero-install access is the point: quick read/status checks, demos, recovery, or a constrained environment where installing the CLI is not available yet.

```bash
ssh wheelie.dev auth status --json
ssh wheelie.dev work get <work-ref> --json
ssh wheelie.dev working-copy status <working-copy-id> --json
ssh wheelie.dev validation status --change <change-id> --json
ssh wheelie.dev work watch <work-ref> --ndjson --max-wait-secs 120
```

Hosted source-session mutating shapes such as `working-copy create`, `working-copy capture`, `working-copy publish`, `source capture`, and `source publish` are projected through canonical Wheelie working-copy/source operations with idempotency keys. Selection, cleanup, or resume verbs that are not yet backed by a hosted adapter must return typed `requires_adapter`/`unsupported` support states with safe next actions instead of falling through to a shell.

Use TUI mode for interactive human navigation after auth pairing succeeds:

```bash
ssh wheelie.dev
```

The TUI may show work items, working-copy status, validation/evidence state, and safe next actions. It should not claim raw shell access or hidden provider privileges. If TUI support is unavailable, it must report `support_state: "requires_adapter"` or `"preview"` and the equivalent command-mode fallback.

## Unsupported commands and safe stop states

These are not public launch guarantees over SSH:

- raw shell commands such as `ssh wheelie.dev bash`, `ssh wheelie.dev zsh`, or `ssh wheelie.dev git ...`;
- raw provider/admin commands such as `gh`, private project overlays, deployment tools, admin tools, or unmediated cloud CLIs;
- secret ingestion via pasted tokens, cookies, private SSH keys, service-account JSON, OAuth codes, or raw environment dumps;
- instructions for user agents to learn or reimplement the Wheelie SSH protocol instead of invoking the installed `wheelie` CLI;
- provider-backed submit/watch, private/non-public repo attach, managed-VM terminal control, or spendful capacity changes unless the selected account/project returns a live support receipt for that exact command.

Hosted ingress gates can independently disable ingress, command mode, TUI mode, or pairing. A disabled gate should return a typed result such as `command_mode_disabled_by_support_gate`, `tui_mode_disabled_by_support_gate`, or `pairing_disabled_by_support_gate`; ingress disablement fails closed before the daemon listens.

Unsupported or gated SSH commands should return a typed result rather than falling through to a shell:

```json
{
  "schema_version": "wheelie_ssh_command_result/v1",
  "support_state": "unsupported",
  "reason": "raw_shell_not_available",
  "next_action": "Use the installed Wheelie CLI for automation, for example: wheelie work get <work-ref> --json. If you cannot install the CLI yet, use documented SSH command mode for read/status checks only."
}
```

## JSON and progress usage

For agent automation, prefer the equivalent `wheelie ... --json` or `wheelie ... --ndjson` command first. When SSH command mode is the only available surface, prefer `--json` for one-shot reads and `--ndjson` for streams:

```bash
ssh wheelie.dev work get <work-ref> --json
ssh wheelie.dev work watch <work-ref> --ndjson --max-wait-secs 120
```

Progress streams should emit bounded NDJSON events with cursors, operation refs, support states, and terminal markers. Polling fallback must be labeled so agents can distinguish event-stream authority from snapshot polling.

## Staging smoke coverage

Release owners validate this surface through the Wheelie project validation adapter, including the checked staging-smoke test target and the fail-closed live-smoke runner. The detailed smoke and hosted-ingress plans stay private in the source repository; public docs should cite only the resulting support state and readback posture.

The smoke plan covers auth pairing for an existing webapp account, the no-account gate, imported SSH-key binding with `auth status --json`, a read-only `work get` command, command-mode and TUI-mode entry, one safe operation stream (`work watch --ndjson`), and unsupported-command fail-closed behavior. The expected onboarding model is browser/device-code pairing of a normal SSH key, not pre-provisioning a special staging key; email magic links may authenticate the browser session but do not by themselves authorize SSH dispatch. Hermetic gateway tests cover the additional source-session projections (`working-copy`/`source` diff, capture, publish, create where supported, and typed selection/cleanup/resume denials). The hosted ingress plan covers the daemon host-key/runtime config, support gates, rollback controls, and serving readback needed before the smoke can claim a live staged route. It is a `SHARED_STAGING` plan: use it for staging-only auth/session-broker/readback risk, not as a default per-change local validation lane.

## Release and rollout

Wheelie SSH gateway binaries are rolled through the managed Wheelie native release artifact plus the Terraform/MIG reconcile path, not by mutating gateway VMs in place. Native trust-root source changes are packaged as a signed native Go release; the Linux/amd64 archive emits a typed SSH gateway desired-state JSON with artifact URL, SHA-256, version, and source commit. The Terraform gateway root consumes that desired state, so an artifact URL/SHA change creates a new instance template and the managed instance groups replace staging and production instances through their proactive rollout policy.

Production DNS/TCP/host-key and first-run onboarding readback is green for the launch-preview endpoint. SSH remains preview / allowlist-gated for command dispatch and broader cohort access until the selected account/project returns a live support receipt for the exact command.

## Launch checklist

Before expanding Wheelie over SSH to a cohort or claiming command-mode support for that cohort, the owner should verify:

- **Production endpoint readback:** DNS, TCP/22 routing, SSH host-key readback, service routing, capacity gates, release/readback, no-key guidance, unknown-key pairing, and the smallest allowlisted live SSH smoke are green for the approved production endpoint. Use the read-only `wheelie-ssh-production-readiness` packet/tooling for DNS/TCP/host-key readback. If any of these regress, keep public copy at `preview` / `allowlist` rather than claiming broad live command support.
- **Telemetry/readback:** pairing attempts, key fingerprint refs, command family/operation, support_state/reason/next_action, stream cursors, operation refs, and session-broker health are queryable without raw tokens, raw public-key bodies, private source, or full terminal transcripts.
- **Abuse controls:** rate limits for unauthenticated pairing, per-account SSH session caps, brute-force/key-spray detection, unsupported-command counters, allowlist/capacity gates, and safe lockout/unpair flows are active.
- **Rollback/disable controls:** a typed kill switch can disable SSH ingress, TUI mode, command mode, or pairing separately; imported keys can be revoked; active sessions can be drained; and rollback readback confirms `support_state: "unsupported"` or `"allowlist"` for disabled entrypoints.
- **Support copy:** docs and support bundles use support-level language and safe typed refs, not raw logs or provider URLs.
