@posthog/wizard 2.17.0 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/{add-mcp-server-to-clients-D4PK6ulR.js → add-mcp-server-to-clients-CjnvTVj0.js} +72 -13
  2. package/dist/add-mcp-server-to-clients-CjnvTVj0.js.map +1 -0
  3. package/dist/{agent-interface-7t5DBo2A.js → agent-interface-CQU6x4Hj.js} +112 -54
  4. package/dist/agent-interface-CQU6x4Hj.js.map +1 -0
  5. package/dist/{agent-runner-CTkKLVhp.js → agent-runner-Cj7saDkL.js} +17 -11
  6. package/dist/{agent-runner-CTkKLVhp.js.map → agent-runner-Cj7saDkL.js.map} +1 -1
  7. package/dist/{analytics-DN_Gy87F.js → analytics-Df-Xb81i.js} +26 -4
  8. package/dist/analytics-Df-Xb81i.js.map +1 -0
  9. package/dist/{api-serd0SMY.js → api-Dw6_orDE.js} +3 -3
  10. package/dist/{api-serd0SMY.js.map → api-Dw6_orDE.js.map} +1 -1
  11. package/dist/bin.js +236 -79
  12. package/dist/bin.js.map +1 -1
  13. package/dist/{ci-install-BbJ7c3WK.js → ci-install-BKAvFfK6.js} +4 -4
  14. package/dist/{ci-install-BbJ7c3WK.js.map → ci-install-BKAvFfK6.js.map} +1 -1
  15. package/dist/{debug-BI-Js0PB.js → debug-Cp_wNn8i.js} +1 -1
  16. package/dist/{debug-Bx7nvCWW.js → debug-DnMO6O8O.js} +36 -21
  17. package/dist/debug-DnMO6O8O.js.map +1 -0
  18. package/dist/{defaults-CPH6eWhN.js → defaults-BNWIWzjc.js} +34 -8
  19. package/dist/defaults-BNWIWzjc.js.map +1 -0
  20. package/dist/{env-api-key-B3gE9Un0.js → env-api-key-MlzJYAvt.js} +1 -1
  21. package/dist/{env-api-key-B3gE9Un0.js.map → env-api-key-MlzJYAvt.js.map} +1 -1
  22. package/dist/{environment-CiZVSSYt.js → environment-Ls0H9ljT.js} +3 -3
  23. package/dist/{environment-CiZVSSYt.js.map → environment-Ls0H9ljT.js.map} +1 -1
  24. package/dist/{file-utils-Dy9JncCo.js → file-utils-VAXoyXVA.js} +1 -1
  25. package/dist/{file-utils-Dy9JncCo.js.map → file-utils-VAXoyXVA.js.map} +1 -1
  26. package/dist/{interactive-BwIzklw0.js → interactive-D15byhpc.js} +2 -2
  27. package/dist/{interactive-BwIzklw0.js.map → interactive-D15byhpc.js.map} +1 -1
  28. package/dist/{mcp-prompt-streaming-8U9Qs9EV.js → mcp-prompt-streaming-DQOTQfW1.js} +4 -4
  29. package/dist/{mcp-prompt-streaming-8U9Qs9EV.js.map → mcp-prompt-streaming-DQOTQfW1.js.map} +1 -1
  30. package/dist/{non-interactive-DTaZnVq_.js → non-interactive-DcFLJtl_.js} +2 -2
  31. package/dist/{non-interactive-DTaZnVq_.js.map → non-interactive-DcFLJtl_.js.map} +1 -1
  32. package/dist/{package-manager-CKQLR20D.js → package-manager-DUPgLGpQ.js} +2 -2
  33. package/dist/{package-manager-CKQLR20D.js.map → package-manager-DUPgLGpQ.js.map} +1 -1
  34. package/dist/{playground-CR81Mwe3.js → playground-BZ0hGjbL.js} +12 -4
  35. package/dist/playground-BZ0hGjbL.js.map +1 -0
  36. package/dist/{posthog-BrLFkaji.js → posthog-Cr37rnla.js} +1 -1
  37. package/dist/{posthog-BrLFkaji.js.map → posthog-Cr37rnla.js.map} +1 -1
  38. package/dist/{posthog-integration-Bv7987YJ.js → posthog-integration-C8qhJnI3.js} +282 -13
  39. package/dist/posthog-integration-C8qhJnI3.js.map +1 -0
  40. package/dist/{provisioning-C96Kw-9D.js → provisioning-C-2ExcqY.js} +3 -3
  41. package/dist/{provisioning-C96Kw-9D.js.map → provisioning-C-2ExcqY.js.map} +1 -1
  42. package/dist/{registry-B9k73FKR.js → registry-hBUgaWFx.js} +4 -4
  43. package/dist/{registry-B9k73FKR.js.map → registry-hBUgaWFx.js.map} +1 -1
  44. package/dist/{setup-utils-Bpfsap9L.js → setup-utils-DetnhXo0.js} +65 -44
  45. package/dist/setup-utils-DetnhXo0.js.map +1 -0
  46. package/dist/{slides-DRbBgsdd.js → slides-mT2s9wM_.js} +180 -71
  47. package/dist/slides-mT2s9wM_.js.map +1 -0
  48. package/dist/{start-tui-BZ7rEf3e.js → start-tui-BfXoErKg.js} +16 -16
  49. package/dist/start-tui-BfXoErKg.js.map +1 -0
  50. package/dist/{steps-DDx35170.js → steps-SoDXSUxe.js} +6 -6
  51. package/dist/{steps-DDx35170.js.map → steps-SoDXSUxe.js.map} +1 -1
  52. package/dist/{task-stream-BI8rJg9H.js → task-stream-CZRj6auI.js} +2 -2
  53. package/dist/{task-stream-BI8rJg9H.js.map → task-stream-CZRj6auI.js.map} +1 -1
  54. package/dist/{telemetry-ByYtIfW0.js → telemetry-CPcMFxcO.js} +2 -2
  55. package/dist/{telemetry-ByYtIfW0.js.map → telemetry-CPcMFxcO.js.map} +1 -1
  56. package/dist/{urls-CTCJIxbR.js → urls-BO7doNJG.js} +2 -2
  57. package/dist/{urls-CTCJIxbR.js.map → urls-BO7doNJG.js.map} +1 -1
  58. package/dist/{wizard-abort-CY0ibdq1.js → wizard-abort-CDXufkqJ.js} +5 -3
  59. package/dist/wizard-abort-CDXufkqJ.js.map +1 -0
  60. package/dist/{wizard-abort-QdRxGQp_.js → wizard-abort-CtMY57ZE.js} +1 -1
  61. package/package.json +1 -1
  62. package/dist/TextBlock-D0Ep3zC9.js +0 -275
  63. package/dist/TextBlock-D0Ep3zC9.js.map +0 -1
  64. package/dist/add-mcp-server-to-clients-D4PK6ulR.js.map +0 -1
  65. package/dist/agent-interface-7t5DBo2A.js.map +0 -1
  66. package/dist/analytics-DN_Gy87F.js.map +0 -1
  67. package/dist/debug-Bx7nvCWW.js.map +0 -1
  68. package/dist/defaults-CPH6eWhN.js.map +0 -1
  69. package/dist/playground-CR81Mwe3.js.map +0 -1
  70. package/dist/posthog-integration-Bv7987YJ.js.map +0 -1
  71. package/dist/setup-utils-Bpfsap9L.js.map +0 -1
  72. package/dist/skill-CPqcV8zp.js +0 -29
  73. package/dist/skill-CPqcV8zp.js.map +0 -1
  74. package/dist/slides-DRbBgsdd.js.map +0 -1
  75. package/dist/start-tui-BZ7rEf3e.js.map +0 -1
  76. package/dist/wizard-abort-CY0ibdq1.js.map +0 -1
@@ -1,8 +1,8 @@
1
- import { p as getUI } from "./debug-Bx7nvCWW.js";
2
- import { t as analytics } from "./analytics-DN_Gy87F.js";
3
- import { t as withProgress } from "./telemetry-ByYtIfW0.js";
4
- import "./setup-utils-Bpfsap9L.js";
5
- import "./add-mcp-server-to-clients-D4PK6ulR.js";
1
+ import { p as getUI } from "./debug-DnMO6O8O.js";
2
+ import { t as analytics } from "./analytics-Df-Xb81i.js";
3
+ import { t as withProgress } from "./telemetry-CPcMFxcO.js";
4
+ import "./setup-utils-DetnhXo0.js";
5
+ import "./add-mcp-server-to-clients-CjnvTVj0.js";
6
6
  import * as fs$1 from "fs";
7
7
  import * as path$1 from "path";
8
8
  import "path";
@@ -143,4 +143,4 @@ const uploadEnvironmentVariablesStep = async (envVars, { integration, session })
143
143
  //#endregion
144
144
  export { uploadEnvironmentVariablesStep };
145
145
 
