fix(ci): resolve main merge check failures

This commit is contained in:
Andrey Avtomonov 2026-05-12 10:39:32 +02:00
commit eb20181689
164 changed files with 9687 additions and 631 deletions

View file

@ -0,0 +1,411 @@
# Agent-Friendly Docs Site 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:** Make `docs-site` discoverable and readable by coding agents through `llms.txt`, bundled markdown, per-page markdown routes, markdown negotiation, and stricter agent-friendly docs content.
**Architecture:** Keep the existing Next 15 + Fumadocs app. Add a small `lib/llm-docs.ts` module that reads Fumadocs pages and builds machine-readable markdown responses, then expose those responses through route handlers and a markdown negotiation proxy. Rewrite existing MDX pages in place so the rendered UI and machine-readable routes share one source of truth.
**Tech Stack:** Next.js 15 App Router, Fumadocs, MDX, TypeScript, pnpm, Node 22.
---
### Task 1: Machine-Readable Docs Routes
**Files:**
- Create: `docs-site/lib/llm-docs.ts`
- Create: `docs-site/app/llms.txt/route.ts`
- Create: `docs-site/app/llms-full.txt/route.ts`
- Create: `docs-site/app/llms.mdx/docs/[[...slug]]/route.ts`
- Modify: `docs-site/next.config.mjs`
- [ ] **Step 1: Add the LLM docs utility**
Create `docs-site/lib/llm-docs.ts` with functions that:
```ts
import { source } from "@/lib/source";
const SITE_ORIGIN = "https://ktx.dev";
export type LlmDocsPage = {
title: string;
description?: string;
url: string;
markdownUrl: string;
slug: string[];
getMarkdown: () => Promise<string>;
};
export function getLlmDocsPages(): LlmDocsPage[] {
return source.getPages().map((page) => ({
title: page.data.title,
description: page.data.description,
url: page.url,
markdownUrl: `${page.url}.md`,
slug: page.slugs,
getMarkdown: async () => normalizeMarkdown(await page.data.getText("raw")),
}));
}
export function getLlmDocsPage(slug: string[] | undefined) {
const page = source.getPage(slug);
if (!page) return null;
return {
title: page.data.title,
description: page.data.description,
url: page.url,
markdownUrl: `${page.url}.md`,
slug: page.slugs,
getMarkdown: async () => normalizeMarkdown(await page.data.getText("raw")),
} satisfies LlmDocsPage;
}
export async function getPageMarkdown(page: LlmDocsPage) {
const body = await page.getMarkdown();
const description = page.description ? `\n\n> ${page.description}` : "";
return `# ${page.title}${description}\n\nCanonical URL: ${page.url}\nMarkdown URL: ${page.markdownUrl}\n\n${body}`;
}
export function buildLlmsTxt() {
const pages = getLlmDocsPages();
const byUrl = new Map(pages.map((page) => [page.url, page]));
const link = (url: string, label: string, fallbackDescription: string) => {
const page = byUrl.get(url);
const description = page?.description ?? fallbackDescription;
return `- [${label}](${url}): ${description}`;
};
return `# KTX
> Agent-native context layer for analytics engineering and database agents.
KTX provides semantic-layer files, warehouse scans, knowledge pages, provenance, and agent-facing tools that help coding agents answer analytics questions without inventing metrics or joins.
## Start Here
${link("/docs/getting-started/introduction", "Introduction", "What KTX is and who it is for")}
${link("/docs/getting-started/quickstart", "Quickstart", "Set up KTX and build your first context")}
${link("/docs/guides/serving-agents", "Serving Agents", "Expose KTX context through MCP and CLI tools")}
${link("/docs/guides/writing-context", "Writing Context", "Write semantic sources and knowledge pages")}
## Machine-Readable Documentation
- [Full documentation](/llms-full.txt): All docs pages in one plain-text markdown response
- [Quickstart markdown](/docs/getting-started/quickstart.md): Raw markdown for the setup guide
- [Agent CLI markdown](/docs/cli-reference/ktx-agent.md): Raw markdown for machine-readable agent commands
- [Serving Agents markdown](/docs/guides/serving-agents.md): Raw markdown for MCP and CLI workflows
## CLI Reference
${link("/docs/cli-reference/ktx-setup", "ktx setup", "Interactive project setup")}
${link("/docs/cli-reference/ktx-agent", "ktx agent", "Machine-readable commands for coding agents")}
${link("/docs/cli-reference/ktx-sl", "ktx sl", "Semantic-layer commands")}
${link("/docs/cli-reference/ktx-wiki", "ktx wiki", "Knowledge page commands")}
${link("/docs/cli-reference/ktx-connection", "ktx connection", "Connection management commands")}
## Integrations
${link("/docs/integrations/agent-clients", "Agent Clients", "Configure Claude Code, Cursor, Codex, and OpenCode")}
${link("/docs/integrations/primary-sources", "Primary Sources", "Connect KTX to databases and warehouses")}
${link("/docs/integrations/context-sources", "Context Sources", "Ingest dbt, LookML, Metabase, Looker, MetricFlow, and Notion")}
`;
}
export async function buildLlmsFullTxt() {
const pages = getLlmDocsPages();
const rendered = await Promise.all(pages.map(getPageMarkdown));
return [`# KTX Full Documentation`, `Source: ${SITE_ORIGIN}`, ...rendered].join("\n\n---\n\n");
}
function normalizeMarkdown(markdown: string) {
return markdown.trim().replace(/\n{3,}/g, "\n\n");
}
```
- [ ] **Step 2: Add route handlers**
Create route files:
```ts
import { buildLlmsTxt } from "@/lib/llm-docs";
export const dynamic = "force-static";
export function GET() {
return new Response(buildLlmsTxt(), {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
}
```
```ts
import { buildLlmsFullTxt } from "@/lib/llm-docs";
export const dynamic = "force-static";
export async function GET() {
return new Response(await buildLlmsFullTxt(), {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
}
```
```ts
import { getLlmDocsPage, getPageMarkdown } from "@/lib/llm-docs";
import { notFound } from "next/navigation";
export const dynamic = "force-static";
export async function GET(
_request: Request,
props: { params: Promise<{ slug?: string[] }> },
) {
const params = await props.params;
const page = getLlmDocsPage(params.slug);
if (!page) notFound();
return new Response(await getPageMarkdown(page), {
headers: { "Content-Type": "text/markdown; charset=utf-8" },
});
}
export function generateStaticParams() {
return getLlmDocsPages().map((page) => ({ slug: page.slug }));
}
```
- [ ] **Step 3: Add `.md` rewrite**
Modify `docs-site/next.config.mjs`:
```js
import { createMDX } from "fumadocs-mdx/next";
const withMDX = createMDX();
/** @type {import('next').NextConfig} */
const config = {
async rewrites() {
return [
{
source: "/docs/:path*.md",
destination: "/llms.mdx/docs/:path*",
},
];
},
};
export default withMDX(config);
```
- [ ] **Step 4: Build check**
Run: `pnpm --filter ktx-docs build`
Expected: Next build completes and static routes include `llms.txt`, `llms-full.txt`, and the LLM markdown route.
### Task 2: Markdown Negotiation
**Files:**
- Create: `docs-site/proxy.ts`
- [ ] **Step 1: Add markdown negotiation proxy**
Create `docs-site/proxy.ts`:
```ts
import { isMarkdownPreferred, rewritePath } from "fumadocs-core/negotiation";
import { NextResponse, type NextRequest } from "next/server";
const { rewrite } = rewritePath("/docs/*path", "/llms.mdx/docs/*path");
export function proxy(request: NextRequest) {
if (!isMarkdownPreferred(request)) {
return NextResponse.next();
}
const rewrittenPath = rewrite(request.nextUrl.pathname);
if (!rewrittenPath) {
return NextResponse.next();
}
return NextResponse.rewrite(new URL(rewrittenPath, request.nextUrl));
}
export const config = {
matcher: ["/docs/:path*"],
};
```
- [ ] **Step 2: Verify build**
Run: `pnpm --filter ktx-docs build`
Expected: Build passes with the proxy included.
### Task 3: Agent-Friendly High-Priority Guides
**Files:**
- Modify: `docs-site/content/docs/getting-started/quickstart.mdx`
- Modify: `docs-site/content/docs/guides/serving-agents.mdx`
- Modify: `docs-site/content/docs/guides/writing-context.mdx`
- [ ] **Step 1: Rewrite quickstart structure**
Add sections for:
- Workflow summary
- Generated files
- Common errors and recovery
Keep existing setup detail, but make each command block copy-pasteable and each expected output complete enough for agents to recognize success.
- [ ] **Step 2: Rewrite Serving Agents as API reference**
Add tables for MCP tool inputs and CLI command inputs. Add workflows:
- Answer an analytics question through MCP
- Answer an analytics question through CLI
- Safely execute SQL with row limits
- [ ] **Step 3: Rewrite Writing Context with schemas and workflows**
Add semantic-source field tables, knowledge-page field tables, and workflows:
- Inspect a source
- Edit and validate a source
- Query through the semantic layer
- Write and search a knowledge page
- [ ] **Step 4: Build check**
Run: `pnpm --filter ktx-docs build`
Expected: MDX compiles without syntax errors.
### Task 4: CLI Reference Normalization
**Files:**
- Modify: `docs-site/content/docs/cli-reference/*.mdx`
- [ ] **Step 1: Normalize every CLI page**
For each CLI reference page, ensure this structure exists:
```md
## Command signature
```bash
ktx <command> [subcommand] [options]
```
## Subcommands
| Subcommand | Description |
|---|---|
## Options
| Flag | Type | Required | Description | Default |
|---|---|---|---|---|
## Examples
```bash
ktx <real-command> --real-flag realistic-value
```
## Output
```text
complete expected output shape
```
## Common errors
| Error | Cause | Recovery |
|---|---|---|
```
Only add sections that are relevant to the command; do not invent output for commands whose output is intentionally interactive.
- [ ] **Step 2: Build check**
Run: `pnpm --filter ktx-docs build`
Expected: MDX compiles without syntax errors.
### Task 5: Integration and Concept Page Polish
**Files:**
- Modify: `docs-site/content/docs/integrations/agent-clients.mdx`
- Modify: `docs-site/content/docs/integrations/primary-sources.mdx`
- Modify: `docs-site/content/docs/integrations/context-sources.mdx`
- Modify: `docs-site/content/docs/concepts/*.mdx`
- Modify: `docs-site/content/docs/benchmarks/link-detection.mdx`
- [ ] **Step 1: Normalize integrations**
Add structured sections for supported values, config snippets, authentication, generated files, and recovery notes. Keep existing examples aligned with current KTX commands.
- [ ] **Step 2: Add agent usage notes**
For concept and benchmark pages, add a compact `## Agent usage notes` section that tells agents when the page is relevant and which concrete page to read next.
- [ ] **Step 3: Build check**
Run: `pnpm --filter ktx-docs build`
Expected: MDX compiles without syntax errors.
### Task 6: Route Verification and Final Checks
**Files:**
- No required source changes unless verification finds a bug.
- [ ] **Step 1: Run production build**
Run: `pnpm --filter ktx-docs build`
Expected: Build succeeds.
- [ ] **Step 2: Run TypeScript check**
Run: `pnpm --filter ktx-docs exec tsc --noEmit`
Expected: TypeScript exits successfully.
- [ ] **Step 3: Start local server**
Run: `pnpm --filter ktx-docs start`
Expected: Server starts on an available port.
- [ ] **Step 4: Verify machine-readable routes**
Run:
```bash
curl -i http://localhost:3000/llms.txt
curl -i http://localhost:3000/llms-full.txt
curl -i http://localhost:3000/docs/getting-started/quickstart.md
curl -i -H "Accept: text/markdown" http://localhost:3000/docs/getting-started/quickstart
curl -i http://localhost:3000/docs/not-a-page.md
```
Expected:
- `/llms.txt`: `200`, `Content-Type: text/plain; charset=utf-8`
- `/llms-full.txt`: `200`, `Content-Type: text/plain; charset=utf-8`
- `/docs/getting-started/quickstart.md`: `200`, `Content-Type: text/markdown; charset=utf-8`
- `/docs/getting-started/quickstart` with `Accept: text/markdown`: `200`, `Content-Type: text/markdown; charset=utf-8`
- `/docs/not-a-page.md`: `404`
- [ ] **Step 5: Inspect final diff**
Run: `git diff --stat && git diff --check`
Expected: Diff contains only docs-site and plan changes, with no whitespace errors.

View file

@ -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<string, string>();
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<void>((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<KtxSetupAgentsResult>>().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<typeof runKtxSetupAgentsStep>[0], io: KtxCliIo) => Promise<KtxSetupAgentsResult>;
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<number> {
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<KtxSetupArgs, { command: 'run' }>,
io: KtxCliIo,
deps: KtxSetupDeps,
): Promise<number> {
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<KtxSetupArgs, { command: 'run' }>,
io: KtxCliIo,
deps: KtxSetupDeps,
): Promise<number> {
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

View file

@ -0,0 +1,171 @@
# Agent-Friendly Docs Site Design
## Goal
Make `docs-site` easier for coding agents and LLM readers to discover, ingest,
and use. The work applies the Vercel Academy agent-friendly docs patterns to the
KTX documentation site while preserving the current Fumadocs + Next.js
architecture.
Success means agents can:
- Discover the documentation from well-known root files.
- Fetch all documentation in one plain-text response.
- Fetch any docs page as markdown without parsing the HTML UI.
- Follow CLI, MCP, setup, integration, and semantic-layer workflows from
structured examples.
- Recover from common setup and command failures using explicit troubleshooting
notes.
## Current State
`docs-site` is a Next 15 app using Fumadocs. Source pages live under
`docs-site/content/docs`, and rendered docs are served under `/docs`.
The site currently has good human-facing MDX pages, but it does not expose:
- `/llms.txt`
- `/llms-full.txt`
- raw markdown routes such as `/docs/getting-started/quickstart.md`
- markdown content negotiation
Many docs pages already use tables and code blocks, but the structure is not
consistently optimized for literal agent parsing. CLI and agent-facing pages are
the highest-priority content because agents are most likely to copy commands and
JSON examples directly.
## Design
### Machine-readable access
Add a small LLM docs utility layer inside `docs-site`:
- `docs-site/lib/llm-docs.ts`
- Converts Fumadocs pages to raw or LLM-readable markdown.
- Builds a stable ordered list of docs pages from `source.getPages()`.
- Produces the `llms.txt` index content.
- Produces the `llms-full.txt` bundled content.
Add routes:
- `docs-site/app/llms.txt/route.ts`
- Returns `text/plain; charset=utf-8`.
- Includes `# KTX`, a blockquote summary, a short description, and sections
linking to key docs, markdown docs, CLI reference pages, integration pages,
and `/llms-full.txt`.
- `docs-site/app/llms-full.txt/route.ts`
- Returns `text/plain; charset=utf-8`.
- Concatenates all docs pages in source order.
- Prefixes each page with a stable heading and canonical `/docs/...` URL.
- `docs-site/app/llms.mdx/docs/[[...slug]]/route.ts`
- Returns one docs page as `text/markdown; charset=utf-8`.
- Uses the same slug shape as `/docs/[[...slug]]`.
- Returns 404 for unknown pages.
Add a Next rewrite in `docs-site/next.config.mjs`:
- `/docs/:path*.md` rewrites to `/llms.mdx/docs/:path*`
Add a markdown negotiation proxy for `/docs/...` requests:
- Requests whose `Accept` header prefers markdown are rewritten to the matching
LLM markdown route.
- Normal browser requests continue to render the existing Fumadocs UI.
- The proxy must leave `/llms.txt`, `/llms-full.txt`, assets, and non-docs
routes unchanged.
### Content rewrite pass
Rewrite the existing MDX content in a bounded, high-impact pass. The intent is
not to expand every page; it is to make every page more literal and consistent
for agents.
Apply these patterns across docs:
- Put command signatures in fenced code blocks.
- Use tables for flags, options, inputs, outputs, supported values, and
environment variables.
- Use realistic values in copy-paste examples.
- Show complete expected command output when output shape matters.
- Add explicit "Common errors" or "Recovery" sections for workflows where a
command can fail for predictable reasons.
- Add workflow sections that chain commands in the order an agent should use
them.
- Avoid placeholders that an agent could copy literally, unless the placeholder
is clearly marked as a value to replace.
Priority pages:
1. `getting-started/quickstart.mdx`
- Add a compact workflow summary.
- Make prerequisites and generated files explicit.
- Add troubleshooting for missing API keys, failed connection tests, daemon
startup, and unbuilt context.
2. `guides/serving-agents.mdx`
- Treat MCP tools and `ktx agent` commands as agent-facing API references.
- Add tool/command input tables, output expectations, safety constraints, and
workflows for answering analytics questions.
3. `guides/writing-context.mdx`
- Add semantic-source schema tables.
- Add workflows for listing, reading, editing, validating, querying, and
writing wiki knowledge.
4. `cli-reference/*.mdx`
- Normalize every command page to: command signature, subcommands table,
option tables, examples, output modes, common errors, and related workflows
where useful.
5. `integrations/agent-clients.mdx`, `integrations/primary-sources.mdx`, and
`integrations/context-sources.mdx`
- Normalize integration setup sections into structured config tables,
copy-paste examples, authentication requirements, and recovery notes.
6. Concept and benchmark pages
- Keep narrative content, but add compact "Agent usage notes" where it helps
agents decide when to read or cite the page.
### Documentation boundaries
The first pass should not introduce a separate public docs tree or a generated
API reference system. It should work with the existing MDX source files and
Fumadocs loader.
Do not add stale compatibility aliases or rename KTX concepts. Keep examples
aligned with commands and files that exist in the standalone KTX repository.
### Testing
Verification commands:
- `pnpm --filter ktx-docs build`
- `pnpm --filter ktx-docs exec tsc --noEmit` after generated Fumadocs source
files exist.
- Route checks against a local docs server:
- `GET /llms.txt` returns 200 and `text/plain`.
- `GET /llms-full.txt` returns 200 and `text/plain`.
- `GET /docs/getting-started/quickstart.md` returns 200 and
`text/markdown`.
- unknown markdown docs paths return 404.
For content checks, inspect the generated markdown responses to confirm they
contain:
- realistic command examples,
- tables,
- full output examples where documented,
- workflow sections,
- recovery/error sections.
## Acceptance Criteria
- `/llms.txt` gives agents a concise index with links to key KTX docs and
`/llms-full.txt`.
- `/llms-full.txt` returns all docs content in source order as plain text.
- Every Fumadocs page can be fetched through a `.md` URL.
- High-priority docs pages use consistent agent-friendly structure.
- The docs site builds successfully.
- Verification results and any skipped checks are reported clearly.

View file

@ -0,0 +1,252 @@
# Demo Guided Tour — Design Spec
## Problem
The "Try KTX with packaged demo data" option in `ktx setup` is completely
disconnected from the real setup wizard. It bypasses all wizard steps, plays
an animated replay in a temp directory, and exits with no bridge to actually
using KTX. Users don't learn the real setup flow and hit a dead end.
## Solution
Redesign the demo option as a **guided tour** that walks the user through the
same setup wizard steps with pre-filled, read-only selections. The tour ends
with a real interactive agents step so the user can immediately use the demo
project with their coding agent.
## Design Decisions
| Decision | Choice | Rationale |
|----------|--------|-----------|
| Implementation strategy | Demo mode flag on existing wizard steps | Maximum code reuse; wizard changes automatically apply to demo |
| LLM/embeddings steps | Skipped | Not relevant to pre-packaged demo data |
| Database selection | PostgreSQL (read-only card) | Pre-filled, matches demo dataset |
| Context sources | dbt, Metabase, Notion (read-only card) | Pre-filled, matches demo dataset |
| Context build | Replay through real progress visualization | Same spinners, progress bars, status icons as real build |
| Agents step | Real interactive step | User actually connects their agent |
| Project location | Temp directory (`/tmp/ktx-demo-{hex}`) | Frictionless, no directory prompt |
| Navigation | Enter to advance, Escape to go back | Consistent with rest of wizard |
## Flow
```
Entry menu: "Try KTX with packaged demo data"
Create demo project in /tmp/ktx-demo-{hex}
Copy pre-packaged assets (demo DB, replay, context artifacts)
┌────────────────────────────────────────────────────────────────┐
│ Demo banner (persistent, shown on every step) │
│ │
│ 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. │
└────────────────────────────────────────────────────────────────┘
Read-only card: Database connection
▸ PostgreSQL (demo warehouse)
[Enter → next, Escape → back to entry menu]
Read-only card: Context sources
▸ dbt
▸ Metabase
▸ Notion
[Enter → next, Escape → back to database card]
Context build replay
Same renderContextBuildView() / repainter as real wizard
Sources: demo-warehouse, dbt, metabase, notion
Replay at slightly faster-than-real pace
Completion summary: business areas, query definitions, knowledge pages
[Enter → next, Escape → back to sources card]
Transition message:
"Demo project is ready — let's connect your agent"
Interactive agents step (real runKtxSetupAgentsStep())
User selects agent target, scope, install mode
[Normal interactive navigation; Escape goes back to replay summary]
Final summary:
★ KTX demo is ready
Agent connected, project path shown
⚠ Temp directory warning
Pointer to `ktx setup` for real data
```
## Step Details
### Demo Banner
Shown at the top of every read-only step. Uses clack box-drawing style:
```
┌ 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.
```
### Read-Only Step Cards
Rendered by a shared `renderDemoCard()` helper:
```typescript
async function renderDemoCard(
title: string,
selections: string[],
io: KtxCliIo,
): Promise<'forward' | 'back'>
```
- Renders a clack-style box with title, bullet list of pre-filled selections,
and navigation hint ("Press Enter to continue, Escape to go back")
- Listens for raw keypresses: Enter → `'forward'`, Escape → `'back'`
- Uses same box-drawing characters and colors as `@clack/prompts`
Card format:
```
┌ {title}
│ ▸ {selection 1}
│ ▸ {selection 2}
│ ...
│ Press Enter to continue, Escape to go back
```
### Demo Step Sequence
The demo reuses the main wizard's step loop with these steps:
```typescript
const demoSteps = ['databases', 'sources', 'context', 'agents'];
```
Steps `databases` and `sources` dispatch to `renderDemoCard()` instead of
their real interactive functions when demo mode is active. Step `context`
dispatches to the replay visualization. Step `agents` runs the real
`runKtxSetupAgentsStep()`.
Back navigation reuses `previousNavigableStepIndex()`. Escaping from the
first step (databases) returns to the entry menu.
### Context Build Replay
Uses the same rendering pipeline as the real context build:
- `renderContextBuildView()` for the progress display
- `createRepainter()` for terminal repainting
- Same spinner frames, progress bars (`████░░░░`), status icons (`✓`, `⠹`, `○`)
- Same source grouping (Primary sources / Context sources)
Sources shown:
```
Primary sources:
✓ demo-warehouse completed · Xs
Context sources:
✓ dbt completed · Xs
✓ metabase completed · Xs
✓ notion completed · Xs
```
Replay timing: events from the pre-packaged replay file are played back at
a slightly faster pace than real-time (compressed to feel brisk but not
instant).
Completion summary uses the existing format:
```
★ KTX finished ingesting your data
✓ Analyzed X business areas
✓ Reconciled — 0 conflicts
KTX created:
📊 X query definitions
📝 X knowledge pages
Press Enter to continue, Escape to go back
```
The exact counts and artifact names come from the pre-packaged demo results
(to be provided by the user as improved demo data).
### Agents Step Transition
A brief message bridges from the read-only tour to the interactive step:
```
┌ 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.
```
Then `runKtxSetupAgentsStep()` runs with the demo project directory,
normal interactive prompts enabled.
### Final Summary
```
★ KTX demo is ready
Your agent is connected to a demo KTX project.
⚠ 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: /tmp/ktx-demo-a1b2c3
```
If the user skips the agents step, replace the first line with manual
agent connection instructions (`ktx setup --agents --project-dir /tmp/...`).
## Implementation Approach
Thread a `demoMode` flag through the main setup loop in `setup.ts`. When
active:
1. Skip `models` and `embeddings` steps entirely
2. Replace `databases` and `sources` step dispatch with `renderDemoCard()`
3. Replace `context` step dispatch with replay visualization
4. Run `agents` step normally
5. Show demo-specific completion summary instead of ready menu
The `renderDemoCard()` helper is a new function in a new file
(e.g. `setup-demo-cards.ts`) that handles read-only card rendering and
keypress listening.
The context build replay reuses existing `renderContextBuildView()` and
`createRepainter()` from `context-build-view.ts`, fed with events from
the pre-packaged replay file at an accelerated playback rate.
## Files Changed
| File | Change |
|------|--------|
| `packages/cli/src/setup.ts` | Add `demoMode` flag to setup loop; skip models/embeddings; dispatch to demo cards for databases/sources; show demo banner; demo completion summary |
| `packages/cli/src/setup-demo-cards.ts` | New file: `renderDemoCard()` helper, demo banner renderer, demo step definitions |
| `packages/cli/src/setup-context.ts` | Support replay mode for demo: feed pre-packaged events at accelerated pace through existing progress view |
| `packages/cli/src/demo.ts` | Remove or simplify `runKtxSetupDemoFromEntryMenu()` — now dispatches to the main setup loop with `demoMode: true` |
| `packages/cli/src/demo-assets.ts` | Update asset list if new demo data is provided; ensure demo project setup writes valid `ktx.yaml` for agent use |
## Open Items
- **Demo data**: User will provide improved pre-packaged results (Postgres,
dbt, Metabase, Notion). Current demo assets may need updating.
- **Replay speed**: Exact acceleration factor TBD — should feel brisk but
give users time to read source names and status transitions. Start with
~2x real-time and adjust.