diff --git a/docs/superpowers/plans/2026-05-11-demo-guided-tour.md b/docs/superpowers/plans/2026-05-11-demo-guided-tour.md new file mode 100644 index 00000000..3204e111 --- /dev/null +++ b/docs/superpowers/plans/2026-05-11-demo-guided-tour.md @@ -0,0 +1,813 @@ +# Demo Guided Tour Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the disconnected "Try KTX with packaged demo data" flow with a guided tour that walks users through the same setup wizard steps using pre-filled, read-only selections, then connects their agent to the populated demo project. + +**Architecture:** A new `setup-demo-tour.ts` module owns the demo tour flow. It renders read-only cards (database, sources), a simulated context build replay using the existing `renderContextBuildView` + `createRepainter` pipeline from `context-build-view.ts`, then hands off to the real `runKtxSetupAgentsStep`. The entry point in `setup.ts` (`runKtxSetupDemoFromEntryMenu`) is rewired to call this new module instead of `runKtxDemo`. + +**Tech Stack:** TypeScript (ESM), Node.js raw stdin for keypress handling, existing `@clack/prompts` visual patterns, vitest for tests. + +--- + +### Task 1: Create `setup-demo-tour.ts` with keypress utility and banner + +**Files:** +- Create: `packages/cli/src/setup-demo-tour.ts` +- Test: `packages/cli/src/setup-demo-tour.test.ts` + +- [ ] **Step 1: Write the failing test for `renderDemoBanner`** + +```typescript +// packages/cli/src/setup-demo-tour.test.ts +import { describe, expect, it } from 'vitest'; +import { renderDemoBanner } from './setup-demo-tour.js'; + +describe('renderDemoBanner', () => { + it('includes demo mode explanation', () => { + const output = renderDemoBanner(); + expect(output).toContain('Demo mode'); + expect(output).toContain('pre-processed'); + expect(output).toContain('read-only'); + }); +}); +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour` +Expected: FAIL — module not found + +- [ ] **Step 3: Implement `renderDemoBanner` and `waitForDemoNavigation`** + +```typescript +// packages/cli/src/setup-demo-tour.ts +import type { KtxCliIo } from './cli-runtime.js'; +import { KtxSetupExitError } from './setup-interrupt.js'; + +const ESC = String.fromCharCode(0x1b); + +function cyan(text: string): string { + return `${ESC}[36m${text}${ESC}[39m`; +} + +function dim(text: string): string { + return `${ESC}[2m${text}${ESC}[22m`; +} + +export function renderDemoBanner(): string { + const lines = [ + '', + `┌ ${cyan('Demo mode')} — data has been pre-processed and KTX context is already built.`, + `│ This walkthrough illustrates the setup steps. Selections are pre-filled and read-only.`, + '', + ]; + return lines.join('\n'); +} + +export async function waitForDemoNavigation( + stdin: NodeJS.ReadStream = process.stdin, +): Promise<'forward' | 'back'> { + return new Promise((resolve, reject) => { + const wasRaw = stdin.isRaw; + if (stdin.setRawMode) stdin.setRawMode(true); + stdin.resume(); + + const onData = (data: Buffer) => { + const key = data.toString(); + if (key === '\r' || key === '\n') { + cleanup(); + resolve('forward'); + } else if (key === '\x1b') { + cleanup(); + resolve('back'); + } else if (key === '\x03') { + cleanup(); + reject(new KtxSetupExitError()); + } + }; + + const cleanup = () => { + stdin.off('data', onData); + if (stdin.setRawMode) stdin.setRawMode(wasRaw ?? false); + }; + + stdin.on('data', onData); + }); +} +``` + +- [ ] **Step 4: Run the test to verify it passes** + +Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add packages/cli/src/setup-demo-tour.ts packages/cli/src/setup-demo-tour.test.ts +git commit -m "feat(cli): add demo tour banner and keypress navigation utility" +``` + +--- + +### Task 2: Add `renderDemoCard` function + +**Files:** +- Modify: `packages/cli/src/setup-demo-tour.ts` +- Modify: `packages/cli/src/setup-demo-tour.test.ts` + +- [ ] **Step 1: Write the failing test for `renderDemoCard`** + +Append to the test file: + +```typescript +import { renderDemoCardContent } from './setup-demo-tour.js'; + +describe('renderDemoCardContent', () => { + it('renders a card with title and selections', () => { + const output = renderDemoCardContent('Database connection', ['PostgreSQL (demo warehouse)']); + expect(output).toContain('Database connection'); + expect(output).toContain('PostgreSQL (demo warehouse)'); + expect(output).toContain('Press Enter to continue'); + expect(output).toContain('Escape to go back'); + }); + + it('renders multiple selections', () => { + const output = renderDemoCardContent('Context sources', ['dbt', 'Metabase', 'Notion']); + expect(output).toContain('dbt'); + expect(output).toContain('Metabase'); + expect(output).toContain('Notion'); + }); +}); +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour` +Expected: FAIL — `renderDemoCardContent` not exported + +- [ ] **Step 3: Implement `renderDemoCardContent` and `renderDemoCard`** + +Add to `setup-demo-tour.ts`: + +```typescript +export function renderDemoCardContent(title: string, selections: string[]): string { + const lines = [ + `┌ ${title}`, + '│', + ...selections.map((s) => `│ ${cyan('▸')} ${s}`), + '│', + `│ ${dim('Press Enter to continue, Escape to go back')}`, + '└', + '', + ]; + return lines.join('\n'); +} + +export async function renderDemoCard( + title: string, + selections: string[], + io: KtxCliIo, + stdin?: NodeJS.ReadStream, + waitNav?: (stdin?: NodeJS.ReadStream) => Promise<'forward' | 'back'>, +): Promise<'forward' | 'back'> { + io.stdout.write(renderDemoBanner()); + io.stdout.write(renderDemoCardContent(title, selections)); + const nav = waitNav ?? waitForDemoNavigation; + return nav(stdin); +} +``` + +- [ ] **Step 4: Run the test to verify it passes** + +Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add packages/cli/src/setup-demo-tour.ts packages/cli/src/setup-demo-tour.test.ts +git commit -m "feat(cli): add demo tour read-only card rendering" +``` + +--- + +### Task 3: Add demo context build replay animation + +**Files:** +- Modify: `packages/cli/src/setup-demo-tour.ts` +- Modify: `packages/cli/src/setup-demo-tour.test.ts` + +- [ ] **Step 1: Write the failing test for demo replay event sequence** + +Append to the test file: + +```typescript +import { buildDemoReplayTimeline, DEMO_REPLAY_TARGETS } from './setup-demo-tour.js'; + +describe('buildDemoReplayTimeline', () => { + it('produces events for all four demo targets', () => { + const events = buildDemoReplayTimeline(); + const connectionIds = new Set(events.map((e) => e.connectionId)); + expect(connectionIds).toEqual(new Set(['demo-warehouse', 'dbt', 'metabase', 'notion'])); + }); + + it('ends with all targets done', () => { + const events = buildDemoReplayTimeline(); + const lastByConnection = new Map(); + for (const e of events) { + lastByConnection.set(e.connectionId, e.status); + } + for (const status of lastByConnection.values()) { + expect(status).toBe('done'); + } + }); + + it('events are sorted by delayMs', () => { + const events = buildDemoReplayTimeline(); + for (let i = 1; i < events.length; i++) { + expect(events[i]!.delayMs).toBeGreaterThanOrEqual(events[i - 1]!.delayMs); + } + }); +}); + +describe('DEMO_REPLAY_TARGETS', () => { + it('has one primary source and three context sources', () => { + expect(DEMO_REPLAY_TARGETS.primarySources).toHaveLength(1); + expect(DEMO_REPLAY_TARGETS.contextSources).toHaveLength(3); + }); +}); +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour` +Expected: FAIL — exports not found + +- [ ] **Step 3: Implement replay timeline and target definitions** + +Add to `setup-demo-tour.ts`: + +```typescript +import type { KtxPublicIngestPlanTarget } from './public-ingest.js'; +import type { ContextBuildTargetState, ContextBuildViewState } from './context-build-view.js'; + +export interface DemoReplayEvent { + delayMs: number; + connectionId: string; + status: 'running' | 'done'; + detailLine: string | null; + summaryText: string | null; +} + +function createDemoTarget(connectionId: string, operation: 'scan' | 'source-ingest', driver: string): KtxPublicIngestPlanTarget { + return { + connectionId, + driver, + operation, + debugCommand: `ktx ${operation === 'scan' ? 'scan' : 'ingest'} ${connectionId}`, + steps: operation === 'scan' ? ['scan'] : ['source-ingest'], + }; +} + +const primaryTarget = createDemoTarget('demo-warehouse', 'scan', 'postgres'); +const dbtTarget = createDemoTarget('dbt', 'source-ingest', 'dbt'); +const metabaseTarget = createDemoTarget('metabase', 'source-ingest', 'metabase'); +const notionTarget = createDemoTarget('notion', 'source-ingest', 'notion'); + +function createTargetState(target: KtxPublicIngestPlanTarget): ContextBuildTargetState { + return { + target, + status: 'queued', + detailLine: null, + summaryText: null, + startedAt: null, + elapsedMs: 0, + }; +} + +export const DEMO_REPLAY_TARGETS = { + primarySources: [primaryTarget], + contextSources: [dbtTarget, metabaseTarget, notionTarget], +}; + +export function buildDemoReplayTimeline(): DemoReplayEvent[] { + return [ + { delayMs: 0, connectionId: 'demo-warehouse', status: 'running', detailLine: 'scanning...', summaryText: null }, + { delayMs: 600, connectionId: 'demo-warehouse', status: 'running', detailLine: '[50%] scanning...', summaryText: null }, + { delayMs: 1200, connectionId: 'demo-warehouse', status: 'done', detailLine: null, summaryText: 'completed' }, + { delayMs: 1200, connectionId: 'dbt', status: 'running', detailLine: 'ingesting...', summaryText: null }, + { delayMs: 1800, connectionId: 'dbt', status: 'running', detailLine: '[60%] ingesting...', summaryText: null }, + { delayMs: 2200, connectionId: 'dbt', status: 'done', detailLine: null, summaryText: 'completed' }, + { delayMs: 2200, connectionId: 'metabase', status: 'running', detailLine: 'ingesting...', summaryText: null }, + { delayMs: 2800, connectionId: 'metabase', status: 'done', detailLine: null, summaryText: 'completed' }, + { delayMs: 2800, connectionId: 'notion', status: 'running', detailLine: 'ingesting...', summaryText: null }, + { delayMs: 3400, connectionId: 'notion', status: 'done', detailLine: null, summaryText: 'completed' }, + ]; +} +``` + +- [ ] **Step 4: Run the test to verify it passes** + +Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour` +Expected: PASS + +- [ ] **Step 5: Implement `runDemoContextReplay` animation driver** + +Add to `setup-demo-tour.ts`: + +```typescript +import { renderContextBuildView, createRepainter } from './context-build-view.js'; + +export async function runDemoContextReplay( + io: KtxCliIo, + stdin?: NodeJS.ReadStream, +): Promise<'forward' | 'back'> { + const repainter = createRepainter(io); + const timeline = buildDemoReplayTimeline(); + + const state: ContextBuildViewState = { + primarySources: DEMO_REPLAY_TARGETS.primarySources.map((t) => createTargetState(t)), + contextSources: DEMO_REPLAY_TARGETS.contextSources.map((t) => createTargetState(t)), + frame: 0, + startedAt: Date.now(), + totalElapsedMs: 0, + }; + + const allTargets = [...state.primarySources, ...state.contextSources]; + const targetMap = new Map(allTargets.map((t) => [t.target.connectionId, t])); + let eventIndex = 0; + const startTime = Date.now(); + const FRAME_MS = 120; + + await new Promise((resolve) => { + const interval = setInterval(() => { + const elapsed = Date.now() - startTime; + state.frame += 1; + state.totalElapsedMs = elapsed; + + while (eventIndex < timeline.length && timeline[eventIndex]!.delayMs <= elapsed) { + const event = timeline[eventIndex]!; + const target = targetMap.get(event.connectionId); + if (target) { + target.status = event.status; + target.detailLine = event.detailLine; + target.summaryText = event.summaryText; + if (event.status === 'running' && target.startedAt === null) { + target.startedAt = Date.now(); + } + if (event.status === 'done') { + target.elapsedMs = target.startedAt ? Date.now() - target.startedAt : 0; + } + } + eventIndex += 1; + } + + for (const t of allTargets) { + if (t.status === 'running' && t.startedAt !== null) { + t.elapsedMs = Date.now() - t.startedAt; + } + } + + repainter.paint(renderContextBuildView(state, { styled: io.stdout.isTTY ?? false, showHint: false })); + + if (eventIndex >= timeline.length && allTargets.every((t) => t.status === 'done')) { + clearInterval(interval); + resolve(); + } + }, FRAME_MS); + }); + + io.stdout.write(renderDemoContextCompletionSummary()); + return waitForDemoNavigation(stdin); +} + +function renderDemoContextCompletionSummary(): string { + const lines = [ + '', + `${cyan('★')} KTX finished ingesting demo data`, + '', + ' Placeholder — final counts will come from pre-packaged demo results.', + '', + ` ${dim('Press Enter to continue, Escape to go back')}`, + '', + ]; + return lines.join('\n'); +} +``` + +Note: `renderDemoContextCompletionSummary` is a placeholder that will be updated when +the user provides the real pre-packaged demo data. The summary counts (business areas, +query definitions, knowledge pages) will be populated from those assets. + +- [ ] **Step 6: Run tests and type-check** + +Run: `pnpm --filter @ktx/cli run type-check && pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour` +Expected: PASS + +- [ ] **Step 7: Commit** + +```bash +git add packages/cli/src/setup-demo-tour.ts packages/cli/src/setup-demo-tour.test.ts +git commit -m "feat(cli): add demo context build replay animation" +``` + +--- + +### Task 4: Add transition message and completion summary + +**Files:** +- Modify: `packages/cli/src/setup-demo-tour.ts` +- Modify: `packages/cli/src/setup-demo-tour.test.ts` + +- [ ] **Step 1: Write the failing tests** + +Append to test file: + +```typescript +import { renderDemoAgentTransition, renderDemoCompletionSummary } from './setup-demo-tour.js'; + +describe('renderDemoAgentTransition', () => { + it('includes transition message about connecting agent', () => { + const output = renderDemoAgentTransition(); + expect(output).toContain('Demo project is ready'); + expect(output).toContain('connect your agent'); + }); +}); + +describe('renderDemoCompletionSummary', () => { + it('includes project path and temp warning', () => { + const output = renderDemoCompletionSummary('/tmp/ktx-demo-abc123', true); + expect(output).toContain('/tmp/ktx-demo-abc123'); + expect(output).toContain('temporary'); + expect(output).toContain('ktx setup'); + }); + + it('shows manual agent instructions when agent not installed', () => { + const output = renderDemoCompletionSummary('/tmp/ktx-demo-abc123', false); + expect(output).toContain('ktx setup --agents'); + }); + + it('shows success message when agent installed', () => { + const output = renderDemoCompletionSummary('/tmp/ktx-demo-abc123', true); + expect(output).toContain('agent is connected'); + }); +}); +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour` +Expected: FAIL — exports not found + +- [ ] **Step 3: Implement transition and completion rendering** + +Add to `setup-demo-tour.ts`: + +```typescript +export function renderDemoAgentTransition(): string { + const lines = [ + '', + `┌ Demo project is ready — let's connect your agent`, + '│', + '│ Your KTX context has been built with demo data.', + '│ Select an agent to start using it.', + '└', + '', + ]; + return lines.join('\n'); +} + +export function renderDemoCompletionSummary(projectDir: string, agentInstalled: boolean): string { + const lines = [ + '', + `${cyan('★')} KTX demo is ready`, + '', + ]; + + if (agentInstalled) { + lines.push(' Your agent is connected to a demo KTX project.'); + } else { + lines.push(' Demo project created. Connect an agent to start using it:'); + lines.push(` $ ktx setup --agents --project-dir ${projectDir}`); + } + + lines.push( + '', + ` ${dim('⚠')} This project is in a temporary directory and will be`, + ` cleaned up by your system. To set up KTX with your own`, + ' data, run: ktx setup', + '', + ` Project: ${projectDir}`, + '', + ); + return lines.join('\n'); +} +``` + +- [ ] **Step 4: Run the test to verify it passes** + +Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add packages/cli/src/setup-demo-tour.ts packages/cli/src/setup-demo-tour.test.ts +git commit -m "feat(cli): add demo tour transition and completion summary" +``` + +--- + +### Task 5: Implement `runDemoTour` orchestrator + +**Files:** +- Modify: `packages/cli/src/setup-demo-tour.ts` +- Modify: `packages/cli/src/setup-demo-tour.test.ts` + +- [ ] **Step 1: Write the failing test for the orchestrator** + +Append to test file: + +```typescript +import { vi } from 'vitest'; +import type { KtxSetupAgentsResult } from './setup-agents.js'; +import { runDemoTour } from './setup-demo-tour.js'; + +describe('runDemoTour', () => { + function createMockIo() { + const chunks: string[] = []; + return { + io: { + stdout: { isTTY: true, columns: 80, write: (chunk: string) => { chunks.push(chunk); } }, + stderr: { write: () => {} }, + }, + chunks, + }; + } + + it('returns 0 on successful tour with agent installed', async () => { + const { io } = createMockIo(); + const mockAgents = vi.fn<() => Promise>().mockResolvedValue({ + status: 'ready', + projectDir: '/tmp/test', + installs: [{ target: 'claude-code' as const, scope: 'project' as const, mode: 'both' as const }], + }); + + const navigation = vi.fn<() => Promise<'forward' | 'back'>>().mockResolvedValue('forward'); + + const result = await runDemoTour( + { inputMode: 'auto' }, + io, + { agents: mockAgents, waitForNavigation: navigation, skipReplayAnimation: true }, + ); + expect(result).toBe(0); + expect(mockAgents).toHaveBeenCalled(); + }); + + it('handles back navigation from first step', async () => { + const { io } = createMockIo(); + const navigation = vi.fn<() => Promise<'forward' | 'back'>>().mockResolvedValue('back'); + + const result = await runDemoTour( + { inputMode: 'auto' }, + io, + { waitForNavigation: navigation, skipReplayAnimation: true }, + ); + expect(result).toBe(0); + }); +}); +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour` +Expected: FAIL — `runDemoTour` not exported or wrong signature + +- [ ] **Step 3: Implement `runDemoTour`** + +Add to `setup-demo-tour.ts`: + +```typescript +import { defaultDemoProjectDir, ensureSeededDemoProject } from './demo-assets.js'; +import type { KtxSetupAgentsResult } from './setup-agents.js'; +import { runKtxSetupAgentsStep } from './setup-agents.js'; + +type DemoStep = 'databases' | 'sources' | 'context' | 'agents'; + +const DEMO_STEPS: DemoStep[] = ['databases', 'sources', 'context', 'agents']; + +export interface DemoTourDeps { + agents?: (args: Parameters[0], io: KtxCliIo) => Promise; + waitForNavigation?: (stdin?: NodeJS.ReadStream) => Promise<'forward' | 'back'>; + ensureProject?: typeof ensureSeededDemoProject; + skipReplayAnimation?: boolean; +} + +export async function runDemoTour( + args: { inputMode: 'auto' | 'disabled' }, + io: KtxCliIo, + deps: DemoTourDeps = {}, +): Promise { + const waitNav = deps.waitForNavigation ?? waitForDemoNavigation; + const ensureProject = deps.ensureProject ?? ensureSeededDemoProject; + + const projectDir = defaultDemoProjectDir(); + await ensureProject({ projectDir }); + + let stepIndex = 0; + + while (stepIndex < DEMO_STEPS.length) { + const step = DEMO_STEPS[stepIndex]!; + let direction: 'forward' | 'back'; + + if (step === 'databases') { + direction = await renderDemoCard('Database connection', ['PostgreSQL (demo warehouse)'], io, undefined, waitNav); + } else if (step === 'sources') { + direction = await renderDemoCard('Context sources', ['dbt', 'Metabase', 'Notion'], io, undefined, waitNav); + } else if (step === 'context') { + io.stdout.write(renderDemoBanner()); + if (deps.skipReplayAnimation) { + direction = await waitNav(); + } else { + direction = await runDemoContextReplay(io); + } + } else { + io.stdout.write(renderDemoAgentTransition()); + const agentsRunner = deps.agents ?? runKtxSetupAgentsStep; + const agentsResult = await agentsRunner( + { + projectDir, + inputMode: args.inputMode, + yes: false, + agents: true, + scope: 'project', + mode: 'both', + skipAgents: false, + }, + io, + ); + const agentInstalled = agentsResult.status === 'ready'; + if (agentsResult.status === 'back') { + direction = 'back'; + } else { + io.stdout.write(renderDemoCompletionSummary(projectDir, agentInstalled)); + return 0; + } + } + + if (direction === 'back') { + if (stepIndex === 0) return 0; + stepIndex -= 1; + } else { + stepIndex += 1; + } + } + + return 0; +} +``` + +- [ ] **Step 4: Run the test to verify it passes** + +Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour` +Expected: PASS + +- [ ] **Step 5: Run type-check** + +Run: `pnpm --filter @ktx/cli run type-check` +Expected: PASS — all types align with existing interfaces + +- [ ] **Step 6: Commit** + +```bash +git add packages/cli/src/setup-demo-tour.ts packages/cli/src/setup-demo-tour.test.ts +git commit -m "feat(cli): add runDemoTour orchestrator with step navigation" +``` + +--- + +### Task 6: Wire up in `setup.ts` + +**Files:** +- Modify: `packages/cli/src/setup.ts` + +- [ ] **Step 1: Read the current `runKtxSetupDemoFromEntryMenu` function** + +Read `packages/cli/src/setup.ts` and locate `runKtxSetupDemoFromEntryMenu` (around lines 218-233). + +Current implementation: +```typescript +async function runKtxSetupDemoFromEntryMenu( + args: Extract, + io: KtxCliIo, + deps: KtxSetupDeps, +): Promise { + const runner = deps.demo ?? (await import('./demo.js')).runKtxDemo; + return await runner( + { + command: 'seeded', + projectDir: defaultDemoProjectDir(), + outputMode: 'viz', + inputMode: args.inputMode, + }, + io, + ); +} +``` + +- [ ] **Step 2: Replace with demo tour call** + +Replace the function body to call `runDemoTour`: + +```typescript +async function runKtxSetupDemoFromEntryMenu( + args: Extract, + io: KtxCliIo, + deps: KtxSetupDeps, +): Promise { + const { runDemoTour } = await import('./setup-demo-tour.js'); + return await runDemoTour( + { inputMode: args.inputMode }, + io, + { agents: deps.agents }, + ); +} +``` + +- [ ] **Step 3: Update imports — remove unused `defaultDemoProjectDir` import if no longer needed elsewhere in setup.ts** + +Check if `defaultDemoProjectDir` is used elsewhere in `setup.ts`. If it's only used +in `runKtxSetupDemoFromEntryMenu`, remove the import. If used elsewhere, keep it. + +Also check if the `KtxDemoArgs` import is still needed. If `runKtxSetupDemoFromEntryMenu` +was the only consumer of `deps.demo` with that type, it may now be unused. Keep the +`demo` slot in `KtxSetupDeps` for backwards compatibility but it will no longer be +called from the entry menu path. + +- [ ] **Step 4: Run type-check and tests** + +Run: `pnpm --filter @ktx/cli run type-check && pnpm --filter @ktx/cli run test` +Expected: PASS — existing tests continue to work, demo tour is now wired in + +- [ ] **Step 5: Commit** + +```bash +git add packages/cli/src/setup.ts +git commit -m "feat(cli): wire demo tour into setup entry menu" +``` + +--- + +### Task 7: End-to-end verification + +**Files:** +- None (verification only) + +- [ ] **Step 1: Run full test suite** + +Run: `pnpm --filter @ktx/cli run test 2>&1 | tee /tmp/ktx-demo-tour-test.log` +Expected: All tests pass. Check the output for any regressions. + +- [ ] **Step 2: Run type-check across workspace** + +Run: `pnpm run type-check` +Expected: PASS + +- [ ] **Step 3: Run pre-commit checks if available** + +Run: `pnpm run check` (if configured) +Expected: PASS + +- [ ] **Step 4: Manual smoke test (if TTY available)** + +Run: `pnpm --filter @ktx/cli run build && node packages/cli/dist/cli.js setup` + +1. Select "Try KTX with packaged demo data" +2. Verify demo banner appears with full explanation text +3. Verify "Database connection" card shows with "PostgreSQL (demo warehouse)" +4. Press Enter → verify "Context sources" card shows with dbt, Metabase, Notion +5. Press Escape → verify you go back to database card +6. Press Enter twice → verify context build replay animation runs +7. Verify completion summary appears after replay +8. Press Enter → verify agents step prompt appears (interactive) +9. Press Escape all the way back → verify you return to entry menu + +- [ ] **Step 5: Final commit if any adjustments needed** + +```bash +git add -A +git commit -m "fix(cli): demo tour adjustments from smoke test" +``` + +--- + +## Open Seams for Demo Data + +When the user provides the real pre-packaged demo results, update these locations: + +1. **`renderDemoContextCompletionSummary()`** in `setup-demo-tour.ts` — replace placeholder text with actual counts (business areas, query definitions, knowledge pages) from the demo data +2. **`buildDemoReplayTimeline()`** in `setup-demo-tour.ts` — adjust timing and progress details to match the real ingestion profile +3. **`demo-assets.ts`** — update `REQUIRED_SEEDED_ASSET_PATHS` and `demoConfig()` if the demo dataset changes from SQLite/Orbit to Postgres/dbt/Metabase/Notion +4. **Pre-packaged asset files** in `packages/cli/assets/demo/` — replace with the new demo dataset