# 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`.