@posthog/wizard 2.33.0 → 2.34.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.
- package/dist/{add-mcp-server-to-clients-Dhq75Hda.js → add-mcp-server-to-clients-BjH4z7gH.js} +4 -4
- package/dist/{add-mcp-server-to-clients-Dhq75Hda.js.map → add-mcp-server-to-clients-BjH4z7gH.js.map} +1 -1
- package/dist/{agent-interface-DCeQ5Oiw.js → agent-interface-BcUv1qeh.js} +329 -44
- package/dist/agent-interface-BcUv1qeh.js.map +1 -0
- package/dist/{agent-runner-BRajaO84.js → agent-runner-8g_pDAVs.js} +29 -11
- package/dist/agent-runner-8g_pDAVs.js.map +1 -0
- package/dist/{analytics-CU4o3eF8.js → analytics-DQ8IS1Hu.js} +2 -2
- package/dist/{analytics-CU4o3eF8.js.map → analytics-DQ8IS1Hu.js.map} +1 -1
- package/dist/{api-BBRfHEZ-.js → api-EghEV_u9.js} +3 -3
- package/dist/{api-BBRfHEZ-.js.map → api-EghEV_u9.js.map} +1 -1
- package/dist/bin.js +35 -35
- package/dist/check-screens.tsx +25 -0
- package/dist/{ci-install-D-otkGW6.js → ci-install-ouGSf4zp.js} +4 -4
- package/dist/{ci-install-D-otkGW6.js.map → ci-install-ouGSf4zp.js.map} +1 -1
- package/dist/{debug-BewTLNNr.js → debug-BM7yop-T.js} +1 -1
- package/dist/{debug-BGMe1wP6.js → debug-Cn4L5Ype.js} +2 -2
- package/dist/{debug-BGMe1wP6.js.map → debug-Cn4L5Ype.js.map} +1 -1
- package/dist/{environment-G41UFzou.js → environment-D2WUIm8o.js} +3 -3
- package/dist/{environment-G41UFzou.js.map → environment-D2WUIm8o.js.map} +1 -1
- package/dist/{file-utils-D9InAvZd.js → file-utils-CdQn6073.js} +2 -2
- package/dist/{file-utils-D9InAvZd.js.map → file-utils-CdQn6073.js.map} +1 -1
- package/dist/{headless-ui-xjHVVUqe.js → headless-ui-CSqtmW-d.js} +2 -2
- package/dist/{headless-ui-xjHVVUqe.js.map → headless-ui-CSqtmW-d.js.map} +1 -1
- package/dist/{interactive-D2CKikzz.js → interactive-CFGcWw43.js} +2 -2
- package/dist/{interactive-D2CKikzz.js.map → interactive-CFGcWw43.js.map} +1 -1
- package/dist/{mcp-prompt-streaming-FrArGwfv.js → mcp-prompt-streaming-XZSpcXkG.js} +9 -6
- package/dist/mcp-prompt-streaming-XZSpcXkG.js.map +1 -0
- package/dist/{non-interactive-BXx6Q-D0.js → non-interactive-D6eksWIw.js} +2 -2
- package/dist/{non-interactive-BXx6Q-D0.js.map → non-interactive-D6eksWIw.js.map} +1 -1
- package/dist/{package-manager-D9ojA4jO.js → package-manager-Dozr4x6K.js} +2 -2
- package/dist/{package-manager-D9ojA4jO.js.map → package-manager-Dozr4x6K.js.map} +1 -1
- package/dist/{playground-Bgtxrusl.js → playground-BB2O8GyY.js} +5 -5
- package/dist/{playground-Bgtxrusl.js.map → playground-BB2O8GyY.js.map} +1 -1
- package/dist/{posthog-integration-gnC9v4kg.js → posthog-integration-C3m_uR5A.js} +12 -12
- package/dist/{posthog-integration-gnC9v4kg.js.map → posthog-integration-C3m_uR5A.js.map} +1 -1
- package/dist/{provisioning-C3qglLdc.js → provisioning-wMzPfaaP.js} +3 -3
- package/dist/{provisioning-C3qglLdc.js.map → provisioning-wMzPfaaP.js.map} +1 -1
- package/dist/{registry-BVrvFTZ0.js → registry-BfTeAX2z.js} +4 -4
- package/dist/{registry-BVrvFTZ0.js.map → registry-BfTeAX2z.js.map} +1 -1
- package/dist/{setup-utils-CanVlGbX.js → setup-utils-B0oz3raQ.js} +53 -11
- package/dist/setup-utils-B0oz3raQ.js.map +1 -0
- package/dist/{start-tui-YE7bybIr.js → start-tui-B9qDcoGA.js} +148 -29
- package/dist/{start-tui-YE7bybIr.js.map → start-tui-B9qDcoGA.js.map} +1 -1
- package/dist/{steps-N20e4IoE.js → steps-BhERMRRI.js} +7 -7
- package/dist/{steps-N20e4IoE.js.map → steps-BhERMRRI.js.map} +1 -1
- package/dist/{store-D15C9YSW.js → store-J2Rv955W.js} +3 -3
- package/dist/{store-D15C9YSW.js.map → store-J2Rv955W.js.map} +1 -1
- package/dist/{telemetry-DknCDWP6.js → telemetry-BTLJ-YnG.js} +3 -3
- package/dist/{telemetry-DknCDWP6.js.map → telemetry-BTLJ-YnG.js.map} +1 -1
- package/dist/{terminal-Cvv0a-7Q.js → terminal-UwqFnPrL.js} +83 -32
- package/dist/terminal-UwqFnPrL.js.map +1 -0
- package/dist/{urls-DrR6F_A3.js → urls-DAVzgHGZ.js} +2 -2
- package/dist/{urls-DrR6F_A3.js.map → urls-DAVzgHGZ.js.map} +1 -1
- package/dist/{wizard-abort-DMvS0YXD.js → wizard-abort-BAB8yvGF.js} +1 -1
- package/dist/{wizard-abort-Cw818xPr.js → wizard-abort-DqeMgCVr.js} +3 -3
- package/dist/{wizard-abort-Cw818xPr.js.map → wizard-abort-DqeMgCVr.js.map} +1 -1
- package/dist/wizard-ui-WZ48rUgr.js.map +1 -1
- package/package.json +1 -1
- package/dist/agent-interface-DCeQ5Oiw.js.map +0 -1
- package/dist/agent-runner-BRajaO84.js.map +0 -1
- package/dist/mcp-prompt-streaming-FrArGwfv.js.map +0 -1
- package/dist/setup-utils-CanVlGbX.js.map +0 -1
- package/dist/terminal-Cvv0a-7Q.js.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { p as getUI } from "./debug-
|
|
2
|
-
import { t as analytics } from "./analytics-
|
|
3
|
-
import { t as withProgress } from "./telemetry-
|
|
4
|
-
import "./setup-utils-
|
|
5
|
-
import "./file-utils-
|
|
6
|
-
import "./add-mcp-server-to-clients-
|
|
1
|
+
import { p as getUI } from "./debug-Cn4L5Ype.js";
|
|
2
|
+
import { t as analytics } from "./analytics-DQ8IS1Hu.js";
|
|
3
|
+
import { t as withProgress } from "./telemetry-BTLJ-YnG.js";
|
|
4
|
+
import "./setup-utils-B0oz3raQ.js";
|
|
5
|
+
import "./file-utils-CdQn6073.js";
|
|
6
|
+
import "./add-mcp-server-to-clients-BjH4z7gH.js";
|
|
7
7
|
import * as fs$1 from "fs";
|
|
8
8
|
import * as path$1 from "path";
|
|
9
9
|
import "path";
|
|
@@ -144,4 +144,4 @@ const uploadEnvironmentVariablesStep = async (envVars, { integration, session })
|
|
|
144
144
|
//#endregion
|
|
145
145
|
export { uploadEnvironmentVariablesStep };
|
|
146
146
|
|
|
147
|
-
//# sourceMappingURL=steps-
|
|
147
|
+
//# sourceMappingURL=steps-BhERMRRI.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"steps-
|
|
1
|
+
{"version":3,"file":"steps-BhERMRRI.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,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-B_-DWIq7.js";
|
|
2
|
-
import { s as logToFile, y as getBlockingServiceKeys } from "./debug-
|
|
2
|
+
import { s as logToFile, y as getBlockingServiceKeys } from "./debug-Cn4L5Ype.js";
|
|
3
3
|
import { n as isTaskStatus } from "./wizard-ui-WZ48rUgr.js";
|
|
4
|
-
import { r as sessionProperties, t as analytics } from "./analytics-
|
|
4
|
+
import { r as sessionProperties, t as analytics } from "./analytics-DQ8IS1Hu.js";
|
|
5
5
|
import { a as buildSession } from "./wizard-session-CN55LYyZ.js";
|
|
6
6
|
import { d as Program, f as getProgramConfig, u as PROGRAM_REGISTRY } from "./bin.js";
|
|
7
7
|
import { atom, map } from "nanostores";
|
|
@@ -760,4 +760,4 @@ var WizardStore = class {
|
|
|
760
760
|
//#endregion
|
|
761
761
|
export { store_exports as n, WizardStore as t };
|
|
762
762
|
|
|
763
|
-
//# sourceMappingURL=store-
|
|
763
|
+
//# sourceMappingURL=store-J2Rv955W.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-D15C9YSW.js","names":[],"sources":["../src/lib/programs/program-step.ts","../src/lib/programs/ai-opt-in-gate.ts","../src/ui/tui/screen-sequences.ts","../src/ui/tui/router.ts","../src/ui/tui/store.ts"],"sourcesContent":["import type { WizardSession, DiscoveredFeature } from '@lib/wizard-session';\nimport type { WizardReadinessResult } from '@lib/health-checks/readiness';\nimport type { ProgramRun } from '@lib/agent/agent-runner';\nimport type { Integration } from '@lib/constants';\nimport type { FrameworkConfig } from '@lib/framework-config';\nimport type { ContentBlock } from '@ui/tui/primitives/index';\nimport type { WizardStore } from '@ui/tui/store';\nimport type { Tip } from '@ui/tui/components/TipsCard';\n\n/**\n * A program step is the primary unit of the wizard's execution model.\n *\n * It can own:\n * - a screen in the TUI (optional — some steps are headless)\n * - agent work via a program reference (optional — some steps are UI-only)\n * - completion and visibility predicates\n *\n * The PostHog integration program is one ordered list of steps.\n * Other programs (e.g. revenue analytics) register a different step list.\n */\n/**\n * Context passed to onInit callbacks — fires when the TUI starts\n * rendering, before bin.ts has assigned the real session.\n */\nexport interface StoreInitContext {\n readonly session: WizardSession;\n readonly setReadinessResult: (result: WizardReadinessResult | null) => void;\n readonly setFrameworkContext: (key: string, value: unknown) => void;\n readonly emitChange: () => void;\n}\n\n/**\n * Context passed to onReady callbacks — fires after bin.ts has assigned\n * the real session, so reading `session.installDir` returns the target\n * project. Use for async pre-program work like prerequisite detection.\n */\nexport interface ProgramReadyContext {\n readonly session: WizardSession;\n readonly setFrameworkContext: (key: string, value: unknown) => void;\n\n // Detection-specific methods — used by core-integration's detect step\n readonly setFrameworkConfig: (\n integration: Integration,\n config: FrameworkConfig,\n ) => void;\n readonly setDetectedFramework: (label: string) => void;\n readonly setSkillId: (skillId: string | null) => void;\n readonly setUnsupportedVersion: (info: {\n current: string;\n minimum: string;\n docsUrl: string;\n }) => void;\n readonly addDiscoveredFeature: (feature: DiscoveredFeature) => void;\n readonly setDetectionComplete: () => void;\n}\n\nexport interface ProgramStep {\n /** Unique identifier for this step */\n id: string;\n\n /** Human-readable label for progress display */\n label: string;\n\n /**\n * TUI screen this step owns, if any.\n * Matches the ScreenId enum values (e.g. 'intro', 'run', 'outro').\n */\n screenId?: string;\n\n /**\n * Whether this step should be visible in the current program.\n * If omitted, the step is always visible.\n */\n show?: (session: WizardSession) => boolean;\n\n /**\n * Exit condition for the screen. Router advances when true.\n * Defaults to `gate` if unset.\n */\n isComplete?: (session: WizardSession) => boolean;\n\n /**\n * Define a gate if your screen needs to await user interactions.\n * bin.ts can `await store.getGate(stepId)` to pause until the\n * predicate becomes true.\n */\n gate?: (session: WizardSession) => boolean;\n\n /**\n * Called once when the TUI starts rendering, with the default\n * session. Use for session-independent fire-and-forget work that\n * should start as early as possible (e.g. health check kicked off\n * while the user is still reading the intro screen). Never fires for\n * a store that isn't rendering screens (tests, playground).\n */\n onInit?: (ctx: StoreInitContext) => void;\n\n /**\n * Called once after bin.ts has assigned the real session to the store,\n * before any gate is awaited. Awaited in sequence with other steps'\n * onReady callbacks. Use for session-dependent pre-program work like\n * scanning the installDir for prerequisites. May be sync or async.\n */\n onReady?: (ctx: ProgramReadyContext) => void | Promise<void>;\n}\n\n/**\n * Declares a program's place in the wizard CLI surface.\n *\n * Mirrors the `cli:` block in context-mill skill configs so wizard-native\n * programs and skill-backed programs share one vocabulary. Field names\n * match `ProgramConfig.command` / `parentCommand` above, so contributors\n * only learn one set of words.\n *\n * - `role: 'command'` — appears as a normal wizard command.\n * - `role: 'skill'` — reachable only via `wizard skill <id>`.\n * - `role: 'internal'` — hidden everywhere, only reachable via the\n * `--skill=<id>` dev escape hatch.\n *\n * Mapping table — declaration on the left, registered command on the right:\n *\n * { role: 'command', → wizard revenue-analytics\n * command: 'revenue-analytics' }\n *\n * { role: 'command', → wizard audit feature-flags\n * parentCommand: 'audit',\n * command: 'feature-flags' }\n *\n * { role: 'skill' } → wizard skill <id>\n *\n * `cli` only configures the command shape — the verbs the user types.\n * Flags and positional args (e.g. `--since=30d`) are configured on\n * `cliOptions`, not here.\n *\n * Naming rule: commands use the full PostHog product name with hyphens\n * (`revenue-analytics`, `feature-flags`, `session-replay`), not\n * abbreviations like `revenue` or `flags`.\n */\nexport interface ProgramCliSurface {\n /** Where the program appears in the wizard CLI surface. */\n role: 'command' | 'skill' | 'internal';\n /**\n * The user-typed word that registers this program (e.g. `'feature-flags'`\n * in `wizard audit feature-flags`, or `'revenue-analytics'` in\n * `wizard revenue-analytics`). Required when `role` is `'command'`.\n */\n command?: string;\n /**\n * The command this program nests under (e.g. `'audit'` for\n * `wizard audit feature-flags`). Omit for flat / standalone commands.\n */\n parentCommand?: string;\n}\n\n/**\n * Uniform configuration for a wizard program.\n *\n * Each program directory exports one of these. The system uses it\n * for CLI registration, sequence/step wiring, and skill bootstrap.\n */\nexport interface ProgramConfig {\n /** CLI command name (e.g. 'revenue-analytics'). Omit for the default program. */\n command?: string;\n /**\n * Parent CLI command to nest this program under. When set, the program is\n * registered as `<parentCommand> <command>` instead of as a top-level\n * command. The parent must itself be a registered subcommand program. Omit\n * for top-level programs.\n */\n parentCommand?: string;\n /** CLI description shown in --help */\n description: string;\n /** Unique program id — matches the Program enum value */\n id: string;\n /**\n * Whether this program's agent run requires third-party AI services.\n *\n * When true (the default), the wizard checks\n * `apiUser.organization.is_ai_data_processing_approved` after auth and\n * renders `AiOptInRequiredScreen` if the org has not opted in. Matches\n * Max's strict reading: only literal `true` proceeds.\n *\n * Opt out (set to `false`) for programs that don't run the agent —\n * doctor, mcp install/remove/tutorial, source-map upload. The safe\n * default is `true` so future programs gate by declaration.\n */\n requiresAi?: boolean;\n /**\n * Context-mill skill ID this program installs and runs. When present,\n * bin.ts seeds `session.skillId` with this value before the TUI renders\n * so intro screens can resolve skill metadata without waiting for the\n * agent run.\n */\n skillId?: string;\n /** The ordered step list */\n steps: ProgramStep[];\n /** Agent run config. Static object or async function for dynamic config. */\n run?: ProgramRun | ((session: WizardSession) => Promise<ProgramRun>);\n /**\n * CI-mode pre-run strategy. When set, runWizardCI awaits this after building\n * the ci:true session and before the agent runs, instead of walking step\n * onReady hooks. Use for headless prerequisite work (e.g. framework\n * detection) that the TUI performs via step onReady callbacks.\n */\n ciPreRun?: (session: WizardSession) => Promise<void>;\n /** Prerequisites: other program ids that must have run first */\n requires?: string[];\n /**\n * Path (relative to installDir) of the report file the program writes.\n * Mirrors `run.reportFile` but lifted to the top level so UI screens can\n * read it synchronously without resolving a deferred `run` function.\n */\n reportFile?: string;\n /**\n * LearnCard deck rendered in the shared `RunScreen` while the agent\n * runs. Lives at `<program>/content/index.tsx` by convention.\n * Programs that ship a custom RunScreen variant (audit) or skip the\n * run step (posthog-doctor) leave this unset.\n */\n getContentBlocks?: (store?: WizardStore) => ContentBlock[];\n /**\n * Tips shown in the run screen's right pane (the `Tips` sidebar) once\n * the LearnCard finishes. Lets a program supply its own explainer copy\n * (e.g. self-driving explaining what signal sources and scouts are)\n * instead of the generic onboarding deck. Unset → `RunScreen` falls back\n * to `DEFAULT_TIPS`, so every other program is unaffected. Lives at\n * `<program>/content/tips.ts` by convention.\n */\n getTips?: (store?: WizardStore) => Tip[];\n /**\n * Subcommand-specific CLI options. Spread into yargs `.options(...)` when the\n * program's subcommand is registered. Program-specific knowledge stays in\n * the program config, not in bin.ts. Typed as `unknown` to avoid pulling a\n * yargs dependency into this module.\n */\n cliOptions?: Record<string, unknown>;\n /**\n * Translate parsed CLI argv into extra options the runner consumes. Runs\n * after yargs validation, before runWizard/runWizardCI. Use this when a flag\n * needs to derive another field (e.g. `--product=statsig` → `skillId:\n * 'migrate-statsig'`).\n */\n mapCliOptions?: (argv: Record<string, unknown>) => Record<string, unknown>;\n /**\n * Extra tool names added on top of BASE_ALLOWED_TOOLS for this program's\n * agent run. Use for tools that only this program needs.\n */\n allowedTools?: readonly string[];\n /**\n * Tool names removed from BASE_ALLOWED_TOOLS for this program's agent\n * run. Use to forbid a base tool — e.g. `['Agent']` to block subagent\n * dispatch in a program whose steps are explicitly single-agent.\n */\n disallowedTools?: readonly string[];\n /**\n * Declares this program's place in the wizard CLI surface. See\n * `ProgramCliSurface` for semantics.\n */\n cli?: ProgramCliSurface;\n}\n\n/**\n * Project program steps into the narrower Screen shape the router consumes.\n *\n * Two things happen here:\n * 1. Headless steps (no `screenId`) are filtered out. The router walks\n * visible screens; gate-only steps like `detect` are store concerns.\n * 2. The step is narrowed to just { id, show, isComplete } — the\n * router has no business touching gate, onInit, or label.\n *\n * This intentional separation keeps the router focused on one question:\n * \"Which screen should be rendered right now?\"\n */\nexport function createProgramSequence(steps: ProgramStep[]): Array<{\n id: string;\n show?: (session: WizardSession) => boolean;\n isComplete?: (session: WizardSession) => boolean;\n}> {\n const entries = steps\n .filter((step) => step.screenId != null)\n .map((step) => ({\n id: step.screenId!,\n show: step.show,\n // `isComplete` defaults to `gate` — for most steps they're the same\n // predicate (e.g. intro: setupConfirmed unblocks bin.ts AND finishes\n // the screen). Only override when the two conditions diverge.\n isComplete: step.isComplete ?? step.gate,\n }));\n\n // Every program ends with the exit screen.\n entries.push({ id: 'exit', show: undefined, isComplete: undefined });\n\n return entries;\n}\n","/**\n * AI opt-in gate — step injection for programs whose agent run sends\n * source to Anthropic Claude.\n *\n * Injected after the `auth` step for every program that doesn't declare\n * `requiresAi: false`. The injected step carries three predicates:\n *\n * show — renders AiOptInRequiredScreen when the org hasn't\n * approved third-party AI\n * isComplete — router advances once approval lands\n * gate — `await store.getGate('ai-opt-in')` parks the agent\n * runner until approval lands. THIS is the enforcement:\n * the screen alone is cosmetic; the gate is what stops\n * source from leaving the machine.\n *\n * Used by BOTH screen-sequences.ts (screen injection) and the store's\n * _initFromProgram (gate registration) so the two layers can't drift.\n *\n * The predicates mirror Max's strict reading of\n * `organization.is_ai_data_processing_approved`: only literal `true`\n * proceeds; `null` / `undefined` / `false` all block. CI sessions skip\n * the gate — `--ci` auto-consents to AI usage per the README, and the\n * interactive kill screen would be unworkable headless.\n */\n\nimport type { WizardSession } from '@lib/wizard-session';\nimport type { ProgramConfig, ProgramStep } from './program-step.js';\n\n/** Step id — also the ScreenId.AiOptIn enum value in screen-sequences. */\nexport const AI_OPT_IN_STEP_ID = 'ai-opt-in';\n\nfunction aiApproved(session: WizardSession): boolean {\n return !!session.apiUser?.organization?.is_ai_data_processing_approved;\n}\n\n/**\n * Returns the program's steps with the AI opt-in gate injected after\n * `auth`. Programs with `requiresAi: false` or no auth step pass\n * through unchanged — without auth, `apiUser` would never be populated\n * for evaluation anyway.\n */\nexport function withAiOptInGate(config: ProgramConfig): ProgramStep[] {\n if (config.requiresAi === false) return config.steps;\n\n const authIdx = config.steps.findIndex((s) => s.id === 'auth');\n if (authIdx === -1) return config.steps;\n\n const gateStep: ProgramStep = {\n id: AI_OPT_IN_STEP_ID,\n label: 'AI opt-in check',\n screenId: AI_OPT_IN_STEP_ID,\n // Only fire once apiUser has actually been populated — between\n // setCredentials and setApiUser there's a brief emitChange window\n // where apiUser is null, and we don't want to flash the gate then.\n show: (session) =>\n !session.ci && session.apiUser != null && !aiApproved(session),\n isComplete: (session) => session.ci || aiApproved(session),\n gate: (session) => session.ci || aiApproved(session),\n };\n\n return [\n ...config.steps.slice(0, authIdx + 1),\n gateStep,\n ...config.steps.slice(authIdx + 1),\n ];\n}\n","/**\n * Screen taxonomy + per-program screen sequences.\n *\n * Owns the ScreenId enum and projects each registered program's steps\n * into the router-shaped screen sequence (filtering headless steps and\n * appending the exit screen). Pure leaf module — no store, no React.\n */\n\nimport type { WizardSession } from '@lib/wizard-session';\nimport {\n PROGRAM_REGISTRY,\n type ProgramId,\n} from '@lib/programs/program-registry';\nimport { createProgramSequence } from '@lib/programs/program-step';\nimport { withAiOptInGate } from '@lib/programs/ai-opt-in-gate';\n\n/** Screens that participate in linear programs. */\nexport enum ScreenId {\n Intro = 'intro',\n RevenueIntro = 'revenue-intro',\n WarehouseIntro = 'warehouse-intro',\n SourceMapsIntro = 'source-maps-intro',\n SourceMapsDetect = 'source-maps-detect',\n SourceMapsOutro = 'source-maps-outro',\n MigrationIntro = 'migration-intro',\n AgentSkillIntro = 'agent-skill-intro',\n SelfDrivingIntro = 'self-driving-intro',\n AuditIntro = 'audit-intro',\n AuditRun = 'audit-run',\n AuditOutro = 'audit-outro',\n HealthCheck = 'health-check',\n DoctorIntro = 'doctor-intro',\n DoctorReport = 'doctor-report',\n Setup = 'setup',\n Auth = 'auth',\n Run = 'run',\n Mcp = 'mcp',\n McpSuggestedPrompts = 'mcp-suggested-prompts',\n SlackConnect = 'slack-connect',\n KeepSkills = 'keep-skills',\n Outro = 'outro',\n Exit = 'exit',\n McpAdd = 'mcp-add',\n McpRemove = 'mcp-remove',\n AiOptIn = 'ai-opt-in',\n}\n\nexport interface Screen {\n /** ScreenId to show */\n id: ScreenId;\n /** If provided, screen is skipped when this returns false. Omit = always show. */\n show?: (session: WizardSession) => boolean;\n /** If provided, screen is considered complete when this returns true. */\n isComplete?: (session: WizardSession) => boolean;\n}\n\n/** An ordered list of screens — a program's screen journey. */\nexport type Sequence = Screen[];\n\n/** All program screen sequences keyed by program id. */\nexport const PROGRAM_SEQUENCES: Record<ProgramId, Sequence> =\n Object.fromEntries(\n PROGRAM_REGISTRY.map((c) => [\n c.id,\n createProgramSequence(withAiOptInGate(c)) as Sequence,\n ]),\n ) as Record<ProgramId, Sequence>;\n","/**\n * WizardRouter — declarative program pipelines + overlay stack.\n *\n * Two layers:\n * Program cursor — linear sequence of screens, advanced with next()\n * Overlay stack — interrupts (outage, auth-expired, etc.) that push/pop\n *\n * The visible screen is: top of overlay stack if non-empty, otherwise the program cursor.\n *\n * Adding a program screen = append to a sequence array.\n * Adding an overlay = call pushOverlay() from anywhere.\n * No switch statements, no hardcoded transitions in business logic.\n */\n\nimport type { WizardSession } from '@lib/wizard-session';\nimport { Program, type ProgramId } from '@lib/programs/program-registry';\nimport {\n PROGRAM_SEQUENCES,\n ScreenId,\n type Screen,\n type Sequence,\n} from './screen-sequences.js';\n\n// Re-export so existing imports from './router.js' keep working\nexport { ScreenId, Program };\nexport type { Screen, Sequence, ProgramId };\n\n// ── ScreenId name taxonomy ──────────────────────────────────────────────\n\n/** Screens that interrupt programs as overlays */\nexport enum Overlay {\n SettingsOverride = 'settings-override',\n ManagedSettings = 'managed-settings',\n PortConflict = 'port-conflict',\n ManualAuthCode = 'manual-auth-code',\n AuthError = 'auth-error',\n SessionTimeout = 'session-timeout',\n WizardAsk = 'wizard-ask',\n}\n\n/** Union of all screen names */\nexport type ScreenName = ScreenId | Overlay;\n\n// ── Router ────────────────────────────────────────────────────────────\n\nexport class WizardRouter {\n private sequence: Sequence;\n private programId: ProgramId;\n private overlays: Overlay[] = [];\n\n constructor(programId: ProgramId = Program.PostHogIntegration) {\n this.programId = programId;\n this.sequence = PROGRAM_SEQUENCES[programId];\n }\n\n /**\n * Resolve which screen should be active based on session state.\n * Walks the program sequence, skipping hidden entries and completed entries,\n * returns the first incomplete screen.\n */\n resolve(session: WizardSession): ScreenName {\n if (this.overlays.length > 0) {\n return this.overlays[this.overlays.length - 1];\n }\n\n for (const entry of this.sequence) {\n if (entry.show && !entry.show(session)) continue;\n if (entry.isComplete && entry.isComplete(session)) continue;\n return entry.id;\n }\n\n // All entries complete — show the last screen (outro)\n return this.sequence[this.sequence.length - 1].id;\n }\n\n /** The screen that should be rendered right now. */\n get activeScreen(): ScreenName {\n // Overlays take priority — resolve() handles this too,\n // but activeScreen is called before session is available in some paths\n if (this.overlays.length > 0) {\n return this.overlays[this.overlays.length - 1];\n }\n return this.sequence[0].id;\n }\n\n /** The id of the active program. */\n get activeProgram(): ProgramId {\n return this.programId;\n }\n\n /** Whether an overlay is currently active. */\n get hasOverlay(): boolean {\n return this.overlays.length > 0;\n }\n\n /**\n * Push an overlay that interrupts the current program.\n * The program resumes when the overlay is dismissed via popOverlay().\n */\n pushOverlay(overlay: Overlay): void {\n this.overlays.push(overlay);\n }\n\n /**\n * Dismiss the topmost overlay. The program screen underneath resumes.\n */\n popOverlay(): void {\n this.overlays.pop();\n }\n\n /**\n * Direction hint for screen transitions.\n */\n private _lastDirection: 'push' | 'pop' | null = null;\n\n get lastNavDirection(): 'push' | 'pop' | null {\n return this._lastDirection;\n }\n\n /** @internal — called by store wrapper to track direction */\n _setDirection(dir: 'push' | 'pop' | null): void {\n this._lastDirection = dir;\n }\n}\n","/**\n * WizardStore — Nanostore-backed reactive store for the TUI.\n * React components subscribe via useSyncExternalStore.\n *\n * The active screen is derived from session state — WizardRouter walks\n * the flow and shows the first step whose `isComplete` is still false.\n *\n * Define a step `gate` if your screen needs to await user interactions.\n * bin.ts calls `await store.getGate(stepId)` to pause until the gate\n * predicate becomes true.\n *\n * All session mutations that affect screen resolution go through\n * explicit setters so emitChange() is always called.\n */\n\nimport { atom, map } from 'nanostores';\nimport { logToFile } from '@utils/debug';\nimport { TaskStatus, isTaskStatus, type AuthErrorDetail } from '@ui/wizard-ui';\nimport {\n type WizardSession,\n type OutroData,\n type DiscoveredFeature,\n type PendingQuestion,\n type AskAnswers,\n AdditionalFeature,\n McpOutcome,\n RunPhase,\n buildSession,\n} from '@lib/wizard-session';\nimport type { SettingsConflict } from '@lib/agent/claude-settings';\nimport {\n WizardReadiness,\n getBlockingServiceKeys,\n type WizardReadinessResult,\n} from '@lib/health-checks/readiness';\nimport { ServiceHealthStatus } from '@lib/health-checks/types';\nimport {\n WizardRouter,\n type ScreenName,\n ScreenId,\n Overlay,\n Program,\n type ProgramId,\n} from './router.js';\nimport { analytics, sessionProperties } from '@utils/analytics';\nimport type {\n StoreInitContext,\n ProgramReadyContext,\n} from '@lib/programs/program-step';\nimport { getProgramConfig } from '@lib/programs/program-registry';\nimport { withAiOptInGate } from '@lib/programs/ai-opt-in-gate';\nimport { EXPANDED_COUNT } from '@ui/tui/constants';\n\nexport { TaskStatus, ScreenId, Overlay, Program, RunPhase, McpOutcome };\nexport type { ScreenName, OutroData, WizardSession, ProgramId };\n\nexport interface TaskItem {\n label: string;\n activeForm?: string;\n status: TaskStatus;\n /** Legacy compat */\n done: boolean;\n}\n\nexport interface PlannedEvent {\n name: string;\n description: string;\n}\n\ninterface GateEntry {\n predicate: (session: WizardSession) => boolean;\n promise: Promise<void>;\n resolve: () => void;\n resolved: boolean;\n}\n\n/**\n * FIFO cap on retained status lines. The status bar is the only consumer and\n * renders at most EXPANDED_COUNT lines, so there is no reason to retain more —\n * the cap is tied to the window it feeds.\n */\nconst MAX_STATUS_MESSAGES = EXPANDED_COUNT;\n\n/**\n * Fired once per blocked readiness result, so we can quantify how often\n * the wizard refuses to start and — crucially — split that between\n * confirmed PostHog outages and probe-level reachability failures that\n * are most likely the user's network. Helps us decide whether the\n * health-check UX is over-firing.\n */\nfunction captureHealthCheckBlocked(result: WizardReadinessResult): void {\n try {\n const health = result.health;\n const blockingKeys = getBlockingServiceKeys(health);\n const blockingStatuses = blockingKeys.map((k) => health[k]?.status);\n\n const allNoConnection =\n blockingStatuses.length > 0 &&\n blockingStatuses.every((s) => s === ServiceHealthStatus.NoConnection);\n const onlyGithubReleases =\n blockingKeys.length === 1 && blockingKeys[0] === 'githubReleases';\n\n const decision = onlyGithubReleases\n ? 'github-releases-down'\n : allNoConnection\n ? 'no-connection'\n : 'confirmed-outage';\n\n const posthogStatus = health.posthogOverall?.status;\n const retriesUsed = Math.max(\n 0,\n ...(['llmGateway', 'mcp', 'githubReleases'] as const).map((k) => {\n const ind = health[k]?.rawIndicator ?? '';\n const m = ind.match(/attempts=(\\d+)/);\n return m ? Number(m[1]) - 1 : 0;\n }),\n );\n\n analytics.wizardCapture('health check blocked', {\n decision,\n blocking_keys: blockingKeys,\n posthog_status_reachable:\n posthogStatus !== ServiceHealthStatus.NoConnection,\n posthog_status_reports_incident:\n posthogStatus === ServiceHealthStatus.Down ||\n posthogStatus === ServiceHealthStatus.Degraded,\n retries_used: retriesUsed,\n });\n } catch (err) {\n logToFile(\n `[health-checks] failed to capture analytics: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n}\n\nexport class WizardStore {\n // ── Internal nanostore atoms ─────────────────────────────────────\n private $session = map<WizardSession>(buildSession({}));\n private $statusMessages = atom<string[]>([]);\n private $statusExpanded = atom(false);\n private $tasks = atom<TaskItem[]>([]);\n private $eventPlan = atom<PlannedEvent[]>([]);\n private $learnCardBlockIdx = atom(0);\n private $learnCardComplete = atom(false);\n private $version = atom(0);\n private $currentStage = atom<{ stage: string; startedAt: number } | null>(\n null,\n );\n\n private _onTasksChanged: (() => void) | null = null;\n /** Last screen seen — used to detect screen transitions for analytics. */\n private _lastScreen: ScreenName | null = null;\n\n /** Hooks run when transitioning onto a screen. */\n private _enterScreenHooks = new Map<ScreenName, (() => void)[]>();\n\n /** Gate promises derived from program step definitions. */\n private _gates = new Map<string, GateEntry>();\n\n version = '';\n\n /** Navigation router — resolves active screen from session state. */\n readonly router: WizardRouter;\n\n /** Blocks agent execution until the settings-override overlay is dismissed. */\n private _resolveSettingsOverride: (() => void) | null = null;\n private _backupAndFixSettings: (() => boolean) | null = null;\n\n /** Blocks OAuth flow until the port-conflict overlay is dismissed. */\n private _resolvePortConflict: (() => void) | null = null;\n\n /** Resolves the OAuth flow with a manually-entered authorization code. */\n private _resolveManualAuthCode: ((code: string) => void) | null = null;\n\n /** Resolves the in-flight wizard_ask request. */\n private _resolvePendingQuestion: ((answers: AskAnswers) => void) | null =\n null;\n\n constructor(program: ProgramId = Program.PostHogIntegration) {\n this.router = new WizardRouter(program);\n this._initFromProgram(program);\n }\n\n /**\n * Scan program steps for gate predicates and create gate promises.\n *\n * Steps are wrapped with withAiOptInGate so the injected ai-opt-in\n * step's gate registers here — the agent runner awaits it (via\n * WizardUI.waitForAiOptIn) before any source leaves the machine.\n * Same wrapper screen-sequences.ts uses, so the gate and its screen\n * can't drift apart.\n */\n private _initFromProgram(program: ProgramId): void {\n const steps = withAiOptInGate(getProgramConfig(program));\n\n // Create gate promises from steps that define them\n for (const step of steps) {\n if (step.gate) {\n let resolve!: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n this._gates.set(step.id, {\n predicate: step.gate,\n promise,\n resolve,\n resolved: false,\n });\n }\n }\n }\n\n /**\n * Run the program steps' onInit callbacks. startTUI calls this once\n * the screens are actually rendering — constructing a store alone\n * (tests, playground) must not fire init work like the health-check\n * pre-flight, whose probes belong only to flows that show its screen.\n */\n runInitHooks(): void {\n const steps = getProgramConfig(this.router.activeProgram).steps;\n const getSession = (): WizardSession => this.session;\n const ctx: StoreInitContext = {\n get session() {\n return getSession();\n },\n setReadinessResult: (r) => this.setReadinessResult(r),\n setFrameworkContext: (k, v) => this.setFrameworkContext(k, v),\n emitChange: () => this.emitChange(),\n };\n for (const step of steps) {\n step.onInit?.(ctx);\n }\n }\n\n /**\n * Run all `onReady` hooks declared by the current flow's steps, in\n * order. Must be called after `store.session = session` so hooks see\n * the real installDir. bin.ts calls this generically — it doesn't\n * need to know which program has which pre-flow work.\n */\n async runReadyHooks(): Promise<void> {\n const steps = getProgramConfig(this.router.activeProgram).steps;\n const ctx: ProgramReadyContext = {\n session: this.session,\n setFrameworkContext: (k, v) => this.setFrameworkContext(k, v),\n setFrameworkConfig: (i, c) => this.setFrameworkConfig(i, c),\n setDetectedFramework: (l) => this.setDetectedFramework(l),\n setSkillId: (id) => this.setSkillId(id),\n setUnsupportedVersion: (info) => this.setUnsupportedVersion(info),\n addDiscoveredFeature: (f) => this.addDiscoveredFeature(f),\n setDetectionComplete: () => this.setDetectionComplete(),\n };\n for (const step of steps) {\n if (step.onReady) {\n await step.onReady(ctx);\n }\n }\n }\n\n // ── Gate API ────────────────────────────────────────────────────\n\n /**\n * Get a gate promise by step ID — the primary blocking checkpoint API\n * for bin.ts. `await store.getGate('...')` parks the caller until the\n * corresponding program step's gate predicate flips to true (if the\n * predicate stays false, the caller stays parked indefinitely — the\n * TUI keeps rendering so the user can resolve whatever is blocking).\n *\n * If the program doesn't define a step with this ID, or the step\n * has no `gate` predicate, this returns an already-resolved promise\n * so bin.ts flows straight through. This lets programs opt in to\n * gates on a per-step basis without bin.ts needing to know which\n * gates exist in which flow.\n */\n getGate(stepId: string): Promise<void> {\n return this._gates.get(stepId)?.promise ?? Promise.resolve();\n }\n\n /**\n * Re-evaluate every gate predicate against the current session and\n * resolve any whose predicate now returns true. Called after every\n * emitChange(), so gates unblock as soon as the session mutation\n * that satisfies them lands. Gates only resolve once — a predicate\n * that goes true → false → true will NOT re-block a caller that\n * already awaited through.\n */\n private _checkGates(): void {\n for (const [, gate] of this._gates) {\n if (!gate.resolved && gate.predicate(this.session)) {\n gate.resolved = true;\n gate.resolve();\n }\n }\n }\n\n // ── State accessors (read from atoms) ────────────────────────────\n\n get session(): WizardSession {\n return this.$session.get();\n }\n\n set session(value: WizardSession) {\n this.$session.set(value);\n this.emitChange();\n }\n\n get statusMessages(): string[] {\n return this.$statusMessages.get();\n }\n\n get tasks(): TaskItem[] {\n return this.$tasks.get();\n }\n\n get eventPlan(): PlannedEvent[] {\n return this.$eventPlan.get();\n }\n\n get currentStage(): { stage: string; startedAt: number } | null {\n return this.$currentStage.get();\n }\n\n /** No-op when the stage hasn't changed, so `startedAt` survives across\n * re-renders and tab switches and measures real stage time. */\n setCurrentStage(stage: string): void {\n const cur = this.$currentStage.get();\n if (cur?.stage === stage) return;\n this.$currentStage.set({ stage, startedAt: Date.now() });\n this.emitChange();\n }\n\n get statusExpanded(): boolean {\n return this.$statusExpanded.get();\n }\n\n toggleStatusExpanded(): void {\n this.$statusExpanded.set(!this.$statusExpanded.get());\n this.emitChange();\n }\n\n setStatusExpanded(expanded: boolean): void {\n if (this.$statusExpanded.get() !== expanded) {\n this.$statusExpanded.set(expanded);\n this.emitChange();\n }\n }\n\n // ── Session setters ─────────────────────────────────────────────\n // Every setter that affects screen resolution calls emitChange().\n // Business logic calls these instead of mutating session directly.\n\n /** Sets setupConfirmed. Gate resolves via _checkGates(). */\n completeSetup(): void {\n this.$session.setKey('setupConfirmed', true);\n analytics.wizardCapture('setup confirmed', sessionProperties(this.session));\n this.emitChange();\n }\n\n setRunPhase(phase: RunPhase): void {\n this.$session.setKey('runPhase', phase);\n this.emitChange();\n }\n\n setCredentials(credentials: WizardSession['credentials']): void {\n this.$session.setKey('credentials', credentials);\n if (credentials?.projectId) {\n analytics.setTag('project_id', credentials.projectId);\n }\n analytics.wizardCapture('auth complete', {\n project_id: credentials?.projectId,\n });\n this.emitChange();\n }\n\n setRoleAtOrganization(role: string | null): void {\n this.$session.setKey('roleAtOrganization', role);\n this.emitChange();\n }\n\n setApiUser(user: WizardSession['apiUser']): void {\n this.$session.setKey('apiUser', user);\n this.emitChange();\n }\n\n setFrameworkConfig(\n integration: WizardSession['integration'],\n config: WizardSession['frameworkConfig'],\n ): void {\n this.$session.setKey('integration', integration);\n this.$session.setKey('frameworkConfig', config);\n this.$session.setKey('unsupportedVersion', null);\n if (integration) analytics.setTag('integration', integration);\n this.emitChange();\n }\n\n setDetectionComplete(): void {\n this.$session.setKey('detectionComplete', true);\n this.emitChange();\n }\n\n setDetectedFramework(label: string): void {\n this.$session.setKey('detectedFrameworkLabel', label);\n analytics.setTag('detected_framework', label);\n this.emitChange();\n }\n\n setSkillId(skillId: string | null): void {\n this.$session.setKey('skillId', skillId);\n this.emitChange();\n }\n\n setUnsupportedVersion(info: {\n current: string;\n minimum: string;\n docsUrl: string;\n }): void {\n this.$session.setKey('unsupportedVersion', info);\n this.emitChange();\n }\n\n setLoginUrl(url: string | null): void {\n this.$session.setKey('loginUrl', url);\n this.emitChange();\n }\n\n setAuthorizeUrl(url: string | null): void {\n this.$session.setKey('authorizeUrl', url);\n this.emitChange();\n }\n\n setReadinessResult(result: WizardReadinessResult | null): void {\n this.$session.setKey('readinessResult', result);\n if (result && result.decision === WizardReadiness.No) {\n captureHealthCheckBlocked(result);\n }\n this.emitChange();\n }\n\n /** User dismissed the blocking outage screen. Gate resolves via _checkGates(). */\n dismissOutage(): void {\n logToFile('[health-checks] user dismissed outage screen, continuing');\n this.$session.setKey('outageDismissed', true);\n this.emitChange();\n }\n\n /**\n * Push the settings-override overlay and return a promise that blocks\n * until the user dismisses it via backupAndFixSettingsOverride().\n */\n showSettingsOverride(\n conflicts: SettingsConflict[],\n backupAndFix: () => boolean,\n ): Promise<void> {\n const allKeys = conflicts.flatMap((c) => c.keys);\n this.$session.setKey('settingsOverrideKeys', allKeys);\n this.$session.setKey('settingsConflicts', conflicts);\n this._backupAndFixSettings = backupAndFix;\n\n const hasReadOnly = conflicts.some((c) => !c.writable);\n if (hasReadOnly) {\n this.pushOverlay(Overlay.ManagedSettings);\n } else {\n this.pushOverlay(Overlay.SettingsOverride);\n }\n\n return new Promise((resolve) => {\n this._resolveSettingsOverride = resolve;\n });\n }\n\n /**\n * Push the port-conflict overlay and return a promise that blocks\n * until the user frees the ports and retries, or exits.\n */\n showPortConflict(processInfo: {\n command: string;\n pid: string;\n port: number;\n user: string;\n }): Promise<void> {\n this.$session.setKey('portConflictProcess', processInfo);\n this.pushOverlay(Overlay.PortConflict);\n return new Promise((resolve) => {\n this._resolvePortConflict = resolve;\n });\n }\n\n /** Dismiss the port-conflict overlay and retry the OAuth port loop. */\n resolvePortConflict(): void {\n this.$session.setKey('portConflictProcess', null);\n this.popOverlay();\n this._resolvePortConflict?.();\n this._resolvePortConflict = null;\n }\n\n /**\n * Return a promise that resolves when the user submits a manually-entered\n * OAuth code via the paste modal. The OAuth flow races this against the\n * local callback server — see `performOAuthFlow`.\n */\n waitForManualAuthCode(): Promise<string> {\n return new Promise<string>((resolve) => {\n this._resolveManualAuthCode = resolve;\n });\n }\n\n /** Open the manual OAuth code-entry overlay over the auth screen. */\n showManualAuthCode(): void {\n this.pushOverlay(Overlay.ManualAuthCode);\n }\n\n /** Dismiss the manual OAuth code overlay without submitting. */\n dismissManualAuthCode(): void {\n this.popOverlay();\n }\n\n /**\n * Submit a manually-entered authorization code: dismiss the overlay and\n * resolve the in-flight OAuth flow so it can exchange the code for a token.\n */\n submitManualAuthCode(code: string): void {\n this.popOverlay();\n this._resolveManualAuthCode?.(code);\n this._resolveManualAuthCode = null;\n }\n\n /**\n * Open the WizardAsk overlay with a set of questions and return a promise\n * that resolves once the user submits answers (or the request is cancelled).\n *\n * Only one request is in flight at a time — calling this while a request\n * is already pending throws.\n */\n requestQuestion(question: PendingQuestion): Promise<AskAnswers> {\n if (this._resolvePendingQuestion) {\n throw new Error(\n 'requestQuestion called while another wizard_ask request is pending',\n );\n }\n this.$session.setKey('pendingQuestion', question);\n this.pushOverlay(Overlay.WizardAsk);\n analytics.wizardCapture('wizard_ask shown', {\n source: question.source,\n question_count: question.questions.length,\n kinds: question.questions.map((q) => q.kind),\n });\n return new Promise<AskAnswers>((resolve) => {\n this._resolvePendingQuestion = resolve;\n });\n }\n\n /**\n * Resolve the in-flight wizard_ask request with the user's answers and\n * dismiss the overlay. Answers flow back to the agent as the tool result.\n */\n resolvePendingQuestion(answers: AskAnswers): void {\n const resolve = this._resolvePendingQuestion;\n this._resolvePendingQuestion = null;\n this.$session.setKey('pendingQuestion', null);\n this.popOverlay();\n resolve?.(answers);\n }\n\n /**\n * Cancel the in-flight wizard_ask request — the bridge sends a sentinel\n * answer (\"__cancelled__\") so the skill can decide how to handle it.\n */\n cancelPendingQuestion(): void {\n const pending = this.session.pendingQuestion;\n if (!pending) return;\n const cancelled: AskAnswers = {};\n for (const q of pending.questions) {\n cancelled[q.id] = '__cancelled__';\n }\n this.resolvePendingQuestion(cancelled);\n }\n\n /**\n * Back up .claude/settings.json. Dismisses the overlay on success.\n */\n backupAndFixSettingsOverride(): boolean {\n const ok = this._backupAndFixSettings?.() ?? false;\n if (ok) {\n this.$session.setKey('settingsOverrideKeys', null);\n this.$session.setKey('settingsConflicts', null);\n this.popOverlay();\n this._resolveSettingsOverride?.();\n this._resolveSettingsOverride = null;\n this._backupAndFixSettings = null;\n }\n return ok;\n }\n\n /** Push the auth-error overlay (no dismiss — user must exit). */\n showAuthError(detail?: AuthErrorDetail): void {\n this.$session.setKey('authErrorDetail', detail ?? null);\n this.pushOverlay(Overlay.AuthError);\n }\n\n /** Push the session-timeout overlay (no dismiss — user must exit). */\n showSessionTimeout(): void {\n this.pushOverlay(Overlay.SessionTimeout);\n }\n\n addDiscoveredFeature(feature: DiscoveredFeature): void {\n if (!this.session.discoveredFeatures.includes(feature)) {\n this.session.discoveredFeatures.push(feature);\n this.emitChange();\n }\n }\n\n /**\n * Enable an additional feature: enqueue it for the stop hook\n * and set any feature-specific session flags.\n */\n enableFeature(feature: AdditionalFeature): void {\n if (!this.session.additionalFeatureQueue.includes(feature)) {\n this.session.additionalFeatureQueue.push(feature);\n }\n // Feature-specific flags\n if (feature === AdditionalFeature.LLM) {\n this.session.llmOptIn = true;\n }\n analytics.wizardCapture('feature enabled', { feature });\n this.emitChange();\n }\n\n setMcpComplete(\n outcome: McpOutcome = McpOutcome.Skipped,\n installedClients: string[] = [],\n featuresSelected?: 'all' | string[],\n ): void {\n this.$session.setKey('mcpComplete', true);\n this.$session.setKey('mcpOutcome', outcome);\n this.$session.setKey('mcpInstalledClients', installedClients);\n const featuresPayload =\n outcome === McpOutcome.Installed && featuresSelected !== undefined\n ? { mcp_features_selected: featuresSelected }\n : {};\n analytics.wizardCapture('mcp complete', {\n mcp_outcome: outcome,\n mcp_installed_clients: installedClients,\n ...featuresPayload,\n ...sessionProperties(this.session),\n });\n this.emitChange();\n }\n\n setSkillsComplete(kept: boolean): void {\n this.$session.setKey('skillsComplete', true);\n analytics.wizardCapture('skills complete', {\n skills_kept: kept,\n ...sessionProperties(this.session),\n });\n this.emitChange();\n }\n\n setMcpSuggestedPromptsDismissed(): void {\n this.$session.setKey('mcpSuggestedPromptsDismissed', true);\n this.emitChange();\n }\n\n setSlackStepDismissed(): void {\n this.$session.setKey('slackStepDismissed', true);\n this.emitChange();\n }\n\n setSlackConnected(connected: boolean): void {\n this.$session.setKey('slackConnected', connected);\n this.emitChange();\n }\n\n setOutroDismissed(): void {\n this.$session.setKey('outroDismissed', true);\n this.emitChange();\n }\n\n setOutroData(data: OutroData): void {\n this.$session.setKey('outroData', data);\n this.emitChange();\n }\n\n setDashboardUrl(url: string): void {\n logToFile(`store.setDashboardUrl: ${url}`);\n this.$session.setKey('dashboardUrl', url);\n this.emitChange();\n }\n\n setNotebookUrl(url: string): void {\n logToFile(`store.setNotebookUrl: ${url}`);\n this.$session.setKey('notebookUrl', url);\n this.emitChange();\n }\n\n setFrameworkContext(key: string, value: unknown): void {\n const ctx = { ...this.$session.get().frameworkContext, [key]: value };\n this.$session.setKey('frameworkContext', ctx);\n this.emitChange();\n }\n\n // ── Derived state ───────────────────────────────────────────────\n\n /**\n * The screen that should be rendered right now.\n * Derived from session state via the router.\n */\n get currentScreen(): ScreenName {\n return this.router.resolve(this.session);\n }\n\n /** Direction hint for screen transitions. */\n get lastNavDirection(): 'push' | 'pop' | null {\n return this.router.lastNavDirection;\n }\n\n // ── Change notification ─────────────────────────────────────────\n\n getVersion(): number {\n return this.$version.get();\n }\n\n /**\n * Notify React that state has changed.\n * The router re-resolves the active screen on next render.\n * Gate predicates are checked and resolved if ready.\n */\n emitChange(): void {\n this.router._setDirection('push');\n this.$version.set(this.$version.get() + 1);\n this._checkGates();\n this._detectTransition();\n }\n\n // ── Overlay navigation ──────────────────────────────────────────\n\n pushOverlay(overlay: Overlay): void {\n this.router._setDirection('push');\n this.router.pushOverlay(overlay);\n this.$version.set(this.$version.get() + 1);\n this._detectTransition();\n }\n\n popOverlay(): void {\n this.router._setDirection('pop');\n this.router.popOverlay();\n this.$version.set(this.$version.get() + 1);\n this._detectTransition();\n }\n\n // ── ScreenId transition analytics ─────────────────────────────────\n\n /**\n * Register a callback to run when transitioning onto the given screen.\n * Fires after every transition that lands on this screen.\n */\n onEnterScreen(screen: ScreenName, fn: () => void): void {\n const list = this._enterScreenHooks.get(screen) ?? [];\n list.push(fn);\n this._enterScreenHooks.set(screen, list);\n }\n\n /**\n * Detect screen transitions, run enter-screen hooks, and fire analytics.\n * Called at the end of emitChange/pushOverlay/popOverlay.\n */\n private _detectTransition(): void {\n const next = this.router.resolve(this.session);\n const prev = this._lastScreen;\n if (next !== prev) {\n // Every event carries the active TUI screen, filling the\n // \"URL / Screen\" column in PostHog.\n analytics.setTag('$screen_name', next);\n }\n if (prev !== null && next !== prev) {\n const hooks = this._enterScreenHooks.get(next);\n if (hooks) {\n for (const fn of hooks) fn();\n }\n analytics.wizardCapture(`screen ${next}`, {\n from_screen: prev,\n program_id: this.router.activeProgram,\n ...sessionProperties(this.session),\n });\n }\n this._lastScreen = next;\n }\n\n // ── Agent observation state ─────────────────────────────────────\n\n pushStatus(message: string): void {\n const msgs = this.$statusMessages.get();\n // Skip consecutive duplicate messages (no allocation on the hot path)\n if (msgs.length > 0 && msgs[msgs.length - 1] === message) return;\n // Nanostore detects change by reference equality, so a new array is\n // required. At the cap, allocate exactly once at the final size (dropping\n // the oldest entry) rather than push-then-truncate.\n const next =\n msgs.length >= MAX_STATUS_MESSAGES\n ? [...msgs.slice(msgs.length - MAX_STATUS_MESSAGES + 1), message]\n : [...msgs, message];\n this.$statusMessages.set(next);\n this.emitChange();\n }\n\n setTasks(tasks: TaskItem[]): void {\n this.$tasks.set(tasks);\n this.emitChange();\n }\n\n updateTask(index: number, done: boolean): void {\n const tasks = this.$tasks.get();\n if (tasks[index]) {\n const updated = [...tasks];\n updated[index] = {\n ...updated[index],\n done,\n status: done ? TaskStatus.Completed : TaskStatus.Pending,\n };\n this.$tasks.set(updated);\n this.emitChange();\n }\n }\n\n setEventPlan(events: PlannedEvent[]): void {\n this.$eventPlan.set(events);\n this.emitChange();\n }\n\n get learnCardBlockIdx(): number {\n return this.$learnCardBlockIdx.get();\n }\n\n setLearnCardBlockIdx(idx: number): void {\n this.$learnCardBlockIdx.set(idx);\n }\n\n get learnCardComplete(): boolean {\n return this.$learnCardComplete.get();\n }\n\n setLearnCardComplete(): void {\n this.$learnCardComplete.set(true);\n this.emitChange();\n }\n\n syncTodos(\n todos: Array<{ content: string; status: string; activeForm?: string }>,\n ): void {\n const incoming = todos.map((t) => {\n const status = isTaskStatus(t.status) ? t.status : TaskStatus.Pending;\n return {\n label: t.content,\n activeForm: t.activeForm,\n status,\n done: status === TaskStatus.Completed,\n };\n });\n\n const incomingLabels = new Set(incoming.map((t) => t.label));\n\n const retained = this.$tasks\n .get()\n .filter((t) => t.done && !incomingLabels.has(t.label));\n\n this.$tasks.set([...retained, ...incoming]);\n this.emitChange();\n this._onTasksChanged?.();\n }\n\n /** Register a listener for task state changes (e.g. task stream push). */\n set onTasksChanged(fn: () => void) {\n this._onTasksChanged = fn;\n }\n\n // ── React integration ───────────────────────────────────────────\n\n subscribe(callback: () => void): () => void {\n return this.$version.listen(() => callback());\n }\n\n getSnapshot(): number {\n return this.$version.get();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"store-J2Rv955W.js","names":[],"sources":["../src/lib/programs/program-step.ts","../src/lib/programs/ai-opt-in-gate.ts","../src/ui/tui/screen-sequences.ts","../src/ui/tui/router.ts","../src/ui/tui/store.ts"],"sourcesContent":["import type { WizardSession, DiscoveredFeature } from '@lib/wizard-session';\nimport type { WizardReadinessResult } from '@lib/health-checks/readiness';\nimport type { ProgramRun } from '@lib/agent/agent-runner';\nimport type { Integration } from '@lib/constants';\nimport type { FrameworkConfig } from '@lib/framework-config';\nimport type { ContentBlock } from '@ui/tui/primitives/index';\nimport type { WizardStore } from '@ui/tui/store';\nimport type { Tip } from '@ui/tui/components/TipsCard';\n\n/**\n * A program step is the primary unit of the wizard's execution model.\n *\n * It can own:\n * - a screen in the TUI (optional — some steps are headless)\n * - agent work via a program reference (optional — some steps are UI-only)\n * - completion and visibility predicates\n *\n * The PostHog integration program is one ordered list of steps.\n * Other programs (e.g. revenue analytics) register a different step list.\n */\n/**\n * Context passed to onInit callbacks — fires when the TUI starts\n * rendering, before bin.ts has assigned the real session.\n */\nexport interface StoreInitContext {\n readonly session: WizardSession;\n readonly setReadinessResult: (result: WizardReadinessResult | null) => void;\n readonly setFrameworkContext: (key: string, value: unknown) => void;\n readonly emitChange: () => void;\n}\n\n/**\n * Context passed to onReady callbacks — fires after bin.ts has assigned\n * the real session, so reading `session.installDir` returns the target\n * project. Use for async pre-program work like prerequisite detection.\n */\nexport interface ProgramReadyContext {\n readonly session: WizardSession;\n readonly setFrameworkContext: (key: string, value: unknown) => void;\n\n // Detection-specific methods — used by core-integration's detect step\n readonly setFrameworkConfig: (\n integration: Integration,\n config: FrameworkConfig,\n ) => void;\n readonly setDetectedFramework: (label: string) => void;\n readonly setSkillId: (skillId: string | null) => void;\n readonly setUnsupportedVersion: (info: {\n current: string;\n minimum: string;\n docsUrl: string;\n }) => void;\n readonly addDiscoveredFeature: (feature: DiscoveredFeature) => void;\n readonly setDetectionComplete: () => void;\n}\n\nexport interface ProgramStep {\n /** Unique identifier for this step */\n id: string;\n\n /** Human-readable label for progress display */\n label: string;\n\n /**\n * TUI screen this step owns, if any.\n * Matches the ScreenId enum values (e.g. 'intro', 'run', 'outro').\n */\n screenId?: string;\n\n /**\n * Whether this step should be visible in the current program.\n * If omitted, the step is always visible.\n */\n show?: (session: WizardSession) => boolean;\n\n /**\n * Exit condition for the screen. Router advances when true.\n * Defaults to `gate` if unset.\n */\n isComplete?: (session: WizardSession) => boolean;\n\n /**\n * Define a gate if your screen needs to await user interactions.\n * bin.ts can `await store.getGate(stepId)` to pause until the\n * predicate becomes true.\n */\n gate?: (session: WizardSession) => boolean;\n\n /**\n * Called once when the TUI starts rendering, with the default\n * session. Use for session-independent fire-and-forget work that\n * should start as early as possible (e.g. health check kicked off\n * while the user is still reading the intro screen). Never fires for\n * a store that isn't rendering screens (tests, playground).\n */\n onInit?: (ctx: StoreInitContext) => void;\n\n /**\n * Called once after bin.ts has assigned the real session to the store,\n * before any gate is awaited. Awaited in sequence with other steps'\n * onReady callbacks. Use for session-dependent pre-program work like\n * scanning the installDir for prerequisites. May be sync or async.\n */\n onReady?: (ctx: ProgramReadyContext) => void | Promise<void>;\n}\n\n/**\n * Declares a program's place in the wizard CLI surface.\n *\n * Mirrors the `cli:` block in context-mill skill configs so wizard-native\n * programs and skill-backed programs share one vocabulary. Field names\n * match `ProgramConfig.command` / `parentCommand` above, so contributors\n * only learn one set of words.\n *\n * - `role: 'command'` — appears as a normal wizard command.\n * - `role: 'skill'` — reachable only via `wizard skill <id>`.\n * - `role: 'internal'` — hidden everywhere, only reachable via the\n * `--skill=<id>` dev escape hatch.\n *\n * Mapping table — declaration on the left, registered command on the right:\n *\n * { role: 'command', → wizard revenue-analytics\n * command: 'revenue-analytics' }\n *\n * { role: 'command', → wizard audit feature-flags\n * parentCommand: 'audit',\n * command: 'feature-flags' }\n *\n * { role: 'skill' } → wizard skill <id>\n *\n * `cli` only configures the command shape — the verbs the user types.\n * Flags and positional args (e.g. `--since=30d`) are configured on\n * `cliOptions`, not here.\n *\n * Naming rule: commands use the full PostHog product name with hyphens\n * (`revenue-analytics`, `feature-flags`, `session-replay`), not\n * abbreviations like `revenue` or `flags`.\n */\nexport interface ProgramCliSurface {\n /** Where the program appears in the wizard CLI surface. */\n role: 'command' | 'skill' | 'internal';\n /**\n * The user-typed word that registers this program (e.g. `'feature-flags'`\n * in `wizard audit feature-flags`, or `'revenue-analytics'` in\n * `wizard revenue-analytics`). Required when `role` is `'command'`.\n */\n command?: string;\n /**\n * The command this program nests under (e.g. `'audit'` for\n * `wizard audit feature-flags`). Omit for flat / standalone commands.\n */\n parentCommand?: string;\n}\n\n/**\n * Uniform configuration for a wizard program.\n *\n * Each program directory exports one of these. The system uses it\n * for CLI registration, sequence/step wiring, and skill bootstrap.\n */\nexport interface ProgramConfig {\n /** CLI command name (e.g. 'revenue-analytics'). Omit for the default program. */\n command?: string;\n /**\n * Parent CLI command to nest this program under. When set, the program is\n * registered as `<parentCommand> <command>` instead of as a top-level\n * command. The parent must itself be a registered subcommand program. Omit\n * for top-level programs.\n */\n parentCommand?: string;\n /** CLI description shown in --help */\n description: string;\n /** Unique program id — matches the Program enum value */\n id: string;\n /**\n * Whether this program's agent run requires third-party AI services.\n *\n * When true (the default), the wizard checks\n * `apiUser.organization.is_ai_data_processing_approved` after auth and\n * renders `AiOptInRequiredScreen` if the org has not opted in. Matches\n * Max's strict reading: only literal `true` proceeds.\n *\n * Opt out (set to `false`) for programs that don't run the agent —\n * doctor, mcp install/remove/tutorial, source-map upload. The safe\n * default is `true` so future programs gate by declaration.\n */\n requiresAi?: boolean;\n /**\n * Context-mill skill ID this program installs and runs. When present,\n * bin.ts seeds `session.skillId` with this value before the TUI renders\n * so intro screens can resolve skill metadata without waiting for the\n * agent run.\n */\n skillId?: string;\n /** The ordered step list */\n steps: ProgramStep[];\n /** Agent run config. Static object or async function for dynamic config. */\n run?: ProgramRun | ((session: WizardSession) => Promise<ProgramRun>);\n /**\n * CI-mode pre-run strategy. When set, runWizardCI awaits this after building\n * the ci:true session and before the agent runs, instead of walking step\n * onReady hooks. Use for headless prerequisite work (e.g. framework\n * detection) that the TUI performs via step onReady callbacks.\n */\n ciPreRun?: (session: WizardSession) => Promise<void>;\n /** Prerequisites: other program ids that must have run first */\n requires?: string[];\n /**\n * Path (relative to installDir) of the report file the program writes.\n * Mirrors `run.reportFile` but lifted to the top level so UI screens can\n * read it synchronously without resolving a deferred `run` function.\n */\n reportFile?: string;\n /**\n * LearnCard deck rendered in the shared `RunScreen` while the agent\n * runs. Lives at `<program>/content/index.tsx` by convention.\n * Programs that ship a custom RunScreen variant (audit) or skip the\n * run step (posthog-doctor) leave this unset.\n */\n getContentBlocks?: (store?: WizardStore) => ContentBlock[];\n /**\n * Tips shown in the run screen's right pane (the `Tips` sidebar) once\n * the LearnCard finishes. Lets a program supply its own explainer copy\n * (e.g. self-driving explaining what signal sources and scouts are)\n * instead of the generic onboarding deck. Unset → `RunScreen` falls back\n * to `DEFAULT_TIPS`, so every other program is unaffected. Lives at\n * `<program>/content/tips.ts` by convention.\n */\n getTips?: (store?: WizardStore) => Tip[];\n /**\n * Subcommand-specific CLI options. Spread into yargs `.options(...)` when the\n * program's subcommand is registered. Program-specific knowledge stays in\n * the program config, not in bin.ts. Typed as `unknown` to avoid pulling a\n * yargs dependency into this module.\n */\n cliOptions?: Record<string, unknown>;\n /**\n * Translate parsed CLI argv into extra options the runner consumes. Runs\n * after yargs validation, before runWizard/runWizardCI. Use this when a flag\n * needs to derive another field (e.g. `--product=statsig` → `skillId:\n * 'migrate-statsig'`).\n */\n mapCliOptions?: (argv: Record<string, unknown>) => Record<string, unknown>;\n /**\n * Extra tool names added on top of BASE_ALLOWED_TOOLS for this program's\n * agent run. Use for tools that only this program needs.\n */\n allowedTools?: readonly string[];\n /**\n * Tool names removed from BASE_ALLOWED_TOOLS for this program's agent\n * run. Use to forbid a base tool — e.g. `['Agent']` to block subagent\n * dispatch in a program whose steps are explicitly single-agent.\n */\n disallowedTools?: readonly string[];\n /**\n * Declares this program's place in the wizard CLI surface. See\n * `ProgramCliSurface` for semantics.\n */\n cli?: ProgramCliSurface;\n}\n\n/**\n * Project program steps into the narrower Screen shape the router consumes.\n *\n * Two things happen here:\n * 1. Headless steps (no `screenId`) are filtered out. The router walks\n * visible screens; gate-only steps like `detect` are store concerns.\n * 2. The step is narrowed to just { id, show, isComplete } — the\n * router has no business touching gate, onInit, or label.\n *\n * This intentional separation keeps the router focused on one question:\n * \"Which screen should be rendered right now?\"\n */\nexport function createProgramSequence(steps: ProgramStep[]): Array<{\n id: string;\n show?: (session: WizardSession) => boolean;\n isComplete?: (session: WizardSession) => boolean;\n}> {\n const entries = steps\n .filter((step) => step.screenId != null)\n .map((step) => ({\n id: step.screenId!,\n show: step.show,\n // `isComplete` defaults to `gate` — for most steps they're the same\n // predicate (e.g. intro: setupConfirmed unblocks bin.ts AND finishes\n // the screen). Only override when the two conditions diverge.\n isComplete: step.isComplete ?? step.gate,\n }));\n\n // Every program ends with the exit screen.\n entries.push({ id: 'exit', show: undefined, isComplete: undefined });\n\n return entries;\n}\n","/**\n * AI opt-in gate — step injection for programs whose agent run sends\n * source to Anthropic Claude.\n *\n * Injected after the `auth` step for every program that doesn't declare\n * `requiresAi: false`. The injected step carries three predicates:\n *\n * show — renders AiOptInRequiredScreen when the org hasn't\n * approved third-party AI\n * isComplete — router advances once approval lands\n * gate — `await store.getGate('ai-opt-in')` parks the agent\n * runner until approval lands. THIS is the enforcement:\n * the screen alone is cosmetic; the gate is what stops\n * source from leaving the machine.\n *\n * Used by BOTH screen-sequences.ts (screen injection) and the store's\n * _initFromProgram (gate registration) so the two layers can't drift.\n *\n * The predicates mirror Max's strict reading of\n * `organization.is_ai_data_processing_approved`: only literal `true`\n * proceeds; `null` / `undefined` / `false` all block. CI sessions skip\n * the gate — `--ci` auto-consents to AI usage per the README, and the\n * interactive kill screen would be unworkable headless.\n */\n\nimport type { WizardSession } from '@lib/wizard-session';\nimport type { ProgramConfig, ProgramStep } from './program-step.js';\n\n/** Step id — also the ScreenId.AiOptIn enum value in screen-sequences. */\nexport const AI_OPT_IN_STEP_ID = 'ai-opt-in';\n\nfunction aiApproved(session: WizardSession): boolean {\n return !!session.apiUser?.organization?.is_ai_data_processing_approved;\n}\n\n/**\n * Returns the program's steps with the AI opt-in gate injected after\n * `auth`. Programs with `requiresAi: false` or no auth step pass\n * through unchanged — without auth, `apiUser` would never be populated\n * for evaluation anyway.\n */\nexport function withAiOptInGate(config: ProgramConfig): ProgramStep[] {\n if (config.requiresAi === false) return config.steps;\n\n const authIdx = config.steps.findIndex((s) => s.id === 'auth');\n if (authIdx === -1) return config.steps;\n\n const gateStep: ProgramStep = {\n id: AI_OPT_IN_STEP_ID,\n label: 'AI opt-in check',\n screenId: AI_OPT_IN_STEP_ID,\n // Only fire once apiUser has actually been populated — between\n // setCredentials and setApiUser there's a brief emitChange window\n // where apiUser is null, and we don't want to flash the gate then.\n show: (session) =>\n !session.ci && session.apiUser != null && !aiApproved(session),\n isComplete: (session) => session.ci || aiApproved(session),\n gate: (session) => session.ci || aiApproved(session),\n };\n\n return [\n ...config.steps.slice(0, authIdx + 1),\n gateStep,\n ...config.steps.slice(authIdx + 1),\n ];\n}\n","/**\n * Screen taxonomy + per-program screen sequences.\n *\n * Owns the ScreenId enum and projects each registered program's steps\n * into the router-shaped screen sequence (filtering headless steps and\n * appending the exit screen). Pure leaf module — no store, no React.\n */\n\nimport type { WizardSession } from '@lib/wizard-session';\nimport {\n PROGRAM_REGISTRY,\n type ProgramId,\n} from '@lib/programs/program-registry';\nimport { createProgramSequence } from '@lib/programs/program-step';\nimport { withAiOptInGate } from '@lib/programs/ai-opt-in-gate';\n\n/** Screens that participate in linear programs. */\nexport enum ScreenId {\n Intro = 'intro',\n RevenueIntro = 'revenue-intro',\n WarehouseIntro = 'warehouse-intro',\n SourceMapsIntro = 'source-maps-intro',\n SourceMapsDetect = 'source-maps-detect',\n SourceMapsOutro = 'source-maps-outro',\n MigrationIntro = 'migration-intro',\n AgentSkillIntro = 'agent-skill-intro',\n SelfDrivingIntro = 'self-driving-intro',\n AuditIntro = 'audit-intro',\n AuditRun = 'audit-run',\n AuditOutro = 'audit-outro',\n HealthCheck = 'health-check',\n DoctorIntro = 'doctor-intro',\n DoctorReport = 'doctor-report',\n Setup = 'setup',\n Auth = 'auth',\n Run = 'run',\n Mcp = 'mcp',\n McpSuggestedPrompts = 'mcp-suggested-prompts',\n SlackConnect = 'slack-connect',\n KeepSkills = 'keep-skills',\n Outro = 'outro',\n Exit = 'exit',\n McpAdd = 'mcp-add',\n McpRemove = 'mcp-remove',\n AiOptIn = 'ai-opt-in',\n}\n\nexport interface Screen {\n /** ScreenId to show */\n id: ScreenId;\n /** If provided, screen is skipped when this returns false. Omit = always show. */\n show?: (session: WizardSession) => boolean;\n /** If provided, screen is considered complete when this returns true. */\n isComplete?: (session: WizardSession) => boolean;\n}\n\n/** An ordered list of screens — a program's screen journey. */\nexport type Sequence = Screen[];\n\n/** All program screen sequences keyed by program id. */\nexport const PROGRAM_SEQUENCES: Record<ProgramId, Sequence> =\n Object.fromEntries(\n PROGRAM_REGISTRY.map((c) => [\n c.id,\n createProgramSequence(withAiOptInGate(c)) as Sequence,\n ]),\n ) as Record<ProgramId, Sequence>;\n","/**\n * WizardRouter — declarative program pipelines + overlay stack.\n *\n * Two layers:\n * Program cursor — linear sequence of screens, advanced with next()\n * Overlay stack — interrupts (outage, auth-expired, etc.) that push/pop\n *\n * The visible screen is: top of overlay stack if non-empty, otherwise the program cursor.\n *\n * Adding a program screen = append to a sequence array.\n * Adding an overlay = call pushOverlay() from anywhere.\n * No switch statements, no hardcoded transitions in business logic.\n */\n\nimport type { WizardSession } from '@lib/wizard-session';\nimport { Program, type ProgramId } from '@lib/programs/program-registry';\nimport {\n PROGRAM_SEQUENCES,\n ScreenId,\n type Screen,\n type Sequence,\n} from './screen-sequences.js';\n\n// Re-export so existing imports from './router.js' keep working\nexport { ScreenId, Program };\nexport type { Screen, Sequence, ProgramId };\n\n// ── ScreenId name taxonomy ──────────────────────────────────────────────\n\n/** Screens that interrupt programs as overlays */\nexport enum Overlay {\n SettingsOverride = 'settings-override',\n ManagedSettings = 'managed-settings',\n PortConflict = 'port-conflict',\n ManualAuthCode = 'manual-auth-code',\n AuthError = 'auth-error',\n SessionTimeout = 'session-timeout',\n WizardAsk = 'wizard-ask',\n}\n\n/** Union of all screen names */\nexport type ScreenName = ScreenId | Overlay;\n\n// ── Router ────────────────────────────────────────────────────────────\n\nexport class WizardRouter {\n private sequence: Sequence;\n private programId: ProgramId;\n private overlays: Overlay[] = [];\n\n constructor(programId: ProgramId = Program.PostHogIntegration) {\n this.programId = programId;\n this.sequence = PROGRAM_SEQUENCES[programId];\n }\n\n /**\n * Resolve which screen should be active based on session state.\n * Walks the program sequence, skipping hidden entries and completed entries,\n * returns the first incomplete screen.\n */\n resolve(session: WizardSession): ScreenName {\n if (this.overlays.length > 0) {\n return this.overlays[this.overlays.length - 1];\n }\n\n for (const entry of this.sequence) {\n if (entry.show && !entry.show(session)) continue;\n if (entry.isComplete && entry.isComplete(session)) continue;\n return entry.id;\n }\n\n // All entries complete — show the last screen (outro)\n return this.sequence[this.sequence.length - 1].id;\n }\n\n /** The screen that should be rendered right now. */\n get activeScreen(): ScreenName {\n // Overlays take priority — resolve() handles this too,\n // but activeScreen is called before session is available in some paths\n if (this.overlays.length > 0) {\n return this.overlays[this.overlays.length - 1];\n }\n return this.sequence[0].id;\n }\n\n /** The id of the active program. */\n get activeProgram(): ProgramId {\n return this.programId;\n }\n\n /** Whether an overlay is currently active. */\n get hasOverlay(): boolean {\n return this.overlays.length > 0;\n }\n\n /**\n * Push an overlay that interrupts the current program.\n * The program resumes when the overlay is dismissed via popOverlay().\n */\n pushOverlay(overlay: Overlay): void {\n this.overlays.push(overlay);\n }\n\n /**\n * Dismiss the topmost overlay. The program screen underneath resumes.\n */\n popOverlay(): void {\n this.overlays.pop();\n }\n\n /**\n * Direction hint for screen transitions.\n */\n private _lastDirection: 'push' | 'pop' | null = null;\n\n get lastNavDirection(): 'push' | 'pop' | null {\n return this._lastDirection;\n }\n\n /** @internal — called by store wrapper to track direction */\n _setDirection(dir: 'push' | 'pop' | null): void {\n this._lastDirection = dir;\n }\n}\n","/**\n * WizardStore — Nanostore-backed reactive store for the TUI.\n * React components subscribe via useSyncExternalStore.\n *\n * The active screen is derived from session state — WizardRouter walks\n * the flow and shows the first step whose `isComplete` is still false.\n *\n * Define a step `gate` if your screen needs to await user interactions.\n * bin.ts calls `await store.getGate(stepId)` to pause until the gate\n * predicate becomes true.\n *\n * All session mutations that affect screen resolution go through\n * explicit setters so emitChange() is always called.\n */\n\nimport { atom, map } from 'nanostores';\nimport { logToFile } from '@utils/debug';\nimport { TaskStatus, isTaskStatus, type AuthErrorDetail } from '@ui/wizard-ui';\nimport {\n type WizardSession,\n type OutroData,\n type DiscoveredFeature,\n type PendingQuestion,\n type AskAnswers,\n AdditionalFeature,\n McpOutcome,\n RunPhase,\n buildSession,\n} from '@lib/wizard-session';\nimport type { SettingsConflict } from '@lib/agent/claude-settings';\nimport {\n WizardReadiness,\n getBlockingServiceKeys,\n type WizardReadinessResult,\n} from '@lib/health-checks/readiness';\nimport { ServiceHealthStatus } from '@lib/health-checks/types';\nimport {\n WizardRouter,\n type ScreenName,\n ScreenId,\n Overlay,\n Program,\n type ProgramId,\n} from './router.js';\nimport { analytics, sessionProperties } from '@utils/analytics';\nimport type {\n StoreInitContext,\n ProgramReadyContext,\n} from '@lib/programs/program-step';\nimport { getProgramConfig } from '@lib/programs/program-registry';\nimport { withAiOptInGate } from '@lib/programs/ai-opt-in-gate';\nimport { EXPANDED_COUNT } from '@ui/tui/constants';\n\nexport { TaskStatus, ScreenId, Overlay, Program, RunPhase, McpOutcome };\nexport type { ScreenName, OutroData, WizardSession, ProgramId };\n\nexport interface TaskItem {\n label: string;\n activeForm?: string;\n status: TaskStatus;\n /** Legacy compat */\n done: boolean;\n}\n\nexport interface PlannedEvent {\n name: string;\n description: string;\n}\n\ninterface GateEntry {\n predicate: (session: WizardSession) => boolean;\n promise: Promise<void>;\n resolve: () => void;\n resolved: boolean;\n}\n\n/**\n * FIFO cap on retained status lines. The status bar is the only consumer and\n * renders at most EXPANDED_COUNT lines, so there is no reason to retain more —\n * the cap is tied to the window it feeds.\n */\nconst MAX_STATUS_MESSAGES = EXPANDED_COUNT;\n\n/**\n * Fired once per blocked readiness result, so we can quantify how often\n * the wizard refuses to start and — crucially — split that between\n * confirmed PostHog outages and probe-level reachability failures that\n * are most likely the user's network. Helps us decide whether the\n * health-check UX is over-firing.\n */\nfunction captureHealthCheckBlocked(result: WizardReadinessResult): void {\n try {\n const health = result.health;\n const blockingKeys = getBlockingServiceKeys(health);\n const blockingStatuses = blockingKeys.map((k) => health[k]?.status);\n\n const allNoConnection =\n blockingStatuses.length > 0 &&\n blockingStatuses.every((s) => s === ServiceHealthStatus.NoConnection);\n const onlyGithubReleases =\n blockingKeys.length === 1 && blockingKeys[0] === 'githubReleases';\n\n const decision = onlyGithubReleases\n ? 'github-releases-down'\n : allNoConnection\n ? 'no-connection'\n : 'confirmed-outage';\n\n const posthogStatus = health.posthogOverall?.status;\n const retriesUsed = Math.max(\n 0,\n ...(['llmGateway', 'mcp', 'githubReleases'] as const).map((k) => {\n const ind = health[k]?.rawIndicator ?? '';\n const m = ind.match(/attempts=(\\d+)/);\n return m ? Number(m[1]) - 1 : 0;\n }),\n );\n\n analytics.wizardCapture('health check blocked', {\n decision,\n blocking_keys: blockingKeys,\n posthog_status_reachable:\n posthogStatus !== ServiceHealthStatus.NoConnection,\n posthog_status_reports_incident:\n posthogStatus === ServiceHealthStatus.Down ||\n posthogStatus === ServiceHealthStatus.Degraded,\n retries_used: retriesUsed,\n });\n } catch (err) {\n logToFile(\n `[health-checks] failed to capture analytics: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n}\n\nexport class WizardStore {\n // ── Internal nanostore atoms ─────────────────────────────────────\n private $session = map<WizardSession>(buildSession({}));\n private $statusMessages = atom<string[]>([]);\n private $statusExpanded = atom(false);\n private $tasks = atom<TaskItem[]>([]);\n private $eventPlan = atom<PlannedEvent[]>([]);\n private $learnCardBlockIdx = atom(0);\n private $learnCardComplete = atom(false);\n private $version = atom(0);\n private $currentStage = atom<{ stage: string; startedAt: number } | null>(\n null,\n );\n\n private _onTasksChanged: (() => void) | null = null;\n /** Last screen seen — used to detect screen transitions for analytics. */\n private _lastScreen: ScreenName | null = null;\n\n /** Hooks run when transitioning onto a screen. */\n private _enterScreenHooks = new Map<ScreenName, (() => void)[]>();\n\n /** Gate promises derived from program step definitions. */\n private _gates = new Map<string, GateEntry>();\n\n version = '';\n\n /** Navigation router — resolves active screen from session state. */\n readonly router: WizardRouter;\n\n /** Blocks agent execution until the settings-override overlay is dismissed. */\n private _resolveSettingsOverride: (() => void) | null = null;\n private _backupAndFixSettings: (() => boolean) | null = null;\n\n /** Blocks OAuth flow until the port-conflict overlay is dismissed. */\n private _resolvePortConflict: (() => void) | null = null;\n\n /** Resolves the OAuth flow with a manually-entered authorization code. */\n private _resolveManualAuthCode: ((code: string) => void) | null = null;\n\n /** Resolves the in-flight wizard_ask request. */\n private _resolvePendingQuestion: ((answers: AskAnswers) => void) | null =\n null;\n\n constructor(program: ProgramId = Program.PostHogIntegration) {\n this.router = new WizardRouter(program);\n this._initFromProgram(program);\n }\n\n /**\n * Scan program steps for gate predicates and create gate promises.\n *\n * Steps are wrapped with withAiOptInGate so the injected ai-opt-in\n * step's gate registers here — the agent runner awaits it (via\n * WizardUI.waitForAiOptIn) before any source leaves the machine.\n * Same wrapper screen-sequences.ts uses, so the gate and its screen\n * can't drift apart.\n */\n private _initFromProgram(program: ProgramId): void {\n const steps = withAiOptInGate(getProgramConfig(program));\n\n // Create gate promises from steps that define them\n for (const step of steps) {\n if (step.gate) {\n let resolve!: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n this._gates.set(step.id, {\n predicate: step.gate,\n promise,\n resolve,\n resolved: false,\n });\n }\n }\n }\n\n /**\n * Run the program steps' onInit callbacks. startTUI calls this once\n * the screens are actually rendering — constructing a store alone\n * (tests, playground) must not fire init work like the health-check\n * pre-flight, whose probes belong only to flows that show its screen.\n */\n runInitHooks(): void {\n const steps = getProgramConfig(this.router.activeProgram).steps;\n const getSession = (): WizardSession => this.session;\n const ctx: StoreInitContext = {\n get session() {\n return getSession();\n },\n setReadinessResult: (r) => this.setReadinessResult(r),\n setFrameworkContext: (k, v) => this.setFrameworkContext(k, v),\n emitChange: () => this.emitChange(),\n };\n for (const step of steps) {\n step.onInit?.(ctx);\n }\n }\n\n /**\n * Run all `onReady` hooks declared by the current flow's steps, in\n * order. Must be called after `store.session = session` so hooks see\n * the real installDir. bin.ts calls this generically — it doesn't\n * need to know which program has which pre-flow work.\n */\n async runReadyHooks(): Promise<void> {\n const steps = getProgramConfig(this.router.activeProgram).steps;\n const ctx: ProgramReadyContext = {\n session: this.session,\n setFrameworkContext: (k, v) => this.setFrameworkContext(k, v),\n setFrameworkConfig: (i, c) => this.setFrameworkConfig(i, c),\n setDetectedFramework: (l) => this.setDetectedFramework(l),\n setSkillId: (id) => this.setSkillId(id),\n setUnsupportedVersion: (info) => this.setUnsupportedVersion(info),\n addDiscoveredFeature: (f) => this.addDiscoveredFeature(f),\n setDetectionComplete: () => this.setDetectionComplete(),\n };\n for (const step of steps) {\n if (step.onReady) {\n await step.onReady(ctx);\n }\n }\n }\n\n // ── Gate API ────────────────────────────────────────────────────\n\n /**\n * Get a gate promise by step ID — the primary blocking checkpoint API\n * for bin.ts. `await store.getGate('...')` parks the caller until the\n * corresponding program step's gate predicate flips to true (if the\n * predicate stays false, the caller stays parked indefinitely — the\n * TUI keeps rendering so the user can resolve whatever is blocking).\n *\n * If the program doesn't define a step with this ID, or the step\n * has no `gate` predicate, this returns an already-resolved promise\n * so bin.ts flows straight through. This lets programs opt in to\n * gates on a per-step basis without bin.ts needing to know which\n * gates exist in which flow.\n */\n getGate(stepId: string): Promise<void> {\n return this._gates.get(stepId)?.promise ?? Promise.resolve();\n }\n\n /**\n * Re-evaluate every gate predicate against the current session and\n * resolve any whose predicate now returns true. Called after every\n * emitChange(), so gates unblock as soon as the session mutation\n * that satisfies them lands. Gates only resolve once — a predicate\n * that goes true → false → true will NOT re-block a caller that\n * already awaited through.\n */\n private _checkGates(): void {\n for (const [, gate] of this._gates) {\n if (!gate.resolved && gate.predicate(this.session)) {\n gate.resolved = true;\n gate.resolve();\n }\n }\n }\n\n // ── State accessors (read from atoms) ────────────────────────────\n\n get session(): WizardSession {\n return this.$session.get();\n }\n\n set session(value: WizardSession) {\n this.$session.set(value);\n this.emitChange();\n }\n\n get statusMessages(): string[] {\n return this.$statusMessages.get();\n }\n\n get tasks(): TaskItem[] {\n return this.$tasks.get();\n }\n\n get eventPlan(): PlannedEvent[] {\n return this.$eventPlan.get();\n }\n\n get currentStage(): { stage: string; startedAt: number } | null {\n return this.$currentStage.get();\n }\n\n /** No-op when the stage hasn't changed, so `startedAt` survives across\n * re-renders and tab switches and measures real stage time. */\n setCurrentStage(stage: string): void {\n const cur = this.$currentStage.get();\n if (cur?.stage === stage) return;\n this.$currentStage.set({ stage, startedAt: Date.now() });\n this.emitChange();\n }\n\n get statusExpanded(): boolean {\n return this.$statusExpanded.get();\n }\n\n toggleStatusExpanded(): void {\n this.$statusExpanded.set(!this.$statusExpanded.get());\n this.emitChange();\n }\n\n setStatusExpanded(expanded: boolean): void {\n if (this.$statusExpanded.get() !== expanded) {\n this.$statusExpanded.set(expanded);\n this.emitChange();\n }\n }\n\n // ── Session setters ─────────────────────────────────────────────\n // Every setter that affects screen resolution calls emitChange().\n // Business logic calls these instead of mutating session directly.\n\n /** Sets setupConfirmed. Gate resolves via _checkGates(). */\n completeSetup(): void {\n this.$session.setKey('setupConfirmed', true);\n analytics.wizardCapture('setup confirmed', sessionProperties(this.session));\n this.emitChange();\n }\n\n setRunPhase(phase: RunPhase): void {\n this.$session.setKey('runPhase', phase);\n this.emitChange();\n }\n\n setCredentials(credentials: WizardSession['credentials']): void {\n this.$session.setKey('credentials', credentials);\n if (credentials?.projectId) {\n analytics.setTag('project_id', credentials.projectId);\n }\n analytics.wizardCapture('auth complete', {\n project_id: credentials?.projectId,\n });\n this.emitChange();\n }\n\n setRoleAtOrganization(role: string | null): void {\n this.$session.setKey('roleAtOrganization', role);\n this.emitChange();\n }\n\n setApiUser(user: WizardSession['apiUser']): void {\n this.$session.setKey('apiUser', user);\n this.emitChange();\n }\n\n setFrameworkConfig(\n integration: WizardSession['integration'],\n config: WizardSession['frameworkConfig'],\n ): void {\n this.$session.setKey('integration', integration);\n this.$session.setKey('frameworkConfig', config);\n this.$session.setKey('unsupportedVersion', null);\n if (integration) analytics.setTag('integration', integration);\n this.emitChange();\n }\n\n setDetectionComplete(): void {\n this.$session.setKey('detectionComplete', true);\n this.emitChange();\n }\n\n setDetectedFramework(label: string): void {\n this.$session.setKey('detectedFrameworkLabel', label);\n analytics.setTag('detected_framework', label);\n this.emitChange();\n }\n\n setSkillId(skillId: string | null): void {\n this.$session.setKey('skillId', skillId);\n this.emitChange();\n }\n\n setUnsupportedVersion(info: {\n current: string;\n minimum: string;\n docsUrl: string;\n }): void {\n this.$session.setKey('unsupportedVersion', info);\n this.emitChange();\n }\n\n setLoginUrl(url: string | null): void {\n this.$session.setKey('loginUrl', url);\n this.emitChange();\n }\n\n setAuthorizeUrl(url: string | null): void {\n this.$session.setKey('authorizeUrl', url);\n this.emitChange();\n }\n\n setReadinessResult(result: WizardReadinessResult | null): void {\n this.$session.setKey('readinessResult', result);\n if (result && result.decision === WizardReadiness.No) {\n captureHealthCheckBlocked(result);\n }\n this.emitChange();\n }\n\n /** User dismissed the blocking outage screen. Gate resolves via _checkGates(). */\n dismissOutage(): void {\n logToFile('[health-checks] user dismissed outage screen, continuing');\n this.$session.setKey('outageDismissed', true);\n this.emitChange();\n }\n\n /**\n * Push the settings-override overlay and return a promise that blocks\n * until the user dismisses it via backupAndFixSettingsOverride().\n */\n showSettingsOverride(\n conflicts: SettingsConflict[],\n backupAndFix: () => boolean,\n ): Promise<void> {\n const allKeys = conflicts.flatMap((c) => c.keys);\n this.$session.setKey('settingsOverrideKeys', allKeys);\n this.$session.setKey('settingsConflicts', conflicts);\n this._backupAndFixSettings = backupAndFix;\n\n const hasReadOnly = conflicts.some((c) => !c.writable);\n if (hasReadOnly) {\n this.pushOverlay(Overlay.ManagedSettings);\n } else {\n this.pushOverlay(Overlay.SettingsOverride);\n }\n\n return new Promise((resolve) => {\n this._resolveSettingsOverride = resolve;\n });\n }\n\n /**\n * Push the port-conflict overlay and return a promise that blocks\n * until the user frees the ports and retries, or exits.\n */\n showPortConflict(processInfo: {\n command: string;\n pid: string;\n port: number;\n user: string;\n }): Promise<void> {\n this.$session.setKey('portConflictProcess', processInfo);\n this.pushOverlay(Overlay.PortConflict);\n return new Promise((resolve) => {\n this._resolvePortConflict = resolve;\n });\n }\n\n /** Dismiss the port-conflict overlay and retry the OAuth port loop. */\n resolvePortConflict(): void {\n this.$session.setKey('portConflictProcess', null);\n this.popOverlay();\n this._resolvePortConflict?.();\n this._resolvePortConflict = null;\n }\n\n /**\n * Return a promise that resolves when the user submits a manually-entered\n * OAuth code via the paste modal. The OAuth flow races this against the\n * local callback server — see `performOAuthFlow`.\n */\n waitForManualAuthCode(): Promise<string> {\n return new Promise<string>((resolve) => {\n this._resolveManualAuthCode = resolve;\n });\n }\n\n /** Open the manual OAuth code-entry overlay over the auth screen. */\n showManualAuthCode(): void {\n this.pushOverlay(Overlay.ManualAuthCode);\n }\n\n /** Dismiss the manual OAuth code overlay without submitting. */\n dismissManualAuthCode(): void {\n this.popOverlay();\n }\n\n /**\n * Submit a manually-entered authorization code: dismiss the overlay and\n * resolve the in-flight OAuth flow so it can exchange the code for a token.\n */\n submitManualAuthCode(code: string): void {\n this.popOverlay();\n this._resolveManualAuthCode?.(code);\n this._resolveManualAuthCode = null;\n }\n\n /**\n * Open the WizardAsk overlay with a set of questions and return a promise\n * that resolves once the user submits answers (or the request is cancelled).\n *\n * Only one request is in flight at a time — calling this while a request\n * is already pending throws.\n */\n requestQuestion(question: PendingQuestion): Promise<AskAnswers> {\n if (this._resolvePendingQuestion) {\n throw new Error(\n 'requestQuestion called while another wizard_ask request is pending',\n );\n }\n this.$session.setKey('pendingQuestion', question);\n this.pushOverlay(Overlay.WizardAsk);\n analytics.wizardCapture('wizard_ask shown', {\n source: question.source,\n question_count: question.questions.length,\n kinds: question.questions.map((q) => q.kind),\n });\n return new Promise<AskAnswers>((resolve) => {\n this._resolvePendingQuestion = resolve;\n });\n }\n\n /**\n * Resolve the in-flight wizard_ask request with the user's answers and\n * dismiss the overlay. Answers flow back to the agent as the tool result.\n */\n resolvePendingQuestion(answers: AskAnswers): void {\n const resolve = this._resolvePendingQuestion;\n this._resolvePendingQuestion = null;\n this.$session.setKey('pendingQuestion', null);\n this.popOverlay();\n resolve?.(answers);\n }\n\n /**\n * Cancel the in-flight wizard_ask request — the bridge sends a sentinel\n * answer (\"__cancelled__\") so the skill can decide how to handle it.\n */\n cancelPendingQuestion(): void {\n const pending = this.session.pendingQuestion;\n if (!pending) return;\n const cancelled: AskAnswers = {};\n for (const q of pending.questions) {\n cancelled[q.id] = '__cancelled__';\n }\n this.resolvePendingQuestion(cancelled);\n }\n\n /**\n * Back up .claude/settings.json. Dismisses the overlay on success.\n */\n backupAndFixSettingsOverride(): boolean {\n const ok = this._backupAndFixSettings?.() ?? false;\n if (ok) {\n this.$session.setKey('settingsOverrideKeys', null);\n this.$session.setKey('settingsConflicts', null);\n this.popOverlay();\n this._resolveSettingsOverride?.();\n this._resolveSettingsOverride = null;\n this._backupAndFixSettings = null;\n }\n return ok;\n }\n\n /** Push the auth-error overlay (no dismiss — user must exit). */\n showAuthError(detail?: AuthErrorDetail): void {\n this.$session.setKey('authErrorDetail', detail ?? null);\n this.pushOverlay(Overlay.AuthError);\n }\n\n /** Push the session-timeout overlay (no dismiss — user must exit). */\n showSessionTimeout(): void {\n this.pushOverlay(Overlay.SessionTimeout);\n }\n\n addDiscoveredFeature(feature: DiscoveredFeature): void {\n if (!this.session.discoveredFeatures.includes(feature)) {\n this.session.discoveredFeatures.push(feature);\n this.emitChange();\n }\n }\n\n /**\n * Enable an additional feature: enqueue it for the stop hook\n * and set any feature-specific session flags.\n */\n enableFeature(feature: AdditionalFeature): void {\n if (!this.session.additionalFeatureQueue.includes(feature)) {\n this.session.additionalFeatureQueue.push(feature);\n }\n // Feature-specific flags\n if (feature === AdditionalFeature.LLM) {\n this.session.llmOptIn = true;\n }\n analytics.wizardCapture('feature enabled', { feature });\n this.emitChange();\n }\n\n setMcpComplete(\n outcome: McpOutcome = McpOutcome.Skipped,\n installedClients: string[] = [],\n featuresSelected?: 'all' | string[],\n ): void {\n this.$session.setKey('mcpComplete', true);\n this.$session.setKey('mcpOutcome', outcome);\n this.$session.setKey('mcpInstalledClients', installedClients);\n const featuresPayload =\n outcome === McpOutcome.Installed && featuresSelected !== undefined\n ? { mcp_features_selected: featuresSelected }\n : {};\n analytics.wizardCapture('mcp complete', {\n mcp_outcome: outcome,\n mcp_installed_clients: installedClients,\n ...featuresPayload,\n ...sessionProperties(this.session),\n });\n this.emitChange();\n }\n\n setSkillsComplete(kept: boolean): void {\n this.$session.setKey('skillsComplete', true);\n analytics.wizardCapture('skills complete', {\n skills_kept: kept,\n ...sessionProperties(this.session),\n });\n this.emitChange();\n }\n\n setMcpSuggestedPromptsDismissed(): void {\n this.$session.setKey('mcpSuggestedPromptsDismissed', true);\n this.emitChange();\n }\n\n setSlackStepDismissed(): void {\n this.$session.setKey('slackStepDismissed', true);\n this.emitChange();\n }\n\n setSlackConnected(connected: boolean): void {\n this.$session.setKey('slackConnected', connected);\n this.emitChange();\n }\n\n setOutroDismissed(): void {\n this.$session.setKey('outroDismissed', true);\n this.emitChange();\n }\n\n setOutroData(data: OutroData): void {\n this.$session.setKey('outroData', data);\n this.emitChange();\n }\n\n setDashboardUrl(url: string): void {\n logToFile(`store.setDashboardUrl: ${url}`);\n this.$session.setKey('dashboardUrl', url);\n this.emitChange();\n }\n\n setNotebookUrl(url: string): void {\n logToFile(`store.setNotebookUrl: ${url}`);\n this.$session.setKey('notebookUrl', url);\n this.emitChange();\n }\n\n setFrameworkContext(key: string, value: unknown): void {\n const ctx = { ...this.$session.get().frameworkContext, [key]: value };\n this.$session.setKey('frameworkContext', ctx);\n this.emitChange();\n }\n\n // ── Derived state ───────────────────────────────────────────────\n\n /**\n * The screen that should be rendered right now.\n * Derived from session state via the router.\n */\n get currentScreen(): ScreenName {\n return this.router.resolve(this.session);\n }\n\n /** Direction hint for screen transitions. */\n get lastNavDirection(): 'push' | 'pop' | null {\n return this.router.lastNavDirection;\n }\n\n // ── Change notification ─────────────────────────────────────────\n\n getVersion(): number {\n return this.$version.get();\n }\n\n /**\n * Notify React that state has changed.\n * The router re-resolves the active screen on next render.\n * Gate predicates are checked and resolved if ready.\n */\n emitChange(): void {\n this.router._setDirection('push');\n this.$version.set(this.$version.get() + 1);\n this._checkGates();\n this._detectTransition();\n }\n\n // ── Overlay navigation ──────────────────────────────────────────\n\n pushOverlay(overlay: Overlay): void {\n this.router._setDirection('push');\n this.router.pushOverlay(overlay);\n this.$version.set(this.$version.get() + 1);\n this._detectTransition();\n }\n\n popOverlay(): void {\n this.router._setDirection('pop');\n this.router.popOverlay();\n this.$version.set(this.$version.get() + 1);\n this._detectTransition();\n }\n\n // ── ScreenId transition analytics ─────────────────────────────────\n\n /**\n * Register a callback to run when transitioning onto the given screen.\n * Fires after every transition that lands on this screen.\n */\n onEnterScreen(screen: ScreenName, fn: () => void): void {\n const list = this._enterScreenHooks.get(screen) ?? [];\n list.push(fn);\n this._enterScreenHooks.set(screen, list);\n }\n\n /**\n * Detect screen transitions, run enter-screen hooks, and fire analytics.\n * Called at the end of emitChange/pushOverlay/popOverlay.\n */\n private _detectTransition(): void {\n const next = this.router.resolve(this.session);\n const prev = this._lastScreen;\n if (next !== prev) {\n // Every event carries the active TUI screen, filling the\n // \"URL / Screen\" column in PostHog.\n analytics.setTag('$screen_name', next);\n }\n if (prev !== null && next !== prev) {\n const hooks = this._enterScreenHooks.get(next);\n if (hooks) {\n for (const fn of hooks) fn();\n }\n analytics.wizardCapture(`screen ${next}`, {\n from_screen: prev,\n program_id: this.router.activeProgram,\n ...sessionProperties(this.session),\n });\n }\n this._lastScreen = next;\n }\n\n // ── Agent observation state ─────────────────────────────────────\n\n pushStatus(message: string): void {\n const msgs = this.$statusMessages.get();\n // Skip consecutive duplicate messages (no allocation on the hot path)\n if (msgs.length > 0 && msgs[msgs.length - 1] === message) return;\n // Nanostore detects change by reference equality, so a new array is\n // required. At the cap, allocate exactly once at the final size (dropping\n // the oldest entry) rather than push-then-truncate.\n const next =\n msgs.length >= MAX_STATUS_MESSAGES\n ? [...msgs.slice(msgs.length - MAX_STATUS_MESSAGES + 1), message]\n : [...msgs, message];\n this.$statusMessages.set(next);\n this.emitChange();\n }\n\n setTasks(tasks: TaskItem[]): void {\n this.$tasks.set(tasks);\n this.emitChange();\n }\n\n updateTask(index: number, done: boolean): void {\n const tasks = this.$tasks.get();\n if (tasks[index]) {\n const updated = [...tasks];\n updated[index] = {\n ...updated[index],\n done,\n status: done ? TaskStatus.Completed : TaskStatus.Pending,\n };\n this.$tasks.set(updated);\n this.emitChange();\n }\n }\n\n setEventPlan(events: PlannedEvent[]): void {\n this.$eventPlan.set(events);\n this.emitChange();\n }\n\n get learnCardBlockIdx(): number {\n return this.$learnCardBlockIdx.get();\n }\n\n setLearnCardBlockIdx(idx: number): void {\n this.$learnCardBlockIdx.set(idx);\n }\n\n get learnCardComplete(): boolean {\n return this.$learnCardComplete.get();\n }\n\n setLearnCardComplete(): void {\n this.$learnCardComplete.set(true);\n this.emitChange();\n }\n\n syncTodos(\n todos: Array<{ content: string; status: string; activeForm?: string }>,\n ): void {\n const incoming = todos.map((t) => {\n const status = isTaskStatus(t.status) ? t.status : TaskStatus.Pending;\n return {\n label: t.content,\n activeForm: t.activeForm,\n status,\n done: status === TaskStatus.Completed,\n };\n });\n\n const incomingLabels = new Set(incoming.map((t) => t.label));\n\n const retained = this.$tasks\n .get()\n .filter((t) => t.done && !incomingLabels.has(t.label));\n\n this.$tasks.set([...retained, ...incoming]);\n this.emitChange();\n this._onTasksChanged?.();\n }\n\n /** Register a listener for task state changes (e.g. task stream push). */\n set onTasksChanged(fn: () => void) {\n this._onTasksChanged = fn;\n }\n\n // ── React integration ───────────────────────────────────────────\n\n subscribe(callback: () => void): () => void {\n return this.$version.listen(() => callback());\n }\n\n getSnapshot(): number {\n return this.$version.get();\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "./debug-
|
|
2
|
-
import { t as analytics } from "./analytics-
|
|
1
|
+
import "./debug-Cn4L5Ype.js";
|
|
2
|
+
import { t as analytics } from "./analytics-DQ8IS1Hu.js";
|
|
3
3
|
import opn from "opn";
|
|
4
4
|
//#region src/utils/links.ts
|
|
5
5
|
/**
|
|
@@ -65,4 +65,4 @@ function updateProgress(step) {
|
|
|
65
65
|
//#endregion
|
|
66
66
|
export { withUtm as i, openTrackedLink as n, setEntryCommand as r, withProgress as t };
|
|
67
67
|
|
|
68
|
-
//# sourceMappingURL=telemetry-
|
|
68
|
+
//# sourceMappingURL=telemetry-BTLJ-YnG.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry-
|
|
1
|
+
{"version":3,"file":"telemetry-BTLJ-YnG.js","names":[],"sources":["../src/utils/links.ts","../src/telemetry.ts"],"sourcesContent":["/**\n * Outbound links: UTM tagging and tracked opens.\n *\n * Every URL the wizard sends a user to — auto-opened or printed in the TUI —\n * carries `utm_source=wizard`, `utm_medium=cli`, `utm_content=<which link>`.\n * The command dimension rides on every wizard event as the `command` tag.\n * Opening a link also captures a wizard event.\n */\nimport opn from 'opn';\nimport { NODE_ENV } from '@env';\nimport { analytics } from './analytics';\n\n/**\n * Record the CLI command this run was started with (e.g. `integrate`,\n * `slack`, `mcp-add`). Set once at dispatch; tagged onto every wizard\n * event so wizard-side events segment by command.\n */\nexport function setEntryCommand(command: string): void {\n analytics.setTag('command', command);\n}\n\n/**\n * Tag a URL with the wizard's UTM params. `content` names the specific link\n * (e.g. `oauth-signup`, `slack-connect-setup`). URLs that already carry a\n * utm_source — or don't parse — are returned untouched.\n */\nexport function withUtm(url: string, content: string): string {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return url;\n }\n if (parsed.searchParams.has('utm_source')) return url;\n parsed.searchParams.set('utm_source', 'wizard');\n parsed.searchParams.set('utm_medium', 'cli');\n parsed.searchParams.set('utm_content', content);\n return parsed.toString();\n}\n\n/**\n * Open a URL in the user's browser, UTM-tagged (pass `skipUtm` for\n * destinations that aren't PostHog properties or are already final), and\n * capture the interaction. `auto` marks opens the wizard initiated itself,\n * as opposed to a user picking a link on screen. opn throws in headless\n * environments — the URL is always also printed on screen, so the failure\n * is swallowed.\n */\nexport function openTrackedLink(\n url: string,\n content: string,\n opts?: { auto?: boolean; skipUtm?: boolean },\n): void {\n const finalUrl = opts?.skipUtm ? url : withUtm(url, content);\n analytics.wizardCapture('link opened', {\n content,\n url: finalUrl,\n auto: opts?.auto ?? false,\n });\n if (NODE_ENV !== 'test') {\n opn(finalUrl, { wait: false }).catch(() => {\n // No browser available — the printed URL is the fallback.\n });\n }\n}\n","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":";;;;;;;;;;;;;;;;;AAiBA,SAAgB,gBAAgB,SAAuB;AACrD,WAAU,OAAO,WAAW,QAAQ;;;;;;;AAQtC,SAAgB,QAAQ,KAAa,SAAyB;CAC5D,IAAI;AACJ,KAAI;AACF,WAAS,IAAI,IAAI,IAAI;SACf;AACN,SAAO;;AAET,KAAI,OAAO,aAAa,IAAI,aAAa,CAAE,QAAO;AAClD,QAAO,aAAa,IAAI,cAAc,SAAS;AAC/C,QAAO,aAAa,IAAI,cAAc,MAAM;AAC5C,QAAO,aAAa,IAAI,eAAe,QAAQ;AAC/C,QAAO,OAAO,UAAU;;;;;;;;;;AAW1B,SAAgB,gBACd,KACA,SACA,MACM;CACN,MAAM,WAAW,MAAM,UAAU,MAAM,QAAQ,KAAK,QAAQ;AAC5D,WAAU,cAAc,eAAe;EACrC;EACA,KAAK;EACL,MAAM,MAAM,QAAQ;EACrB,CAAC;AAEA,KAAI,UAAU,EAAE,MAAM,OAAO,CAAC,CAAC,YAAY,GAEzC;;;;AC5DN,SAAgB,aAAgB,MAAc,UAAsB;AAClE,gBAAe,KAAK;AACpB,QAAO,UAAU;;AAGnB,SAAgB,eAAe,MAAc;AAC3C,WAAU,OAAO,YAAY,KAAK"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { P as POSTHOG_APP_URL, et as getSkillsBaseUrl, g as SERVICE_LABELS, s as logToFile } from "./debug-
|
|
2
|
-
import { t as analytics } from "./analytics-
|
|
3
|
-
import { i as withUtm, n as openTrackedLink } from "./telemetry-
|
|
4
|
-
import { t as getOrAskForProjectData } from "./setup-utils-
|
|
5
|
-
import { n as getCloudUrlFromRegion } from "./urls-
|
|
6
|
-
import { a as fetchUserData, i as fetchSlackConnected } from "./api-
|
|
1
|
+
import { P as POSTHOG_APP_URL, et as getSkillsBaseUrl, g as SERVICE_LABELS, s as logToFile } from "./debug-Cn4L5Ype.js";
|
|
2
|
+
import { t as analytics } from "./analytics-DQ8IS1Hu.js";
|
|
3
|
+
import { i as withUtm, n as openTrackedLink } from "./telemetry-BTLJ-YnG.js";
|
|
4
|
+
import { t as getOrAskForProjectData } from "./setup-utils-B0oz3raQ.js";
|
|
5
|
+
import { n as getCloudUrlFromRegion } from "./urls-DAVzgHGZ.js";
|
|
6
|
+
import { a as fetchUserData, i as fetchSlackConnected } from "./api-EghEV_u9.js";
|
|
7
7
|
import "./wizard-session-CN55LYyZ.js";
|
|
8
|
-
import {
|
|
9
|
-
import { c as isClearBlock, d as Colors, f as Icons, l as isLinesBlock, o as TextBlock, s as computeVisibleRange, u as isObjectBlock } from "./posthog-integration-
|
|
8
|
+
import { O as AUDIT_SEVERITY_STYLE, b as fetchSkillMenu } from "./agent-interface-BcUv1qeh.js";
|
|
9
|
+
import { c as isClearBlock, d as Colors, f as Icons, l as isLinesBlock, o as TextBlock, s as computeVisibleRange, u as isObjectBlock } from "./posthog-integration-C3m_uR5A.js";
|
|
10
10
|
import { a as ConfirmButton, d as Program, g as getKindMeta, i as useKeyboardHintsContext, n as useKeyBindings, o as PromptLabel, r as KeyboardHintsProvider, t as PickerMenu } from "./bin.js";
|
|
11
|
-
import "./store-
|
|
11
|
+
import "./store-J2Rv955W.js";
|
|
12
12
|
import { n as AVAILABLE_FEATURES, o as isAllFeaturesSelected, t as ALL_FEATURE_VALUES } from "./defaults-DII5CAog.js";
|
|
13
13
|
import opn from "opn";
|
|
14
14
|
import * as fs$1 from "fs";
|
|
@@ -499,10 +499,9 @@ const ModalOverlay = ({ borderColor, title, titleColor, width = 68, children, fe
|
|
|
499
499
|
const ESC = String.fromCharCode(27);
|
|
500
500
|
const BEL = String.fromCharCode(7);
|
|
501
501
|
const OSC_8 = `${ESC}]8;;`;
|
|
502
|
+
const ELLIPSIS = "…";
|
|
502
503
|
/** Matches an http(s) URL run (no surrounding whitespace). */
|
|
503
504
|
const URL_RUN = /https?:\/\/[^\s]+/g;
|
|
504
|
-
/** A line whose entire (trimmed) content is a single URL. */
|
|
505
|
-
const STANDALONE_URL_LINE = /^https?:\/\/\S+$/;
|
|
506
505
|
/** Trailing characters that are punctuation around a URL, not part of it. */
|
|
507
506
|
const TRAILING_PUNCTUATION = /[.,;:!?)\]}>'"]+$/;
|
|
508
507
|
function trimTrailingPunctuation(url) {
|
|
@@ -516,17 +515,49 @@ function trimTrailingPunctuation(url) {
|
|
|
516
515
|
function osc8Hyperlink(url, label = url) {
|
|
517
516
|
return `${OSC_8}${url}${BEL}${label}${OSC_8}${BEL}`;
|
|
518
517
|
}
|
|
518
|
+
/**
|
|
519
|
+
* Shorten a URL for *display only*, keeping the trust-relevant head visible.
|
|
520
|
+
*
|
|
521
|
+
* A long authorize URL is mostly query-string noise (`?client_id=…&state=…`).
|
|
522
|
+
* The part a user needs to judge where they're being sent, the scheme and host,
|
|
523
|
+
* sits at the front. So we always keep `scheme://host` intact and collapse the
|
|
524
|
+
* long path/query tail to an ellipsis, never the other way round.
|
|
525
|
+
*
|
|
526
|
+
* This is purely the visible label. Callers pair it with `osc8Hyperlink(url,
|
|
527
|
+
* label)` so the click target, and the copy/open keybinds, stay the full URL.
|
|
528
|
+
* Returns the URL unchanged when it already fits within `maxLength`.
|
|
529
|
+
*/
|
|
530
|
+
function truncateUrlLabel(url, maxLength = 56) {
|
|
531
|
+
if (url.length <= maxLength) return url;
|
|
532
|
+
let head;
|
|
533
|
+
let tail;
|
|
534
|
+
try {
|
|
535
|
+
const parsed = new URL(url);
|
|
536
|
+
head = `${parsed.protocol}//${parsed.host}`;
|
|
537
|
+
tail = url.slice(head.length);
|
|
538
|
+
} catch {
|
|
539
|
+
return url.slice(0, Math.max(1, maxLength - 1)) + ELLIPSIS;
|
|
540
|
+
}
|
|
541
|
+
if (head.length >= maxLength - 1) return tail.length > 0 ? head + ELLIPSIS : head;
|
|
542
|
+
const room = maxLength - head.length - 1;
|
|
543
|
+
return head + tail.slice(0, room) + ELLIPSIS;
|
|
544
|
+
}
|
|
519
545
|
/** Extract every http(s) URL in `text`, trailing punctuation removed. */
|
|
520
546
|
function extractUrls(text) {
|
|
521
547
|
return (text.match(URL_RUN) ?? []).map(trimTrailingPunctuation);
|
|
522
548
|
}
|
|
523
549
|
/**
|
|
524
|
-
* Split prompt text into renderable segments, breaking
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
550
|
+
* Split prompt text into renderable segments, breaking *every* URL out onto its
|
|
551
|
+
* own `url` segment — whether it sits alone on a line or inline within prose.
|
|
552
|
+
* Pulling it out matters: `LinkText` renders a `url` segment as a single OSC 8
|
|
553
|
+
* hyperlink, so the click target stays the exact full URL even when it wraps. A
|
|
554
|
+
* URL left inline in a `text` segment is plain text the terminal may auto-detect
|
|
555
|
+
* per *visual* line, which opens a broken half-URL when it wraps.
|
|
556
|
+
*
|
|
557
|
+
* Consecutive URL-free lines stay grouped in one `text` segment (newlines
|
|
558
|
+
* preserved) so paragraph spacing is unchanged. The prose surrounding an inline
|
|
559
|
+
* URL is kept as `text` segments before/after it (trailing URL punctuation stays
|
|
560
|
+
* with the prose), so it reads the same minus the link being on its own line.
|
|
530
561
|
*/
|
|
531
562
|
function splitPromptIntoSegments(text) {
|
|
532
563
|
const segments = [];
|
|
@@ -541,14 +572,34 @@ function splitPromptIntoSegments(text) {
|
|
|
541
572
|
}
|
|
542
573
|
};
|
|
543
574
|
for (const line of text.split("\n")) {
|
|
544
|
-
|
|
545
|
-
if (
|
|
546
|
-
|
|
575
|
+
URL_RUN.lastIndex = 0;
|
|
576
|
+
if (!URL_RUN.test(line)) {
|
|
577
|
+
buffer.push(line);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
flush();
|
|
581
|
+
let cursor = 0;
|
|
582
|
+
URL_RUN.lastIndex = 0;
|
|
583
|
+
let match;
|
|
584
|
+
while ((match = URL_RUN.exec(line)) !== null) {
|
|
585
|
+
const url = trimTrailingPunctuation(match[0]);
|
|
586
|
+
const before = line.slice(cursor, match.index).trim();
|
|
587
|
+
if (before) segments.push({
|
|
588
|
+
type: "text",
|
|
589
|
+
value: before
|
|
590
|
+
});
|
|
547
591
|
segments.push({
|
|
548
592
|
type: "url",
|
|
549
|
-
value:
|
|
593
|
+
value: url
|
|
550
594
|
});
|
|
551
|
-
|
|
595
|
+
cursor = match.index + url.length;
|
|
596
|
+
URL_RUN.lastIndex = cursor;
|
|
597
|
+
}
|
|
598
|
+
const after = line.slice(cursor).trim();
|
|
599
|
+
if (after) segments.push({
|
|
600
|
+
type: "text",
|
|
601
|
+
value: after
|
|
602
|
+
});
|
|
552
603
|
}
|
|
553
604
|
flush();
|
|
554
605
|
return segments;
|
|
@@ -556,15 +607,15 @@ function splitPromptIntoSegments(text) {
|
|
|
556
607
|
//#endregion
|
|
557
608
|
//#region src/ui/tui/primitives/LinkText.tsx
|
|
558
609
|
/**
|
|
559
|
-
* LinkText — renders prompt text with
|
|
610
|
+
* LinkText — renders prompt text with URLs as OSC 8 hyperlinks.
|
|
560
611
|
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
*
|
|
565
|
-
*
|
|
566
|
-
*
|
|
567
|
-
*
|
|
612
|
+
* Every URL (standalone or inline in prose) is pulled onto its own line. Long
|
|
613
|
+
* URLs are truncated for display (`scheme://host` kept, the noisy path/query
|
|
614
|
+
* tail collapsed to an ellipsis) so the overlay stays clean instead of a URL
|
|
615
|
+
* wrapping across several lines. The OSC 8 escape carries the *full* URL as the
|
|
616
|
+
* click target regardless of the shortened label, and `WizardAskScreen` opens
|
|
617
|
+
* (`o`) / copies (`c`) the full URL too, so no path loses the real destination.
|
|
618
|
+
* Prose renders unchanged.
|
|
568
619
|
*
|
|
569
620
|
* Used by the `wizard_ask` overlay only for programs that opt into rich link
|
|
570
621
|
* rendering (see `PendingQuestion.richLinks`). Other flows are untouched.
|
|
@@ -576,7 +627,7 @@ const LinkText = ({ text }) => {
|
|
|
576
627
|
color: Colors.accent,
|
|
577
628
|
underline: true,
|
|
578
629
|
wrap: "wrap",
|
|
579
|
-
children: osc8Hyperlink(segment.value)
|
|
630
|
+
children: osc8Hyperlink(segment.value, truncateUrlLabel(segment.value))
|
|
580
631
|
}, i) : /* @__PURE__ */ jsx(Text, { children: segment.value }, i))
|
|
581
632
|
});
|
|
582
633
|
};
|
|
@@ -6071,4 +6122,4 @@ function releaseTerminal() {
|
|
|
6071
6122
|
//#endregion
|
|
6072
6123
|
export { ConfirmationInput as A, TabContainer as C, LinkText as D, LogViewer as E, SplitView as F, CardLayout as I, useStdoutDimensions as M, ProgressList as N, extractUrls as O, LoadingBox as P, HNViewer as S, EventPlanViewer as T, ServiceHealthList as _, useSkillEntry as a, LearnCard as b, AUDIT_AREA_SLIDES as c, McpSuggestedPromptsScreen as d, TAILORED_ROLES as f, SEVERITY_ORDER as g, SEVERITY_LABEL as h, SkillSourceInfo as i, GroupedPickerMenu as j, ModalOverlay as k, VisualBox as l, IssueTable as m, releaseTerminal as n, OutroScreen as o, McpScreen as p, AiOptInRequiredScreen as r, SlackConnectScreen as s, enterDarkTerminal as t, AuditChecksViewer as u, VisualizerTab as v, ScreenContainer as w, ContentSequencer as x, TipsCard as y };
|
|
6073
6124
|
|
|
6074
|
-
//# sourceMappingURL=terminal-
|
|
6125
|
+
//# sourceMappingURL=terminal-UwqFnPrL.js.map
|