From 66e6d29b1c3a5bd3223bf74b98b59ef5c98f7d5b Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Wed, 8 Apr 2026 15:16:19 -0400 Subject: [PATCH 1/3] Flags --- src/lib/agent-interface.ts | 5 + src/lib/agent-runner.ts | 301 ++++++++++++++----------- src/lib/legacy/integration-prompt.ts | 82 +++++++ src/lib/legacy/single-query-runner.ts | 73 ++++++ src/lib/wizard-tools.ts | 34 ++- src/lib/workflow-queue.ts | 2 +- src/ui/logging-ui.ts | 6 +- src/ui/tui/primitives/ProgressList.tsx | 10 +- src/ui/tui/screens/RunScreen.tsx | 60 ++--- src/ui/tui/store.ts | 3 - src/ui/wizard-ui.ts | 4 +- 11 files changed, 382 insertions(+), 198 deletions(-) create mode 100644 src/lib/legacy/integration-prompt.ts create mode 100644 src/lib/legacy/single-query-runner.ts diff --git a/src/lib/agent-interface.ts b/src/lib/agent-interface.ts index 84d1249f..da7360e6 100644 --- a/src/lib/agent-interface.ts +++ b/src/lib/agent-interface.ts @@ -647,10 +647,15 @@ export async function initializeAgent( }; // Add in-process wizard tools (env files, package manager detection, skill loading) + // Use v2 skill format when the queued workflow flag is on + const skillFormat = + config.wizardFlags?.['wizard-queued-workflow'] === 'true' ? 'v2' : 'v1'; + const wizardToolsServer = await createWizardToolsServer({ workingDirectory: config.workingDirectory, detectPackageManager: config.detectPackageManager, skillsBaseUrl: config.skillsBaseUrl, + skillFormat, }); mcpServers['wizard-tools'] = wizardToolsServer; diff --git a/src/lib/agent-runner.ts b/src/lib/agent-runner.ts index c6a4b9bc..88ca6f15 100644 --- a/src/lib/agent-runner.ts +++ b/src/lib/agent-runner.ts @@ -14,7 +14,7 @@ import type { PackageDotJson } from '../utils/package-json'; import type { WizardOptions } from '../utils/types'; import { WIZARD_INTERACTION_EVENT_NAME } from './constants'; import { analytics } from '../utils/analytics'; -import { getUI } from '../ui'; +import { getUI, type SpinnerHandle } from '../ui'; import { initializeAgent, runAgent, @@ -45,6 +45,7 @@ import { parseWorkflowStepsFromSkillMd, type WizardWorkflowQueueItem, } from './workflow-queue'; +import { runSingleQueryFlow } from './legacy/single-query-runner'; const WIZARD_SKILL_ID_SIGNAL = '[WIZARD-SKILL-ID]'; @@ -276,114 +277,34 @@ export async function runAgentWizard( ? createBenchmarkPipeline(spinner, sessionToOptions(session)) : undefined; - // ── Step 1: Bootstrap — install the skill and get its ID ── + // ── Feature flag: queued workflow vs old single-query flow ── + const useQueuedWorkflow = wizardFlags['wizard-queued-workflow'] === 'true'; + logToFile(`[agent-runner] wizard-queued-workflow=${useQueuedWorkflow}`); - let agentResult = await runAgent( - agent, - buildBootstrapPrompt(config, promptContext, frameworkContext), - sessionToOptions(session), - spinner, - { - estimatedDurationMinutes: config.ui.estimatedDurationMinutes, - spinnerMessage: 'Preparing integration...', - successMessage: 'Integration prepared', - errorMessage: 'Integration failed during bootstrap', - additionalFeatureQueue: [], - requestRemark: false, - captureOutputText: true, - captureSessionId: true, - finalizeMiddleware: false, - }, - middleware, - ); - - const queuedSessionId = agentResult.sessionId; - const installedSkillId = - extractInstalledSkillId(agentResult.outputText ?? '') ?? undefined; - - if (!installedSkillId) { - await wizardAbort({ - message: - 'The wizard could not determine which integration skill was installed during bootstrap.', - error: new WizardError('Bootstrap step did not emit installed skill id'), - }); - } - - // ── Step 2: Read SKILL.md and seed the queue from its frontmatter ── - - if (!agentResult.error && installedSkillId) { - const skillMdPath = path.join( - session.installDir, - '.claude', - 'skills', - installedSkillId, - 'SKILL.md', - ); - const skillMdContent = fs.readFileSync(skillMdPath, 'utf-8'); - const workflowSteps = parseWorkflowStepsFromSkillMd(skillMdContent); + let agentResult: Awaited>; - if (workflowSteps.length === 0) { - logToFile( - '[agent-runner] No workflow steps found in SKILL.md frontmatter, aborting', - ); - await wizardAbort({ - message: - 'The installed skill does not contain workflow steps in its metadata.', - error: new WizardError('No workflow steps in SKILL.md frontmatter'), - }); - } - - logToFile( - `[agent-runner] Seeded queue from SKILL.md: ${workflowSteps - .map((s) => s.stepId) - .join(', ')}`, + if (useQueuedWorkflow) { + agentResult = await runQueuedWorkflow( + agent, + config, + session, + promptContext, + frameworkContext, + spinner, + middleware, ); - - // ── Step 3: Execute workflow steps + env-vars from the queue ── - - const queue = createPostBootstrapQueue(workflowSteps); - getUI().setWorkQueue(queue); - - while (queue.length > 0) { - const queueItem = queue.dequeue()!; - - getUI().setCurrentQueueItem({ id: queueItem.id, label: queueItem.label }); - - const prompt = buildQueuedPrompt( - queueItem, - config, - promptContext, - installedSkillId, - ); - - agentResult = await runAgent( - agent, - prompt, - sessionToOptions(session), - spinner, - { - estimatedDurationMinutes: config.ui.estimatedDurationMinutes, - spinnerMessage: getQueueSpinnerMessage(queueItem), - successMessage: getQueueSuccessMessage(queueItem, config), - errorMessage: `Integration failed during ${queueItem.id}`, - additionalFeatureQueue: - queueItem.id === 'env-vars' ? session.additionalFeatureQueue : [], - resumeSessionId: queuedSessionId, - requestRemark: queueItem.id === 'env-vars', - captureOutputText: false, - captureSessionId: false, - finalizeMiddleware: queue.length === 0, - }, - middleware, - ); - - getUI().completeQueueItem({ id: queueItem.id, label: queueItem.label }); - - if (agentResult.error) { - break; - } - } - getUI().setCurrentQueueItem(null); + } else { + // OLD FLOW — single monolithic prompt (see legacy/ folder) + agentResult = await runSingleQueryFlow({ + agent, + config, + session, + options: sessionToOptions(session), + spinner, + promptContext, + frameworkContext, + middleware, + }); } // Handle error cases detected in agent output @@ -493,19 +414,141 @@ export async function runAgentWizard( await analytics.shutdown('success'); } -/** - * Build the integration prompt for the agent. - */ +// ── NEW FLOW: Queued workflow (wizard-queued-workflow=true) ────────── + +type PromptContext = { + frameworkVersion: string; + typescript: boolean; + projectApiKey: string; + host: string; + projectId: number; +}; + +async function runQueuedWorkflow( + agent: Awaited>, + config: FrameworkConfig, + session: WizardSession, + promptContext: PromptContext, + frameworkContext: Record, + spinner: SpinnerHandle, + middleware?: Parameters[5], +): Promise>> { + // Step 1: Bootstrap — install the skill and get its ID + let agentResult = await runAgent( + agent, + buildBootstrapPrompt(config, promptContext, frameworkContext), + sessionToOptions(session), + spinner, + { + estimatedDurationMinutes: config.ui.estimatedDurationMinutes, + spinnerMessage: 'Preparing integration...', + successMessage: 'Integration prepared', + errorMessage: 'Integration failed during bootstrap', + additionalFeatureQueue: [], + requestRemark: false, + captureOutputText: true, + captureSessionId: true, + finalizeMiddleware: false, + }, + middleware, + ); + + const queuedSessionId = agentResult.sessionId; + const installedSkillId = + extractInstalledSkillId(agentResult.outputText ?? '') ?? undefined; + + if (!installedSkillId) { + await wizardAbort({ + message: + 'The wizard could not determine which integration skill was installed during bootstrap.', + error: new WizardError('Bootstrap step did not emit installed skill id'), + }); + } + + // Step 2: Read SKILL.md and seed the queue from its frontmatter + if (!agentResult.error && installedSkillId) { + const skillMdPath = path.join( + session.installDir, + '.claude', + 'skills', + installedSkillId, + 'SKILL.md', + ); + const skillMdContent = fs.readFileSync(skillMdPath, 'utf-8'); + const workflowSteps = parseWorkflowStepsFromSkillMd(skillMdContent); + + if (workflowSteps.length === 0) { + logToFile( + '[agent-runner] No workflow steps found in SKILL.md frontmatter, aborting', + ); + await wizardAbort({ + message: + 'The installed skill does not contain workflow steps in its metadata.', + error: new WizardError('No workflow steps in SKILL.md frontmatter'), + }); + } + + logToFile( + `[agent-runner] Seeded queue from SKILL.md: ${workflowSteps + .map((s) => s.stepId) + .join(', ')}`, + ); + + // Step 3: Execute workflow steps + env-vars from the queue + const queue = createPostBootstrapQueue(workflowSteps); + getUI().setWorkQueue(queue); + + while (queue.length > 0) { + const queueItem = queue.dequeue()!; + + getUI().setCurrentQueueItem({ id: queueItem.id, label: queueItem.label }); + + const prompt = buildQueuedPrompt( + queueItem, + config, + promptContext, + installedSkillId, + ); + + agentResult = await runAgent( + agent, + prompt, + sessionToOptions(session), + spinner, + { + estimatedDurationMinutes: config.ui.estimatedDurationMinutes, + spinnerMessage: getQueueSpinnerMessage(queueItem), + successMessage: getQueueSuccessMessage(queueItem, config), + errorMessage: `Integration failed during ${queueItem.id}`, + additionalFeatureQueue: + queueItem.id === 'env-vars' ? session.additionalFeatureQueue : [], + resumeSessionId: queuedSessionId, + requestRemark: queueItem.id === 'env-vars', + captureOutputText: false, + captureSessionId: false, + finalizeMiddleware: queue.length === 0, + }, + middleware, + ); + + getUI().completeQueueItem({ id: queueItem.id, label: queueItem.label }); + + if (agentResult.error) { + break; + } + } + getUI().setCurrentQueueItem(null); + } + + return agentResult; +} + +// ── Queued workflow prompt builders ───────────────────────────────── + function buildQueuedPrompt( queueItem: WizardWorkflowQueueItem, config: FrameworkConfig, - context: { - frameworkVersion: string; - typescript: boolean; - projectApiKey: string; - host: string; - projectId: number; - }, + context: PromptContext, installedSkillId: string, ): string { if (queueItem.kind === 'workflow') { @@ -520,13 +563,7 @@ function buildQueuedPrompt( function buildProjectContextBlock( config: FrameworkConfig, - context: { - frameworkVersion: string; - typescript: boolean; - projectApiKey: string; - host: string; - projectId: number; - }, + context: PromptContext, frameworkContext: Record, ): string { const additionalLines = config.prompts.getAdditionalContextLines @@ -552,13 +589,7 @@ function buildProjectContextBlock( function buildBootstrapPrompt( config: FrameworkConfig, - context: { - frameworkVersion: string; - typescript: boolean; - projectApiKey: string; - host: string; - projectId: number; - }, + context: PromptContext, frameworkContext: Record, ): string { return `You have access to the PostHog MCP server which provides skills to integrate PostHog into this ${ @@ -603,6 +634,8 @@ function buildWorkflowStepPrompt( Read and follow this workflow reference: \`.claude/skills/${installedSkillId}/references/${referenceFilename}\` +Before starting work, use TodoWrite to create your task plan. Update it as you complete each task. + Important: - Complete only this workflow step. - Do NOT continue to any other workflow file. @@ -612,13 +645,7 @@ Important: function buildEnvVarPrompt( config: FrameworkConfig, - context: { - frameworkVersion: string; - typescript: boolean; - projectApiKey: string; - host: string; - projectId: number; - }, + context: PromptContext, ): string { return `Continue the existing conversation. diff --git a/src/lib/legacy/integration-prompt.ts b/src/lib/legacy/integration-prompt.ts new file mode 100644 index 00000000..058c0563 --- /dev/null +++ b/src/lib/legacy/integration-prompt.ts @@ -0,0 +1,82 @@ +/** + * OLD FLOW: Single monolithic integration prompt. + * + * Used when the `wizard-queued-workflow` feature flag is OFF. + * This prompt tells the agent to do everything in one shot: + * load skill menu → install skill → follow workflow files → set env vars. + * + * Delete this file once the queued workflow is the only path. + */ + +import { + DEFAULT_PACKAGE_INSTALLATION, + type FrameworkConfig, +} from '../framework-config.js'; +import { AgentSignals } from '../agent-interface.js'; + +export function buildIntegrationPrompt( + config: FrameworkConfig, + context: { + frameworkVersion: string; + typescript: boolean; + projectApiKey: string; + host: string; + projectId: number; + }, + frameworkContext: Record, +): string { + const additionalLines = config.prompts.getAdditionalContextLines + ? config.prompts.getAdditionalContextLines(frameworkContext) + : []; + + const additionalContext = + additionalLines.length > 0 + ? '\n' + additionalLines.map((line) => `- ${line}`).join('\n') + : ''; + + return `You have access to the PostHog MCP server which provides skills to integrate PostHog into this ${ + config.metadata.name + } project. + +Project context: +- PostHog Project ID: ${context.projectId} +- Framework: ${config.metadata.name} ${context.frameworkVersion} +- TypeScript: ${context.typescript ? 'Yes' : 'No'} +- PostHog public token: ${context.projectApiKey} +- PostHog Host: ${context.host} +- Project type: ${config.prompts.projectTypeDetection} +- Package installation: ${ + config.prompts.packageInstallation ?? DEFAULT_PACKAGE_INSTALLATION + }${additionalContext} + +Instructions (follow these steps IN ORDER - do not skip or reorder): + +STEP 1: Call load_skill_menu (from the wizard-tools MCP server) to see available skills. + If the tool fails, emit: ${ + AgentSignals.ERROR_MCP_MISSING + } Could not load skill menu and halt. + + Choose a skill from the \`integration\` category that matches this project's framework. Do NOT pick skills from other categories (llm-analytics, error-tracking, feature-flags, omnibus, etc.) — those are handled separately. + If no suitable integration skill is found, emit: ${ + AgentSignals.ERROR_RESOURCE_MISSING + } Could not find a suitable skill for this project. + +STEP 2: Call install_skill (from the wizard-tools MCP server) with the chosen skill ID (e.g., "integration-nextjs-app-router"). + Do NOT run any shell commands to install skills. + +STEP 3: Load the installed skill's SKILL.md file to understand what references are available. + +STEP 4: Follow the skill's workflow files in sequence. Look for numbered workflow files in the references (e.g., files with patterns like "1.0-", "1.1-", "1.2-"). Start with the first one and proceed through each step until completion. Each workflow file will tell you what to do and which file comes next. Never directly write PostHog tokens directly to code files; always use environment variables. + +STEP 5: Set up environment variables for PostHog using the wizard-tools MCP server (this runs locally — secret values never leave the machine): + - Use check_env_keys to see which keys already exist in the project's .env file (e.g. .env.local or .env). + - Use set_env_values to create or update the PostHog public token and host, using the appropriate environment variable naming convention for ${ + config.metadata.name + }, which you'll find in example code. The tool will also ensure .gitignore coverage. Don't assume the presence of keys means the value is up to date. Write the correct value each time. + - Reference these environment variables in the code files you create instead of hardcoding the public token and host. + +Important: Use the detect_package_manager tool (from the wizard-tools MCP server) to determine which package manager the project uses. Do not manually search for lockfiles or config files. Always install packages as a background task. Don't await completion; proceed with other work immediately after starting the installation. You must read a file immediately before attempting to write it, even if you have previously read it; failure to do so will cause a tool failure. + + +`; +} diff --git a/src/lib/legacy/single-query-runner.ts b/src/lib/legacy/single-query-runner.ts new file mode 100644 index 00000000..e271e1e6 --- /dev/null +++ b/src/lib/legacy/single-query-runner.ts @@ -0,0 +1,73 @@ +/** + * OLD FLOW: Single-query agent runner. + * + * Used when the `wizard-queued-workflow` feature flag is OFF. + * Sends one monolithic prompt that does everything in a single agent conversation. + * + * Delete this file once the queued workflow is the only path. + */ + +import { SPINNER_MESSAGE, type FrameworkConfig } from '../framework-config.js'; +import type { WizardSession } from '../wizard-session.js'; +import { runAgent } from '../agent-interface.js'; +import type { SpinnerHandle } from '../../ui/wizard-ui.js'; +import type { WizardOptions } from '../../utils/types.js'; +import { getUI } from '../../ui/index.js'; +import { buildIntegrationPrompt } from './integration-prompt'; + +/** + * OLD FLOW: Run the entire integration in a single agent query. + * + * Enqueues a single "Integration" queue item so the RunScreen + * displays tasks under it like the queued workflow does. + * + * Returns the agent result so the caller can handle errors uniformly. + */ +export async function runSingleQueryFlow(args: { + agent: Awaited< + ReturnType + >; + config: FrameworkConfig; + session: WizardSession; + options: WizardOptions; + spinner: SpinnerHandle; + promptContext: { + frameworkVersion: string; + typescript: boolean; + projectApiKey: string; + host: string; + projectId: number; + }; + frameworkContext: Record; + middleware?: Parameters[5]; +}) { + const integrationPrompt = buildIntegrationPrompt( + args.config, + args.promptContext, + args.frameworkContext, + ); + + // Set a single queue item so the RunScreen shows tasks nested under it + const queueItem = { id: 'integration', label: 'Integration' }; + getUI().setCurrentQueueItem(queueItem); + + const result = await runAgent( + args.agent, + integrationPrompt, + args.options, + args.spinner, + { + estimatedDurationMinutes: args.config.ui.estimatedDurationMinutes, + spinnerMessage: SPINNER_MESSAGE, + successMessage: args.config.ui.successMessage, + errorMessage: 'Integration failed', + additionalFeatureQueue: args.session.additionalFeatureQueue, + }, + args.middleware, + ); + + getUI().completeQueueItem(queueItem); + getUI().setCurrentQueueItem(null); + + return result; +} diff --git a/src/lib/wizard-tools.ts b/src/lib/wizard-tools.ts index 206c0183..7be7301e 100644 --- a/src/lib/wizard-tools.ts +++ b/src/lib/wizard-tools.ts @@ -30,7 +30,12 @@ async function getSDKModule(): Promise { // Skill types // --------------------------------------------------------------------------- -export type SkillEntry = { id: string; name: string; downloadUrl: string }; +export type SkillEntry = { + id: string; + name: string; + downloadUrl: string; + downloadUrlV2?: string; +}; export interface SkillMenu { categories: Record; @@ -77,6 +82,7 @@ export function downloadSkill( skillEntry: SkillEntry, installDir: string, skillsRoot?: string, + format: 'v1' | 'v2' = 'v1', ): { success: boolean; error?: string } { const skillDir = skillsRoot ? path.join(installDir, skillsRoot, skillEntry.id) @@ -84,8 +90,13 @@ export function downloadSkill( const tmpFile = `/tmp/posthog-skill-${skillEntry.id}.zip`; try { + const url = + format === 'v2' && skillEntry.downloadUrlV2 + ? skillEntry.downloadUrlV2 + : skillEntry.downloadUrl; + fs.mkdirSync(skillDir, { recursive: true }); - execFileSync('curl', ['-sL', skillEntry.downloadUrl, '-o', tmpFile], { + execFileSync('curl', ['-sL', url, '-o', tmpFile], { timeout: 30000, }); execFileSync('unzip', ['-o', tmpFile, '-d', skillDir], { @@ -98,7 +109,7 @@ export function downloadSkill( } logToFile( - `downloadSkill: installed ${skillEntry.id} from ${skillEntry.downloadUrl}`, + `downloadSkill: installed ${skillEntry.id} (${format}) from ${url}`, ); return { success: true }; } catch (err: any) { @@ -120,6 +131,9 @@ export interface WizardToolsOptions { /** Base URL for the skills server (e.g. http://localhost:8765 or GitHub releases URL) */ skillsBaseUrl: string; + + /** Skill format: v1 (continuation links in body) or v2 (workflow frontmatter). Default: v1. */ + skillFormat?: 'v1' | 'v2'; } // --------------------------------------------------------------------------- @@ -229,7 +243,12 @@ const SERVER_NAME = 'wizard-tools'; * Must be called asynchronously because the SDK is an ESM module loaded via dynamic import. */ export async function createWizardToolsServer(options: WizardToolsOptions) { - const { workingDirectory, detectPackageManager, skillsBaseUrl } = options; + const { + workingDirectory, + detectPackageManager, + skillsBaseUrl, + skillFormat = 'v1', + } = options; const sdk = await getSDKModule(); const { tool, createSdkMcpServer } = sdk; @@ -448,7 +467,12 @@ export async function createWizardToolsServer(options: WizardToolsOptions) { }; } - const result = downloadSkill(skill, workingDirectory); + const result = downloadSkill( + skill, + workingDirectory, + undefined, + skillFormat, + ); if (result.success) { return { content: [ diff --git a/src/lib/workflow-queue.ts b/src/lib/workflow-queue.ts index e3b47e29..20244943 100644 --- a/src/lib/workflow-queue.ts +++ b/src/lib/workflow-queue.ts @@ -130,7 +130,7 @@ export function createPostBootstrapQueue( ): WizardWorkflowQueue { const items: WizardWorkflowQueueItem[] = [ ...steps.map( - (step): WizardWorkflowQueueItem => ({ + (step, _): WizardWorkflowQueueItem => ({ id: `workflow:${step.stepId}`, kind: 'workflow', referenceFilename: step.referenceFilename, diff --git a/src/ui/logging-ui.ts b/src/ui/logging-ui.ts index 574c66f5..b7aa746f 100644 --- a/src/ui/logging-ui.ts +++ b/src/ui/logging-ui.ts @@ -4,9 +4,9 @@ * No prompts, no TUI, no interactivity. Just console output. */ -import { TaskStatus, type WizardUI, type SpinnerHandle } from './wizard-ui'; -import type { SettingsConflict } from '../lib/agent-interface'; -import type { WizardWorkflowQueue } from '../lib/workflow-queue'; +import { TaskStatus, type WizardUI, type SpinnerHandle } from './wizard-ui.js'; +import type { SettingsConflict } from '../lib/agent-interface.js'; +import type { WizardWorkflowQueue } from '../lib/workflow-queue.js'; export class LoggingUI implements WizardUI { intro(message: string): void { diff --git a/src/ui/tui/primitives/ProgressList.tsx b/src/ui/tui/primitives/ProgressList.tsx index 40355d2c..dfe30d89 100644 --- a/src/ui/tui/primitives/ProgressList.tsx +++ b/src/ui/tui/primitives/ProgressList.tsx @@ -23,8 +23,12 @@ interface ProgressListProps { } export const ProgressList = ({ items, title }: ProgressListProps) => { - const completed = items.filter((t) => t.status === 'completed').length; - const total = items.length; + // Only count top-level items (not indented sub-tasks) for progress + const topLevel = items.filter((t) => !t.indent); + const completed = topLevel.filter( + (t) => t.status === TaskStatus.Completed, + ).length; + const total = topLevel.length; return ( @@ -69,7 +73,7 @@ export const ProgressList = ({ items, title }: ProgressListProps) => { {completed < total ? `Progress: ${completed}/${total} completed` - : 'Cleaning up...'} + : 'Thinking about next steps...'} )} diff --git a/src/ui/tui/screens/RunScreen.tsx b/src/ui/tui/screens/RunScreen.tsx index b0811dfd..2410ce3d 100644 --- a/src/ui/tui/screens/RunScreen.tsx +++ b/src/ui/tui/screens/RunScreen.tsx @@ -41,53 +41,23 @@ export const RunScreen = ({ store }: RunScreenProps) => { const [columns] = useStdoutDimensions(); - // Build stage-grouped progress items - const progressItems: ProgressItem[] = []; - const current = store.currentQueueItem; - const completed = store.completedQueueItems; - const pendingQueue = store.workQueue?.toArray() ?? []; + const progressItems: ProgressItem[] = store.tasks.map((t) => ({ + label: t.label, + activeForm: t.activeForm, + status: t.status, + })); - // Completed stages - for (const item of completed) { - progressItems.push({ - label: item.label, - status: TaskStatus.Completed, - }); - } - - // Current stage header + nested agent tasks - if (current) { - progressItems.push({ - label: current.label, - activeForm: current.label, - status: TaskStatus.InProgress, - }); - // Nest agent tasks under current stage - for (const t of store.tasks) { - progressItems.push({ - label: t.label, - activeForm: t.activeForm, - status: t.status, - indent: 1, - }); - } - } - - // Pending queue items - for (const item of pendingQueue) { - progressItems.push({ - label: item.label, - status: TaskStatus.Pending, - }); - } - - // Additional features waiting + // When all tasks are done but the queue has features, show a transitional item const featureQueue = store.session.additionalFeatureQueue; - for (const feature of featureQueue) { - const nextLabel = ADDITIONAL_FEATURE_LABELS[feature]; + const allDone = + progressItems.length > 0 && + progressItems.every((t) => t.status === TaskStatus.Completed); + if (allDone && featureQueue.length > 0) { + const nextLabel = ADDITIONAL_FEATURE_LABELS[featureQueue[0]]; progressItems.push({ label: `Set up ${nextLabel}`, - status: TaskStatus.Pending, + activeForm: `Setting up ${nextLabel}...`, + status: TaskStatus.InProgress, }); } @@ -99,7 +69,9 @@ export const RunScreen = ({ store }: RunScreenProps) => { ) : ( store.setLearnCardComplete()} /> ); - const progressList = ; + const progressList = ( + + ); // On narrow terminals, drop the learn pane and show only progress const statusComponent = diff --git a/src/ui/tui/store.ts b/src/ui/tui/store.ts index d5d95584..6f690d8d 100644 --- a/src/ui/tui/store.ts +++ b/src/ui/tui/store.ts @@ -183,9 +183,6 @@ export class WizardStore { setCurrentQueueItem(item: { id: string; label: string } | null): void { this.$currentQueueItem.set(item); - // Clear agent tasks when transitioning to a new stage — - // each stage gets a fresh task list from TodoWrite - this.$tasks.set([]); this.emitChange(); } diff --git a/src/ui/wizard-ui.ts b/src/ui/wizard-ui.ts index 8a8beafc..85ea6c49 100644 --- a/src/ui/wizard-ui.ts +++ b/src/ui/wizard-ui.ts @@ -8,8 +8,8 @@ * Session-mutating methods trigger reactive screen resolution in the TUI. */ -import type { SettingsConflict } from '../lib/agent-interface'; -import type { WizardWorkflowQueue } from '../lib/workflow-queue'; +import type { SettingsConflict } from '../lib/agent-interface.js'; +import type { WizardWorkflowQueue } from '../lib/workflow-queue.js'; export enum TaskStatus { Pending = 'pending', From 3d0035e0a71839f811c9d8de70db3d3994c113a9 Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Wed, 8 Apr 2026 17:18:05 -0400 Subject: [PATCH 2/3] More clean ups --- README.md | 79 +++--- src/lib/__tests__/workflow-queue.test.ts | 69 +---- src/lib/agent-runner.ts | 309 +--------------------- src/lib/queued-workflow-runner.ts | 315 +++++++++++++++++++++++ src/lib/workflow-queue.ts | 22 -- 5 files changed, 359 insertions(+), 435 deletions(-) create mode 100644 src/lib/queued-workflow-runner.ts diff --git a/README.md b/README.md index af86c33e..367b9dde 100644 --- a/README.md +++ b/README.md @@ -218,28 +218,22 @@ The workflow is converted to `FlowEntry[]` via `workflowToFlowEntries()` and fed ### 3. Agent runner (`src/lib/agent-runner.ts`) -Once gates resolve, `runAgentWizard()` runs. This is where the queue takes over: +Once gates resolve, `runAgentWizard()` runs. It initializes the agent, evaluates feature flags, then forks based on `wizard-queued-workflow`: -**Bootstrap query** — A standalone query tells the agent to load the skill menu, pick and install a skill, read SKILL.md, and emit the installed skill ID via `[WIZARD-SKILL-ID] `. The model does NOT know about the queue — it just prepares the skill. +**Flag OFF (legacy)** — `runSingleQueryFlow()` in `src/lib/legacy/single-query-runner.ts` sends a single monolithic prompt (from `legacy/integration-prompt.ts`) that does everything in one agent conversation. This is the original behavior. -**SKILL.md parsing** — After bootstrap, the runner reads `.claude/skills//SKILL.md` from disk and parses the `workflow` array from its YAML frontmatter using `parseWorkflowStepsFromSkillMd()`. This produces a `WorkflowStepSeed[]` with step ids, reference filenames, and display titles. +**Flag ON (queued workflow)** — `runQueuedWorkflow()` in `src/lib/queued-workflow-runner.ts`: -**Queue seeding** — `createPostBootstrapQueue(steps)` builds a `WizardWorkflowQueue` from the parsed steps plus an `env-vars` step at the end. The queue is set on the store via `getUI().setWorkQueue(queue)` so the TUI can display it and dynamically enqueue new work. +1. **Bootstrap query** — installs the skill and emits `[WIZARD-SKILL-ID] ` +2. **SKILL.md parsing** — reads `workflow[]` from YAML frontmatter → `WorkflowStepSeed[]` +3. **Queue seeding** — `createPostBootstrapQueue(steps)` builds a `WizardWorkflowQueue` +4. **Execution loop** — pops items, sends per-step prompts, continues the same conversation via `resumeSessionId` -**Execution loop** — The runner pops items from the queue one at a time: -``` -while (queue.length > 0) { - dequeue → setCurrentQueueItem → build prompt → runAgent → completeQueueItem -} -``` - -Each `runAgent` call continues the same conversation via `resumeSessionId`. The model sees one prompt per step — either "read and follow this reference file" (for workflow items) or "set up environment variables" (for env-vars). The stop hook only fires the remark/feature-queue on the last item. +The skill format (v1 vs v2) is also controlled by the flag. v1 skills have continuation links in reference bodies. v2 skills have `workflow[]` frontmatter and no continuation links. ### 4. TUI progress tracking -During the run, the RunScreen displays a stage-grouped progress list. Stage headers come from queue item labels (which come from SKILL.md frontmatter titles). Nested tasks come from the agent's `TodoWrite` tool calls. When the runner advances to a new queue item, `setCurrentQueueItem()` fires, the store clears the task list, and the previous item moves to the completed list. - -The queue is reactive on the store — `enqueue()` and `dequeue()` trigger `emitChange()` which re-renders the UI immediately. +The RunScreen shows a flat task list from `store.tasks`, populated by the agent's `TodoWrite` tool calls. This is identical in both legacy and queued modes. The queue drives agent execution behind the scenes but doesn't affect the task display. ### 5. Post-run (`agent-runner.ts` after loop) @@ -255,27 +249,26 @@ bin.ts │ │ │ └─ Workflow (WorkflowStep[]) │ │ - │ └─ workflowToFlowEntries() → FlowEntry[] → Router (screen resolution) + │ └─ workflowToFlowEntries() → FlowEntry[] → Router │ ├─ await setupComplete (gate) ├─ await healthGateComplete (gate) │ └─ runAgentWizard() │ - ├─ Bootstrap query → skill installed → [WIZARD-SKILL-ID] + ├─ initializeAgent (skillFormat: v1 or v2 based on flag) │ - ├─ Read SKILL.md → parseWorkflowStepsFromSkillMd() → WorkflowStepSeed[] - │ - ├─ createPostBootstrapQueue(steps) → WizardWorkflowQueue - │ │ - │ └─ setWorkQueue(queue) → store (reactive, UI can enqueue) - │ - └─ while (queue.length > 0) + └─ if wizard-queued-workflow? │ - ├─ dequeue → setCurrentQueueItem - ├─ buildWorkflowStepPrompt / buildEnvVarPrompt - ├─ runAgent (continued conversation) - └─ completeQueueItem + ├─ YES → runQueuedWorkflow() [queued-workflow-runner.ts] + │ ├─ Bootstrap → [WIZARD-SKILL-ID] + │ ├─ Parse SKILL.md frontmatter → WorkflowStepSeed[] + │ ├─ createPostBootstrapQueue → WizardWorkflowQueue + │ └─ while (queue.length > 0) + │ dequeue → prompt → runAgent (continued) → complete + │ + └─ NO → runSingleQueryFlow() [legacy/single-query-runner.ts] + └─ buildIntegrationPrompt → single runAgent call ``` # Workflow queue @@ -289,19 +282,21 @@ The skill generator in `context-mill` writes a `workflow` array into each integr name: integration-nextjs-app-router workflow: - step_id: 1.0-begin - reference: basic-integration-1.0-begin.md - title: PostHog Setup - Begin + reference: basic-integration-v2-1.0-begin.md + title: Analyze project and plan events next: - - basic-integration-1.1-edit.md + - basic-integration-v2-1.1-edit.md - step_id: 1.1-edit - reference: basic-integration-1.1-edit.md - title: PostHog Setup - Edit + reference: basic-integration-v2-1.1-edit.md + title: Implement PostHog next: - - basic-integration-1.2-revise.md + - basic-integration-v2-1.2-revise.md # ... --- ``` +Only v2 skills (served from `basic-integration-v2/`) have this frontmatter. v1 skills use continuation links in the reference file bodies instead. + - `step_id` — unique identifier for the step - `reference` — filename in the skill's `references/` directory - `title` — human-readable label shown in the TUI progress list @@ -342,19 +337,17 @@ The queue is reactive — mutations trigger UI re-renders. Items enqueued while ## TUI progress display -The RunScreen shows a stage-grouped progress list: +The RunScreen shows a flat task list from the agent's `TodoWrite` calls — identical in both legacy and queued modes: ``` -☑ PostHog Setup - Begin -▶ PostHog Setup - Edit - ☑ Add PostHog to auth.ts - ▶ Add PostHog to checkout.ts -○ PostHog Setup - Revise -○ PostHog Setup - Conclusion -○ Environment variables +☑ Analyze project structure and plan events +▶ Installing posthog-node and implementing PostHog +○ Review and fix errors +○ Set up environment variables +○ Create PostHog dashboard and wrap up ``` -Stage headers come from queue item labels. Nested tasks come from the agent's `TodoWrite` calls. Tasks reset when the runner advances to a new stage. +The queue drives execution behind the scenes but the task list is entirely agent-controlled. ## Defining a workflow diff --git a/src/lib/__tests__/workflow-queue.test.ts b/src/lib/__tests__/workflow-queue.test.ts index 547af0c7..1becad06 100644 --- a/src/lib/__tests__/workflow-queue.test.ts +++ b/src/lib/__tests__/workflow-queue.test.ts @@ -1,6 +1,5 @@ import { WizardWorkflowQueue, - createInitialWizardWorkflowQueue, createPostBootstrapQueue, parseWorkflowStepsFromSkillMd, type WorkflowStepSeed, @@ -30,73 +29,7 @@ const BASIC_INTEGRATION_STEPS: WorkflowStepSeed[] = [ ]; describe('WizardWorkflowQueue', () => { - it('seeds a queue from workflow steps in the expected order', () => { - const queue = createInitialWizardWorkflowQueue(BASIC_INTEGRATION_STEPS); - - expect(queue.toArray()).toEqual([ - { id: 'bootstrap', kind: 'bootstrap', label: 'Preparing integration' }, - { - id: 'workflow:1.0-begin', - kind: 'workflow', - referenceFilename: 'basic-integration-1.0-begin.md', - label: 'PostHog Setup - Begin', - }, - { - id: 'workflow:1.1-edit', - kind: 'workflow', - referenceFilename: 'basic-integration-1.1-edit.md', - label: 'PostHog Setup - Edit', - }, - { - id: 'workflow:1.2-revise', - kind: 'workflow', - referenceFilename: 'basic-integration-1.2-revise.md', - label: 'PostHog Setup - Revise', - }, - { - id: 'workflow:1.3-conclude', - kind: 'workflow', - referenceFilename: 'basic-integration-1.3-conclude.md', - label: 'PostHog Setup - Conclusion', - }, - { id: 'env-vars', kind: 'env-vars', label: 'Environment variables' }, - ]); - }); - - it('builds a queue from arbitrary steps, not just basic-integration', () => { - const customSteps: WorkflowStepSeed[] = [ - { - stepId: 'setup', - referenceFilename: 'feature-flags-setup.md', - title: 'Setup', - }, - { - stepId: 'verify', - referenceFilename: 'feature-flags-verify.md', - title: 'Verify', - }, - ]; - const queue = createInitialWizardWorkflowQueue(customSteps); - - expect(queue.toArray()).toEqual([ - { id: 'bootstrap', kind: 'bootstrap', label: 'Preparing integration' }, - { - id: 'workflow:setup', - kind: 'workflow', - referenceFilename: 'feature-flags-setup.md', - label: 'Setup', - }, - { - id: 'workflow:verify', - kind: 'workflow', - referenceFilename: 'feature-flags-verify.md', - label: 'Verify', - }, - { id: 'env-vars', kind: 'env-vars', label: 'Environment variables' }, - ]); - }); - - it('createPostBootstrapQueue omits bootstrap', () => { + it('createPostBootstrapQueue builds queue without bootstrap', () => { const queue = createPostBootstrapQueue(BASIC_INTEGRATION_STEPS); const items = queue.toArray(); diff --git a/src/lib/agent-runner.ts b/src/lib/agent-runner.ts index 88ca6f15..5e120842 100644 --- a/src/lib/agent-runner.ts +++ b/src/lib/agent-runner.ts @@ -1,9 +1,4 @@ -import fs from 'fs'; -import path from 'path'; -import { - DEFAULT_PACKAGE_INSTALLATION, - type FrameworkConfig, -} from './framework-config'; +import { type FrameworkConfig } from './framework-config'; import { type WizardSession, OutroKind } from './wizard-session'; import { tryGetPackageJson, @@ -14,12 +9,12 @@ import type { PackageDotJson } from '../utils/package-json'; import type { WizardOptions } from '../utils/types'; import { WIZARD_INTERACTION_EVENT_NAME } from './constants'; import { analytics } from '../utils/analytics'; -import { getUI, type SpinnerHandle } from '../ui'; +import { getUI } from '../ui'; import { initializeAgent, runAgent, - AgentSignals, AgentErrorType, + AgentSignals, buildWizardMetadata, checkAllSettingsConflicts, backupAndFixClaudeSettings, @@ -40,14 +35,8 @@ import { registerCleanup, } from '../utils/wizard-abort'; import { formatScanReport, writeScanReport } from './yara-hooks'; -import { - createPostBootstrapQueue, - parseWorkflowStepsFromSkillMd, - type WizardWorkflowQueueItem, -} from './workflow-queue'; import { runSingleQueryFlow } from './legacy/single-query-runner'; - -const WIZARD_SKILL_ID_SIGNAL = '[WIZARD-SKILL-ID]'; +import { runQueuedWorkflow } from './queued-workflow-runner'; /** * Build a WizardOptions bag from a WizardSession (for code that still expects WizardOptions). @@ -288,6 +277,7 @@ export async function runAgentWizard( agent, config, session, + sessionToOptions(session), promptContext, frameworkContext, spinner, @@ -414,290 +404,5 @@ export async function runAgentWizard( await analytics.shutdown('success'); } -// ── NEW FLOW: Queued workflow (wizard-queued-workflow=true) ────────── - -type PromptContext = { - frameworkVersion: string; - typescript: boolean; - projectApiKey: string; - host: string; - projectId: number; -}; - -async function runQueuedWorkflow( - agent: Awaited>, - config: FrameworkConfig, - session: WizardSession, - promptContext: PromptContext, - frameworkContext: Record, - spinner: SpinnerHandle, - middleware?: Parameters[5], -): Promise>> { - // Step 1: Bootstrap — install the skill and get its ID - let agentResult = await runAgent( - agent, - buildBootstrapPrompt(config, promptContext, frameworkContext), - sessionToOptions(session), - spinner, - { - estimatedDurationMinutes: config.ui.estimatedDurationMinutes, - spinnerMessage: 'Preparing integration...', - successMessage: 'Integration prepared', - errorMessage: 'Integration failed during bootstrap', - additionalFeatureQueue: [], - requestRemark: false, - captureOutputText: true, - captureSessionId: true, - finalizeMiddleware: false, - }, - middleware, - ); - - const queuedSessionId = agentResult.sessionId; - const installedSkillId = - extractInstalledSkillId(agentResult.outputText ?? '') ?? undefined; - - if (!installedSkillId) { - await wizardAbort({ - message: - 'The wizard could not determine which integration skill was installed during bootstrap.', - error: new WizardError('Bootstrap step did not emit installed skill id'), - }); - } - - // Step 2: Read SKILL.md and seed the queue from its frontmatter - if (!agentResult.error && installedSkillId) { - const skillMdPath = path.join( - session.installDir, - '.claude', - 'skills', - installedSkillId, - 'SKILL.md', - ); - const skillMdContent = fs.readFileSync(skillMdPath, 'utf-8'); - const workflowSteps = parseWorkflowStepsFromSkillMd(skillMdContent); - - if (workflowSteps.length === 0) { - logToFile( - '[agent-runner] No workflow steps found in SKILL.md frontmatter, aborting', - ); - await wizardAbort({ - message: - 'The installed skill does not contain workflow steps in its metadata.', - error: new WizardError('No workflow steps in SKILL.md frontmatter'), - }); - } - - logToFile( - `[agent-runner] Seeded queue from SKILL.md: ${workflowSteps - .map((s) => s.stepId) - .join(', ')}`, - ); - - // Step 3: Execute workflow steps + env-vars from the queue - const queue = createPostBootstrapQueue(workflowSteps); - getUI().setWorkQueue(queue); - - while (queue.length > 0) { - const queueItem = queue.dequeue()!; - - getUI().setCurrentQueueItem({ id: queueItem.id, label: queueItem.label }); - - const prompt = buildQueuedPrompt( - queueItem, - config, - promptContext, - installedSkillId, - ); - - agentResult = await runAgent( - agent, - prompt, - sessionToOptions(session), - spinner, - { - estimatedDurationMinutes: config.ui.estimatedDurationMinutes, - spinnerMessage: getQueueSpinnerMessage(queueItem), - successMessage: getQueueSuccessMessage(queueItem, config), - errorMessage: `Integration failed during ${queueItem.id}`, - additionalFeatureQueue: - queueItem.id === 'env-vars' ? session.additionalFeatureQueue : [], - resumeSessionId: queuedSessionId, - requestRemark: queueItem.id === 'env-vars', - captureOutputText: false, - captureSessionId: false, - finalizeMiddleware: queue.length === 0, - }, - middleware, - ); - - getUI().completeQueueItem({ id: queueItem.id, label: queueItem.label }); - - if (agentResult.error) { - break; - } - } - getUI().setCurrentQueueItem(null); - } - - return agentResult; -} - -// ── Queued workflow prompt builders ───────────────────────────────── - -function buildQueuedPrompt( - queueItem: WizardWorkflowQueueItem, - config: FrameworkConfig, - context: PromptContext, - installedSkillId: string, -): string { - if (queueItem.kind === 'workflow') { - return buildWorkflowStepPrompt( - queueItem.referenceFilename, - installedSkillId, - ); - } - - return buildEnvVarPrompt(config, context); -} - -function buildProjectContextBlock( - config: FrameworkConfig, - context: PromptContext, - frameworkContext: Record, -): string { - const additionalLines = config.prompts.getAdditionalContextLines - ? config.prompts.getAdditionalContextLines(frameworkContext) - : []; - - const additionalContext = - additionalLines.length > 0 - ? '\n' + additionalLines.map((line) => `- ${line}`).join('\n') - : ''; - - return `Project context: -- PostHog Project ID: ${context.projectId} -- Framework: ${config.metadata.name} ${context.frameworkVersion} -- TypeScript: ${context.typescript ? 'Yes' : 'No'} -- PostHog public token: ${context.projectApiKey} -- PostHog Host: ${context.host} -- Project type: ${config.prompts.projectTypeDetection} -- Package installation: ${ - config.prompts.packageInstallation ?? DEFAULT_PACKAGE_INSTALLATION - }${additionalContext}`; -} - -function buildBootstrapPrompt( - config: FrameworkConfig, - context: PromptContext, - frameworkContext: Record, -): string { - return `You have access to the PostHog MCP server which provides skills to integrate PostHog into this ${ - config.metadata.name - } project. - -${buildProjectContextBlock(config, context, frameworkContext)} - -STEP 1: Call load_skill_menu (from the wizard-tools MCP server) to see available skills. - If the tool fails, emit: ${ - AgentSignals.ERROR_MCP_MISSING - } Could not load skill menu and halt. - - Choose a skill from the \`integration\` category that matches this project's framework. Do NOT pick skills from other categories (llm-analytics, error-tracking, feature-flags, omnibus, etc.) — those are handled separately. - If no suitable integration skill is found, emit: ${ - AgentSignals.ERROR_RESOURCE_MISSING - } Could not find a suitable skill for this project. - -STEP 2: Call install_skill (from the wizard-tools MCP server) with the chosen skill ID (e.g., "integration-nextjs-app-router"). - Do NOT run any shell commands to install skills. - -STEP 3: Load the installed skill's SKILL.md file to understand what references are available. - -STEP 4: When preparation is complete, emit exactly one line in this format: -${WIZARD_SKILL_ID_SIGNAL} - -Important: -- Do NOT execute any of the workflow reference files yet. -- Do NOT set up environment variables yet. -- Stop after preparation is complete. -- Use the detect_package_manager tool (from the wizard-tools MCP server) to determine which package manager the project uses. Do not manually search for lockfiles or config files. Always install packages as a background task. Don't await completion; proceed with other work immediately after starting the installation. You must read a file immediately before attempting to write it, even if you have previously read it; failure to do so will cause a tool failure. - -`; -} - -function buildWorkflowStepPrompt( - referenceFilename: string, - installedSkillId: string, -): string { - return `Continue the existing conversation. - -Read and follow this workflow reference: -\`.claude/skills/${installedSkillId}/references/${referenceFilename}\` - -Before starting work, use TodoWrite to create your task plan. Update it as you complete each task. - -Important: -- Complete only this workflow step. -- Do NOT continue to any other workflow file. -- Do NOT set up environment variables yet. -- Stop when this step is complete.`; -} - -function buildEnvVarPrompt( - config: FrameworkConfig, - context: PromptContext, -): string { - return `Continue the existing conversation. - -Execute the final queued environment-variable setup step for this ${ - config.metadata.name - } project. - -${buildProjectContextBlock(config, context, {})} - -Set up environment variables for PostHog using the wizard-tools MCP server (this runs locally — secret values never leave the machine): -- Use check_env_keys to see which keys already exist in the project's .env file (e.g. .env.local or .env). -- Use set_env_values to create or update the PostHog public token and host, using the appropriate environment variable naming convention for ${ - config.metadata.name - }, which you'll find in example code. The tool will also ensure .gitignore coverage. Don't assume the presence of keys means the value is up to date. Write the correct value each time. -- Reference these environment variables in the code files you create instead of hardcoding the public token and host. - -Stop after the environment-variable setup step is complete.`; -} - -function getQueueSpinnerMessage(queueItem: WizardWorkflowQueueItem): string { - switch (queueItem.kind) { - case 'bootstrap': - return 'Preparing integration...'; - case 'workflow': - return `Running step ${queueItem.id.replace('workflow:', '')}...`; - case 'env-vars': - return 'Finalizing environment variables...'; - } -} - -function getQueueSuccessMessage( - queueItem: WizardWorkflowQueueItem, - config: FrameworkConfig, -): string { - switch (queueItem.kind) { - case 'bootstrap': - return 'Integration prepared'; - case 'workflow': - return `Step ${queueItem.id.replace('workflow:', '')} complete`; - case 'env-vars': - return config.ui.successMessage; - } -} - -export function extractInstalledSkillId(outputText: string): string | null { - const match = outputText.match( - new RegExp( - `${WIZARD_SKILL_ID_SIGNAL.replace( - /[.*+?^${}()|[\]\\]/g, - '\\$&', - )}\\s+([A-Za-z0-9._-]+)`, - ), - ); - return match?.[1] ?? null; -} +// Re-export for tests +export { extractInstalledSkillId } from './queued-workflow-runner'; diff --git a/src/lib/queued-workflow-runner.ts b/src/lib/queued-workflow-runner.ts new file mode 100644 index 00000000..1150eb37 --- /dev/null +++ b/src/lib/queued-workflow-runner.ts @@ -0,0 +1,315 @@ +/** + * NEW FLOW: Queued workflow runner. + * + * Used when the `wizard-queued-workflow` feature flag is ON. + * Bootstrap → parse SKILL.md frontmatter → per-step continued queries. + * + * Delete the legacy/ folder once this is the only path. + */ + +import fs from 'fs'; +import path from 'path'; +import { + DEFAULT_PACKAGE_INSTALLATION, + type FrameworkConfig, +} from './framework-config'; +import type { WizardSession } from './wizard-session'; +import type { WizardOptions } from '../utils/types'; +import { getUI, type SpinnerHandle } from '../ui'; +import { initializeAgent, runAgent, AgentSignals } from './agent-interface'; +import { logToFile } from '../utils/debug'; +import { wizardAbort, WizardError } from '../utils/wizard-abort'; +import { + createPostBootstrapQueue, + parseWorkflowStepsFromSkillMd, + type WizardWorkflowQueueItem, +} from './workflow-queue'; + +const WIZARD_SKILL_ID_SIGNAL = '[WIZARD-SKILL-ID]'; + +export type PromptContext = { + frameworkVersion: string; + typescript: boolean; + projectApiKey: string; + host: string; + projectId: number; +}; + +export async function runQueuedWorkflow( + agent: Awaited>, + config: FrameworkConfig, + session: WizardSession, + options: WizardOptions, + promptContext: PromptContext, + frameworkContext: Record, + spinner: SpinnerHandle, + middleware?: Parameters[5], +): Promise>> { + // Step 1: Bootstrap — install the skill and get its ID + let agentResult = await runAgent( + agent, + buildBootstrapPrompt(config, promptContext, frameworkContext), + options, + spinner, + { + estimatedDurationMinutes: config.ui.estimatedDurationMinutes, + spinnerMessage: 'Preparing integration...', + successMessage: 'Integration prepared', + errorMessage: 'Integration failed during bootstrap', + additionalFeatureQueue: [], + requestRemark: false, + captureOutputText: true, + captureSessionId: true, + finalizeMiddleware: false, + }, + middleware, + ); + + const queuedSessionId = agentResult.sessionId; + const installedSkillId = + extractInstalledSkillId(agentResult.outputText ?? '') ?? undefined; + + if (!installedSkillId) { + await wizardAbort({ + message: + 'The wizard could not determine which integration skill was installed during bootstrap.', + error: new WizardError('Bootstrap step did not emit installed skill id'), + }); + } + + // Step 2: Read SKILL.md and seed the queue from its frontmatter + if (!agentResult.error && installedSkillId) { + const skillMdPath = path.join( + session.installDir, + '.claude', + 'skills', + installedSkillId, + 'SKILL.md', + ); + const skillMdContent = fs.readFileSync(skillMdPath, 'utf-8'); + const workflowSteps = parseWorkflowStepsFromSkillMd(skillMdContent); + + if (workflowSteps.length === 0) { + logToFile( + '[agent-runner] No workflow steps found in SKILL.md frontmatter, aborting', + ); + await wizardAbort({ + message: + 'The installed skill does not contain workflow steps in its metadata.', + error: new WizardError('No workflow steps in SKILL.md frontmatter'), + }); + } + + logToFile( + `[agent-runner] Seeded queue from SKILL.md: ${workflowSteps + .map((s) => s.stepId) + .join(', ')}`, + ); + + // Step 3: Execute workflow steps + env-vars from the queue + const queue = createPostBootstrapQueue(workflowSteps); + getUI().setWorkQueue(queue); + + while (queue.length > 0) { + const queueItem = queue.dequeue()!; + + getUI().setCurrentQueueItem({ id: queueItem.id, label: queueItem.label }); + + const prompt = buildQueuedPrompt( + queueItem, + config, + promptContext, + installedSkillId, + ); + + agentResult = await runAgent( + agent, + prompt, + options, + spinner, + { + estimatedDurationMinutes: config.ui.estimatedDurationMinutes, + spinnerMessage: getQueueSpinnerMessage(queueItem), + successMessage: getQueueSuccessMessage(queueItem, config), + errorMessage: `Integration failed during ${queueItem.id}`, + additionalFeatureQueue: + queueItem.id === 'env-vars' ? session.additionalFeatureQueue : [], + resumeSessionId: queuedSessionId, + requestRemark: queueItem.id === 'env-vars', + captureOutputText: false, + captureSessionId: false, + finalizeMiddleware: queue.length === 0, + }, + middleware, + ); + + getUI().completeQueueItem({ id: queueItem.id, label: queueItem.label }); + + if (agentResult.error) { + break; + } + } + getUI().setCurrentQueueItem(null); + } + + return agentResult; +} + +export function extractInstalledSkillId(outputText: string): string | null { + const match = outputText.match( + new RegExp( + `${WIZARD_SKILL_ID_SIGNAL.replace( + /[.*+?^${}()|[\]\\]/g, + '\\$&', + )}\\s+([A-Za-z0-9._-]+)`, + ), + ); + return match?.[1] ?? null; +} + +// ── Prompt builders ───────────────────────────────────────────────── + +function buildQueuedPrompt( + queueItem: WizardWorkflowQueueItem, + config: FrameworkConfig, + context: PromptContext, + installedSkillId: string, +): string { + if (queueItem.kind === 'workflow') { + return buildWorkflowStepPrompt( + queueItem.referenceFilename, + installedSkillId, + ); + } + + return buildEnvVarPrompt(config, context); +} + +function buildProjectContextBlock( + config: FrameworkConfig, + context: PromptContext, + frameworkContext: Record, +): string { + const additionalLines = config.prompts.getAdditionalContextLines + ? config.prompts.getAdditionalContextLines(frameworkContext) + : []; + + const additionalContext = + additionalLines.length > 0 + ? '\n' + additionalLines.map((line) => `- ${line}`).join('\n') + : ''; + + return `Project context: +- PostHog Project ID: ${context.projectId} +- Framework: ${config.metadata.name} ${context.frameworkVersion} +- TypeScript: ${context.typescript ? 'Yes' : 'No'} +- PostHog public token: ${context.projectApiKey} +- PostHog Host: ${context.host} +- Project type: ${config.prompts.projectTypeDetection} +- Package installation: ${ + config.prompts.packageInstallation ?? DEFAULT_PACKAGE_INSTALLATION + }${additionalContext}`; +} + +function buildBootstrapPrompt( + config: FrameworkConfig, + context: PromptContext, + frameworkContext: Record, +): string { + return `You have access to the PostHog MCP server which provides skills to integrate PostHog into this ${ + config.metadata.name + } project. + +${buildProjectContextBlock(config, context, frameworkContext)} + +STEP 1: Call load_skill_menu (from the wizard-tools MCP server) to see available skills. + If the tool fails, emit: ${ + AgentSignals.ERROR_MCP_MISSING + } Could not load skill menu and halt. + + Choose a skill from the \`integration\` category that matches this project's framework. Do NOT pick skills from other categories (llm-analytics, error-tracking, feature-flags, omnibus, etc.) — those are handled separately. + If no suitable integration skill is found, emit: ${ + AgentSignals.ERROR_RESOURCE_MISSING + } Could not find a suitable skill for this project. + +STEP 2: Call install_skill (from the wizard-tools MCP server) with the chosen skill ID (e.g., "integration-nextjs-app-router"). + Do NOT run any shell commands to install skills. + +STEP 3: Load the installed skill's SKILL.md file to understand what references are available. + +STEP 4: When preparation is complete, emit exactly one line in this format: +${WIZARD_SKILL_ID_SIGNAL} + +Important: +- Do NOT execute any of the workflow reference files yet. +- Do NOT set up environment variables yet. +- Stop after preparation is complete. +- Use the detect_package_manager tool (from the wizard-tools MCP server) to determine which package manager the project uses. Do not manually search for lockfiles or config files. Always install packages as a background task. Don't await completion; proceed with other work immediately after starting the installation. You must read a file immediately before attempting to write it, even if you have previously read it; failure to do so will cause a tool failure. + +`; +} + +function buildWorkflowStepPrompt( + referenceFilename: string, + installedSkillId: string, +): string { + return `Continue the existing conversation. + +Read and follow this workflow reference: +\`.claude/skills/${installedSkillId}/references/${referenceFilename}\` + +Before starting work, use TodoWrite to create your task plan. Update it as you complete each task. + +Important: +- Complete only this workflow step. +- Do NOT continue to any other workflow file. +- Do NOT set up environment variables yet. +- Stop when this step is complete.`; +} + +function buildEnvVarPrompt( + config: FrameworkConfig, + context: PromptContext, +): string { + return `Continue the existing conversation. + +Execute the final queued environment-variable setup step for this ${ + config.metadata.name + } project. + +${buildProjectContextBlock(config, context, {})} + +Set up environment variables for PostHog using the wizard-tools MCP server (this runs locally — secret values never leave the machine): +- Use check_env_keys to see which keys already exist in the project's .env file (e.g. .env.local or .env). +- Use set_env_values to create or update the PostHog public token and host, using the appropriate environment variable naming convention for ${ + config.metadata.name + }, which you'll find in example code. The tool will also ensure .gitignore coverage. Don't assume the presence of keys means the value is up to date. Write the correct value each time. +- Reference these environment variables in the code files you create instead of hardcoding the public token and host. + +Stop after the environment-variable setup step is complete.`; +} + +function getQueueSpinnerMessage(queueItem: WizardWorkflowQueueItem): string { + switch (queueItem.kind) { + case 'bootstrap': + return 'Preparing integration...'; + case 'workflow': + return `Running step ${queueItem.id.replace('workflow:', '')}...`; + case 'env-vars': + return 'Finalizing environment variables...'; + } +} + +function getQueueSuccessMessage( + queueItem: WizardWorkflowQueueItem, + config: FrameworkConfig, +): string { + switch (queueItem.kind) { + case 'bootstrap': + return 'Integration prepared'; + case 'workflow': + return `Step ${queueItem.id.replace('workflow:', '')} complete`; + case 'env-vars': + return config.ui.successMessage; + } +} diff --git a/src/lib/workflow-queue.ts b/src/lib/workflow-queue.ts index 20244943..a453065a 100644 --- a/src/lib/workflow-queue.ts +++ b/src/lib/workflow-queue.ts @@ -99,28 +99,6 @@ export function parseWorkflowStepsFromSkillMd( return steps; } -/** - * Build the initial queue from an ordered list of workflow steps. - * The queue is always: bootstrap → workflow steps → env-vars. - */ -export function createInitialWizardWorkflowQueue( - steps: WorkflowStepSeed[], -): WizardWorkflowQueue { - const items: WizardWorkflowQueueItem[] = [ - { id: 'bootstrap', kind: 'bootstrap', label: 'Preparing integration' }, - ...steps.map( - (step): WizardWorkflowQueueItem => ({ - id: `workflow:${step.stepId}`, - kind: 'workflow', - referenceFilename: step.referenceFilename, - label: step.title, - }), - ), - { id: 'env-vars', kind: 'env-vars', label: 'Environment variables' }, - ]; - return new WizardWorkflowQueue(items); -} - /** * Build a queue with only workflow steps + env-vars (no bootstrap). * Used after bootstrap has already run and SKILL.md has been parsed. From 3c0adec04062c09f80935245e1c2ba79244e2e29 Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Wed, 8 Apr 2026 17:53:23 -0400 Subject: [PATCH 3/3] More clean up --- README.md | 226 ++++++++----------------------------- src/lib/agent-interface.ts | 6 +- src/lib/wizard-tools.ts | 27 ++--- 3 files changed, 59 insertions(+), 200 deletions(-) diff --git a/README.md b/README.md index 367b9dde..f738e00d 100644 --- a/README.md +++ b/README.md @@ -198,205 +198,71 @@ To make your version of a tool usable with a one-line `npx` command: your project directory 3. Now you can run it with `npx yourpackagename` -# Wizard execution flow +# Execution flow -## Full lifecycle - -When a user runs `npx @posthog/wizard`, here's what happens end-to-end: - -### 1. CLI parsing and framework detection (`bin.ts` → `src/run.ts`) - -`bin.ts` parses CLI args, checks Node version, and calls `runWizard()` in `src/run.ts`. The run function detects the project framework (Next.js, React, etc.) by inspecting `package.json` and project structure, then loads the matching `FrameworkConfig` from `src/frameworks/`. - -### 2. TUI startup and UI flow (`src/ui/tui/start-tui.ts`) - -The TUI renders and the user progresses through screens. Screen order is driven by a `Workflow` — an ordered list of `WorkflowStep` objects defined in `src/lib/workflows/posthog-integration.ts`. Each step declares which screen it owns and when that screen is complete. - -The workflow is converted to `FlowEntry[]` via `workflowToFlowEntries()` and fed to the router. The router walks the entries, skipping completed/hidden screens, and returns the first incomplete one. This is reactive — every session mutation re-resolves the active screen. - -**Gate steps** block downstream code. The `intro` step has `gate: 'setup'` — `bin.ts` awaits `store.setupComplete` before proceeding. The `health-check` step has `gate: 'health'` — `bin.ts` awaits `store.healthGateComplete`. - -### 3. Agent runner (`src/lib/agent-runner.ts`) - -Once gates resolve, `runAgentWizard()` runs. It initializes the agent, evaluates feature flags, then forks based on `wizard-queued-workflow`: - -**Flag OFF (legacy)** — `runSingleQueryFlow()` in `src/lib/legacy/single-query-runner.ts` sends a single monolithic prompt (from `legacy/integration-prompt.ts`) that does everything in one agent conversation. This is the original behavior. - -**Flag ON (queued workflow)** — `runQueuedWorkflow()` in `src/lib/queued-workflow-runner.ts`: - -1. **Bootstrap query** — installs the skill and emits `[WIZARD-SKILL-ID] ` -2. **SKILL.md parsing** — reads `workflow[]` from YAML frontmatter → `WorkflowStepSeed[]` -3. **Queue seeding** — `createPostBootstrapQueue(steps)` builds a `WizardWorkflowQueue` -4. **Execution loop** — pops items, sends per-step prompts, continues the same conversation via `resumeSessionId` - -The skill format (v1 vs v2) is also controlled by the flag. v1 skills have continuation links in reference bodies. v2 skills have `workflow[]` frontmatter and no continuation links. - -### 4. TUI progress tracking - -The RunScreen shows a flat task list from `store.tasks`, populated by the agent's `TodoWrite` tool calls. This is identical in both legacy and queued modes. The queue drives agent execution behind the scenes but doesn't affect the task display. - -### 5. Post-run (`agent-runner.ts` after loop) - -After the queue drains: error handling, env var upload to hosting providers, outro data construction, analytics shutdown. - -## Data flow diagram - -``` -bin.ts - │ - ├─ Framework detection → FrameworkConfig - ├─ TUI startup → WizardStore + Router - │ │ - │ └─ Workflow (WorkflowStep[]) - │ │ - │ └─ workflowToFlowEntries() → FlowEntry[] → Router - │ - ├─ await setupComplete (gate) - ├─ await healthGateComplete (gate) - │ - └─ runAgentWizard() - │ - ├─ initializeAgent (skillFormat: v1 or v2 based on flag) - │ - └─ if wizard-queued-workflow? - │ - ├─ YES → runQueuedWorkflow() [queued-workflow-runner.ts] - │ ├─ Bootstrap → [WIZARD-SKILL-ID] - │ ├─ Parse SKILL.md frontmatter → WorkflowStepSeed[] - │ ├─ createPostBootstrapQueue → WizardWorkflowQueue - │ └─ while (queue.length > 0) - │ dequeue → prompt → runAgent (continued) → complete - │ - └─ NO → runSingleQueryFlow() [legacy/single-query-runner.ts] - └─ buildIntegrationPrompt → single runAgent call ``` - -# Workflow queue - -## SKILL.md frontmatter format - -The skill generator in `context-mill` writes a `workflow` array into each integration skill's frontmatter: - -```yaml ---- -name: integration-nextjs-app-router -workflow: - - step_id: 1.0-begin - reference: basic-integration-v2-1.0-begin.md - title: Analyze project and plan events - next: - - basic-integration-v2-1.1-edit.md - - step_id: 1.1-edit - reference: basic-integration-v2-1.1-edit.md - title: Implement PostHog - next: - - basic-integration-v2-1.2-revise.md - # ... ---- +bin.ts → detect framework → start TUI → await gates → runAgentWizard() + │ + wizard-queued-workflow flag? + │ │ + ON OFF + │ │ + queued-workflow-runner legacy/single-query-runner + │ │ + bootstrap query one big prompt + parse SKILL.md (does everything) + queue loop + per-step prompts ``` -Only v2 skills (served from `basic-integration-v2/`) have this frontmatter. v1 skills use continuation links in the reference file bodies instead. - -- `step_id` — unique identifier for the step -- `reference` — filename in the skill's `references/` directory -- `title` — human-readable label shown in the TUI progress list -- `next` — array of next step references (for future parallelization) - -## Queue item types - -```typescript -type WizardWorkflowQueueItem = - | { id: 'bootstrap'; kind: 'bootstrap'; label: string } - | { id: string; kind: 'workflow'; referenceFilename: string; label: string } - | { id: 'env-vars'; kind: 'env-vars'; label: string }; -``` +## 1. CLI + framework detection -## Enqueueing work dynamically - -The queue is exposed to the UI via `store.workQueue`. To add work during a run: - -```typescript -// Insert at front of queue (runs next) -store.workQueue.enqueueNext({ - id: 'my-task', - kind: 'workflow', - referenceFilename: 'my-reference.md', - label: 'My custom step', -}); - -// Append to end of queue -store.workQueue.enqueue({ - id: 'my-task', - kind: 'workflow', - referenceFilename: 'my-reference.md', - label: 'My custom step', -}); -``` +`bin.ts` → `src/run.ts` → detects framework → loads `FrameworkConfig`. -The queue is reactive — mutations trigger UI re-renders. Items enqueued while the runner loop is active will be picked up when the current step finishes. +## 2. TUI screens -## TUI progress display +Screen flow is a `Workflow` (`WorkflowStep[]`) defined in `src/lib/workflows/posthog-integration.ts`. Converted to `FlowEntry[]` via `workflowToFlowEntries()` for the router. Gate steps (`setup`, `health`) block the agent runner until the user completes them. -The RunScreen shows a flat task list from the agent's `TodoWrite` calls — identical in both legacy and queued modes: +## 3. Agent runner -``` -☑ Analyze project structure and plan events -▶ Installing posthog-node and implementing PostHog -○ Review and fix errors -○ Set up environment variables -○ Create PostHog dashboard and wrap up -``` +`agent-runner.ts` initializes the agent, then forks on the `wizard-queued-workflow` feature flag. -The queue drives execution behind the scenes but the task list is entirely agent-controlled. +**Flag OFF** → `legacy/single-query-runner.ts` — one prompt, one `runAgent` call. Skills downloaded as v1 (continuation links in reference bodies). -## Defining a workflow +**Flag ON** → `queued-workflow-runner.ts` — bootstrap installs the skill, SKILL.md frontmatter is parsed into a queue, then each step runs as a continued conversation. Skills downloaded as v2 (workflow metadata in frontmatter, no continuation links). -A workflow is an ordered list of `WorkflowStep` objects. Each step can own a screen, agent work, or both. +## 4. Queued workflow detail -```typescript -// src/lib/workflow-step.ts -interface WorkflowStep { - id: string; // unique step id - label: string; // shown in progress list - screen?: string; // TUI screen (e.g. 'intro', 'run') - show?: (session: WizardSession) => boolean; // visibility predicate - isComplete?: (session: WizardSession) => boolean; // completion predicate - gate?: 'setup' | 'health'; // blocks downstream code -} -``` +1. **Bootstrap** — installs skill, emits `[WIZARD-SKILL-ID] ` +2. **Parse SKILL.md** — `parseWorkflowStepsFromSkillMd()` extracts `workflow[]` from frontmatter +3. **Seed queue** — `createPostBootstrapQueue(steps)` builds a `WizardWorkflowQueue` +4. **Execute** — dequeue → build prompt → `runAgent` with `resumeSessionId` → repeat -The current PostHog integration workflow is defined in `src/lib/workflows/posthog-integration.ts`: - -```typescript -export const POSTHOG_INTEGRATION_WORKFLOW: Workflow = [ - { id: 'intro', label: 'Welcome', screen: 'intro', gate: 'setup', isComplete: ... }, - { id: 'health', label: 'Health check', screen: 'health-check', gate: 'health', ... }, - { id: 'setup', label: 'Setup', screen: 'setup', show: needsSetup, ... }, - { id: 'auth', label: 'Authentication', screen: 'auth', isComplete: ... }, - { id: 'run', label: 'Integration', screen: 'run', isComplete: ... }, - { id: 'mcp', label: 'MCP servers', screen: 'mcp', isComplete: ... }, - { id: 'outro', label: 'Done', screen: 'outro', isComplete: ... }, - { id: 'skills', label: 'Skills', screen: 'skills' }, -]; -``` +The queue is reactive on the store — UI or business logic can `enqueue()` / `enqueueNext()` during the run. -### Creating a new workflow +## 5. v1 vs v2 skills -1. Create a new file in `src/lib/workflows/` (e.g. `feature-flags.ts`) -2. Export a `Workflow` array with your steps -3. Each step with a `screen` field needs a matching component in the screen registry -4. The flow engine converts your workflow to `FlowEntry[]` via `workflowToFlowEntries()` — the existing router handles the rest -5. Agent work steps are seeded from SKILL.md frontmatter at runtime, not from the workflow definition +Controlled by the `wizard-queued-workflow` flag. The `install_skill` MCP tool checks `useV2Skills` and picks the right download URL. -### How the pieces connect +| | v1 (legacy) | v2 (queued) | +|---|---|---| +| Source files | `llm-prompts/basic-integration/` | `llm-prompts/basic-integration-v2/` | +| Step ordering | Continuation links in body | `workflow[]` in SKILL.md frontmatter | +| `next` defined in | Computed by generator | Workflow file frontmatter | +| Served from | `dist/skills/` | `dist/basic-integration-v2/` | -``` -WorkflowStep[] ──workflowToFlowEntries()──> FlowEntry[] ──> Router (screen resolution) - │ -SKILL.md frontmatter ──parseWorkflowStepsFromSkillMd()──> Queue ──> Agent runner (per-step queries) -``` +## 6. Key files -The workflow definition owns the UI flow. The SKILL.md frontmatter owns the agent work sequence. Both run during the same wizard session. +| File | Purpose | +|---|---| +| `src/lib/agent-runner.ts` | Shared setup, flag fork, error handling | +| `src/lib/queued-workflow-runner.ts` | New flow: bootstrap → queue → per-step prompts | +| `src/lib/legacy/single-query-runner.ts` | Old flow: one prompt, one call | +| `src/lib/legacy/integration-prompt.ts` | Old flow: prompt builder | +| `src/lib/workflow-queue.ts` | Queue, parser, seed functions | +| `src/lib/workflow-step.ts` | `WorkflowStep` interface, `workflowToFlowEntries()` | +| `src/lib/workflows/posthog-integration.ts` | TUI screen flow as `WorkflowStep[]` | +| `src/lib/wizard-tools.ts` | MCP tools including `install_skill` (v1/v2) | # Health checks diff --git a/src/lib/agent-interface.ts b/src/lib/agent-interface.ts index da7360e6..45bc8344 100644 --- a/src/lib/agent-interface.ts +++ b/src/lib/agent-interface.ts @@ -647,15 +647,11 @@ export async function initializeAgent( }; // Add in-process wizard tools (env files, package manager detection, skill loading) - // Use v2 skill format when the queued workflow flag is on - const skillFormat = - config.wizardFlags?.['wizard-queued-workflow'] === 'true' ? 'v2' : 'v1'; - const wizardToolsServer = await createWizardToolsServer({ workingDirectory: config.workingDirectory, detectPackageManager: config.detectPackageManager, skillsBaseUrl: config.skillsBaseUrl, - skillFormat, + useV2Skills: config.wizardFlags?.['wizard-queued-workflow'] === 'true', }); mcpServers['wizard-tools'] = wizardToolsServer; diff --git a/src/lib/wizard-tools.ts b/src/lib/wizard-tools.ts index 7be7301e..e62ef2ad 100644 --- a/src/lib/wizard-tools.ts +++ b/src/lib/wizard-tools.ts @@ -75,26 +75,25 @@ export async function fetchSkillMenu( /** * Download and extract a skill. - * By default installs to `/.claude/skills//`. - * Pass `skillsRoot` to override the base directory (e.g. `.posthog/skills`). + * Installs to `/.claude/skills//` by default, or `///` if specified. + * When `useV2` is true, downloads the v2 variant (workflow frontmatter) if available. */ export function downloadSkill( skillEntry: SkillEntry, installDir: string, skillsRoot?: string, - format: 'v1' | 'v2' = 'v1', + useV2 = false, ): { success: boolean; error?: string } { const skillDir = skillsRoot ? path.join(installDir, skillsRoot, skillEntry.id) : path.join(installDir, '.claude', 'skills', skillEntry.id); const tmpFile = `/tmp/posthog-skill-${skillEntry.id}.zip`; + const url = + useV2 && skillEntry.downloadUrlV2 + ? skillEntry.downloadUrlV2 + : skillEntry.downloadUrl; try { - const url = - format === 'v2' && skillEntry.downloadUrlV2 - ? skillEntry.downloadUrlV2 - : skillEntry.downloadUrl; - fs.mkdirSync(skillDir, { recursive: true }); execFileSync('curl', ['-sL', url, '-o', tmpFile], { timeout: 30000, @@ -108,9 +107,7 @@ export function downloadSkill( /* ignore cleanup errors */ } - logToFile( - `downloadSkill: installed ${skillEntry.id} (${format}) from ${url}`, - ); + logToFile(`downloadSkill: installed ${skillEntry.id} from ${url}`); return { success: true }; } catch (err: any) { logToFile(`downloadSkill: error: ${err.message}`); @@ -132,8 +129,8 @@ export interface WizardToolsOptions { /** Base URL for the skills server (e.g. http://localhost:8765 or GitHub releases URL) */ skillsBaseUrl: string; - /** Skill format: v1 (continuation links in body) or v2 (workflow frontmatter). Default: v1. */ - skillFormat?: 'v1' | 'v2'; + /** When true, download v2 skills (workflow frontmatter) instead of v1 (continuation links). */ + useV2Skills?: boolean; } // --------------------------------------------------------------------------- @@ -247,7 +244,7 @@ export async function createWizardToolsServer(options: WizardToolsOptions) { workingDirectory, detectPackageManager, skillsBaseUrl, - skillFormat = 'v1', + useV2Skills = false, } = options; const sdk = await getSDKModule(); const { tool, createSdkMcpServer } = sdk; @@ -471,7 +468,7 @@ export async function createWizardToolsServer(options: WizardToolsOptions) { skill, workingDirectory, undefined, - skillFormat, + useV2Skills, ); if (result.success) { return {