# CLI Flows
## Back navigation
Pressing **Esc** on a decision-point screen steps the user back to the previous
decision so they can change their answer. Implemented declaratively via
`FlowEntry.revert` callbacks in `src/ui/tui/flows.ts` — each entry that supports
back-nav describes how to un-complete itself, and the router walks backwards to
find the most recent revertible entry.
Entries without a `revert` act as a **back-stop wall**:
- **Run** — the agent has executed; backing past it would re-run
instrumentation.
- **Intro** — first screen; nothing to back to.
Entries whose `revert` returns `false` are transparent — the router walks
through them. Setup uses this to walk past when there are no user-answered
questions to pop; CreateProject uses this so back-nav from DataSetup lands on
Auth (not the create-project form).
Per-screen Esc behavior:
| Screen | Esc action |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Auth | Back → SignupFullName / ToS / SignupEmail (whichever has the most recent meaningful revert on the create-account path), else RegionSelect |
| SignupFullName | Back → SignupEmail (clears email + ceremony state via `setSignupEmail(null)`'s bound reset, so the next pass re-probes from scratch) |
| ToS | Back → SignupEmail (clears email + ceremony state — same reason as SignupFullName) |
| SigningUp | Transparent (`revert: () => false`) — back-walk skips this entry; no clean undo for an in-flight network call |
| SignupEmail | Back → RegionSelect (clears captured email _and_ `signupRequiredFields` / `signupAuth` / `signupAbandoned` via `setSignupEmail(null)`) |
| DataSetup | Back → Auth (clears org/project selection) |
| ActivationOptions | Back → DataSetup (re-runs activation check) |
| Setup | Pops one answered question; if none, walks back further |
| Slack | Back → DataIngestionCheck or Mcp |
| DataIngestionCheck | Back → Mcp; **Enter** or **q** skip verification (with confirm if no events yet); **x** exits to resume later |
| CreateProject | Cancel (existing) — also functions as back to Auth |
| FeatureOptIn | Skip (confirms with no features selected — Esc=skip is the |
| | one screen that breaks the convention; hint bar makes it |
| | explicit) |
| Intro | Cancel wizard (existing) |
| Outro | Close report dialog (existing) |
| RegionSelect / Mcp | No-op (no revertible step before them) |
### Signup ceremony invariants
- `setSignupEmail(null)` clears **all** ceremony state (`signupRequiredFields`,
`signupAuth`, `signupAbandoned`) so any back-nav path that rewinds to the
email step automatically invalidates the prior probe response. The ceremony is
a single conceptual unit keyed to the email being present.
- `SignupFullName.revert` and `ToS.revert` return `false` when the screen was
skipped (server never asked, value never set) so the back-walk continues past
them rather than firing a no-op revert that traps the user.
- `SigningUpScreen` is the only signup screen with network I/O. Its
`useAsyncEffect` writes one of `signupAuth` (success) / `signupRequiredFields`
(needs more info) / `signupAbandoned` (redirect or error). The auth task in
`default.ts` waits on this settle before opening browser OAuth (see
`isAuthTaskGateReady` in `src/commands/helpers.ts`).
The `[Esc] Back` hint appears in `KeyHintBar` only when back is actually
available, so it never lies about what the keystroke will do.
---
## Slash commands
The CLI keeps a persistent prompt open at all times (like Claude). Slash
commands can be run at any point during the wizard to change settings or trigger
actions.
| Command | Description |
| ----------- | ----------------------------------------------------------------- |
| `/region` | Switch the data-center region (US or EU) — re-triggers data setup |
| `/org` | Switch the active org |
| `/project` | Switch the active project |
| `/login` | Re-authenticate |
| `/logout` | Clear stored credentials |
| `/whoami` | Show current user, org, and project |
| `/mcp` | Install or remove the Amplitude MCP server |
| `/slack` | Set up Amplitude Slack integration |
| `/feedback` | Send product feedback (optionally with opt-in system diagnostics) |
| `/test` | Run a prompt-skill demo (confirm + choose) |
| `/snake` | Play Snake |
| `/exit` | Exit the wizard |
---
## Top-level commands
```mermaid
---
title: Top-level commands
---
flowchart TD
CLI["amplitude-wizard CLI"] --> CMD{Command?}
CMD --> LOGIN["login"]
CMD --> LOGOUT["logout"]
CMD --> WHOAMI["whoami"]
CMD --> FEEDBACK["feedback"]
CMD --> SLACK_CMD["slack"]
CMD --> REGION_CMD["region"]
CMD --> MCP_CMD["mcp add / mcp remove"]
CMD --> COMPLETION["completion"]
CMD --> WIZARD["wizard (default)"]
CMD --> AGENT["wizard --agent
(structured JSON output for automation)"]
FEEDBACK --> FEEDBACK_CONSENT{"Include diagnostic info?
(consent prompt)"}
FEEDBACK_CONSENT -->|yes| FEEDBACK_COLLECT["Collect diagnostics
(system, codebase, session)"]
FEEDBACK_COLLECT --> FEEDBACK_SEND["Send feedback + diagnostics via Node SDK"]
FEEDBACK_CONSENT -->|no| FEEDBACK_SEND_BARE["Send feedback via Node SDK"]
AGENT --> AGENT_UI["AgentUI — non-interactive, JSON-line output
structured exit codes (0/1/2/3/4/10/130)"]
AGENT_UI --> AGENT_RUN["SDK installation agent run"]
AGENT_RUN --> AGENT_POLL["Data ingestion check
(polls MCP every 30s · up to 30 min)
emits events_detected result event · auto-advances"]
AGENT_POLL -->|events detected| AGENT_DONE["outro — events_detected NDJSON result"]
AGENT_POLL -->|timeout| AGENT_TIMEOUT["outro — log warning, continue"]
LOGIN --> LOGIN_CHECK{~/.ampli.json valid?}
LOGIN_CHECK -->|yes| LOGIN_DONE["Display logged-in user"]
LOGIN_CHECK -->|no| OAUTH["OAuth flow"] --> STORE["Store token"] --> LOGIN_DONE
LOGOUT --> CLEAR["Clear ~/.ampli.json"]
WHOAMI --> CHECK_TOKEN{Token exists?}
CHECK_TOKEN -->|yes| SHOW_USER["Show name, email, zone"]
CHECK_TOKEN -->|no| NOT_LOGGED_IN["Not logged in"]
SLACK_CMD --> SLACK_TUI["Launch TUI SlackSetup flow"]
REGION_CMD --> REGION_TUI["Launch TUI RegionSelect flow"]
MCP_CMD --> MCP_INSTALL["Install/remove MCP server in editors"]
COMPLETION --> SHELL_COMP["Generate zsh/bash completions"]
WIZARD --> MODE{Mode?}
MODE -->|--ci| CI["CI mode (--org, --project, --api-key, --install-dir)"]
MODE -->|default| W["See: Wizard flow"]
```
---
## Wizard flow
```mermaid
---
title: Wizard flow
---
flowchart TD
INTRO["IntroScreen
(shows detected framework — detection runs before TUI starts;
falls back to generic if undetected)
One Continue action (path derived from session.userEmail), then framework/region/directory/cancel
(non-interactive: --auth-onboarding sign-in or create-account)
If a crash-recovery checkpoint exists for this project,
the user is prompted to resume or start fresh"]
INTRO --> REGION_SELECT
REGION_SELECT["RegionSelect: US or EU?
(Enter = US default · skipped for returning users)"]
REGION_SELECT --> CREATE_ACCOUNT_PATH
CREATE_ACCOUNT_PATH{Create-account path?
— authOnboardingPath set by Continue handler from session.userEmail
— CLI --auth-onboarding create-account in non-TUI modes}
CREATE_ACCOUNT_PATH -->|no — signed in or sign-in path forced| AUTH
CREATE_ACCOUNT_PATH -->|yes — unauthenticated| SIGNUP_EMAIL["SignupEmailScreen
(collect user email · Tab abandons signup to browser OAuth)"]
SIGNUP_EMAIL -->|Tab — already have an account| AUTH
SIGNUP_EMAIL -->|Enter — email submitted| SIGNING_UP_PROBE["SigningUpScreen
(POST email-only — server decides next step)"]
SIGNING_UP_PROBE --> SERVER_RESPONSE{server response}
SERVER_RESPONSE -->|oauth — success| AUTH
SERVER_RESPONSE -->|requires_auth — existing user| AUTH
SERVER_RESPONSE -->|error| AUTH
SERVER_RESPONSE -->|needs_information — new user, asks for fields| TOS["ToSScreen
(only renders post-needs_information — never asked of redirect/error users)"]
TOS --> SIGNUP_FULL_NAME["SignupFullNameScreen
(only when server includes 'full_name' in required AND signupFullName is null;
--full-name pre-fill skips this screen)"]
SIGNUP_FULL_NAME --> SIGNING_UP_RETRY["SigningUpScreen
(re-POST with full_name)"]
SIGNING_UP_RETRY --> AUTH
subgraph AUTH ["Auth / Account Setup (AuthScreen)"]
SUSI["See: SUSI flow
(OAuth → org → project → API key)"]
end
AUTH --> DATA_SETUP["DataSetupScreen
(activation check — sets activationLevel: none / partial / full)"]
DATA_SETUP --> DATA_CHECK{activationLevel?}
DATA_CHECK -->|partial — SDK installed, few events| ACTIVATION_OPTIONS["ActivationOptionsScreen
(help me test / I'm blocked / exit)"]
DATA_CHECK -->|full — 50+ events| MCP_SCREEN
DATA_CHECK -->|none — no SDK, no events| SETUP_Q
ACTIVATION_OPTIONS -->|test locally or blocked| SETUP_Q
ACTIVATION_OPTIONS -->|exit| OUTRO
SETUP_Q{Unresolved setup questions?}
SETUP_Q -->|no| FEATURE_OPTIN
SETUP_Q -->|yes| SETUP["SetupScreen
(per-framework questions)"]
SETUP --> FEATURE_OPTIN
FEATURE_OPTIN{Opt-in features discovered?}
FEATURE_OPTIN -->|none, or CI/agent mode| RUN
FEATURE_OPTIN -->|one or more| OPTIN_SCREEN["FeatureOptInScreen
(multi-select picklist · all on by default · ESC skips)"]
OPTIN_SCREEN --> RUN
SLASH_REGION["/region slash command"] -. available any time .-> REGION_SELECT
subgraph AGENT_RUN ["Agent Run (RunScreen)"]
RUN["RunScreen"] --> AGENT["Claude agent runs"]
AGENT --> SDK_INSTALL["1. Install SDK + add initialization code"]
SDK_INSTALL --> INSTRUMENT["2. Instrument events from approved plan"]
INSTRUMENT --> ADDITIONAL["3. Drain additional feature queue
(LLM, Session Replay shown as tasks)"]
ADDITIONAL --> FEATURES{Stripe detected?}
FEATURES -->|yes| STRIPE_TIP["Show Stripe doc-link tip"] --> OUTCOME
FEATURES -->|no| OUTCOME
AGENT --> OUTCOME{Outcome?}
OUTCOME -->|success| POST["Upload env vars to hosting"]
OUTCOME -->|error| ERR["Set error state"]
end
POST --> MCP_SCREEN["McpScreen
(install MCP server · skipped on error)"]
ERR --> OUTRO["See: Outro flow"]
MCP_SCREEN --> DATA_INGESTION["DataIngestionCheckScreen
(polls MCP first, then activation API every 30s · full users pass immediately)
Enter or q skips verification (confirm if no events yet) · x exits to resume later
BrailleSpinner + coaching tips while waiting · celebration with event preview on success"]
DATA_INGESTION --> CHECKLIST["ChecklistScreen
(first chart · first dashboard · taxonomy @todo)
dashboard unlocks after chart · user can skip any item"]
CHECKLIST --> SLACK_SCREEN["SlackScreen
(connect Slack — skipped on error)"]
SLACK_SCREEN --> OUTRO
SUSI -. overlay .-> OUTAGE["OutageScreen"]
```
> The `SettingsOverrideScreen` overlay was removed. The wizard now scopes its
> gateway env to `.claude/settings.local.json` (machine-local, gitignored) so
> the user's checked-in `.claude/settings.json` is never touched. See
> `src/lib/claude-settings-scope.ts`.
---
## Activation Check flow
```mermaid
---
title: Activation Check flow
---
flowchart TD
EVENTS{Events ingested?}
EVENTS -->|50+| ACTIVATED["Activated → proceed to data check"]
EVENTS -->|1–49| ONBOARDED["Onboarded, not yet activated"]
EVENTS -->|0| SNIPPET
ONBOARDED --> SNIPPET{Snippet configured?}
SNIPPET -->|no| SETUP_SNIPPET["See: Framework Detection flow
(set up snippet)"] --> UNBLOCKED
SNIPPET -->|yes| DEPLOYED{App deployed?}
DEPLOYED -->|yes| UNBLOCKED
DEPLOYED -->|no| UNBLOCKED
UNBLOCKED{"What would you like to do?"}
UNBLOCKED -->|help me test locally| FRAMEWORK["See: Framework Detection flow"]
UNBLOCKED -->|I'm done for now| EXIT["Exit — resume when data arrives"]
UNBLOCKED -->|I'm blocked| AGENT["See: Agent Run
(debug mode)"]
UNBLOCKED -->|take me to the docs| DOCS["Open docs in browser"] --> UNBLOCKED
```
---
## SUSI flow
The SUSI flow runs inside `AuthScreen`. Authentication happens via Amplitude
OAuth (browser redirect). No email is entered in the wizard itself.
> **Terminology.** User-facing copy says "project" (matching the Amplitude
> website). The backend GraphQL API still uses `workspaces` as the field name,
> so adapter code maps `workspaces` → `projects` at the TS boundary
> (`AmplitudeOrg.projects`). Environment selection is a separate, distinct step
> from project selection — the env picker reads "Select an environment", not
> "Select a project".
```mermaid
---
title: SUSI flow
---
flowchart TD
OAUTH_WAIT["Show OAuth spinner + login URL
(bin.ts opens browser, AuthScreen waits)"]
OAUTH_WAIT --> OAUTH_DONE["OAuth completes — region auto-detected from token"]
OAUTH_DONE --> RETURNING{Returning user?
(credentials resolved silently from disk)}
RETURNING -->|no — fresh login| ORG_COUNT
RETURNING -->|yes| ACCOUNT_CONFIRM["Account confirmation
Enter = continue · C = change project · N = new project · Esc = cancel"]
ACCOUNT_CONFIRM -->|Enter — confirmed| DONE
ACCOUNT_CONFIRM -->|C — change project| ORG_COUNT
ACCOUNT_CONFIRM -->|N — new project| CREATE_PROJECT["CreateProjectScreen
(creates a fresh Amplitude project)"] --> DONE
ORG_COUNT{How many orgs?}
ORG_COUNT -->|1| PROJECT_COUNT
ORG_COUNT -->|many| ORG_PICKER["Picker: select org"] --> PROJECT_COUNT
PROJECT_COUNT{How many projects?}
PROJECT_COUNT -->|1| WRITE_AMPLI
PROJECT_COUNT -->|many| WS_PICKER["Picker: select project"] --> WRITE_AMPLI
WRITE_AMPLI["Write ~/.ampli.json
(OrgId, ProjectId, Zone)
(legacy files with WorkspaceId are auto-migrated on read)"]
WRITE_AMPLI --> KEY_CHECK{Saved API key?
keychain or .env.local}
KEY_CHECK -->|yes| AUTO_KEY["Auto-advance — no prompt"]
KEY_CHECK -->|no| KEY_INPUT["Text input: paste Amplitude API key"]
KEY_INPUT --> PERSIST["Persist key to system keychain
or .env.local (gitignored)"]
AUTO_KEY --> DONE["Credentials set → DataSetup"]
PERSIST --> DONE
```
---
## Data Setup flow
> **Partially implemented.** `DataSetupScreen` sets `activationLevel` (none /
> partial / full). `DataIngestionCheckScreen` polls for events; the user can
> skip verification (Enter or q, with a confirm step when nothing has been
> observed yet) or exit to resume later (x). `ChecklistScreen` offers first
> chart and first dashboard via browser deep-links. Taxonomy agent and direct
> GraphQL chart/dashboard creation are planned. See
> `features/05-data-setup-flow.feature` for the full target behaviour.
```mermaid
---
title: Data Setup flow (planned)
---
flowchart TD
START["Project created"] --> CHOICE{How do you want
to get started?}
CHOICE -->|data onboarding wizard| CONFIGURE["Data Setup (configure)"]
CHOICE -->|taxonomy agent| TAXONOMY["Taxonomy Agent"]
CONFIGURE --> INGESTED{Events successfully
ingested?}
INGESTED -->|no| DONE["→ Wizard flow: data check"]
INGESTED -->|yes| CHECKLIST
TAXONOMY --> CHECKLIST
CHECKLIST["Checklist: taxonomy / first chart / first dash
(show completed items, offer remaining)"]
CHECKLIST -->|run taxonomy agent| TAXONOMY_RUN["Taxonomy Agent"] --> CHECKLIST
CHECKLIST -->|create first chart| CHART_RUN["First Chart"] --> CHECKLIST
CHECKLIST -->|create first dash — unlocked after chart| DASH_RUN["First Dash"] --> CHECKLIST
CHECKLIST -->|all three complete| OPEN_SITE["Open dashboard in browser"]
OPEN_SITE --> DONE
```
---
## Framework Detection flow
> **Implementation note:** Detection runs eagerly in `run.ts` before the TUI
> starts. The result (or generic fallback) is stored in `session.integration`
> and displayed in `IntroScreen`, where the user confirms or exits. `--menu`
> skips auto-detection and shows a picker inside IntroScreen instead.
```mermaid
---
title: Framework Detection flow
---
flowchart TD
PRE["run.ts — before TUI starts"]
PRE --> DETECT{Auto-detect framework?}
DETECT -->|success| STORE["Store integration in session"]
DETECT -->|failed| GENERIC["Store Generic integration in session"] --> STORE
DETECT -->|--menu flag| PICKER["Framework picker menu in IntroScreen"] --> STORE
STORE --> INTRO["IntroScreen shows result"]
INTRO --> CONFIRM{User confirms?}
CONFIRM -->|cancel| EXIT["Exit"]
CONFIRM -->|continue| SETUP_Q{Unresolved setup questions?}
SETUP_Q -->|no| PLAN["→ PlanScreen"]
SETUP_Q -->|yes| SETUP["SetupScreen"]
SETUP --> ANSWER{Answer auto-detectable?}
ANSWER -->|yes| PLAN
ANSWER -->|no| PICKER_Q["PickerMenu for question"] --> PLAN
```
---
## Outro flow
```mermaid
---
title: Outro flow
---
flowchart TD
OUTRO["OutroScreen"] --> OUTCOME{Outcome?}
OUTCOME -->|success| SUCCESS["Show changes, events, docs/continue URLs"]
OUTCOME -->|error| ERR{Auth error?}
OUTCOME -->|cancel| CANCEL["Show cancel message"]
ERR -->|no| ERR_GENERIC["Show error message"]
ERR -->|"auth — recoverable
(access token expired mid-run,
refresh token still valid)"| ERR_RERUN["Show 'session expired, re-run to refresh'
promptLogin = false · creds preserved"]
ERR -->|"auth — re-login required
(refresh token rejected: invalid_grant,
OR Amplitude OAuth needs-auth)"| ERR_REAUTH["Show 'couldn't refresh your session — log in again'
promptLogin = true · stored session cleared
(next run forces fresh browser OAuth)"]
SUCCESS --> EXIT["Press key to exit"]
ERR_GENERIC --> EXIT
ERR_RERUN --> EXIT
ERR_REAUTH --> EXIT
CANCEL --> EXIT
```
**Auth-failure recovery routing.** Not all `AUTH_ERROR` outros are equal:
- **Recoverable** — the access token expired mid-run but the stored refresh
token is still good. Silent refresh on the next launch fixes it, so we keep
credentials and tell the user to simply re-run (`promptLogin = false`).
- **Re-login required** — the refresh token itself was rejected (`invalid_grant`
from the OAuth token endpoint) or Amplitude OAuth reported `needs-auth`.
Re-running can only re-refresh the same dead token and loops on the identical
failure. The wizard clears the stored OAuth session (`clearStoredCredentials`)
so the next launch forces a fresh browser login, sets `promptLogin = true`,
and the copy steers the user to log in again (equivalent to `/logout` →
`/login`). This breaks the repeat-failure loop observed in Sentry
`WIZARD-CLI-F`.