146
- //# sourceMappingURL=steps-DDx35170.js.map
146
+ //# sourceMappingURL=steps-SoDXSUxe.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"steps-DDx35170.js","names":["path","fs","spawnSync","spawn"],"sources":["../src/steps/upload-environment-variables/EnvironmentProvider.ts","../src/steps/upload-environment-variables/providers/vercel.ts","../src/steps/upload-environment-variables/index.ts"],"sourcesContent":["export abstract class EnvironmentProvider {\n protected options: { installDir: string };\n\n name: string;\n\n constructor(options: { installDir: string }) {\n this.options = options;\n }\n\n abstract detect(): Promise<boolean>;\n\n abstract uploadEnvVars(\n vars: Record<string, string>,\n ): Promise<Record<string, boolean>>;\n}\n","import { execSync, spawn, spawnSync } from 'child_process';\nimport { EnvironmentProvider } from '@steps/upload-environment-variables/EnvironmentProvider';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { getUI } from '@ui';\nimport { analytics } from '@utils/analytics';\n\nexport class VercelEnvironmentProvider extends EnvironmentProvider {\n name = 'Vercel';\n environments = ['production', 'preview', 'development'];\n\n constructor(options: { installDir: string }) {\n super(options);\n }\n\n // eslint-disable-next-line @typescript-eslint/require-await\n async detect(): Promise<boolean> {\n const vercelDetected =\n this.hasVercelCli() && this.isProjectLinked() && this.isAuthenticated();\n\n analytics.setTag('vercel-detected', vercelDetected);\n\n return vercelDetected;\n }\n\n hasDotVercelDir(): boolean {\n const dotVercelDir = path.join(this.options.installDir, '.vercel');\n return fs.existsSync(dotVercelDir);\n }\n\n hasVercelCli(): boolean {\n try {\n execSync('vercel --version', { stdio: 'ignore' });\n analytics.setTag('vercel-cli-installed', true);\n return true;\n } catch {\n analytics.setTag('vercel-cli-installed', false);\n return false;\n }\n }\n\n isProjectLinked(): boolean {\n const isProjectLinked = fs.existsSync(\n path.join(this.options.installDir, '.vercel', 'project.json'),\n );\n\n analytics.setTag('vercel-project-linked', isProjectLinked);\n\n return isProjectLinked;\n }\n\n isAuthenticated(): boolean {\n const result = spawnSync('vercel', ['whoami'], {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'], // suppress prompts\n env: {\n ...process.env,\n FORCE_COLOR: '0', // avoid ANSI formatting\n CI: '1', // hint to CLI that it's a non-interactive env\n },\n });\n\n const output = (\n String(result.stdout) + String(result.stderr)\n ).toLowerCase();\n\n if (\n output.includes('log in to vercel') ||\n output.includes('vercel login') ||\n result.status !== 0\n ) {\n analytics.setTag('vercel-authenticated', false);\n return false;\n }\n\n analytics.setTag('vercel-authenticated', true);\n\n return true;\n }\n\n async uploadEnvironmentVariable(\n key: string,\n value: string,\n environment: string,\n ): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const proc = spawn('vercel', ['env', 'add', key, environment], {\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n let stderr = '';\n proc.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n proc.stdin.write(value);\n proc.stdin.end();\n\n proc.on('close', (code) => {\n if (\n stderr.includes('already exists') ||\n stderr.includes('already been added') ||\n stderr.includes('vercel env rm')\n ) {\n reject(\n new Error(\n `❌ Environment variable ${key} already exists in ${this.name}. Please upload it manually.`,\n ),\n );\n } else if (code === 0) {\n resolve();\n } else {\n reject(\n new Error(\n `❌ Failed to upload environment variable ${key} to ${this.name}. Please upload it manually.`,\n ),\n );\n }\n });\n });\n }\n\n async uploadEnvVars(\n vars: Record<string, string>,\n ): Promise<Record<string, boolean>> {\n const results: Record<string, boolean> = {};\n\n for (const [key, value] of Object.entries(vars)) {\n const spinner = getUI().spinner();\n\n spinner.start(`Uploading ${key} to ${this.name}...`);\n await Promise.all(\n this.environments.map((environment) =>\n this.uploadEnvironmentVariable(key, value, environment),\n ),\n )\n .then(() => {\n spinner.stop(`✅ Uploaded ${key} to ${this.name}`);\n results[key] = true;\n })\n .catch((err) => {\n spinner.stop(\n err instanceof Error\n ? err.message\n : `❌ Failed to upload environment variables to ${this.name}. Please upload it manually.`,\n );\n results[key] = false;\n });\n }\n\n return results;\n }\n}\n","import type { Integration } from '@lib/constants';\nimport { withProgress } from '../../telemetry';\nimport { analytics } from '@utils/analytics';\nimport { getUI } from '@ui';\nimport type { WizardSession } from '@lib/wizard-session';\nimport { EnvironmentProvider } from './EnvironmentProvider';\nimport { VercelEnvironmentProvider } from './providers/vercel';\n\nexport const uploadEnvironmentVariablesStep = async (\n envVars: Record<string, string>,\n {\n integration,\n session,\n }: {\n integration: Integration;\n session: WizardSession;\n },\n): Promise<string[]> => {\n const providers: EnvironmentProvider[] = [\n new VercelEnvironmentProvider({ installDir: session.installDir }),\n ];\n\n let provider: EnvironmentProvider | null = null;\n\n for (const p of providers) {\n if (await p.detect()) {\n provider = p;\n break;\n }\n }\n\n if (!provider) {\n analytics.wizardCapture('env upload skipped', {\n reason: 'no environment provider found',\n integration,\n });\n return [];\n }\n\n // Auto-accept — the agent already wrote env vars via MCP tools\n getUI().log.info(`Uploading environment variables to ${provider.name}...`);\n\n const results = await withProgress(\n 'uploading environment variables',\n async () => {\n return await provider.uploadEnvVars(envVars);\n },\n );\n\n analytics.wizardCapture('env uploaded', {\n provider: provider.name,\n integration,\n });\n\n return Object.keys(results).filter((key) => results[key]);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"steps-SoDXSUxe.js","names":["path","fs","spawnSync","spawn"],"sources":["../src/steps/upload-environment-variables/EnvironmentProvider.ts","../src/steps/upload-environment-variables/providers/vercel.ts","../src/steps/upload-environment-variables/index.ts"],"sourcesContent":["export abstract class EnvironmentProvider {\n protected options: { installDir: string };\n\n name: string;\n\n constructor(options: { installDir: string }) {\n this.options = options;\n }\n\n abstract detect(): Promise<boolean>;\n\n abstract uploadEnvVars(\n vars: Record<string, string>,\n ): Promise<Record<string, boolean>>;\n}\n","import { execSync, spawn, spawnSync } from 'child_process';\nimport { EnvironmentProvider } from '@steps/upload-environment-variables/EnvironmentProvider';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { getUI } from '@ui';\nimport { analytics } from '@utils/analytics';\n\nexport class VercelEnvironmentProvider extends EnvironmentProvider {\n name = 'Vercel';\n environments = ['production', 'preview', 'development'];\n\n constructor(options: { installDir: string }) {\n super(options);\n }\n\n // eslint-disable-next-line @typescript-eslint/require-await\n async detect(): Promise<boolean> {\n const vercelDetected =\n this.hasVercelCli() && this.isProjectLinked() && this.isAuthenticated();\n\n analytics.setTag('vercel-detected', vercelDetected);\n\n return vercelDetected;\n }\n\n hasDotVercelDir(): boolean {\n const dotVercelDir = path.join(this.options.installDir, '.vercel');\n return fs.existsSync(dotVercelDir);\n }\n\n hasVercelCli(): boolean {\n try {\n execSync('vercel --version', { stdio: 'ignore' });\n analytics.setTag('vercel-cli-installed', true);\n return true;\n } catch {\n analytics.setTag('vercel-cli-installed', false);\n return false;\n }\n }\n\n isProjectLinked(): boolean {\n const isProjectLinked = fs.existsSync(\n path.join(this.options.installDir, '.vercel', 'project.json'),\n );\n\n analytics.setTag('vercel-project-linked', isProjectLinked);\n\n return isProjectLinked;\n }\n\n isAuthenticated(): boolean {\n const result = spawnSync('vercel', ['whoami'], {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'], // suppress prompts\n env: {\n ...process.env,\n FORCE_COLOR: '0', // avoid ANSI formatting\n CI: '1', // hint to CLI that it's a non-interactive env\n },\n });\n\n const output = (\n String(result.stdout) + String(result.stderr)\n ).toLowerCase();\n\n if (\n output.includes('log in to vercel') ||\n output.includes('vercel login') ||\n result.status !== 0\n ) {\n analytics.setTag('vercel-authenticated', false);\n return false;\n }\n\n analytics.setTag('vercel-authenticated', true);\n\n return true;\n }\n\n async uploadEnvironmentVariable(\n key: string,\n value: string,\n environment: string,\n ): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const proc = spawn('vercel', ['env', 'add', key, environment], {\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n let stderr = '';\n proc.stderr.on('data', (data) => {\n stderr += data.toString();\n });\n\n proc.stdin.write(value);\n proc.stdin.end();\n\n proc.on('close', (code) => {\n if (\n stderr.includes('already exists') ||\n stderr.includes('already been added') ||\n stderr.includes('vercel env rm')\n ) {\n reject(\n new Error(\n `❌ Environment variable ${key} already exists in ${this.name}. Please upload it manually.`,\n ),\n );\n } else if (code === 0) {\n resolve();\n } else {\n reject(\n new Error(\n `❌ Failed to upload environment variable ${key} to ${this.name}. Please upload it manually.`,\n ),\n );\n }\n });\n });\n }\n\n async uploadEnvVars(\n vars: Record<string, string>,\n ): Promise<Record<string, boolean>> {\n const results: Record<string, boolean> = {};\n\n for (const [key, value] of Object.entries(vars)) {\n const spinner = getUI().spinner();\n\n spinner.start(`Uploading ${key} to ${this.name}...`);\n await Promise.all(\n this.environments.map((environment) =>\n this.uploadEnvironmentVariable(key, value, environment),\n ),\n )\n .then(() => {\n spinner.stop(`✅ Uploaded ${key} to ${this.name}`);\n results[key] = true;\n })\n .catch((err) => {\n spinner.stop(\n err instanceof Error\n ? err.message\n : `❌ Failed to upload environment variables to ${this.name}. Please upload it manually.`,\n );\n results[key] = false;\n });\n }\n\n return results;\n }\n}\n","import type { Integration } from '@lib/constants';\nimport { withProgress } from '../../telemetry';\nimport { analytics } from '@utils/analytics';\nimport { getUI } from '@ui';\nimport type { WizardSession } from '@lib/wizard-session';\nimport { EnvironmentProvider } from './EnvironmentProvider';\nimport { VercelEnvironmentProvider } from './providers/vercel';\n\nexport const uploadEnvironmentVariablesStep = async (\n envVars: Record<string, string>,\n {\n integration,\n session,\n }: {\n integration: Integration;\n session: WizardSession;\n },\n): Promise<string[]> => {\n const providers: EnvironmentProvider[] = [\n new VercelEnvironmentProvider({ installDir: session.installDir }),\n ];\n\n let provider: EnvironmentProvider | null = null;\n\n for (const p of providers) {\n if (await p.detect()) {\n provider = p;\n break;\n }\n }\n\n if (!provider) {\n analytics.wizardCapture('env upload skipped', {\n reason: 'no environment provider found',\n integration,\n });\n return [];\n }\n\n // Auto-accept — the agent already wrote env vars via MCP tools\n getUI().log.info(`Uploading environment variables to ${provider.name}...`);\n\n const results = await withProgress(\n 'uploading environment variables',\n async () => {\n return await provider.uploadEnvVars(envVars);\n },\n );\n\n analytics.wizardCapture('env uploaded', {\n provider: provider.name,\n integration,\n });\n\n return Object.keys(results).filter((key) => results[key]);\n};\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import "./wizard-ui-YdGFRyu_.js";
2
2
  import "./wizard-session-d27JGRGi.js";
3
- import "./posthog-BrLFkaji.js";
3
+ import "./posthog-Cr37rnla.js";
4
4
  //#region src/lib/task-stream/task-stream-push.ts
5
5
  /** Trailing-edge debounce window for non-phase-change emits. */
6
6
  const DEBOUNCE_MS = 250;
