Guided First-Run Onboarding — Design
radioactive_ralph cold on an interactive terminal — nothing installed, no supervisor running, no project initialized — offer to set everything up in one guided, consent-gated step instead of printing four commands and exiting.Goal: When a user runs radioactive_ralph cold on an interactive terminal —
nothing installed, no supervisor running, no project initialized — offer to set
everything up in one guided, consent-gated step instead of printing four
commands and exiting.
Status: design. Author: agent (under the full-autonomy onboarding mandate, 2026-07-17). Follows the merged supervisor architecture (AGENTS.md).
Native Windows override (2026-07-26): The Windows SCM safety contract supersedes this historical design's native Windows SCM path. On native Windows the wizard skips service installation and offers only the limited foreground supervisor/client control plane; provider-backed execution uses the Linux build in WSL2. The service-install flow below applies to supported macOS and Linux service backends.
Problem
Today a cold radioactive_ralph invocation:
- auto-routes to
--init(registers the project), then - tries
supervisor.Find, fails, and prints:and exits non-zero.radioactive_ralph: no supervisor is running. Install the durable background service: radioactive_ralph service install or run one in the foreground (dies with this terminal): radioactive_ralph --supervisor
That is correct and honest, but it is a dead end for a first-time human: they
must read the message, choose between two options, run a second command, then
re-run the client. The seam for a real wizard is already flagged in
cmd/radioactive_ralph/init_cmd.go ("the full interactive wizard is a later
phase").
Non-negotiable constraints
- Never prompt on a non-interactive stdin/stdout (a pipe, CI,
go test). The existing "print the exact commands, exit non-zero" path MUST remain verbatim for that case —tests/e2eandcmdtests assert it. The wizard is strictly gated ontui.IsTerminal()(which already gates the TUI) AND stdin being a terminal. - Consent before any outward-facing action. Installing a background service registers a launchd/systemd/SCM unit — a real, persistent, system-level change. The wizard MUST show exactly what it will create (the state-root path, the DB path, the service unit name + path) and get an explicit yes before doing it. Default to the safe choice on bare Enter is acceptable only for the foreground option, never for service install.
- Idempotent. Safe to run repeatedly. If the service is already installed or the supervisor already running, the wizard detects that and skips ahead rather than erroring or double-installing.
- Graceful degradation. If
service installfails or isn't permitted (locked-down machine, no launchd/systemd/SCM access), fall back to offering the foreground--supervisorpath, and if the user declines everything, exit via the existing print-commands path (still non-zero) — never leave the user in a half-configured state. - No new heavy deps. Reuse
service(install/status),supervisor(Find/Acquire discovery),store/xdg(paths), and a minimal prompt helper (stdlibbufioover stdin). No survey/tui library for the wizard itself — a plain, scriptable Y/n/quit prompt keeps it testable and consistent.
Flow
radioactive_ralph (cold, interactive TTY)
│
├─ resolve/register project (existing ensureProjectKnown → runInitMode)
│
├─ supervisor.Find(stateRoot)
│ ├─ reachable ──────────────► launch TUI (unchanged)
│ └─ not reachable ──► FIRST-RUN WIZARD:
│
│ ┌─────────────────────────────────────────────────────────────┐
│ │ "No supervisor is running yet. Ralph can set this up:" │
│ │ • state dir: <xdg.StateRoot()> │
│ │ • database: <stateRoot>/state.db │
│ │ • service: <service.UnitName(backend)> (<unit path>) │
│ │ │
│ │ Install the background service and start it now? [Y/n/q] │
│ └─────────────────────────────────────────────────────────────┘
│ │
│ ├─ Y (default): service.Install → start → poll Find until up
│ │ ├─ success ─────────► launch TUI
│ │ └─ install failed ──► offer foreground fallback ↓
│ │
│ ├─ n: "Run a foreground supervisor in another terminal? [y/N]"
│ │ ├─ y: print the exact `--supervisor` command, exit 0
│ │ │ (user runs it; nothing outward-facing done)
│ │ └─ N/q: existing print-commands path, exit non-zero
│ │
│ └─ q: existing print-commands path, exit non-zeroOn a non-TTY invocation the whole wizard block is skipped and the current print-commands-and-exit-nonzero behavior runs unchanged.
Components
internal/onboard/— a new package holding the wizard as a testable unit:type Prompter interface { Confirm(question string, defaultYes bool) (bool, error) }— abstracts stdin so tests inject scripted answers; the real impl reads a line from stdin and interpretsy/n/q/empty.type Plan struct { StateDir, DBPath, ServiceUnit, ServiceUnitPath string; Backend service.Backend }— the "what will be created" summary, computed fromxdg+service.func Run(ctx, deps) (Outcome, error)— drives the flow above against injected deps (Prompter, a "find supervisor" func, an "install service" func, a "wait until reachable" func, an output writer). Pure orchestration, no direct syscalls — every side-effecting dependency is an injected function so the flow is unit-testable without installing a real service.Outcome∈ {SupervisorReady, PrintedForegroundHint, PrintedCommands, Declined} so the caller (client.go) knows whether to proceed to the TUI.
cmd/radioactive_ralph/client.go— theErrNoSupervisorbranch callsonboard.Runwhen interactive; onSupervisorReadyit falls through to the TUI; otherwise it returns the same non-zero "no supervisor listening" error as today (so scripts and the existing tests are unaffected).
Testing
- Unit tests in
internal/onboarddriveRunwith a scriptedPrompterand fake dep funcs, asserting each branch: Y→install→ready; Y→install-fails→ foreground-fallback; n→foreground; n→N→print-commands; q→print-commands. No real service is installed. - A
cmdtest confirms the NON-interactive path is byte-for-byte unchanged (the wizard never runs; the existing message + non-zero exit). - The real binary is driven manually (interactive) once to confirm the prompts render and the happy path installs + reaches the TUI — recorded in the PR.
Explicitly out of scope (later items)
- The GUI onboarding surface (the Fyne app will reuse
internal/onboard'sPlan+Runorchestration behind its own consent dialog). - Uninstall/repair flows beyond what
service uninstallalready gives.
