An interactive CLI (npx @amplitude/wizard) that instruments apps with Amplitude analytics. It authenticates the user, detects their framework, runs a Claude-powered agent (via Claude Agent SDK) to set up the SDK and events, and guides them through their first chart and dashboard.
The CLI behaves like Claude Code: a persistent prompt stays open throughout the session, and slash commands can be invoked at any time to change settings or trigger actions.
The designed flows in docs/flows.md define the intended user experience and should inform all implementation decisions. Before adding, changing, or removing any screen, step, or decision point, consult the flow diagrams. If you believe a flow needs to change, update docs/flows.md first and note it in your work.
The flows cover:
- Wizard flow — the main spine of the interactive TUI
- Activation Check flow — evaluates whether a returning user's project is ingesting events, and routes them accordingly
- SUSI flow — sign up / sign in, org and project selection for new users
- Data Setup flow — taxonomy agent, first chart, first dashboard after events are ingested
- Org / Project Selection flow — picker UI for switching org or project (reached via the framework-detection flow or the
/regionslash command's follow-up pickers) - Framework Detection flow — auto-detect or manual selection, plus setup question disambiguation
- Outro flow — success, error, and cancel states after the agent run
bin.ts— CLI entry point, yargs command definitions, mode flags (Node >=20 required by package metadata). Supports--agent(NDJSON machine output),--ci/--yes(non-interactive)src/run.ts— main wizard orchestration, ties TUI to sessionsrc/ui/agent-ui.ts—AgentUI— NDJSONWizardUIimplementation for--agentmode. Auto-approves prompts, emits structured JSON events to stdout, redacts secrets from output
Built with Ink (React for CLIs) + nanostores for reactive state. Features journey stepper, keyboard hint bar, dissolve transitions, error boundaries, and resilience utilities.
| File / Dir | Role |
|---|---|
App.tsx |
Root component — layout, screen resolution, DissolveTransition, ScreenErrorBoundary |
start-tui.ts |
Entry point — OSC terminal color detection, Ink app bootstrap |
ink-ui.ts |
InkUI — TUI WizardUI implementation, delegates to WizardStore |
store.ts |
WizardStore, Screen, Overlay, Flow — nanostore-backed reactive state |
router.ts |
WizardRouter — resolves active screen from session state via flow pipeline; manages overlay stack |
flows.ts |
Declarative flow pipelines (Screen + Flow enums, FlowEntry arrays) |
screen-registry.tsx |
Maps the 25 screen/overlay names (19 Screen + 6 Overlay) to React components |
screens/ |
20 screen .tsx files (Auth, Run, Outro, MCP, Slack, etc.). 19 are wired through screen-registry.tsx; EventPlanFullScreen is rendered as a standalone full-screen view by App.tsx and does not get its own router entry |
components/ |
ConsoleView, JourneyStepper, HeaderBar, KeyHintBar, AmplitudeLogo, BrailleSpinner |
hooks/ |
useWizardStore (stable subscription), useAsyncEffect (AbortController-based), useScreenInput, useEscapeBack, useStdoutDimensions |
utils/ |
withTimeout, withRetry, classifyError, diagnostics (flow evaluation + sanitized snapshots) |
styles.ts |
Design tokens and color palette |
console-commands.ts |
Slash command registration and dispatch |
context/ |
React context providers |
primitives/ |
Low-level UI building blocks |
services/ |
TUI-specific service modules |
Esc / back-navigation (Ink):
@inkjs/uiTextInputwires its own stdin handler; Esc does not surface as router back by default. Parent screens must useuseScreenInput(or equivalent) if users should leave the step with Esc.ConfirmationInputmaps Esc toonCancel. Combining it withuseEscapeBackon the same surface causes double handling unless you gateuseEscapeBackto phases without the confirm UI, or implementonCancelas “store.canGoBack()→store.goBack(), else skip/cancel” (seeMcpScreen/SlackScreen).- Prefer
useScreenInputover Ink’s rawuseInputon wizard screens so input respectsCommandModeContextwhile the slash command bar is active.
Machine-consumable execution mode for CI pipelines and agent orchestrators. Uses AgentUI (src/ui/agent-ui.ts) which implements WizardUI via NDJSON streaming to stdout. All prompts auto-approve. Stack traces and credentials are redacted from output. See src/lib/mode-config.ts for mode resolution logic and src/lib/exit-codes.ts for structured exit codes.
| File | Role |
|---|---|
wizard-session.ts |
WizardSession — single source of truth for all wizard state. Includes RunPhase, McpOutcome, OutroKind, etc. |
agent-interface.ts |
Creates and runs the Claude agent via @anthropic-ai/claude-agent-sdk. Configures MCP servers, hooks, model, permissions |
agent-runner.ts |
Universal agent-powered wizard runner. Orchestrates the full flow for any framework |
agent-hooks.ts |
Hook callbacks for agent lifecycle events (stop, tool use, etc.) |
framework-config.ts |
FrameworkConfig<TContext> interface — the contract every framework implements |
registry.ts |
FRAMEWORK_REGISTRY — maps Integration enum values to FrameworkConfig objects |
constants.ts |
Integration enum (detection/display order matters), env flags, URLs |
commandments.ts |
Wizard-wide system prompt rules always appended to the agent |
wizard-tools.ts |
In-process MCP server consumed by the wizard's own internal Claude agent. Tools: check_env_keys, set_env_values, detect_package_manager, confirm_event_plan, confirm, choose, report_status, wizard_feedback (plus load_skill_menu / install_skill, currently disabled — see comment in createWizardToolsServer). Distinct from wizard-mcp-server.ts below |
wizard-mcp-server.ts |
External stdio MCP server invoked via amplitude-wizard mcp serve. Read-only — wraps agent-ops.ts so third-party AI coding agents (Claude Code, Cursor, Codex) can call wizard ops as typed tools instead of parsing CLI stdout |
mcp-with-fallback.ts |
callAmplitudeMcp<T> — resilient MCP helper. Tries a direct HTTP call to the Amplitude MCP server; if it returns null or throws (e.g. tool removed), falls back to a minimal Claude agent with only the Amplitude MCP configured. Accepts abortSignal for clean exit handling. Use this for any new MCP-based data fetching. |
safe-tools.ts |
Allowlisted tools for the agent sandbox |
middleware/ |
Benchmark pipeline, message schemas |
health-checks/ |
Runtime health check utilities |
package-manager-detection.ts |
Detects npm/yarn/pnpm/bun/pip/etc. |
mode-config.ts |
resolveMode() — determines ExecutionMode (interactive / ci / agent) from CLI flags and TTY state |
exit-codes.ts |
ExitCode enum — structured exit codes (0 success, 2 invalid args, 3 auth, 4 network, 10 agent failed, 130 cancelled) |
session-checkpoint.ts |
Session checkpointing — saves/loads sanitized wizard state to a temp file for crash recovery. Zod-validated, 24-hour TTL, scoped per install directory |
orchestration/ |
v2 foundation — durable orchestration store (sessions, tasks, subagents, ownership, last-stopping-point). Source of truth for wizard tasks / task / sessions / session / resume / orchestration status and the new MCP-server read tools (deferred to PR 3). See docs/orchestration.md |
Each framework has its own directory containing a *-wizard-agent.ts file that exports a FrameworkConfig. Detection order is defined by the Integration enum in constants.ts.
Supported frameworks: Next.js, Vue, React Router, Django, Flask, FastAPI, Swift, React Native, Android, Flutter, Go, Java, Unreal, Unity, Python (fallback), JavaScript/Node (fallback), JavaScript/Web (fallback), Generic (ultimate fallback)
Adding a new framework? Mirror an existing framework under src/frameworks/
and its matching skill under skills/integration/, then follow the checklist
below.
Skills are bundled markdown-based instructions that the agent can follow during runs.
skills/integration/— Per-framework SDK integration guides (step-by-step workflows with reference docs)skills/instrumentation/— Analytics instrumentation skills (event discovery, pattern matching, diff intake)skills/taxonomy/— Quickstart taxonomy agent and chart/dashboard planning skills
All three categories are owned and built by amplitude/context-hub and pulled into the wizard via pnpm skills:refresh. context-hub is the single source of truth: integration skills are generated from transformation-config/, taxonomy and instrumentation skills live in context-hub/skills/.
Pinned, reproducible refresh. The exact context-hub release used by wizard skills is pinned in .context-hub-version (e.g. v1.2.6). pnpm skills:refresh downloads the ZIPs attached to that release tag — every refresh produces the same skills regardless of who runs it.
Source selection (in order):
CONTEXT_HUB_DIST=/path/to/dist/skills— explicit local overrideWIZARD_FORCE_REMOTE_SKILLS=1— always use the GitHub release (ignore any sibling clone)WIZARD_USE_LOCAL_SKILLS=1+ sibling../context-hub/dist/skills/— opt in to local-dev mode (must runcd ../context-hub && pnpm buildfirst)- Default: download the pinned tag from
amplitude/context-hubreleases
Why this matters: before this pin existed, the script silently preferred any sibling
../context-hub/clone and would shipversion: devSKILL.md frontmatter intowizard/skills/whenever a developer happened to have one checked out. PR #538 caught the regression. The new default is "remote, pinned" — local sibling mode is opt-in.
Dev-version guard. After every refresh the script greps for version: dev in any extracted SKILL.md and fails the script if found, unless WIZARD_ALLOW_DEV_SKILLS=1 is explicitly set (testing only — never commit the result).
Bumping the pin:
echo "v1.2.7" > .context-hub-version
pnpm skills:verify-pin # confirms the tag exists on amplitude/context-hub
pnpm skills:refresh # re-downloads with the new tag
git commit .context-hub-version skills/Post-agent discrete steps: MCP server installation into editors, env var upload, prettier formatting.
OAuth flow, analytics tracking, env var handling, API key storage, debug logging, URL construction, package manager detection, shell completions, Anthropic status checks, custom headers.
Logging — two distinct paths, don't confuse them:
src/lib/observability/logger.ts— structured runtime logger. Use this for diagnostic / debug / lifecycle logs that need to land in the per-project log file with redaction, run IDs, and correlation. Entry point:createLogger('my-module'). This is the canonical logger for new code insrc/lib/,src/ui/, andsrc/utils/. It never callsconsole.logdirectly (Ink owns stdout in TUI mode).src/utils/logging.ts— chalk-coloured terminal output. Helpers (green,red,dim,yellow,cyan) for non-Ink CLI command UX (e.g.amplitude-wizard login,whoami). Callsconsole.logby design. Only appropriate insrc/commands/and similar non-TUI command handlers.
If a callsite is inside the TUI or runs during a wizard session, prefer observability/logger.ts. Bare console.log in production source paths is an anti-pattern.
Key additions:
atomic-write.ts— crash-safe JSON writes via temp-file + rename. Used by session checkpointing and config persistence.token-refresh.ts— silent OAuth token refresh using stored refresh tokens. Proactively refreshes 5 minutes before expiry, falls back to full browser auth on failure.storage-paths.ts— single source of truth for every path the wizard reads or writes. Per-user cache at~/.amplitude/wizard/, per-project metadata at<installDir>/.amplitude/. Override the cache root withAMPLITUDE_WIZARD_CACHE_DIR(used by tests).storage-migration.ts— one-shot migration from the old$TMPDIR/amplitude-wizard-*+ project-root dotfile layout to~/.amplitude/wizard/and<installDir>/.amplitude/. Idempotent, runs at startup. Retire once telemetry confirms the legacy paths are no longer present in the field.
The wizard persists state across several layers, each with different scope and lifetime:
| Layer | File / Location | Scope | Lifetime | Contents |
|---|---|---|---|---|
| OAuth tokens | ~/.amplitude/wizard/oauth-session.json (legacy ~/.ampli.json is read for one minor cycle but no longer written — Phase G-1) |
Per user | Until expiry (silent refresh via token-refresh.ts) |
Access token, refresh token, expiry timestamp. Written with atomicWriteJSON() at 0o600. |
| API key store | ~/.amplitude/wizard/credentials.json (fallback <project>/.env.local) |
Per project | Persistent | Amplitude project API key. Mode 0o600, keyed by hashed install-dir. Replaces the previous keychain backend, which triggered an OS unlock prompt on every launch. |
| Per-project debug log | ~/.amplitude/wizard/runs/<sha256(installDir)>/log.txt (+ log.ndjson) |
Per project | 5 MB rotation | Structured wizard logs. Two parallel runs in different directories no longer collide. |
| Session checkpoint | ~/.amplitude/wizard/runs/<sha256(installDir)>/checkpoint.json |
Per install directory | 24 hours | Intro state, region, org/project selection, framework detection. Zod-validated on load. No credentials. |
| Plans + agent state | ~/.amplitude/wizard/plans/<planId>.json, ~/.amplitude/wizard/state/<attemptId>.json |
Per plan / per attempt | 24 h / per-run | wizard plan output and agent compaction-recovery snapshots. |
| Project metadata | <installDir>/.amplitude/events.json, <installDir>/.amplitude/project-binding.json, <installDir>/.amplitude/dashboard.json (dashboard path gitignored; legacy <installDir>/ampli.json is read for one minor cycle but no longer written — Phase G-1) |
Per project | Persistent | Approved event plan + org/project binding + dashboard URL. |
| In-memory store | WizardStore (nanostores) |
Per run | Process lifetime | Full session state, tasks, prompts, overlays, UI state |
The /diagnostics slash command prints the full layout for the current project — useful when filing a bug report.
Security invariants:
- Credential files use
0o600permissions (owner read/write only) - Security- and recovery-sensitive JSON uses
atomicWriteJSON()(temp-file + rename) so a crash mid-write leaves the prior file intact — including OAuth tokens, API key store, checkpoints, plans, agent recovery snapshots, and update-check cache. Append-only logs (log.txt/log.ndjson), directory creation, streamed or editor-facing writes, and intentional exceptions (e.g..env.localhandling per platform/editor constraints) are outside that contract. Concurrent wizard runs coordinate via the apply lock (separate from JSON atomic writes). - Checkpoint files never contain tokens, API keys, or access tokens
- Config scoping validates org ID against live data to prevent cross-project leakage
- Zone priority: CLI flag > env var > stored config (prevents env var pollution across projects)
-
Run the
/reflectskill on the session and paste the numbered checklist into the PR description (or link to it). Treat that as part of the PR artifact, not optional narration. Human-oriented PR steps also live inCONTRIBUTING.md. When running/reflect, treat this repo’sCLAUDE.mdas the canonical place to de-dupe proposals — a global~/.claude/CLAUDE.mdmay be absent in worktrees or sandboxes. -
If
git statusshows your branch is behind its upstream (e.g.origin/your-branch), rungit pull --rebase origin <branch>beforegit pushso the push is fast-forward and history stays linear. -
After you
git pusha branch, prefer opening the PR with the GitHub CLI:gh pr create --fill(or pass--title/--bodyexplicitly). Ifghis not installed or authenticated, use the compare URL yourgit pushprinted instead. -
After changing
src/ui/tui/screens,flows.ts,router.ts, orstore.tsnavigation-related code, run Vitest in a stable pool before pushing (avoids fork timeouts / flakes on wide runs):pnpm exec vitest run --pool=forks --maxWorkers=1 \ src/ui/tui/__tests__/router.test.ts \ src/ui/tui/__tests__/flow-invariants.test.tsAdd any
src/ui/tui/screens/__tests__/files that cover screens you edited.
This repo enforces conventional commit PR titles and commit messages. The type prefix must be one of: feat, fix, docs, test, ci, refactor, perf, chore, revert. Example: feat: add org picker to auth flow.
- Property-key naming. Event properties, user properties, and group-identify keys are all lowercase-with-spaces:
'org id','project id','project name','duration ms','error message','detected framework'. When adding a newwizardCapture/captureWizardErrorcall, spell multi-word keys as quoted strings — don't use TypeScript property shorthand ({ durationMs }) for multi-word names. Single-word keys (integration,status,attempt,region,mode) and Amplitude-reserved keys starting with$($app_name,$error) pass through untouched. Note: these replaced the olderworkspace_id/workspace_namekeys as part of the workspace → project rename. - Group analytics. Every event is automatically associated with the
'org id'group viasetGroup()insideidentifyUser()(src/utils/analytics.ts). Do not re-passorgIdper event. - Dev vs prod telemetry. Local dev runs (
NODE_ENV=development, set bypnpm try/pnpm dev) route telemetry to the dev Amplitude project. Prod builds use the production key. Both keys mirror the App API's ampli config and point at the mainamplitude/Amplitudeproject — same one the rest of the Amplitude app writes to.
- Screens are passive. Screens observe session state and render accordingly. They do not own navigation logic — the router derives the active screen from session state.
- Session is the single source of truth. All state lives in
WizardSession. Screens and steps read from and write to the session; they do not communicate directly. - Flows are declarative. Each flow is a pipeline of
{ screen, show, isComplete }entries. Navigation advances automatically whenisCompletereturns true. - Overlays interrupt without breaking flow.
OutageScreenand other overlays are pushed onto an overlay stack and popped when resolved, resuming the flow where it left off. Overlay enum (src/ui/tui/router.ts):Outage,Snake,Mcp,Slack,Logout,Login. - Slash commands are always available.
/region,/login,/logout,/whoami,/create-project,/mcp,/slack,/feedback,/clear,/help,/debug,/diagnostics,/diff,/snake,/version,/exitmust be interceptable at any point in the session. The canonical list lives insrc/ui/tui/console-commands.ts— update both together. - Framework configs are data-driven. No switch statements or per-framework routing. Everything goes through
FrameworkConfig+FRAMEWORK_REGISTRY. The universal runner handles all shared behavior. - Agent commandments (
src/lib/commandments.ts) are always injected as system prompt. Key rules: never hardcode secrets, always usewizard-toolsMCP for env vars and package manager detection, must callconfirm_event_planbefore writingtrack()calls. - Detection order matters. The
Integrationenum order inconstants.tscontrols both auto-detection priority (first match wins) and display order in the CLI select menu.
pnpm try # run the wizard locally (from source, no build needed)
pnpm try --agent # run in agent mode (NDJSON output, auto-approve)
pnpm try --yes # run in CI mode (alias for --ci, non-interactive)
pnpm build # compile TypeScript
pnpm test # run unit tests (vitest)
pnpm test:watch # run unit tests in watch mode
pnpm test:bdd # run BDD/Cucumber tests (features/*.feature)
pnpm test:e2e # build + run e2e tests
pnpm test:proxy # validate proxy health, models, streaming
pnpm lint # run prettier + eslint checks
pnpm fix # auto-fix lint issues
pnpm flows # render docs/flows.md diagrams to docs/diagrams/
pnpm skills:refresh # pull all skills from context-hub (pinned tag in .context-hub-version)
pnpm skills:verify-pin # confirm the pinned tag exists on amplitude/context-hub- Focused TUI runs: when iterating on Ink screens or the router, prefer
pnpm exec vitest run --pool=forks --maxWorkers=1 <paths…>
so workers stay predictable; use fullpnpm testbefore merge when practical. - Unit tests:
src/**/__tests__/— vitest, run withpnpm test - Router + flow tests:
src/ui/tui/__tests__/router.test.ts— parameterized router resolution tests.src/ui/tui/__tests__/flow-invariants.test.ts— fast-check property-based tests verifying flow invariants (24 tests: no backward navigation, unauthenticated users never see Run, error state skips post-success screens, etc.) - BDD tests:
features/— Cucumber.js feature files and step definitions, run withpnpm test:bdd - E2e tests:
e2e-tests/— build + run against test applications ine2e-tests/test-applications/ - Proxy tests:
vitest.config.proxy.ts— validate LLM proxy connectivity
GitHub Actions workflows in .github/workflows/:
build.yml— build + unit tests on push/PRbehavior-driven-tests.yml— BDD tests on push/PRpr-conventional-commit.yml— enforces conventional commit PR titlespublish.yml— npm publish flowrelease-please.yml— automated release PRsrefresh-instrumentation-skills.yml/refresh-integration-skills.yml— skill refresh automation
docs/flows.md— flow diagrams (source of truth for UX)docs/architecture.md— high-level architecture overviewdocs/dual-mode-architecture.md— TUI + agent + CI mode architecturedocs/mcp-installation.md— how MCP server installation works across editorsdocs/critical-files.md— files ranked by blast radiusdocs/engineering-patterns.md— async safety, retry, error classification patternsdocs/external-services.md— third-party services the wizard talks todocs/ux-improvements.md— UX backlog and recently-shipped polishdocs/releasing.md— release process and versioning
Before any work in src/ui/tui/**, read docs/design/wizard-design-kit.md — principles, color/glyph tokens, voice library, receipts format, and the per-screen iteration prompt template. The kit is the contract; deviations need explicit approval.