@@ -192,4 +192,4 @@ var TaskStreamPush = class {
192
192
  //#endregion
193
193
  export { TaskStreamPush };
194
194
 
195
- //# sourceMappingURL=task-stream-BI8rJg9H.js.map
195
+ //# sourceMappingURL=task-stream-CZRj6auI.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"task-stream-BI8rJg9H.js","names":[],"sources":["../src/lib/task-stream/task-stream-push.ts"],"sourcesContent":["/**\n * Task-stream push — subscribes to WizardStore, builds payloads,\n * and fans out async to all registered destinations.\n *\n * Behaviour:\n * - `attach(store)` subscribe to store changes\n * - task updates debounced 250ms (trailing edge)\n * - phase transitions flush immediately, bypass debounce\n * - RunPhase.Idle skipped (no push)\n * - enabled === false attach is a no-op\n * - shutdown(timeoutMs) cancel pending, flush terminal phase\n * with timeout, never throw\n *\n * Concurrency: only one fan-out at a time. Emits during an in-flight\n * push are coalesced — at most one follow-up push fires with the\n * latest state once the current one settles.\n */\n\nimport type { WizardStore, TaskItem } from '@ui/tui/store';\nimport { TaskStatus } from '@ui/wizard-ui';\nimport { RunPhase, OutroKind, type OutroData } from '@lib/wizard-session';\nimport {\n type TaskStreamDestination,\n type TaskStreamUpdate,\n type StreamTask,\n type TaskStreamError,\n StreamTaskStatus,\n StreamEvent,\n} from './types';\n\n/** Trailing-edge debounce window for non-phase-change emits. */\nconst DEBOUNCE_MS = 250;\n/** Default shutdown timeout for the final terminal flush. */\nconst DEFAULT_SHUTDOWN_TIMEOUT_MS = 2000;\n\nconst STATUS_MAP: Record<TaskStatus, StreamTaskStatus> = {\n [TaskStatus.Pending]: StreamTaskStatus.Pending,\n [TaskStatus.InProgress]: StreamTaskStatus.InProgress,\n [TaskStatus.Completed]: StreamTaskStatus.Completed,\n};\n\nfunction buildTasks(items: TaskItem[]): StreamTask[] {\n return items.map((item, i) => ({\n id: String(i),\n title: item.label,\n status: STATUS_MAP[item.status] ?? StreamTaskStatus.Pending,\n }));\n}\n\n/** Drop \".SSSZ\" → \"Z\" so session_id segments stay routing-safe. */\nfunction secondPrecisionIso(d: Date): string {\n return d.toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\n/**\n * `workflow_id` and `skill_id` end up unescaped in Redis pub/sub\n * channel names, so the backend rejects anything outside\n * `^[A-Za-z0-9_.-]{1,255}$` with a 400. All current values already\n * comply; this is defence in depth in case a future caller passes\n * something with `:`, spaces, or other separators.\n */\nfunction sanitizeChannelId(value: string): string {\n return value.replace(/[^A-Za-z0-9_.-]/g, '-').slice(0, 255);\n}\n\nfunction buildError(\n phase: RunPhase,\n outroData: OutroData | null,\n): TaskStreamError | undefined {\n if (phase !== RunPhase.Error) return undefined;\n if (outroData?.kind === OutroKind.Error) {\n const message = outroData.message ?? outroData.body ?? 'Wizard run failed';\n return { type: 'wizard_error', message };\n }\n return { type: 'wizard_error', message: 'Wizard run failed' };\n}\n\nexport interface TaskStreamPushOptions {\n store: WizardStore;\n programId: string;\n destinations: TaskStreamDestination[];\n /** When false, `attach` is a no-op and no destination ever fires. */\n enabled?: boolean;\n}\n\nexport class TaskStreamPush {\n private readonly store: WizardStore;\n private readonly destinations: TaskStreamDestination[];\n private readonly startedAt: string;\n private readonly programId: string;\n private readonly sessionId: string;\n\n private enabled: boolean;\n private created = false;\n private lastPushedPhase: RunPhase | null = null;\n\n private unsubscribe: (() => void) | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private inFlight: Promise<void> | null = null;\n private needsAnotherPush = false;\n private shuttingDown = false;\n\n constructor(opts: TaskStreamPushOptions) {\n this.store = opts.store;\n this.programId = sanitizeChannelId(opts.programId);\n this.destinations = opts.destinations;\n this.enabled = opts.enabled ?? true;\n this.startedAt = secondPrecisionIso(new Date());\n // skillId may not be set yet — fall back to programId so the\n // session_id is stable for the whole run regardless of when the\n // program metadata is populated.\n const skillId = sanitizeChannelId(\n this.store.session.skillId ?? this.programId,\n );\n this.sessionId = `${this.programId}-${skillId}-${this.startedAt}`;\n }\n\n /**\n * Subscribe to store changes. No-op when `enabled === false`.\n * Idempotent — repeat calls are ignored.\n */\n attach(store?: WizardStore): void {\n if (!this.enabled) return;\n if (this.unsubscribe) return;\n const target = store ?? this.store;\n this.unsubscribe = target.subscribe(() => this.onStoreChange());\n }\n\n /** Stop subscribing. Does not flush. */\n detach(): void {\n if (this.unsubscribe) {\n this.unsubscribe();\n this.unsubscribe = null;\n }\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n }\n\n /**\n * Cancel pending debounce, flush one final push if the current\n * phase is terminal, and resolve. Never throws. Bounded by\n * `timeoutMs` — if a destination hangs, this returns anyway.\n */\n async shutdown(\n timeoutMs: number = DEFAULT_SHUTDOWN_TIMEOUT_MS,\n ): Promise<void> {\n this.shuttingDown = true;\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n this.detach();\n if (!this.enabled) return;\n\n const phase = this.store.session.runPhase;\n const isTerminal = phase === RunPhase.Completed || phase === RunPhase.Error;\n if (!isTerminal) return;\n\n const flush = this.flush();\n if (timeoutMs <= 0) return;\n await Promise.race([\n flush,\n new Promise<void>((resolve) => setTimeout(resolve, timeoutMs)),\n ]);\n }\n\n /**\n * Imperative push — fires immediately regardless of phase. Kept as\n * the building block for both subscription-driven and direct calls.\n */\n async push(): Promise<void> {\n await this.flush();\n }\n\n // ── Internal ────────────────────────────────────────────────────\n\n private onStoreChange(): void {\n if (!this.enabled || this.shuttingDown) return;\n const phase = this.store.session.runPhase;\n if (phase === RunPhase.Idle) return;\n\n // A push is already in flight — coalesce. The in-flight push's\n // settle handler will trigger one follow-up with the latest state.\n if (this.inFlight) {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n this.needsAnotherPush = true;\n return;\n }\n\n const phaseChanged = phase !== this.lastPushedPhase;\n if (phaseChanged) {\n // Phase transitions bypass the debounce: the web app needs to\n // see Running → Completed as soon as it lands.\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n void this.flush();\n return;\n }\n\n // Task updates can arrive faster than we want to push. Debounce\n // them — the last update in a burst wins.\n if (this.debounceTimer) return;\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = null;\n void this.flush();\n }, DEBOUNCE_MS);\n }\n\n /**\n * Fan out the current state to every destination. Serialized — if\n * a flush is already running, mark \"needs another\" and let the\n * in-flight one schedule the follow-up when it settles.\n */\n private flush(): Promise<void> {\n if (this.inFlight) {\n this.needsAnotherPush = true;\n return this.inFlight;\n }\n\n const run = async (): Promise<void> => {\n try {\n await this.sendOnce();\n } finally {\n this.inFlight = null;\n if (this.needsAnotherPush) {\n this.needsAnotherPush = false;\n // Re-enter to push the latest snapshot.\n await this.flush();\n }\n }\n };\n\n this.inFlight = run();\n return this.inFlight;\n }\n\n private async sendOnce(): Promise<void> {\n const { session, tasks, eventPlan } = this.store;\n const skillId = sanitizeChannelId(session.skillId ?? this.programId);\n const phase = session.runPhase;\n\n const payload: TaskStreamUpdate = {\n session_id: this.sessionId,\n workflow_id: this.programId,\n skill_id: skillId,\n started_at: this.startedAt,\n run_phase: phase,\n tasks: buildTasks(tasks),\n event_plan: eventPlan.length > 0 ? { events: eventPlan } : undefined,\n error: buildError(phase, session.outroData),\n timestamp: new Date().toISOString(),\n };\n\n let event: StreamEvent;\n if (!this.created) {\n this.created = true;\n event = StreamEvent.Create;\n } else if (phase === RunPhase.Completed) {\n event = StreamEvent.Complete;\n } else if (phase === RunPhase.Error) {\n event = StreamEvent.Error;\n } else {\n event = StreamEvent.Update;\n }\n\n this.lastPushedPhase = phase;\n\n await Promise.all(\n this.destinations.map((d) =>\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n d.send(event, payload).catch(() => {}),\n ),\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"task-stream-CZRj6auI.js","names":[],"sources":["../src/lib/task-stream/task-stream-push.ts"],"sourcesContent":["/**\n * Task-stream push — subscribes to WizardStore, builds payloads,\n * and fans out async to all registered destinations.\n *\n * Behaviour:\n * - `attach(store)` subscribe to store changes\n * - task updates debounced 250ms (trailing edge)\n * - phase transitions flush immediately, bypass debounce\n * - RunPhase.Idle skipped (no push)\n * - enabled === false attach is a no-op\n * - shutdown(timeoutMs) cancel pending, flush terminal phase\n * with timeout, never throw\n *\n * Concurrency: only one fan-out at a time. Emits during an in-flight\n * push are coalesced — at most one follow-up push fires with the\n * latest state once the current one settles.\n */\n\nimport type { WizardStore, TaskItem } from '@ui/tui/store';\nimport { TaskStatus } from '@ui/wizard-ui';\nimport { RunPhase, OutroKind, type OutroData } from '@lib/wizard-session';\nimport {\n type TaskStreamDestination,\n type TaskStreamUpdate,\n type StreamTask,\n type TaskStreamError,\n StreamTaskStatus,\n StreamEvent,\n} from './types';\n\n/** Trailing-edge debounce window for non-phase-change emits. */\nconst DEBOUNCE_MS = 250;\n/** Default shutdown timeout for the final terminal flush. */\nconst DEFAULT_SHUTDOWN_TIMEOUT_MS = 2000;\n\nconst STATUS_MAP: Record<TaskStatus, StreamTaskStatus> = {\n [TaskStatus.Pending]: StreamTaskStatus.Pending,\n [TaskStatus.InProgress]: StreamTaskStatus.InProgress,\n [TaskStatus.Completed]: StreamTaskStatus.Completed,\n};\n\nfunction buildTasks(items: TaskItem[]): StreamTask[] {\n return items.map((item, i) => ({\n id: String(i),\n title: item.label,\n status: STATUS_MAP[item.status] ?? StreamTaskStatus.Pending,\n }));\n}\n\n/** Drop \".SSSZ\" → \"Z\" so session_id segments stay routing-safe. */\nfunction secondPrecisionIso(d: Date): string {\n return d.toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\n/**\n * `workflow_id` and `skill_id` end up unescaped in Redis pub/sub\n * channel names, so the backend rejects anything outside\n * `^[A-Za-z0-9_.-]{1,255}$` with a 400. All current values already\n * comply; this is defence in depth in case a future caller passes\n * something with `:`, spaces, or other separators.\n */\nfunction sanitizeChannelId(value: string): string {\n return value.replace(/[^A-Za-z0-9_.-]/g, '-').slice(0, 255);\n}\n\nfunction buildError(\n phase: RunPhase,\n outroData: OutroData | null,\n): TaskStreamError | undefined {\n if (phase !== RunPhase.Error) return undefined;\n if (outroData?.kind === OutroKind.Error) {\n const message = outroData.message ?? outroData.body ?? 'Wizard run failed';\n return { type: 'wizard_error', message };\n }\n return { type: 'wizard_error', message: 'Wizard run failed' };\n}\n\nexport interface TaskStreamPushOptions {\n store: WizardStore;\n programId: string;\n destinations: TaskStreamDestination[];\n /** When false, `attach` is a no-op and no destination ever fires. */\n enabled?: boolean;\n}\n\nexport class TaskStreamPush {\n private readonly store: WizardStore;\n private readonly destinations: TaskStreamDestination[];\n private readonly startedAt: string;\n private readonly programId: string;\n private readonly sessionId: string;\n\n private enabled: boolean;\n private created = false;\n private lastPushedPhase: RunPhase | null = null;\n\n private unsubscribe: (() => void) | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private inFlight: Promise<void> | null = null;\n private needsAnotherPush = false;\n private shuttingDown = false;\n\n constructor(opts: TaskStreamPushOptions) {\n this.store = opts.store;\n this.programId = sanitizeChannelId(opts.programId);\n this.destinations = opts.destinations;\n this.enabled = opts.enabled ?? true;\n this.startedAt = secondPrecisionIso(new Date());\n // skillId may not be set yet — fall back to programId so the\n // session_id is stable for the whole run regardless of when the\n // program metadata is populated.\n const skillId = sanitizeChannelId(\n this.store.session.skillId ?? this.programId,\n );\n this.sessionId = `${this.programId}-${skillId}-${this.startedAt}`;\n }\n\n /**\n * Subscribe to store changes. No-op when `enabled === false`.\n * Idempotent — repeat calls are ignored.\n */\n attach(store?: WizardStore): void {\n if (!this.enabled) return;\n if (this.unsubscribe) return;\n const target = store ?? this.store;\n this.unsubscribe = target.subscribe(() => this.onStoreChange());\n }\n\n /** Stop subscribing. Does not flush. */\n detach(): void {\n if (this.unsubscribe) {\n this.unsubscribe();\n this.unsubscribe = null;\n }\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n }\n\n /**\n * Cancel pending debounce, flush one final push if the current\n * phase is terminal, and resolve. Never throws. Bounded by\n * `timeoutMs` — if a destination hangs, this returns anyway.\n */\n async shutdown(\n timeoutMs: number = DEFAULT_SHUTDOWN_TIMEOUT_MS,\n ): Promise<void> {\n this.shuttingDown = true;\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n this.detach();\n if (!this.enabled) return;\n\n const phase = this.store.session.runPhase;\n const isTerminal = phase === RunPhase.Completed || phase === RunPhase.Error;\n if (!isTerminal) return;\n\n const flush = this.flush();\n if (timeoutMs <= 0) return;\n await Promise.race([\n flush,\n new Promise<void>((resolve) => setTimeout(resolve, timeoutMs)),\n ]);\n }\n\n /**\n * Imperative push — fires immediately regardless of phase. Kept as\n * the building block for both subscription-driven and direct calls.\n */\n async push(): Promise<void> {\n await this.flush();\n }\n\n // ── Internal ────────────────────────────────────────────────────\n\n private onStoreChange(): void {\n if (!this.enabled || this.shuttingDown) return;\n const phase = this.store.session.runPhase;\n if (phase === RunPhase.Idle) return;\n\n // A push is already in flight — coalesce. The in-flight push's\n // settle handler will trigger one follow-up with the latest state.\n if (this.inFlight) {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n this.needsAnotherPush = true;\n return;\n }\n\n const phaseChanged = phase !== this.lastPushedPhase;\n if (phaseChanged) {\n // Phase transitions bypass the debounce: the web app needs to\n // see Running → Completed as soon as it lands.\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n void this.flush();\n return;\n }\n\n // Task updates can arrive faster than we want to push. Debounce\n // them — the last update in a burst wins.\n if (this.debounceTimer) return;\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = null;\n void this.flush();\n }, DEBOUNCE_MS);\n }\n\n /**\n * Fan out the current state to every destination. Serialized — if\n * a flush is already running, mark \"needs another\" and let the\n * in-flight one schedule the follow-up when it settles.\n */\n private flush(): Promise<void> {\n if (this.inFlight) {\n this.needsAnotherPush = true;\n return this.inFlight;\n }\n\n const run = async (): Promise<void> => {\n try {\n await this.sendOnce();\n } finally {\n this.inFlight = null;\n if (this.needsAnotherPush) {\n this.needsAnotherPush = false;\n // Re-enter to push the latest snapshot.\n await this.flush();\n }\n }\n };\n\n this.inFlight = run();\n return this.inFlight;\n }\n\n private async sendOnce(): Promise<void> {\n const { session, tasks, eventPlan } = this.store;\n const skillId = sanitizeChannelId(session.skillId ?? this.programId);\n const phase = session.runPhase;\n\n const payload: TaskStreamUpdate = {\n session_id: this.sessionId,\n workflow_id: this.programId,\n skill_id: skillId,\n started_at: this.startedAt,\n run_phase: phase,\n tasks: buildTasks(tasks),\n event_plan: eventPlan.length > 0 ? { events: eventPlan } : undefined,\n error: buildError(phase, session.outroData),\n timestamp: new Date().toISOString(),\n };\n\n let event: StreamEvent;\n if (!this.created) {\n this.created = true;\n event = StreamEvent.Create;\n } else if (phase === RunPhase.Completed) {\n event = StreamEvent.Complete;\n } else if (phase === RunPhase.Error) {\n event = StreamEvent.Error;\n } else {\n event = StreamEvent.Update;\n }\n\n this.lastPushedPhase = phase;\n\n await Promise.all(\n this.destinations.map((d) =>\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n d.send(event, payload).catch(() => {}),\n ),\n );\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as analytics } from "./analytics-DN_Gy87F.js";
1
+ import { t as analytics } from "./analytics-Df-Xb81i.js";
2
2
  //#region src/telemetry.ts
3
3
  function withProgress(step, callback) {
4
4
  updateProgress(step);
@@ -10,4 +10,4 @@ function updateProgress(step) {
10
10
  //#endregion
11
11
  export { withProgress as t };
12
12
 
13
- //# sourceMappingURL=telemetry-ByYtIfW0.js.map
13
+ //# sourceMappingURL=telemetry-CPcMFxcO.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry-ByYtIfW0.js","names":[],"sources":["../src/telemetry.ts"],"sourcesContent":["import { analytics } from '@utils/analytics';\n\nexport function withProgress<T>(step: string, callback: () => T): T {\n updateProgress(step);\n return callback();\n}\n\nexport function updateProgress(step: string) {\n analytics.setTag('progress', step);\n}\n"],"mappings":";;AAEA,SAAgB,aAAgB,MAAc,UAAsB;AAClE,gBAAe,KAAK;AACpB,QAAO,UAAU;;AAGnB,SAAgB,eAAe,MAAc;AAC3C,WAAU,OAAO,YAAY,KAAK"}
1
+ {"version":3,"file":"telemetry-CPcMFxcO.js","names":[],"sources":["../src/telemetry.ts"],"sourcesContent":["import { analytics } from '@utils/analytics';\n\nexport function withProgress<T>(step: string, callback: () => T): T {\n updateProgress(step);\n return callback();\n}\n\nexport function updateProgress(step: string) {\n analytics.setTag('progress', step);\n}\n"],"mappings":";;AAEA,SAAgB,aAAgB,MAAc,UAAsB;AAClE,gBAAe,KAAK;AACpB,QAAO,UAAU;;AAGnB,SAAgB,eAAe,MAAc;AAC3C,WAAU,OAAO,YAAY,KAAK"}
@@ -1,4 +1,4 @@
1
- import { W as WIZARD_USER_AGENT } from "./debug-Bx7nvCWW.js";
1
+ import { W as WIZARD_USER_AGENT } from "./debug-DnMO6O8O.js";
2
2
  import axios from "axios";
3
3
  //#region src/utils/urls.ts
4
4
  const getUiHostFromHost = (host) => {
@@ -32,4 +32,4 @@ const getLlmGatewayUrlFromHost = (host) => {
32
32
  //#endregion
33
33
  export { getUiHostFromHost as a, getLlmGatewayUrlFromHost as i, getCloudUrlFromRegion as n, getHostFromRegion as r, detectRegionFromToken as t };
34
34
 
35
- //# sourceMappingURL=urls-CTCJIxbR.js.map
35
+ //# sourceMappingURL=urls-BO7doNJG.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"urls-CTCJIxbR.js","names":[],"sources":["../src/utils/urls.ts"],"sourcesContent":["import axios from 'axios';\nimport { IS_DEV, WIZARD_USER_AGENT } from '@lib/constants';\nimport type { CloudRegion } from './types';\n\nexport const getAssetHostFromHost = (host: string) => {\n if (host.includes('us.i.posthog.com')) {\n return 'https://us-assets.i.posthog.com';\n }\n\n if (host.includes('eu.i.posthog.com')) {\n return 'https://eu-assets.i.posthog.com';\n }\n\n return host;\n};\n\nexport const getUiHostFromHost = (host: string) => {\n if (host.includes('us.i.posthog.com')) {\n return 'https://us.posthog.com';\n }\n\n if (host.includes('eu.i.posthog.com')) {\n return 'https://eu.posthog.com';\n }\n\n return host;\n};\n\nexport const getHostFromRegion = (region: CloudRegion) => {\n if (IS_DEV) {\n return 'http://localhost:8010';\n }\n\n if (region === 'eu') {\n return 'https://eu.i.posthog.com';\n }\n\n return 'https://us.i.posthog.com';\n};\n\nexport const getCloudUrlFromRegion = (region: CloudRegion) => {\n if (IS_DEV) {\n return 'http://localhost:8010';\n }\n\n if (region === 'eu') {\n return 'https://eu.posthog.com';\n }\n\n return 'https://us.posthog.com';\n};\n\nexport async function detectRegionFromToken(\n accessToken: string,\n): Promise<CloudRegion> {\n if (IS_DEV) {\n return 'us';\n }\n\n const headers = {\n Authorization: `Bearer ${accessToken}`,\n 'User-Agent': WIZARD_USER_AGENT,\n };\n\n const [usResult, euResult] = await Promise.allSettled([\n axios.get('https://us.posthog.com/api/users/@me/', { headers }),\n axios.get('https://eu.posthog.com/api/users/@me/', { headers }),\n ]);\n\n if (usResult.status === 'fulfilled') return 'us';\n if (euResult.status === 'fulfilled') return 'eu';\n\n throw new Error(\n 'Could not determine cloud region from access token. Please check your PostHog account.',\n );\n}\n\nexport const getLlmGatewayUrlFromHost = (host: string) => {\n if (host.includes('localhost')) {\n return 'http://localhost:3308/wizard';\n }\n\n if (host.includes('eu.posthog.com') || host.includes('eu.i.posthog.com')) {\n return 'https://gateway.eu.posthog.com/wizard';\n }\n\n return 'https://gateway.us.posthog.com/wizard';\n};\n"],"mappings":";;;AAgBA,MAAa,qBAAqB,SAAiB;AACjD,KAAI,KAAK,SAAS,mBAAmB,CACnC,QAAO;AAGT,KAAI,KAAK,SAAS,mBAAmB,CACnC,QAAO;AAGT,QAAO;;AAGT,MAAa,qBAAqB,WAAwB;AAKxD,KAAI,WAAW,KACb,QAAO;AAGT,QAAO;;AAGT,MAAa,yBAAyB,WAAwB;AAK5D,KAAI,WAAW,KACb,QAAO;AAGT,QAAO;;AAGT,eAAsB,sBACpB,aACsB;CAKtB,MAAM,UAAU;EACd,eAAe,UAAU;EACzB,cAAc;EACf;CAED,MAAM,CAAC,UAAU,YAAY,MAAM,QAAQ,WAAW,CACpD,MAAM,IAAI,yCAAyC,EAAE,SAAS,CAAC,EAC/D,MAAM,IAAI,yCAAyC,EAAE,SAAS,CAAC,CAChE,CAAC;AAEF,KAAI,SAAS,WAAW,YAAa,QAAO;AAC5C,KAAI,SAAS,WAAW,YAAa,QAAO;AAE5C,OAAM,IAAI,MACR,yFACD;;AAGH,MAAa,4BAA4B,SAAiB;AACxD,KAAI,KAAK,SAAS,YAAY,CAC5B,QAAO;AAGT,KAAI,KAAK,SAAS,iBAAiB,IAAI,KAAK,SAAS,mBAAmB,CACtE,QAAO;AAGT,QAAO"}
1
+ {"version":3,"file":"urls-BO7doNJG.js","names":[],"sources":["../src/utils/urls.ts"],"sourcesContent":["import axios from 'axios';\nimport { IS_DEV, WIZARD_USER_AGENT } from '@lib/constants';\nimport type { CloudRegion } from './types';\n\nexport const getAssetHostFromHost = (host: string) => {\n if (host.includes('us.i.posthog.com')) {\n return 'https://us-assets.i.posthog.com';\n }\n\n if (host.includes('eu.i.posthog.com')) {\n return 'https://eu-assets.i.posthog.com';\n }\n\n return host;\n};\n\nexport const getUiHostFromHost = (host: string) => {\n if (host.includes('us.i.posthog.com')) {\n return 'https://us.posthog.com';\n }\n\n if (host.includes('eu.i.posthog.com')) {\n return 'https://eu.posthog.com';\n }\n\n return host;\n};\n\nexport const getHostFromRegion = (region: CloudRegion) => {\n if (IS_DEV) {\n return 'http://localhost:8010';\n }\n\n if (region === 'eu') {\n return 'https://eu.i.posthog.com';\n }\n\n return 'https://us.i.posthog.com';\n};\n\nexport const getCloudUrlFromRegion = (region: CloudRegion) => {\n if (IS_DEV) {\n return 'http://localhost:8010';\n }\n\n if (region === 'eu') {\n return 'https://eu.posthog.com';\n }\n\n return 'https://us.posthog.com';\n};\n\nexport async function detectRegionFromToken(\n accessToken: string,\n): Promise<CloudRegion> {\n if (IS_DEV) {\n return 'us';\n }\n\n const headers = {\n Authorization: `Bearer ${accessToken}`,\n 'User-Agent': WIZARD_USER_AGENT,\n };\n\n const [usResult, euResult] = await Promise.allSettled([\n axios.get('https://us.posthog.com/api/users/@me/', { headers }),\n axios.get('https://eu.posthog.com/api/users/@me/', { headers }),\n ]);\n\n if (usResult.status === 'fulfilled') return 'us';\n if (euResult.status === 'fulfilled') return 'eu';\n\n throw new Error(\n 'Could not determine cloud region from access token. Please check your PostHog account.',\n );\n}\n\nexport const getLlmGatewayUrlFromHost = (host: string) => {\n if (host.includes('localhost')) {\n return 'http://localhost:3308/wizard';\n }\n\n if (host.includes('eu.posthog.com') || host.includes('eu.i.posthog.com')) {\n return 'https://gateway.eu.posthog.com/wizard';\n }\n\n return 'https://gateway.us.posthog.com/wizard';\n};\n"],"mappings":";;;AAgBA,MAAa,qBAAqB,SAAiB;AACjD,KAAI,KAAK,SAAS,mBAAmB,CACnC,QAAO;AAGT,KAAI,KAAK,SAAS,mBAAmB,CACnC,QAAO;AAGT,QAAO;;AAGT,MAAa,qBAAqB,WAAwB;AAKxD,KAAI,WAAW,KACb,QAAO;AAGT,QAAO;;AAGT,MAAa,yBAAyB,WAAwB;AAK5D,KAAI,WAAW,KACb,QAAO;AAGT,QAAO;;AAGT,eAAsB,sBACpB,aACsB;CAKtB,MAAM,UAAU;EACd,eAAe,UAAU;EACzB,cAAc;EACf;CAED,MAAM,CAAC,UAAU,YAAY,MAAM,QAAQ,WAAW,CACpD,MAAM,IAAI,yCAAyC,EAAE,SAAS,CAAC,EAC/D,MAAM,IAAI,yCAAyC,EAAE,SAAS,CAAC,CAChE,CAAC;AAEF,KAAI,SAAS,WAAW,YAAa,QAAO;AAC5C,KAAI,SAAS,WAAW,YAAa,QAAO;AAE5C,OAAM,IAAI,MACR,yFACD;;AAGH,MAAa,4BAA4B,SAAiB;AACxD,KAAI,KAAK,SAAS,YAAY,CAC5B,QAAO;AAGT,KAAI,KAAK,SAAS,iBAAiB,IAAI,KAAK,SAAS,mBAAmB,CACtE,QAAO;AAGT,QAAO"}
@@ -1,5 +1,5 @@
1
- import { p as getUI } from "./debug-Bx7nvCWW.js";
2
- import { t as analytics } from "./analytics-DN_Gy87F.js";
1
+ import { p as getUI, s as logToFile } from "./debug-DnMO6O8O.js";
2
+ import { t as analytics } from "./analytics-Df-Xb81i.js";
3
3
  //#region src/utils/wizard-abort.ts
4
4
  /**
5
5
  * Single exit point for the wizard. Use instead of process.exit() directly.
@@ -22,6 +22,8 @@ function registerCleanup(fn) {
22
22
  }
23
23
  async function wizardAbort(options) {
24
24
  const { message = "Wizard setup cancelled.", outroData, error, exitCode = 1 } = options ?? {};
25
+ logToFile(`[wizard-abort] exitCode=${exitCode}, message: ${message}`);
26
+ if (error) logToFile("[wizard-abort] error:", error);
25
27
  for (const fn of cleanupFns) try {
26
28
  fn();
27
29
  } catch {}
@@ -38,4 +40,4 @@ async function wizardAbort(options) {
38
40
  //#endregion
39
41
  export { registerCleanup as n, wizardAbort as r, WizardError as t };
40
42
 
41
- //# sourceMappingURL=wizard-abort-CY0ibdq1.js.map
43
+ //# sourceMappingURL=wizard-abort-CDXufkqJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wizard-abort-CDXufkqJ.js","names":[],"sources":["../src/utils/wizard-abort.ts"],"sourcesContent":["/**\n * Single exit point for the wizard. Use instead of process.exit() directly.\n *\n * Sequence: cleanup -> error capture (optional) -> analytics shutdown -> outro -> process.exit\n *\n * WizardError is a data carrier passed to wizardAbort() for analytics context, never thrown.\n * The legacy abort() in setup-utils.ts delegates here.\n */\nimport { analytics } from './analytics';\nimport { logToFile } from './debug';\nimport { getUI } from '@ui';\nimport { OutroKind, type OutroData } from '@lib/wizard-session';\n\nexport class WizardError extends Error {\n constructor(\n message: string,\n public readonly context?: Record<string, unknown>,\n ) {\n super(message);\n this.name = 'WizardError';\n }\n}\n\ninterface WizardAbortOptions {\n message?: string;\n /** Structured error data. Renders via `outroError` instead of `outro`. */\n outroData?: OutroData;\n error?: Error | WizardError;\n exitCode?: number;\n}\n\nconst cleanupFns: Array<() => void> = [];\n\nexport function registerCleanup(fn: () => void): void {\n cleanupFns.push(fn);\n}\n\nexport function clearCleanup(): void {\n cleanupFns.length = 0;\n}\n\nexport async function wizardAbort(\n options?: WizardAbortOptions,\n): Promise<never> {\n const {\n message = 'Wizard setup cancelled.',\n outroData,\n error,\n exitCode = 1,\n } = options ?? {};\n\n logToFile(`[wizard-abort] exitCode=${exitCode}, message: ${message}`);\n if (error) {\n logToFile('[wizard-abort] error:', error);\n }\n\n // 1. Run registered cleanup functions\n for (const fn of cleanupFns) {\n try {\n fn();\n } catch {\n /* cleanup should not prevent exit */\n }\n }\n\n // 2. Capture error in analytics (if provided)\n if (error) {\n analytics.captureException(error, {\n ...((error instanceof WizardError && error.context) || {}),\n });\n }\n\n // 3. Shutdown analytics\n await analytics.shutdown(error ? 'error' : 'cancelled');\n\n // 4. Render the error outro. Synthesize OutroData from `message`\n // when the caller didn't provide structured data.\n const ui = getUI();\n ui.outroError(outroData ?? { kind: OutroKind.Error, message });\n\n // 5. Wait for the user to dismiss the outro screen. In a TUI this gives\n // them time to read the error; in non-TUI environments it resolves\n // immediately.\n await ui.waitForOutroDismissed();\n\n // 6. Exit (fires 'exit' event so TUI cleanup runs)\n return process.exit(exitCode);\n}\n"],"mappings":";;;;;;;;;;;AAaA,IAAa,cAAb,cAAiC,MAAM;CACrC,YACE,SACA,SACA;AACA,QAAM,QAAQ;AAFE,OAAA,UAAA;AAGhB,OAAK,OAAO;;;AAYhB,MAAM,aAAgC,EAAE;AAExC,SAAgB,gBAAgB,IAAsB;AACpD,YAAW,KAAK,GAAG;;AAOrB,eAAsB,YACpB,SACgB;CAChB,MAAM,EACJ,UAAU,2BACV,WACA,OACA,WAAW,MACT,WAAW,EAAE;AAEjB,WAAU,2BAA2B,SAAS,aAAa,UAAU;AACrE,KAAI,MACF,WAAU,yBAAyB,MAAM;AAI3C,MAAK,MAAM,MAAM,WACf,KAAI;AACF,MAAI;SACE;AAMV,KAAI,MACF,WAAU,iBAAiB,OAAO,EAChC,GAAK,iBAAiB,eAAe,MAAM,WAAY,EAAE,EAC1D,CAAC;AAIJ,OAAM,UAAU,SAAS,QAAQ,UAAU,YAAY;CAIvD,MAAM,KAAK,OAAO;AAClB,IAAG,WAAW,aAAa;EAAE,MAAA;EAAuB;EAAS,CAAC;AAK9D,OAAM,GAAG,uBAAuB;AAGhC,QAAO,QAAQ,KAAK,SAAS"}
@@ -1,2 +1,2 @@
1
- import { r as wizardAbort, t as WizardError } from "./wizard-abort-CY0ibdq1.js";
1
+ import { r as wizardAbort, t as WizardError } from "./wizard-abort-CDXufkqJ.js";
2
2
  export { WizardError, wizardAbort };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/wizard",
3
- "version": "2.17.0",
3
+ "version": "2.19.0",
4
4
  "homepage": "https://github.com/PostHog/wizard",
5
5
  "repository": "https://github.com/PostHog/wizard",
6
6
  "description": "The PostHog wizard helps you to configure your project",
@@ -1,275 +0,0 @@
1
- import { _ as SIGNUP_WIZARD_READINESS_CONFIG, v as evaluateWizardReadiness, y as getBlockingServiceKeys } from "./debug-Bx7nvCWW.js";
2
- import { Text } from "ink";
3
- import { useEffect, useMemo, useRef, useState } from "react";
4
- import { jsx, jsxs } from "react/jsx-runtime";
5
- //#region src/lib/programs/shared/health-check-step.ts
6
- function healthCheckReady(session) {
7
- if (!session.readinessResult) return false;
8
- if (session.signup) {
9
- const hardBlocking = getBlockingServiceKeys(session.readinessResult.health, SIGNUP_WIZARD_READINESS_CONFIG);
10
- const defaultBlocking = getBlockingServiceKeys(session.readinessResult.health);
11
- if (hardBlocking.length === 0 && defaultBlocking.length === 0) return true;
12
- return session.outageDismissed;
13
- }
14
- if (session.readinessResult.decision === "no") return session.outageDismissed;
15
- return true;
16
- }
17
- const HEALTH_CHECK_STEP = {
18
- id: "health-check",
19
- label: "Health check",
20
- screenId: "health-check",
21
- gate: healthCheckReady,
22
- onInit: (ctx) => {
23
- evaluateWizardReadiness().then((readiness) => {
24
- ctx.setReadinessResult(readiness);
25
- }).catch(() => {
26
- ctx.setReadinessResult({
27
- decision: "yes",
28
- health: {},
29
- reasons: []
30
- });
31
- });
32
- }
33
- };
34
- //#endregion
35
- //#region src/ui/tui/styles.ts
36
- const Colors = {
37
- primary: "cyan",
38
- accent: "#DC9300",
39
- titleColor: "#3D2800",
40
- success: "green",
41
- error: "red",
42
- muted: "gray"
43
- };
44
- const Icons = {
45
- diamond: "◆",
46
- diamondOpen: "◇",
47
- check: "✔",
48
- warning: "⚠",
49
- squareFilled: "◼",
50
- squareOpen: "◻",
51
- triangleRight: "▶",
52
- triangleSmallRight: "▸",
53
- bullet: "•"
54
- };
55
- //#endregion
56
- //#region src/ui/tui/primitives/text-helpers.ts
57
- /**
58
- * Text-splitting helpers for sentence boundary detection.
59
- * Used by TextBlock for animation pauses at punctuation.
60
- */
61
- /** Split text into sentences (keeps the delimiter attached) */
62
- function splitSentences(text) {
63
- const parts = [];
64
- const re = /[^.!?]*[.!?]+\s*/g;
65
- let match;
66
- let lastIndex = 0;
67
- while ((match = re.exec(text)) !== null) {
68
- parts.push(match[0]);
69
- lastIndex = re.lastIndex;
70
- }
71
- if (lastIndex < text.length) parts.push(text.slice(lastIndex));
72
- return parts;
73
- }
74
- /** Build a set of character indices where sentences end (for typewriter pause) */
75
- function sentenceEndChars(text) {
76
- const ends = /* @__PURE__ */ new Set();
77
- const sentences = splitSentences(text);
78
- let pos = 0;
79
- for (const s of sentences) {
80
- pos += s.length;
81
- ends.add(pos - 1);
82
- }
83
- return ends;
84
- }
85
- /** Build a set of word indices where sentences end (for word-by-word pause) */
86
- function sentenceEndWords(text) {
87
- const ends = /* @__PURE__ */ new Set();
88
- const sentences = splitSentences(text);
89
- let wordCount = 0;
90
- for (const s of sentences) {
91
- const words = s.trim().split(/\s+/).filter(Boolean);
92
- wordCount += words.length;
93
- ends.add(wordCount - 1);
94
- }
95
- return ends;
96
- }
97
- //#endregion
98
- //#region src/ui/tui/primitives/content-types.ts
99
- /** Type guard for lines blocks. */
100
- function isLinesBlock(block) {
101
- return typeof block !== "string" && "type" in block && block.type === "lines";
102
- }
103
- /** Type guard for clear blocks. */
104
- function isClearBlock(block) {
105
- return typeof block !== "string" && "type" in block && block.type === "clear";
106
- }
107
- /** Type guard for object blocks (text or node content). */
108
- function isObjectBlock(block) {
109
- return typeof block !== "string" && !("type" in block);
110
- }
111
- //#endregion
112
- //#region src/ui/tui/primitives/layout-helpers.ts
113
- /**
114
- * Estimate the number of terminal rows a content block will occupy,
115
- * including 1 row of marginBottom.
116
- */
117
- function estimateBlockHeight(block, availableWidth) {
118
- if (typeof block === "string") return wordWrap(block, availableWidth).length + 1;
119
- if (isClearBlock(block)) return 0;
120
- if (isLinesBlock(block)) return block.lines.length + 1;
121
- if (isObjectBlock(block)) {
122
- if (typeof block.content === "string") return wordWrap(block.content, availableWidth).length + 1;
123
- return 4;
124
- }
125
- return 1;
126
- }
127
- /**
128
- * Given all blocks, the active index, available width, and a row budget,
129
- * return [startIdx, endIdx] — the range of blocks to render.
130
- *
131
- * Always includes activeIdx. Walks backward to include as many completed
132
- * blocks as fit within maxHeight.
133
- */
134
- function computeVisibleRange(blocks, activeIdx, availableWidth, maxHeight) {
135
- const budget = Math.max(4, maxHeight - 2);
136
- let totalHeight = estimateBlockHeight(blocks[activeIdx], availableWidth);
137
- let start = activeIdx;
138
- for (let i = activeIdx - 1; i >= 0; i--) {
139
- if (isClearBlock(blocks[i])) break;
140
- const h = estimateBlockHeight(blocks[i], availableWidth);
141
- if (totalHeight + h > budget) break;
142
- totalHeight += h;
143
- start = i;
144
- }
145
- return [start, activeIdx];
146
- }
147
- /**
148
- * Word-wrap text at clean word boundaries. Always returns pre-wrapped text
149
- * joined with \n — this avoids Ink's native wrap which can leave leading
150
- * spaces on continuation lines.
151
- *
152
- * Uses a 1-char safety margin so slight width estimate mismatches don't
153
- * cause Ink to re-wrap our already-wrapped lines.
154
- */
155
- function wordWrap(text, availableWidth) {
156
- const safeWidth = Math.max(10, availableWidth - 1);
157
- const words = text.split(/\s+/);
158
- const lines = [];
159
- let currentLine = "";
160
- for (const word of words) if (currentLine.length + word.length + 1 > safeWidth && currentLine.length > 0) {
161
- lines.push(currentLine);
162
- currentLine = word;
163
- } else currentLine += (currentLine.length > 0 ? " " : "") + word;
164
- if (currentLine.length > 0) lines.push(currentLine);
165
- return lines;
166
- }
167
- /**
168
- * Word-wrap text and return only the last `maxRows` lines.
169
- * Used for intra-block truncation when a single text block exceeds the viewport.
170
- * Also used for normal rendering to avoid Ink's leading-space wrap artifacts.
171
- */
172
- function wrapAndTruncate(text, availableWidth, maxRows) {
173
- const lines = wordWrap(text, availableWidth);
174
- if (lines.length <= maxRows) return lines.join("\n");
175
- return lines.slice(-maxRows).join("\n");
176
- }
177
- //#endregion
178
- //#region src/ui/tui/primitives/TextBlock.tsx
179
- /**
180
- * TextBlock — Animates a single string paragraph.
181
- *
182
- * Self-contained: owns its own animIdx and timer.
183
- * Calls onComplete() when the animation finishes.
184
- *
185
- * Five animation modes:
186
- * 1. Typewriter — character-by-character reveal
187
- * 2. Word by word — each word appears in order
188
- * 3. Sentence by sentence — sentences appear one at a time
189
- * 4. Paragraph fade — paragraph appears at full opacity immediately
190
- * 5. Sentence fade — paragraph dim, sentences light up in order
191
- */
192
- /** Default interval per mode (ms) */
193
- const TEXT_REVEAL_MODE_DEFAULTS = {
194
- [1]: 240,
195
- [0]: 32,
196
- [2]: 1800,
197
- [3]: 4800,
198
- [4]: 2400
199
- };
200
- const TextBlock = ({ text, active, completed, onComplete, mode, bullet, animationInterval, sentenceInterval = 1600, maxHeight, availableWidth, dimWhenComplete = true }) => {
201
- const speed = animationInterval ?? TEXT_REVEAL_MODE_DEFAULTS[mode];
202
- const [animIdx, setAnimIdx] = useState(mode === 4 ? 1 : 0);
203
- const resetRef = useRef(0);
204
- const prevMode = useRef(mode);
205
- if (prevMode.current !== mode) {
206
- prevMode.current = mode;
207
- resetRef.current += 1;
208
- setAnimIdx(mode === 4 ? 1 : 0);
209
- }
210
- const words = text.split(/\s+/);
211
- const sentences = splitSentences(text);
212
- const sentenceCharEnds = useMemo(() => sentenceEndChars(text), [text]);
213
- const sentenceWordEnds = useMemo(() => sentenceEndWords(text), [text]);
214
- const isDone = mode === 0 ? animIdx >= text.length : mode === 3 ? true : mode === 1 ? animIdx >= words.length : mode === 4 || mode === 2 ? animIdx >= sentences.length : true;
215
- useEffect(() => {
216
- if (isDone && active) onComplete();
217
- }, [
218
- isDone,
219
- active,
220
- onComplete
221
- ]);
222
- useEffect(() => {
223
- if (!active || mode === 3 || isDone) return;
224
- const token = resetRef.current;
225
- const isFirstTick = animIdx === 0;
226
- let delay = isFirstTick ? 0 : speed;
227
- if (!isFirstTick && mode === 0 && animIdx > 0 && sentenceCharEnds.has(animIdx - 1)) delay = sentenceInterval;
228
- else if (!isFirstTick && mode === 1 && animIdx > 0 && sentenceWordEnds.has(animIdx - 1)) delay = sentenceInterval;
229
- const timer = setTimeout(() => {
230
- if (token !== resetRef.current) return;
231
- setAnimIdx((c) => c + 1);
232
- }, delay);
233
- return () => clearTimeout(timer);
234
- }, [
235
- active,
236
- mode,
237
- animIdx,
238
- isDone,
239
- speed,
240
- sentenceInterval,
241
- sentenceCharEnds,
242
- sentenceWordEnds
243
- ]);
244
- const wrap = (visibleText) => {
245
- if (availableWidth == null) return visibleText;
246
- if (maxHeight == null) return wrapAndTruncate(visibleText, availableWidth, Infinity);
247
- return wrapAndTruncate(visibleText, availableWidth, maxHeight);
248
- };
249
- if (completed) return /* @__PURE__ */ jsxs(Text, {
250
- dimColor: dimWhenComplete,
251
- children: [bullet, wrap(text)]
252
- });
253
- if (mode === 0) {
254
- const revealed = text.slice(0, animIdx);
255
- return /* @__PURE__ */ jsxs(Text, { children: [
256
- bullet,
257
- wrap(/[.!?]\s*$/.test(revealed) ? revealed.trimEnd() : revealed),
258
- /* @__PURE__ */ jsx(Text, {
259
- color: Colors.muted,
260
- children: "▌"
261
- })
262
- ] });
263
- }
264
- if (mode === 1) return /* @__PURE__ */ jsxs(Text, { children: [bullet, wrap(words.slice(0, animIdx).join(" "))] });
265
- if (mode === 3) return /* @__PURE__ */ jsxs(Text, { children: [bullet, wrap(text)] });
266
- if (mode === 2) return /* @__PURE__ */ jsxs(Text, { children: [bullet, wrap(sentences.slice(0, animIdx).join(""))] });
267
- return /* @__PURE__ */ jsxs(Text, { children: [bullet, sentences.map((s, si) => /* @__PURE__ */ jsx(Text, {
268
- dimColor: si >= animIdx,
269
- children: s
270
- }, si))] });
271
- };
272
- //#endregion
273
- export { isObjectBlock as a, HEALTH_CHECK_STEP as c, isLinesBlock as i, computeVisibleRange as n, Colors as o, isClearBlock as r, Icons as s, TextBlock as t };
274
-
275
- //# sourceMappingURL=TextBlock-D0Ep3zC9.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"TextBlock-D0Ep3zC9.js","names":[],"sources":["../src/lib/programs/shared/health-check-step.ts","../src/ui/tui/styles.ts","../src/ui/tui/primitives/text-helpers.ts","../src/ui/tui/primitives/content-types.ts","../src/ui/tui/primitives/layout-helpers.ts","../src/ui/tui/primitives/TextBlock.tsx"],"sourcesContent":["/**\n * Shared health-check step used by every program that runs an agent.\n *\n * Renders the HealthCheckScreen between intro and auth, kicks off the\n * readiness probe in onInit, and gates the screen on either a clean\n * readiness result or an explicit user dismissal of the outage.\n *\n * Programs without this step that hit a blocking outage gridlock the\n * router: agent-runner calls wizardAbort, which awaits outroDismissed,\n * but the router can't advance past the still-incomplete auth step to\n * render the OutroScreen.\n */\n\nimport type { ProgramStep } from '@lib/programs/program-step';\nimport type { WizardSession } from '@lib/wizard-session';\nimport {\n evaluateWizardReadiness,\n WizardReadiness,\n SIGNUP_WIZARD_READINESS_CONFIG,\n getBlockingServiceKeys,\n} from '@lib/health-checks/readiness';\n\nexport function healthCheckReady(session: WizardSession): boolean {\n if (!session.readinessResult) return false;\n\n if (session.signup) {\n const hardBlocking = getBlockingServiceKeys(\n session.readinessResult.health,\n SIGNUP_WIZARD_READINESS_CONFIG,\n );\n const defaultBlocking = getBlockingServiceKeys(\n session.readinessResult.health,\n );\n if (hardBlocking.length === 0 && defaultBlocking.length === 0) return true;\n return session.outageDismissed;\n }\n\n if (session.readinessResult.decision === WizardReadiness.No) {\n return session.outageDismissed;\n }\n return true;\n}\n\nexport const HEALTH_CHECK_STEP: ProgramStep = {\n id: 'health-check',\n label: 'Health check',\n screenId: 'health-check',\n gate: healthCheckReady,\n onInit: (ctx) => {\n evaluateWizardReadiness()\n .then((readiness) => {\n ctx.setReadinessResult(readiness);\n })\n .catch(() => {\n ctx.setReadinessResult({\n decision: WizardReadiness.Yes,\n health: {} as never,\n reasons: [],\n });\n });\n },\n};\n","/**\n * Shared style constants for TUI layout primitives.\n */\n\nexport enum HAlign {\n Left = 'flex-start',\n Center = 'center',\n Right = 'flex-end',\n}\n\nexport enum VAlign {\n Top = 'flex-start',\n Center = 'center',\n Bottom = 'flex-end',\n}\n\nexport const Colors = {\n primary: 'cyan',\n accent: '#DC9300',\n titleColor: '#3D2800',\n success: 'green',\n error: 'red',\n muted: 'gray',\n} as const;\n\nexport const Icons = {\n diamond: '\\u25C6',\n diamondOpen: '\\u25C7',\n check: '\\u2714',\n warning: '\\u26A0',\n squareFilled: '\\u25FC',\n squareOpen: '\\u25FB',\n triangleRight: '\\u25B6',\n triangleSmallRight: '\\u25B8',\n bullet: '\\u2022',\n} as const;\n","/**\n * Text-splitting helpers for sentence boundary detection.\n * Used by TextBlock for animation pauses at punctuation.\n */\n\n/** Split text into sentences (keeps the delimiter attached) */\nexport function splitSentences(text: string): string[] {\n const parts: string[] = [];\n const re = /[^.!?]*[.!?]+\\s*/g;\n let match: RegExpExecArray | null;\n let lastIndex = 0;\n while ((match = re.exec(text)) !== null) {\n parts.push(match[0]);\n lastIndex = re.lastIndex;\n }\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n return parts;\n}\n\n/** Build a set of character indices where sentences end (for typewriter pause) */\nexport function sentenceEndChars(text: string): Set<number> {\n const ends = new Set<number>();\n const sentences = splitSentences(text);\n let pos = 0;\n for (const s of sentences) {\n pos += s.length;\n ends.add(pos - 1);\n }\n return ends;\n}\n\n/** Build a set of word indices where sentences end (for word-by-word pause) */\nexport function sentenceEndWords(text: string): Set<number> {\n const ends = new Set<number>();\n const sentences = splitSentences(text);\n let wordCount = 0;\n for (const s of sentences) {\n const words = s.trim().split(/\\s+/).filter(Boolean);\n wordCount += words.length;\n ends.add(wordCount - 1);\n }\n return ends;\n}\n","/**\n * Content block types and type guards.\n *\n * Extracted from ContentSequencer so that pure-logic modules (like\n * layout-helpers) can import them without pulling in Ink/React.\n */\n\nimport type { ReactNode } from 'react';\nimport type { TextRevealMode } from './TextBlock.js';\n\n/** Object form — string or ReactNode content with per-block overrides. */\nexport interface ContentObjectBlock {\n content: string | ReactNode;\n mode?: TextRevealMode;\n animationInterval?: number;\n sentenceInterval?: number;\n pause?: number;\n persist?: boolean;\n /**\n * When the sequencer advances past this block, should it render dim?\n * Defaults to `true` — the standard \"completed step\" treatment. Set to\n * `false` to keep the block at full opacity even after it's complete\n * (useful for headings or greetings that should stay readable while\n * later blocks animate in).\n *\n * Only meaningful for blocks whose `content` is a string (TextBlock\n * renders the dim treatment). NodeBlocks render whatever JSX you pass\n * verbatim regardless of completion state.\n */\n dimWhenComplete?: boolean;\n}\n\n/** Lines block — reveals ReactNode lines one at a time. */\nexport interface ContentLinesBlock {\n type: 'lines';\n lines: ReactNode[];\n interval?: number;\n pause?: number;\n}\n\n/** Clear block — page break that hides all prior blocks. */\nexport interface ContentClearBlock {\n type: 'clear';\n pause?: number;\n}\n\n/** A content block in the sequence. Bare strings are sugar for { content: '...' }. */\nexport type ContentBlock =\n | string\n | ContentObjectBlock\n | ContentLinesBlock\n | ContentClearBlock;\n\n/** Type guard for lines blocks. */\nexport function isLinesBlock(block: ContentBlock): block is ContentLinesBlock {\n return typeof block !== 'string' && 'type' in block && block.type === 'lines';\n}\n\n/** Type guard for clear blocks. */\nexport function isClearBlock(block: ContentBlock): block is ContentClearBlock {\n return typeof block !== 'string' && 'type' in block && block.type === 'clear';\n}\n\n/** Type guard for object blocks (text or node content). */\nexport function isObjectBlock(\n block: ContentBlock,\n): block is ContentObjectBlock {\n return typeof block !== 'string' && !('type' in block);\n}\n","/**\n * Layout helpers — pure functions for height estimation and viewport eviction.\n *\n * These are the core of the responsive content system. They estimate how many\n * terminal rows a content block will occupy and determine which blocks fit\n * within a given height budget.\n */\n\nimport type { ContentBlock } from './content-types.js';\nimport { isLinesBlock, isClearBlock, isObjectBlock } from './content-types.js';\n\n/**\n * Estimate the number of terminal rows a content block will occupy,\n * including 1 row of marginBottom.\n */\nexport function estimateBlockHeight(\n block: ContentBlock,\n availableWidth: number,\n): number {\n if (typeof block === 'string') {\n return wordWrap(block, availableWidth).length + 1; // +1 for marginBottom\n }\n\n if (isClearBlock(block)) return 0;\n\n if (isLinesBlock(block)) {\n return block.lines.length + 1;\n }\n\n if (isObjectBlock(block)) {\n if (typeof block.content === 'string') {\n return wordWrap(block.content, availableWidth).length + 1;\n }\n return 4; // conservative fixed estimate for ReactNode\n }\n\n return 1;\n}\n\n/**\n * Given all blocks, the active index, available width, and a row budget,\n * return [startIdx, endIdx] — the range of blocks to render.\n *\n * Always includes activeIdx. Walks backward to include as many completed\n * blocks as fit within maxHeight.\n */\nexport function computeVisibleRange(\n blocks: ContentBlock[],\n activeIdx: number,\n availableWidth: number,\n maxHeight: number,\n): [number, number] {\n // Reserve a 2-row buffer so resize-induced estimate drift doesn't\n // cause overflow=\"hidden\" to clip the margin between blocks.\n const budget = Math.max(4, maxHeight - 2);\n\n let totalHeight = estimateBlockHeight(blocks[activeIdx], availableWidth);\n let start = activeIdx;\n\n for (let i = activeIdx - 1; i >= 0; i--) {\n // Clear blocks act as a hard boundary — don't show anything before them\n if (isClearBlock(blocks[i])) break;\n const h = estimateBlockHeight(blocks[i], availableWidth);\n if (totalHeight + h > budget) break;\n totalHeight += h;\n start = i;\n }\n\n return [start, activeIdx];\n}\n\n/**\n * Word-wrap text at clean word boundaries. Always returns pre-wrapped text\n * joined with \\n — this avoids Ink's native wrap which can leave leading\n * spaces on continuation lines.\n *\n * Uses a 1-char safety margin so slight width estimate mismatches don't\n * cause Ink to re-wrap our already-wrapped lines.\n */\nexport function wordWrap(text: string, availableWidth: number): string[] {\n const safeWidth = Math.max(10, availableWidth - 1);\n const words = text.split(/\\s+/);\n const lines: string[] = [];\n let currentLine = '';\n\n for (const word of words) {\n if (\n currentLine.length + word.length + 1 > safeWidth &&\n currentLine.length > 0\n ) {\n lines.push(currentLine);\n currentLine = word;\n } else {\n currentLine += (currentLine.length > 0 ? ' ' : '') + word;\n }\n }\n if (currentLine.length > 0) {\n lines.push(currentLine);\n }\n\n return lines;\n}\n\n/**\n * Word-wrap text and return only the last `maxRows` lines.\n * Used for intra-block truncation when a single text block exceeds the viewport.\n * Also used for normal rendering to avoid Ink's leading-space wrap artifacts.\n */\nexport function wrapAndTruncate(\n text: string,\n availableWidth: number,\n maxRows: number,\n): string {\n const lines = wordWrap(text, availableWidth);\n\n if (lines.length <= maxRows) {\n return lines.join('\\n');\n }\n\n return lines.slice(-maxRows).join('\\n');\n}\n","/**\n * TextBlock — Animates a single string paragraph.\n *\n * Self-contained: owns its own animIdx and timer.\n * Calls onComplete() when the animation finishes.\n *\n * Five animation modes:\n * 1. Typewriter — character-by-character reveal\n * 2. Word by word — each word appears in order\n * 3. Sentence by sentence — sentences appear one at a time\n * 4. Paragraph fade — paragraph appears at full opacity immediately\n * 5. Sentence fade — paragraph dim, sentences light up in order\n */\n\nimport { Text } from 'ink';\nimport { useState, useEffect, useRef, useMemo, type ReactNode } from 'react';\nimport { Colors } from '@ui/tui/styles';\nimport {\n splitSentences,\n sentenceEndChars,\n sentenceEndWords,\n} from './text-helpers.js';\nimport { wrapAndTruncate } from './layout-helpers.js';\n\nexport enum TextRevealMode {\n Typewriter = 0,\n WordByWord = 1,\n SentenceBySentence = 2,\n ParagraphFade = 3,\n SentenceFade = 4,\n}\n\nexport const TEXT_REVEAL_MODE_LABELS = [\n 'Typewriter',\n 'Word by word',\n 'Sentence by sentence',\n 'Paragraph fade',\n 'Sentence fade',\n];\n\nexport const TEXT_REVEAL_MODE_COUNT = 5;\n\n/** Default interval per mode (ms) */\nexport const TEXT_REVEAL_MODE_DEFAULTS: Record<TextRevealMode, number> = {\n [TextRevealMode.WordByWord]: 240,\n [TextRevealMode.Typewriter]: 32,\n [TextRevealMode.SentenceBySentence]: 1800,\n [TextRevealMode.ParagraphFade]: 4800,\n [TextRevealMode.SentenceFade]: 2400,\n};\n\ninterface TextBlockProps {\n text: string;\n active: boolean;\n completed: boolean;\n onComplete: () => void;\n mode: TextRevealMode;\n bullet?: ReactNode;\n animationInterval?: number;\n sentenceInterval?: number;\n /** Max rows this block may occupy. When exceeded, top lines are truncated. */\n maxHeight?: number;\n /** Available text width in columns (for truncation wrapping). */\n availableWidth?: number;\n /**\n * When `completed === true`, render the text dim. Defaults to `true` —\n * the standard \"completed step\" treatment. Pass `false` to keep the\n * text at full opacity even after the sequencer has moved on.\n */\n dimWhenComplete?: boolean;\n}\n\nexport const TextBlock = ({\n text,\n active,\n completed,\n onComplete,\n mode,\n bullet,\n animationInterval,\n sentenceInterval = 1600,\n maxHeight,\n availableWidth,\n dimWhenComplete = true,\n}: TextBlockProps) => {\n const speed = animationInterval ?? TEXT_REVEAL_MODE_DEFAULTS[mode];\n\n const [animIdx, setAnimIdx] = useState(\n mode === TextRevealMode.SentenceFade ? 1 : 0,\n );\n\n // Reset synchronously during render to avoid a one-frame flash\n const resetRef = useRef(0);\n const prevMode = useRef(mode);\n if (prevMode.current !== mode) {\n prevMode.current = mode;\n resetRef.current += 1;\n setAnimIdx(mode === TextRevealMode.SentenceFade ? 1 : 0);\n }\n\n const words = text.split(/\\s+/);\n const sentences = splitSentences(text);\n\n const sentenceCharEnds = useMemo(() => sentenceEndChars(text), [text]);\n const sentenceWordEnds = useMemo(() => sentenceEndWords(text), [text]);\n\n const isDone =\n mode === TextRevealMode.Typewriter\n ? animIdx >= text.length\n : mode === TextRevealMode.ParagraphFade\n ? true\n : mode === TextRevealMode.WordByWord\n ? animIdx >= words.length\n : mode === TextRevealMode.SentenceFade ||\n mode === TextRevealMode.SentenceBySentence\n ? animIdx >= sentences.length\n : true;\n\n // Fire onComplete when done\n useEffect(() => {\n if (isDone && active) onComplete();\n }, [isDone, active, onComplete]);\n\n // Animate: single effect for all tick-based modes\n useEffect(() => {\n if (!active || mode === TextRevealMode.ParagraphFade || isDone) return;\n const token = resetRef.current;\n\n const isFirstTick = animIdx === 0;\n\n let delay = isFirstTick ? 0 : speed;\n if (\n !isFirstTick &&\n mode === TextRevealMode.Typewriter &&\n animIdx > 0 &&\n sentenceCharEnds.has(animIdx - 1)\n ) {\n delay = sentenceInterval;\n } else if (\n !isFirstTick &&\n mode === TextRevealMode.WordByWord &&\n animIdx > 0 &&\n sentenceWordEnds.has(animIdx - 1)\n ) {\n delay = sentenceInterval;\n }\n\n const timer = setTimeout(() => {\n if (token !== resetRef.current) return;\n setAnimIdx((c) => c + 1);\n }, delay);\n return () => clearTimeout(timer);\n }, [\n active,\n mode,\n animIdx,\n isDone,\n speed,\n sentenceInterval,\n sentenceCharEnds,\n sentenceWordEnds,\n ]);\n\n // Pre-wrap text ourselves to avoid Ink's native wrap leaving leading spaces\n // on continuation lines. When maxHeight is set, also truncates to last N rows.\n const wrap = (visibleText: string): string => {\n if (availableWidth == null) return visibleText;\n if (maxHeight == null) {\n return wrapAndTruncate(visibleText, availableWidth, Infinity);\n }\n return wrapAndTruncate(visibleText, availableWidth, maxHeight);\n };\n\n // Completed: dimmed by default, but can be overridden per-block.\n if (completed) {\n return (\n <Text dimColor={dimWhenComplete}>\n {bullet}\n {wrap(text)}\n </Text>\n );\n }\n\n // Active: mode-specific rendering\n if (mode === TextRevealMode.Typewriter) {\n const revealed = text.slice(0, animIdx);\n const atSentenceEnd = /[.!?]\\s*$/.test(revealed);\n const display = atSentenceEnd ? revealed.trimEnd() : revealed;\n return (\n <Text>\n {bullet}\n {wrap(display)}\n <Text color={Colors.muted}>{'\\u258C'}</Text>\n </Text>\n );\n }\n\n if (mode === TextRevealMode.WordByWord) {\n const visible = words.slice(0, animIdx).join(' ');\n return (\n <Text>\n {bullet}\n {wrap(visible)}\n </Text>\n );\n }\n\n if (mode === TextRevealMode.ParagraphFade) {\n return (\n <Text>\n {bullet}\n {wrap(text)}\n </Text>\n );\n }\n\n if (mode === TextRevealMode.SentenceBySentence) {\n const visible = sentences.slice(0, animIdx).join('');\n return (\n <Text>\n {bullet}\n {wrap(visible)}\n </Text>\n );\n }\n\n // SentenceFade\n return (\n <Text>\n {bullet}\n {sentences.map((s, si) => (\n <Text key={si} dimColor={si >= animIdx}>\n {s}\n </Text>\n ))}\n </Text>\n );\n};\n"],"mappings":";;;;;AAsBA,SAAgB,iBAAiB,SAAiC;AAChE,KAAI,CAAC,QAAQ,gBAAiB,QAAO;AAErC,KAAI,QAAQ,QAAQ;EAClB,MAAM,eAAe,uBACnB,QAAQ,gBAAgB,QACxB,+BACD;EACD,MAAM,kBAAkB,uBACtB,QAAQ,gBAAgB,OACzB;AACD,MAAI,aAAa,WAAW,KAAK,gBAAgB,WAAW,EAAG,QAAO;AACtE,SAAO,QAAQ;;AAGjB,KAAI,QAAQ,gBAAgB,aAAA,KAC1B,QAAO,QAAQ;AAEjB,QAAO;;AAGT,MAAa,oBAAiC;CAC5C,IAAI;CACJ,OAAO;CACP,UAAU;CACV,MAAM;CACN,SAAS,QAAQ;AACf,2BAAyB,CACtB,MAAM,cAAc;AACnB,OAAI,mBAAmB,UAAU;IACjC,CACD,YAAY;AACX,OAAI,mBAAmB;IACrB,UAAA;IACA,QAAQ,EAAE;IACV,SAAS,EAAE;IACZ,CAAC;IACF;;CAEP;;;AC7CD,MAAa,SAAS;CACpB,SAAS;CACT,QAAQ;CACR,YAAY;CACZ,SAAS;CACT,OAAO;CACP,OAAO;CACR;AAED,MAAa,QAAQ;CACnB,SAAS;CACT,aAAa;CACb,OAAO;CACP,SAAS;CACT,cAAc;CACd,YAAY;CACZ,eAAe;CACf,oBAAoB;CACpB,QAAQ;CACT;;;;;;;;AC7BD,SAAgB,eAAe,MAAwB;CACrD,MAAM,QAAkB,EAAE;CAC1B,MAAM,KAAK;CACX,IAAI;CACJ,IAAI,YAAY;AAChB,SAAQ,QAAQ,GAAG,KAAK,KAAK,MAAM,MAAM;AACvC,QAAM,KAAK,MAAM,GAAG;AACpB,cAAY,GAAG;;AAEjB,KAAI,YAAY,KAAK,OACnB,OAAM,KAAK,KAAK,MAAM,UAAU,CAAC;AAEnC,QAAO;;;AAIT,SAAgB,iBAAiB,MAA2B;CAC1D,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,YAAY,eAAe,KAAK;CACtC,IAAI,MAAM;AACV,MAAK,MAAM,KAAK,WAAW;AACzB,SAAO,EAAE;AACT,OAAK,IAAI,MAAM,EAAE;;AAEnB,QAAO;;;AAIT,SAAgB,iBAAiB,MAA2B;CAC1D,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,YAAY,eAAe,KAAK;CACtC,IAAI,YAAY;AAChB,MAAK,MAAM,KAAK,WAAW;EACzB,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,MAAM,CAAC,OAAO,QAAQ;AACnD,eAAa,MAAM;AACnB,OAAK,IAAI,YAAY,EAAE;;AAEzB,QAAO;;;;;ACWT,SAAgB,aAAa,OAAiD;AAC5E,QAAO,OAAO,UAAU,YAAY,UAAU,SAAS,MAAM,SAAS;;;AAIxE,SAAgB,aAAa,OAAiD;AAC5E,QAAO,OAAO,UAAU,YAAY,UAAU,SAAS,MAAM,SAAS;;;AAIxE,SAAgB,cACd,OAC6B;AAC7B,QAAO,OAAO,UAAU,YAAY,EAAE,UAAU;;;;;;;;ACpDlD,SAAgB,oBACd,OACA,gBACQ;AACR,KAAI,OAAO,UAAU,SACnB,QAAO,SAAS,OAAO,eAAe,CAAC,SAAS;AAGlD,KAAI,aAAa,MAAM,CAAE,QAAO;AAEhC,KAAI,aAAa,MAAM,CACrB,QAAO,MAAM,MAAM,SAAS;AAG9B,KAAI,cAAc,MAAM,EAAE;AACxB,MAAI,OAAO,MAAM,YAAY,SAC3B,QAAO,SAAS,MAAM,SAAS,eAAe,CAAC,SAAS;AAE1D,SAAO;;AAGT,QAAO;;;;;;;;;AAUT,SAAgB,oBACd,QACA,WACA,gBACA,WACkB;CAGlB,MAAM,SAAS,KAAK,IAAI,GAAG,YAAY,EAAE;CAEzC,IAAI,cAAc,oBAAoB,OAAO,YAAY,eAAe;CACxE,IAAI,QAAQ;AAEZ,MAAK,IAAI,IAAI,YAAY,GAAG,KAAK,GAAG,KAAK;AAEvC,MAAI,aAAa,OAAO,GAAG,CAAE;EAC7B,MAAM,IAAI,oBAAoB,OAAO,IAAI,eAAe;AACxD,MAAI,cAAc,IAAI,OAAQ;AAC9B,iBAAe;AACf,UAAQ;;AAGV,QAAO,CAAC,OAAO,UAAU;;;;;;;;;;AAW3B,SAAgB,SAAS,MAAc,gBAAkC;CACvE,MAAM,YAAY,KAAK,IAAI,IAAI,iBAAiB,EAAE;CAClD,MAAM,QAAQ,KAAK,MAAM,MAAM;CAC/B,MAAM,QAAkB,EAAE;CAC1B,IAAI,cAAc;AAElB,MAAK,MAAM,QAAQ,MACjB,KACE,YAAY,SAAS,KAAK,SAAS,IAAI,aACvC,YAAY,SAAS,GACrB;AACA,QAAM,KAAK,YAAY;AACvB,gBAAc;OAEd,iBAAgB,YAAY,SAAS,IAAI,MAAM,MAAM;AAGzD,KAAI,YAAY,SAAS,EACvB,OAAM,KAAK,YAAY;AAGzB,QAAO;;;;;;;AAQT,SAAgB,gBACd,MACA,gBACA,SACQ;CACR,MAAM,QAAQ,SAAS,MAAM,eAAe;AAE5C,KAAI,MAAM,UAAU,QAClB,QAAO,MAAM,KAAK,KAAK;AAGzB,QAAO,MAAM,MAAM,CAAC,QAAQ,CAAC,KAAK,KAAK;;;;;;;;;;;;;;;;;;AC5EzC,MAAa,4BAA4D;MAC1C;MACA;MACQ;MACL;MACD;CAChC;AAuBD,MAAa,aAAa,EACxB,MACA,QACA,WACA,YACA,MACA,QACA,mBACA,mBAAmB,MACnB,WACA,gBACA,kBAAkB,WACE;CACpB,MAAM,QAAQ,qBAAqB,0BAA0B;CAE7D,MAAM,CAAC,SAAS,cAAc,SAC5B,SAAA,IAAuC,IAAI,EAC5C;CAGD,MAAM,WAAW,OAAO,EAAE;CAC1B,MAAM,WAAW,OAAO,KAAK;AAC7B,KAAI,SAAS,YAAY,MAAM;AAC7B,WAAS,UAAU;AACnB,WAAS,WAAW;AACpB,aAAW,SAAA,IAAuC,IAAI,EAAE;;CAG1D,MAAM,QAAQ,KAAK,MAAM,MAAM;CAC/B,MAAM,YAAY,eAAe,KAAK;CAEtC,MAAM,mBAAmB,cAAc,iBAAiB,KAAK,EAAE,CAAC,KAAK,CAAC;CACtE,MAAM,mBAAmB,cAAc,iBAAiB,KAAK,EAAE,CAAC,KAAK,CAAC;CAEtE,MAAM,SACJ,SAAA,IACI,WAAW,KAAK,SAChB,SAAA,IACA,OACA,SAAA,IACA,WAAW,MAAM,SACjB,SAAA,KACA,SAAA,IACA,WAAW,UAAU,SACrB;AAGN,iBAAgB;AACd,MAAI,UAAU,OAAQ,aAAY;IACjC;EAAC;EAAQ;EAAQ;EAAW,CAAC;AAGhC,iBAAgB;AACd,MAAI,CAAC,UAAU,SAAA,KAAyC,OAAQ;EAChE,MAAM,QAAQ,SAAS;EAEvB,MAAM,cAAc,YAAY;EAEhC,IAAI,QAAQ,cAAc,IAAI;AAC9B,MACE,CAAC,eACD,SAAA,KACA,UAAU,KACV,iBAAiB,IAAI,UAAU,EAAE,CAEjC,SAAQ;WAER,CAAC,eACD,SAAA,KACA,UAAU,KACV,iBAAiB,IAAI,UAAU,EAAE,CAEjC,SAAQ;EAGV,MAAM,QAAQ,iBAAiB;AAC7B,OAAI,UAAU,SAAS,QAAS;AAChC,eAAY,MAAM,IAAI,EAAE;KACvB,MAAM;AACT,eAAa,aAAa,MAAM;IAC/B;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CAIF,MAAM,QAAQ,gBAAgC;AAC5C,MAAI,kBAAkB,KAAM,QAAO;AACnC,MAAI,aAAa,KACf,QAAO,gBAAgB,aAAa,gBAAgB,SAAS;AAE/D,SAAO,gBAAgB,aAAa,gBAAgB,UAAU;;AAIhE,KAAI,UACF,QACE,qBAAC,MAAD;EAAM,UAAU;YAAhB,CACG,QACA,KAAK,KAAK,CACN;;AAKX,KAAI,SAAA,GAAoC;EACtC,MAAM,WAAW,KAAK,MAAM,GAAG,QAAQ;AAGvC,SACE,qBAAC,MAAD,EAAA,UAAA;GACG;GACA,KALiB,YAAY,KAAK,SAAS,GAChB,SAAS,SAAS,GAAG,SAInC;GACd,oBAAC,MAAD;IAAM,OAAO,OAAO;cAAQ;IAAgB,CAAA;GACvC,EAAA,CAAA;;AAIX,KAAI,SAAA,EAEF,QACE,qBAAC,MAAD,EAAA,UAAA,CACG,QACA,KAJW,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,IAAI,CAI/B,CACT,EAAA,CAAA;AAIX,KAAI,SAAA,EACF,QACE,qBAAC,MAAD,EAAA,UAAA,CACG,QACA,KAAK,KAAK,CACN,EAAA,CAAA;AAIX,KAAI,SAAA,EAEF,QACE,qBAAC,MAAD,EAAA,UAAA,CACG,QACA,KAJW,UAAU,MAAM,GAAG,QAAQ,CAAC,KAAK,GAAG,CAIlC,CACT,EAAA,CAAA;AAKX,QACE,qBAAC,MAAD,EAAA,UAAA,CACG,QACA,UAAU,KAAK,GAAG,OACjB,oBAAC,MAAD;EAAe,UAAU,MAAM;YAC5B;EACI,EAFI,GAEJ,CACP,CACG,EAAA,CAAA"}