{"repo": "anhonestboy/agentflow", "files": {"src/cli.ts": {"6a36ef7461aac6f7246c55477f7802fe4ec1ee53": {"t": 1785745562.3378549, "n": 18604, "h": "b4d9c2026f734cf9", "c": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport type { ExecutorResolver } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { resolveModel } from './model-resolver.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport { runInit } from './commands/init.js';\nimport { OpenRouterExecutor } from './executors/openrouter-executor.js';\nimport { DeepSeekExecutor } from './executors/deepseek-executor.js';\nimport { HermesExecutor } from './executors/hermes-executor.js';\nimport { AgentSdkExecutor } from './executors/agent-sdk-executor.js';\nimport { buildRunReport, usageTotals, formatCostLine } from './cli-report.js';\nimport { AGENTFLOW_VERSION } from './version.js';\nimport type { WorkflowIR, AgentDef, WorkflowInstance } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n if (!existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\n/** Create an ExecutorResolver that resolves the model for each agent */\nfunction createExecutorResolver(\n toolRegistry: ReturnType,\n): ExecutorResolver {\n return (agent: AgentDef) => {\n const modelConfig = resolveModel(agent.model);\n process.stderr.write(\n chalk.dim(` šŸ“¦ [${agent.id}] model: ${modelConfig.model} (${modelConfig.provider})\\n`),\n );\n\n switch (modelConfig.provider) {\n case 'claude':\n return new ClaudeExecutor({ toolRegistry, model: modelConfig.model });\n case 'openrouter':\n return new OpenRouterExecutor(modelConfig.model);\n case 'deepseek':\n return new DeepSeekExecutor(modelConfig.model);\n case 'hermes':\n return new HermesExecutor();\n case 'agent-sdk':\n return new AgentSdkExecutor(modelConfig.model);\n case 'ollama':\n default:\n return new OllamaExecutor(modelConfig);\n }\n };\n}\n\n/**\n * Print phase results, loop info, cost, and a terminal summary, then exit with\n * an honest code: 0 completed, 1 failed, 2 paused (gate/HITL).\n */\nfunction printResultsAndExit(\n instance: WorkflowInstance,\n ctx: { file: string; command: 'run' | 'resume'; outputDir: string; stateDir?: string },\n): never {\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Cost (stable, parseable line)\n console.log(chalk.bold(`\\nšŸ’° ${formatCostLine(usageTotals(instance))}`));\n\n // Instance / persistence info\n const stateName = `${instance.instance_id}.state.json`;\n const statePath = ctx.stateDir ? `${ctx.stateDir}/${stateName}` : stateName;\n console.log(chalk.bold(`\\nšŸ“¦ Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${statePath}`));\n console.log(chalk.dim(` Outputs saved to: ${ctx.outputDir}/`));\n\n // Terminal summary + honest exit code\n const report = buildRunReport(instance, { file: ctx.file, command: ctx.command });\n const color =\n report.exitCode === 0 ? chalk.green : report.exitCode === 2 ? chalk.yellow : chalk.red;\n console.log();\n for (const line of report.lines) console.log(color(line));\n process.exit(report.exitCode);\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version(AGENTFLOW_VERSION)\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// init command\nprogram\n .command('init')\n .description('Interactive configuration wizard')\n .action(async () => {\n try {\n await runInit();\n } catch (err) {\n if ((err as Error).name === 'ExitPromptError') {\n console.log(chalk.yellow('\\nSetup cancelled.'));\n process.exit(0);\n }\n throw err;\n }\n });\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'no loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agents: ${agentCount} | Phases: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with per-agent model resolution')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option(\n '--approve-irreversible',\n 'Allow execution of phases marked irreversible (money, deploys, deletions)',\n )\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/, or $AGENTFLOW_OUTPUT_DIR)',\n )\n .option(\n '--state-dir ',\n 'Directory for .state.json files (default: CWD, or $AGENTFLOW_STATE_DIR)',\n )\n .action(\n async (\n file: string,\n options: {\n input?: string;\n mock?: boolean;\n outputDir?: string;\n stateDir?: string;\n approveIrreversible?: boolean;\n },\n ) => {\n if (!existsSync('agentflow.config.json')) {\n console.log(\n chalk.yellow('āš ļø agentflow.config.json not found — using built-in defaults.\\n'),\n );\n console.log(chalk.dim(' Run \"agentflow init\" to customize models and providers.\\n'));\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection: per-agent model resolution\n const outputDir =\n options.outputDir ?? process.env.AGENTFLOW_OUTPUT_DIR ?? `./output/${ir.workflow.id}`;\n const stateDir = options.stateDir ?? process.env.AGENTFLOW_STATE_DIR;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, {\n outputDir,\n stateDir,\n approveIrreversible: options.approveIrreversible,\n });\n // SIGTERM/SIGINT (e.g. flow's timeout path) → checkpoint and stop cleanly.\n runner.enableGracefulShutdown();\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n printResultsAndExit(instance, { file, command: 'run', outputDir, stateDir });\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option(\n '--approve-irreversible',\n 'Allow execution of phases marked irreversible (money, deploys, deletions)',\n )\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/, or $AGENTFLOW_OUTPUT_DIR)',\n )\n .option(\n '--state-dir ',\n 'Directory to load/save .state.json (default: CWD, or $AGENTFLOW_STATE_DIR)',\n )\n .action(\n async (\n file: string,\n options: {\n instance?: string;\n mock?: boolean;\n outputDir?: string;\n stateDir?: string;\n approveIrreversible?: boolean;\n },\n ) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection: per-agent model resolution\n const outputDir =\n options.outputDir ?? process.env.AGENTFLOW_OUTPUT_DIR ?? `./output/${ir.workflow.id}`;\n const stateDir = options.stateDir ?? process.env.AGENTFLOW_STATE_DIR;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, {\n outputDir,\n stateDir,\n approveIrreversible: options.approveIrreversible,\n });\n runner.enableGracefulShutdown();\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n printResultsAndExit(instance, { file, command: 'resume', outputDir, stateDir });\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// mcp-config command\nprogram\n .command('mcp-config')\n .description('Print the MCP config JSON to add to Claude Code settings')\n .option('--workflows-dir ', 'Directory containing .aflow files', process.cwd())\n .action((options: { workflowsDir: string }) => {\n // Load local .env if present\n if (existsSync('.env')) {\n const envContent = readFileSync('.env', 'utf-8');\n for (const line of envContent.split('\\n')) {\n const match = line.match(/^([^=]+)=(.*)$/);\n if (match && !process.env[match[1]]) {\n process.env[match[1]] = match[2].trim();\n }\n }\n }\n\n const workflowsDir = resolve(options.workflowsDir);\n const env: Record = {\n AGENTFLOW_WORKFLOWS_DIR: workflowsDir,\n OLLAMA_BASE_URL: 'http://localhost:11434',\n };\n if (process.env['ANTHROPIC_API_KEY'])\n env['ANTHROPIC_API_KEY'] = process.env['ANTHROPIC_API_KEY'];\n if (process.env['OPENROUTER_API_KEY'])\n env['OPENROUTER_API_KEY'] = process.env['OPENROUTER_API_KEY'];\n if (process.env['DEEPSEEK_API_KEY']) env['DEEPSEEK_API_KEY'] = process.env['DEEPSEEK_API_KEY'];\n\n const config = {\n mcpServers: {\n agentflow: {\n command: 'npx',\n args: ['-y', '--package=@anhonestboy/agentflow', 'agentflow-mcp'],\n env,\n },\n },\n };\n\n console.log(chalk.bold('\\nšŸ“‹ Add this to ~/.claude/settings.json under \"mcpServers\":\\n'));\n console.log(JSON.stringify(config, null, 2));\n if (\n !process.env['ANTHROPIC_API_KEY'] &&\n !process.env['OPENROUTER_API_KEY'] &&\n !process.env['DEEPSEEK_API_KEY']\n ) {\n console.log(chalk.yellow('\\nāš ļø No API keys found in .env — run \"agentflow init\" first.'));\n }\n console.log();\n });\n\n// models command\nprogram\n .command('models')\n .description('List configured models and test connectivity')\n .action(async () => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init'));\n process.exit(1);\n }\n const raw = readFileSync('agentflow.config.json', 'utf-8');\n const config = JSON.parse(raw) as {\n models: Record;\n };\n\n console.log(chalk.bold('\\nšŸ¤– Configured models:\\n'));\n for (const [alias, cfg] of Object.entries(config.models)) {\n const label = `${alias}`.padEnd(20);\n const provider = cfg.provider.padEnd(12);\n const model = cfg.model ?? '(auto)';\n console.log(` ${chalk.cyan(label)} ${chalk.dim(provider)} ${model}`);\n }\n\n // Connectivity checks\n console.log(chalk.bold('\\nšŸ”Œ Connectivity:\\n'));\n\n if (process.env.ANTHROPIC_API_KEY) {\n console.log(` ${chalk.green('āœ…')} Claude (ANTHROPIC_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} Claude (ANTHROPIC_API_KEY not set)`);\n }\n\n if (process.env.OPENROUTER_API_KEY) {\n console.log(` ${chalk.green('āœ…')} OpenRouter (OPENROUTER_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} OpenRouter (OPENROUTER_API_KEY not set)`);\n }\n\n if (process.env.DEEPSEEK_API_KEY) {\n console.log(` ${chalk.green('āœ…')} DeepSeek (DEEPSEEK_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} DeepSeek (DEEPSEEK_API_KEY not set)`);\n }\n\n try {\n const ollamaBase = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434';\n const res = await fetch(`${ollamaBase}/api/tags`, {\n signal: AbortSignal.timeout(2000),\n });\n if (res.ok) {\n const data = (await res.json()) as { models: Array<{ name: string }> };\n console.log(\n ` ${chalk.green('āœ…')} Ollama (${data.models.length} models installed at ${ollamaBase})`,\n );\n } else {\n console.log(` ${chalk.yellow('āš ļø')} Ollama (response ${res.status})`);\n }\n } catch {\n console.log(` ${chalk.dim('ā—‹')} Ollama (unreachable)`);\n }\n\n console.log();\n });\n\nprogram.parse();\n"}, "4a72d250cd816bf10c58ccb3f91d12e23f88f677": {"t": 1785754240.3746665, "n": 18604, "h": "b4d9c2026f734cf9", "c": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport type { ExecutorResolver } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { resolveModel } from './model-resolver.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport { runInit } from './commands/init.js';\nimport { OpenRouterExecutor } from './executors/openrouter-executor.js';\nimport { DeepSeekExecutor } from './executors/deepseek-executor.js';\nimport { HermesExecutor } from './executors/hermes-executor.js';\nimport { AgentSdkExecutor } from './executors/agent-sdk-executor.js';\nimport { buildRunReport, usageTotals, formatCostLine } from './cli-report.js';\nimport { AGENTFLOW_VERSION } from './version.js';\nimport type { WorkflowIR, AgentDef, WorkflowInstance } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n if (!existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\n/** Create an ExecutorResolver that resolves the model for each agent */\nfunction createExecutorResolver(\n toolRegistry: ReturnType,\n): ExecutorResolver {\n return (agent: AgentDef) => {\n const modelConfig = resolveModel(agent.model);\n process.stderr.write(\n chalk.dim(` šŸ“¦ [${agent.id}] model: ${modelConfig.model} (${modelConfig.provider})\\n`),\n );\n\n switch (modelConfig.provider) {\n case 'claude':\n return new ClaudeExecutor({ toolRegistry, model: modelConfig.model });\n case 'openrouter':\n return new OpenRouterExecutor(modelConfig.model);\n case 'deepseek':\n return new DeepSeekExecutor(modelConfig.model);\n case 'hermes':\n return new HermesExecutor();\n case 'agent-sdk':\n return new AgentSdkExecutor(modelConfig.model);\n case 'ollama':\n default:\n return new OllamaExecutor(modelConfig);\n }\n };\n}\n\n/**\n * Print phase results, loop info, cost, and a terminal summary, then exit with\n * an honest code: 0 completed, 1 failed, 2 paused (gate/HITL).\n */\nfunction printResultsAndExit(\n instance: WorkflowInstance,\n ctx: { file: string; command: 'run' | 'resume'; outputDir: string; stateDir?: string },\n): never {\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Cost (stable, parseable line)\n console.log(chalk.bold(`\\nšŸ’° ${formatCostLine(usageTotals(instance))}`));\n\n // Instance / persistence info\n const stateName = `${instance.instance_id}.state.json`;\n const statePath = ctx.stateDir ? `${ctx.stateDir}/${stateName}` : stateName;\n console.log(chalk.bold(`\\nšŸ“¦ Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${statePath}`));\n console.log(chalk.dim(` Outputs saved to: ${ctx.outputDir}/`));\n\n // Terminal summary + honest exit code\n const report = buildRunReport(instance, { file: ctx.file, command: ctx.command });\n const color =\n report.exitCode === 0 ? chalk.green : report.exitCode === 2 ? chalk.yellow : chalk.red;\n console.log();\n for (const line of report.lines) console.log(color(line));\n process.exit(report.exitCode);\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version(AGENTFLOW_VERSION)\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// init command\nprogram\n .command('init')\n .description('Interactive configuration wizard')\n .action(async () => {\n try {\n await runInit();\n } catch (err) {\n if ((err as Error).name === 'ExitPromptError') {\n console.log(chalk.yellow('\\nSetup cancelled.'));\n process.exit(0);\n }\n throw err;\n }\n });\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'no loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agents: ${agentCount} | Phases: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with per-agent model resolution')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option(\n '--approve-irreversible',\n 'Allow execution of phases marked irreversible (money, deploys, deletions)',\n )\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/, or $AGENTFLOW_OUTPUT_DIR)',\n )\n .option(\n '--state-dir ',\n 'Directory for .state.json files (default: CWD, or $AGENTFLOW_STATE_DIR)',\n )\n .action(\n async (\n file: string,\n options: {\n input?: string;\n mock?: boolean;\n outputDir?: string;\n stateDir?: string;\n approveIrreversible?: boolean;\n },\n ) => {\n if (!existsSync('agentflow.config.json')) {\n console.log(\n chalk.yellow('āš ļø agentflow.config.json not found — using built-in defaults.\\n'),\n );\n console.log(chalk.dim(' Run \"agentflow init\" to customize models and providers.\\n'));\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection: per-agent model resolution\n const outputDir =\n options.outputDir ?? process.env.AGENTFLOW_OUTPUT_DIR ?? `./output/${ir.workflow.id}`;\n const stateDir = options.stateDir ?? process.env.AGENTFLOW_STATE_DIR;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, {\n outputDir,\n stateDir,\n approveIrreversible: options.approveIrreversible,\n });\n // SIGTERM/SIGINT (e.g. flow's timeout path) → checkpoint and stop cleanly.\n runner.enableGracefulShutdown();\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n printResultsAndExit(instance, { file, command: 'run', outputDir, stateDir });\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option(\n '--approve-irreversible',\n 'Allow execution of phases marked irreversible (money, deploys, deletions)',\n )\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/, or $AGENTFLOW_OUTPUT_DIR)',\n )\n .option(\n '--state-dir ',\n 'Directory to load/save .state.json (default: CWD, or $AGENTFLOW_STATE_DIR)',\n )\n .action(\n async (\n file: string,\n options: {\n instance?: string;\n mock?: boolean;\n outputDir?: string;\n stateDir?: string;\n approveIrreversible?: boolean;\n },\n ) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection: per-agent model resolution\n const outputDir =\n options.outputDir ?? process.env.AGENTFLOW_OUTPUT_DIR ?? `./output/${ir.workflow.id}`;\n const stateDir = options.stateDir ?? process.env.AGENTFLOW_STATE_DIR;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, {\n outputDir,\n stateDir,\n approveIrreversible: options.approveIrreversible,\n });\n runner.enableGracefulShutdown();\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n printResultsAndExit(instance, { file, command: 'resume', outputDir, stateDir });\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// mcp-config command\nprogram\n .command('mcp-config')\n .description('Print the MCP config JSON to add to Claude Code settings')\n .option('--workflows-dir ', 'Directory containing .aflow files', process.cwd())\n .action((options: { workflowsDir: string }) => {\n // Load local .env if present\n if (existsSync('.env')) {\n const envContent = readFileSync('.env', 'utf-8');\n for (const line of envContent.split('\\n')) {\n const match = line.match(/^([^=]+)=(.*)$/);\n if (match && !process.env[match[1]]) {\n process.env[match[1]] = match[2].trim();\n }\n }\n }\n\n const workflowsDir = resolve(options.workflowsDir);\n const env: Record = {\n AGENTFLOW_WORKFLOWS_DIR: workflowsDir,\n OLLAMA_BASE_URL: 'http://localhost:11434',\n };\n if (process.env['ANTHROPIC_API_KEY'])\n env['ANTHROPIC_API_KEY'] = process.env['ANTHROPIC_API_KEY'];\n if (process.env['OPENROUTER_API_KEY'])\n env['OPENROUTER_API_KEY'] = process.env['OPENROUTER_API_KEY'];\n if (process.env['DEEPSEEK_API_KEY']) env['DEEPSEEK_API_KEY'] = process.env['DEEPSEEK_API_KEY'];\n\n const config = {\n mcpServers: {\n agentflow: {\n command: 'npx',\n args: ['-y', '--package=@anhonestboy/agentflow', 'agentflow-mcp'],\n env,\n },\n },\n };\n\n console.log(chalk.bold('\\nšŸ“‹ Add this to ~/.claude/settings.json under \"mcpServers\":\\n'));\n console.log(JSON.stringify(config, null, 2));\n if (\n !process.env['ANTHROPIC_API_KEY'] &&\n !process.env['OPENROUTER_API_KEY'] &&\n !process.env['DEEPSEEK_API_KEY']\n ) {\n console.log(chalk.yellow('\\nāš ļø No API keys found in .env — run \"agentflow init\" first.'));\n }\n console.log();\n });\n\n// models command\nprogram\n .command('models')\n .description('List configured models and test connectivity')\n .action(async () => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init'));\n process.exit(1);\n }\n const raw = readFileSync('agentflow.config.json', 'utf-8');\n const config = JSON.parse(raw) as {\n models: Record;\n };\n\n console.log(chalk.bold('\\nšŸ¤– Configured models:\\n'));\n for (const [alias, cfg] of Object.entries(config.models)) {\n const label = `${alias}`.padEnd(20);\n const provider = cfg.provider.padEnd(12);\n const model = cfg.model ?? '(auto)';\n console.log(` ${chalk.cyan(label)} ${chalk.dim(provider)} ${model}`);\n }\n\n // Connectivity checks\n console.log(chalk.bold('\\nšŸ”Œ Connectivity:\\n'));\n\n if (process.env.ANTHROPIC_API_KEY) {\n console.log(` ${chalk.green('āœ…')} Claude (ANTHROPIC_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} Claude (ANTHROPIC_API_KEY not set)`);\n }\n\n if (process.env.OPENROUTER_API_KEY) {\n console.log(` ${chalk.green('āœ…')} OpenRouter (OPENROUTER_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} OpenRouter (OPENROUTER_API_KEY not set)`);\n }\n\n if (process.env.DEEPSEEK_API_KEY) {\n console.log(` ${chalk.green('āœ…')} DeepSeek (DEEPSEEK_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} DeepSeek (DEEPSEEK_API_KEY not set)`);\n }\n\n try {\n const ollamaBase = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434';\n const res = await fetch(`${ollamaBase}/api/tags`, {\n signal: AbortSignal.timeout(2000),\n });\n if (res.ok) {\n const data = (await res.json()) as { models: Array<{ name: string }> };\n console.log(\n ` ${chalk.green('āœ…')} Ollama (${data.models.length} models installed at ${ollamaBase})`,\n );\n } else {\n console.log(` ${chalk.yellow('āš ļø')} Ollama (response ${res.status})`);\n }\n } catch {\n console.log(` ${chalk.dim('ā—‹')} Ollama (unreachable)`);\n }\n\n console.log();\n });\n\nprogram.parse();\n"}, "78402f4f31536e8479fc9e5bdc658d113b81fde5": {"t": 1785754244.687004, "n": 18544, "h": "a46766826fab5506", "c": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport type { ExecutorResolver } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { resolveModel } from './model-resolver.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport { runInit } from './commands/init.js';\nimport { OpenRouterExecutor } from './executors/openrouter-executor.js';\nimport { DeepSeekExecutor } from './executors/deepseek-executor.js';\nimport { HermesExecutor } from './executors/hermes-executor.js';\nimport { AgentSdkExecutor } from './executors/agent-sdk-executor.js';\nimport { buildRunReport, usageTotals, formatCostLine } from './cli-report.js';\nimport type { WorkflowIR, AgentDef, WorkflowInstance } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n if (!existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\n/** Create an ExecutorResolver that resolves the model for each agent */\nfunction createExecutorResolver(\n toolRegistry: ReturnType,\n): ExecutorResolver {\n return (agent: AgentDef) => {\n const modelConfig = resolveModel(agent.model);\n process.stderr.write(\n chalk.dim(` šŸ“¦ [${agent.id}] model: ${modelConfig.model} (${modelConfig.provider})\\n`),\n );\n\n switch (modelConfig.provider) {\n case 'claude':\n return new ClaudeExecutor({ toolRegistry, model: modelConfig.model });\n case 'openrouter':\n return new OpenRouterExecutor(modelConfig.model);\n case 'deepseek':\n return new DeepSeekExecutor(modelConfig.model);\n case 'hermes':\n return new HermesExecutor();\n case 'agent-sdk':\n return new AgentSdkExecutor(modelConfig.model);\n case 'ollama':\n default:\n return new OllamaExecutor(modelConfig);\n }\n };\n}\n\n/**\n * Print phase results, loop info, cost, and a terminal summary, then exit with\n * an honest code: 0 completed, 1 failed, 2 paused (gate/HITL).\n */\nfunction printResultsAndExit(\n instance: WorkflowInstance,\n ctx: { file: string; command: 'run' | 'resume'; outputDir: string; stateDir?: string },\n): never {\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Cost (stable, parseable line)\n console.log(chalk.bold(`\\nšŸ’° ${formatCostLine(usageTotals(instance))}`));\n\n // Instance / persistence info\n const stateName = `${instance.instance_id}.state.json`;\n const statePath = ctx.stateDir ? `${ctx.stateDir}/${stateName}` : stateName;\n console.log(chalk.bold(`\\nšŸ“¦ Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${statePath}`));\n console.log(chalk.dim(` Outputs saved to: ${ctx.outputDir}/`));\n\n // Terminal summary + honest exit code\n const report = buildRunReport(instance, { file: ctx.file, command: ctx.command });\n const color =\n report.exitCode === 0 ? chalk.green : report.exitCode === 2 ? chalk.yellow : chalk.red;\n console.log();\n for (const line of report.lines) console.log(color(line));\n process.exit(report.exitCode);\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version('0.1.0')\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// init command\nprogram\n .command('init')\n .description('Interactive configuration wizard')\n .action(async () => {\n try {\n await runInit();\n } catch (err) {\n if ((err as Error).name === 'ExitPromptError') {\n console.log(chalk.yellow('\\nSetup cancelled.'));\n process.exit(0);\n }\n throw err;\n }\n });\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'no loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agents: ${agentCount} | Phases: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with per-agent model resolution')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option(\n '--approve-irreversible',\n 'Allow execution of phases marked irreversible (money, deploys, deletions)',\n )\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/, or $AGENTFLOW_OUTPUT_DIR)',\n )\n .option(\n '--state-dir ',\n 'Directory for .state.json files (default: CWD, or $AGENTFLOW_STATE_DIR)',\n )\n .action(\n async (\n file: string,\n options: {\n input?: string;\n mock?: boolean;\n outputDir?: string;\n stateDir?: string;\n approveIrreversible?: boolean;\n },\n ) => {\n if (!existsSync('agentflow.config.json')) {\n console.log(\n chalk.yellow('āš ļø agentflow.config.json not found — using built-in defaults.\\n'),\n );\n console.log(chalk.dim(' Run \"agentflow init\" to customize models and providers.\\n'));\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection: per-agent model resolution\n const outputDir =\n options.outputDir ?? process.env.AGENTFLOW_OUTPUT_DIR ?? `./output/${ir.workflow.id}`;\n const stateDir = options.stateDir ?? process.env.AGENTFLOW_STATE_DIR;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, {\n outputDir,\n stateDir,\n approveIrreversible: options.approveIrreversible,\n });\n // SIGTERM/SIGINT (e.g. flow's timeout path) → checkpoint and stop cleanly.\n runner.enableGracefulShutdown();\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n printResultsAndExit(instance, { file, command: 'run', outputDir, stateDir });\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option(\n '--approve-irreversible',\n 'Allow execution of phases marked irreversible (money, deploys, deletions)',\n )\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/, or $AGENTFLOW_OUTPUT_DIR)',\n )\n .option(\n '--state-dir ',\n 'Directory to load/save .state.json (default: CWD, or $AGENTFLOW_STATE_DIR)',\n )\n .action(\n async (\n file: string,\n options: {\n instance?: string;\n mock?: boolean;\n outputDir?: string;\n stateDir?: string;\n approveIrreversible?: boolean;\n },\n ) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection: per-agent model resolution\n const outputDir =\n options.outputDir ?? process.env.AGENTFLOW_OUTPUT_DIR ?? `./output/${ir.workflow.id}`;\n const stateDir = options.stateDir ?? process.env.AGENTFLOW_STATE_DIR;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, {\n outputDir,\n stateDir,\n approveIrreversible: options.approveIrreversible,\n });\n runner.enableGracefulShutdown();\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n printResultsAndExit(instance, { file, command: 'resume', outputDir, stateDir });\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// mcp-config command\nprogram\n .command('mcp-config')\n .description('Print the MCP config JSON to add to Claude Code settings')\n .option('--workflows-dir ', 'Directory containing .aflow files', process.cwd())\n .action((options: { workflowsDir: string }) => {\n // Load local .env if present\n if (existsSync('.env')) {\n const envContent = readFileSync('.env', 'utf-8');\n for (const line of envContent.split('\\n')) {\n const match = line.match(/^([^=]+)=(.*)$/);\n if (match && !process.env[match[1]]) {\n process.env[match[1]] = match[2].trim();\n }\n }\n }\n\n const workflowsDir = resolve(options.workflowsDir);\n const env: Record = {\n AGENTFLOW_WORKFLOWS_DIR: workflowsDir,\n OLLAMA_BASE_URL: 'http://localhost:11434',\n };\n if (process.env['ANTHROPIC_API_KEY'])\n env['ANTHROPIC_API_KEY'] = process.env['ANTHROPIC_API_KEY'];\n if (process.env['OPENROUTER_API_KEY'])\n env['OPENROUTER_API_KEY'] = process.env['OPENROUTER_API_KEY'];\n if (process.env['DEEPSEEK_API_KEY']) env['DEEPSEEK_API_KEY'] = process.env['DEEPSEEK_API_KEY'];\n\n const config = {\n mcpServers: {\n agentflow: {\n command: 'npx',\n args: ['-y', '--package=@anhonestboy/agentflow', 'agentflow-mcp'],\n env,\n },\n },\n };\n\n console.log(chalk.bold('\\nšŸ“‹ Add this to ~/.claude/settings.json under \"mcpServers\":\\n'));\n console.log(JSON.stringify(config, null, 2));\n if (\n !process.env['ANTHROPIC_API_KEY'] &&\n !process.env['OPENROUTER_API_KEY'] &&\n !process.env['DEEPSEEK_API_KEY']\n ) {\n console.log(chalk.yellow('\\nāš ļø No API keys found in .env — run \"agentflow init\" first.'));\n }\n console.log();\n });\n\n// models command\nprogram\n .command('models')\n .description('List configured models and test connectivity')\n .action(async () => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init'));\n process.exit(1);\n }\n const raw = readFileSync('agentflow.config.json', 'utf-8');\n const config = JSON.parse(raw) as {\n models: Record;\n };\n\n console.log(chalk.bold('\\nšŸ¤– Configured models:\\n'));\n for (const [alias, cfg] of Object.entries(config.models)) {\n const label = `${alias}`.padEnd(20);\n const provider = cfg.provider.padEnd(12);\n const model = cfg.model ?? '(auto)';\n console.log(` ${chalk.cyan(label)} ${chalk.dim(provider)} ${model}`);\n }\n\n // Connectivity checks\n console.log(chalk.bold('\\nšŸ”Œ Connectivity:\\n'));\n\n if (process.env.ANTHROPIC_API_KEY) {\n console.log(` ${chalk.green('āœ…')} Claude (ANTHROPIC_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} Claude (ANTHROPIC_API_KEY not set)`);\n }\n\n if (process.env.OPENROUTER_API_KEY) {\n console.log(` ${chalk.green('āœ…')} OpenRouter (OPENROUTER_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} OpenRouter (OPENROUTER_API_KEY not set)`);\n }\n\n if (process.env.DEEPSEEK_API_KEY) {\n console.log(` ${chalk.green('āœ…')} DeepSeek (DEEPSEEK_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} DeepSeek (DEEPSEEK_API_KEY not set)`);\n }\n\n try {\n const ollamaBase = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434';\n const res = await fetch(`${ollamaBase}/api/tags`, {\n signal: AbortSignal.timeout(2000),\n });\n if (res.ok) {\n const data = (await res.json()) as { models: Array<{ name: string }> };\n console.log(\n ` ${chalk.green('āœ…')} Ollama (${data.models.length} models installed at ${ollamaBase})`,\n );\n } else {\n console.log(` ${chalk.yellow('āš ļø')} Ollama (response ${res.status})`);\n }\n } catch {\n console.log(` ${chalk.dim('ā—‹')} Ollama (unreachable)`);\n }\n\n console.log();\n });\n\nprogram.parse();\n"}, "3f88de4e61ab3e769c0c9ed2e62e1064cf31b7d5": {"t": 1785754246.0722091, "n": 18518, "h": "3a6830e4a37cd391", "c": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport type { ExecutorResolver } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { resolveModel } from './model-resolver.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport { runInit } from './commands/init.js';\nimport { OpenRouterExecutor } from './executors/openrouter-executor.js';\nimport { DeepSeekExecutor } from './executors/deepseek-executor.js';\nimport { HermesExecutor } from './executors/hermes-executor.js';\nimport { AgentSdkExecutor } from './executors/agent-sdk-executor.js';\nimport { buildRunReport, usageTotals, formatCostLine } from './cli-report.js';\nimport type { WorkflowIR, AgentDef, WorkflowInstance } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n if (!existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\n/** Create an ExecutorResolver that resolves the model for each agent */\nfunction createExecutorResolver(\n toolRegistry: ReturnType,\n): ExecutorResolver {\n return (agent: AgentDef) => {\n const modelConfig = resolveModel(agent.model);\n process.stderr.write(\n chalk.dim(` šŸ“¦ [${agent.id}] model: ${modelConfig.model} (${modelConfig.provider})\\n`),\n );\n\n switch (modelConfig.provider) {\n case 'claude':\n return new ClaudeExecutor({ toolRegistry });\n case 'openrouter':\n return new OpenRouterExecutor(modelConfig.model);\n case 'deepseek':\n return new DeepSeekExecutor(modelConfig.model);\n case 'hermes':\n return new HermesExecutor();\n case 'agent-sdk':\n return new AgentSdkExecutor(modelConfig.model);\n case 'ollama':\n default:\n return new OllamaExecutor(modelConfig);\n }\n };\n}\n\n/**\n * Print phase results, loop info, cost, and a terminal summary, then exit with\n * an honest code: 0 completed, 1 failed, 2 paused (gate/HITL).\n */\nfunction printResultsAndExit(\n instance: WorkflowInstance,\n ctx: { file: string; command: 'run' | 'resume'; outputDir: string; stateDir?: string },\n): never {\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Cost (stable, parseable line)\n console.log(chalk.bold(`\\nšŸ’° ${formatCostLine(usageTotals(instance))}`));\n\n // Instance / persistence info\n const stateName = `${instance.instance_id}.state.json`;\n const statePath = ctx.stateDir ? `${ctx.stateDir}/${stateName}` : stateName;\n console.log(chalk.bold(`\\nšŸ“¦ Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${statePath}`));\n console.log(chalk.dim(` Outputs saved to: ${ctx.outputDir}/`));\n\n // Terminal summary + honest exit code\n const report = buildRunReport(instance, { file: ctx.file, command: ctx.command });\n const color =\n report.exitCode === 0 ? chalk.green : report.exitCode === 2 ? chalk.yellow : chalk.red;\n console.log();\n for (const line of report.lines) console.log(color(line));\n process.exit(report.exitCode);\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version('0.1.0')\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// init command\nprogram\n .command('init')\n .description('Interactive configuration wizard')\n .action(async () => {\n try {\n await runInit();\n } catch (err) {\n if ((err as Error).name === 'ExitPromptError') {\n console.log(chalk.yellow('\\nSetup cancelled.'));\n process.exit(0);\n }\n throw err;\n }\n });\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'no loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agents: ${agentCount} | Phases: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with per-agent model resolution')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option(\n '--approve-irreversible',\n 'Allow execution of phases marked irreversible (money, deploys, deletions)',\n )\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/, or $AGENTFLOW_OUTPUT_DIR)',\n )\n .option(\n '--state-dir ',\n 'Directory for .state.json files (default: CWD, or $AGENTFLOW_STATE_DIR)',\n )\n .action(\n async (\n file: string,\n options: {\n input?: string;\n mock?: boolean;\n outputDir?: string;\n stateDir?: string;\n approveIrreversible?: boolean;\n },\n ) => {\n if (!existsSync('agentflow.config.json')) {\n console.log(\n chalk.yellow('āš ļø agentflow.config.json not found — using built-in defaults.\\n'),\n );\n console.log(chalk.dim(' Run \"agentflow init\" to customize models and providers.\\n'));\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection: per-agent model resolution\n const outputDir =\n options.outputDir ?? process.env.AGENTFLOW_OUTPUT_DIR ?? `./output/${ir.workflow.id}`;\n const stateDir = options.stateDir ?? process.env.AGENTFLOW_STATE_DIR;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, {\n outputDir,\n stateDir,\n approveIrreversible: options.approveIrreversible,\n });\n // SIGTERM/SIGINT (e.g. flow's timeout path) → checkpoint and stop cleanly.\n runner.enableGracefulShutdown();\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n printResultsAndExit(instance, { file, command: 'run', outputDir, stateDir });\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option(\n '--approve-irreversible',\n 'Allow execution of phases marked irreversible (money, deploys, deletions)',\n )\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/, or $AGENTFLOW_OUTPUT_DIR)',\n )\n .option(\n '--state-dir ',\n 'Directory to load/save .state.json (default: CWD, or $AGENTFLOW_STATE_DIR)',\n )\n .action(\n async (\n file: string,\n options: {\n instance?: string;\n mock?: boolean;\n outputDir?: string;\n stateDir?: string;\n approveIrreversible?: boolean;\n },\n ) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection: per-agent model resolution\n const outputDir =\n options.outputDir ?? process.env.AGENTFLOW_OUTPUT_DIR ?? `./output/${ir.workflow.id}`;\n const stateDir = options.stateDir ?? process.env.AGENTFLOW_STATE_DIR;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, {\n outputDir,\n stateDir,\n approveIrreversible: options.approveIrreversible,\n });\n runner.enableGracefulShutdown();\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n printResultsAndExit(instance, { file, command: 'resume', outputDir, stateDir });\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// mcp-config command\nprogram\n .command('mcp-config')\n .description('Print the MCP config JSON to add to Claude Code settings')\n .option('--workflows-dir ', 'Directory containing .aflow files', process.cwd())\n .action((options: { workflowsDir: string }) => {\n // Load local .env if present\n if (existsSync('.env')) {\n const envContent = readFileSync('.env', 'utf-8');\n for (const line of envContent.split('\\n')) {\n const match = line.match(/^([^=]+)=(.*)$/);\n if (match && !process.env[match[1]]) {\n process.env[match[1]] = match[2].trim();\n }\n }\n }\n\n const workflowsDir = resolve(options.workflowsDir);\n const env: Record = {\n AGENTFLOW_WORKFLOWS_DIR: workflowsDir,\n OLLAMA_BASE_URL: 'http://localhost:11434',\n };\n if (process.env['ANTHROPIC_API_KEY'])\n env['ANTHROPIC_API_KEY'] = process.env['ANTHROPIC_API_KEY'];\n if (process.env['OPENROUTER_API_KEY'])\n env['OPENROUTER_API_KEY'] = process.env['OPENROUTER_API_KEY'];\n if (process.env['DEEPSEEK_API_KEY']) env['DEEPSEEK_API_KEY'] = process.env['DEEPSEEK_API_KEY'];\n\n const config = {\n mcpServers: {\n agentflow: {\n command: 'npx',\n args: ['-y', '--package=@anhonestboy/agentflow', 'agentflow-mcp'],\n env,\n },\n },\n };\n\n console.log(chalk.bold('\\nšŸ“‹ Add this to ~/.claude/settings.json under \"mcpServers\":\\n'));\n console.log(JSON.stringify(config, null, 2));\n if (\n !process.env['ANTHROPIC_API_KEY'] &&\n !process.env['OPENROUTER_API_KEY'] &&\n !process.env['DEEPSEEK_API_KEY']\n ) {\n console.log(chalk.yellow('\\nāš ļø No API keys found in .env — run \"agentflow init\" first.'));\n }\n console.log();\n });\n\n// models command\nprogram\n .command('models')\n .description('List configured models and test connectivity')\n .action(async () => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init'));\n process.exit(1);\n }\n const raw = readFileSync('agentflow.config.json', 'utf-8');\n const config = JSON.parse(raw) as {\n models: Record;\n };\n\n console.log(chalk.bold('\\nšŸ¤– Configured models:\\n'));\n for (const [alias, cfg] of Object.entries(config.models)) {\n const label = `${alias}`.padEnd(20);\n const provider = cfg.provider.padEnd(12);\n const model = cfg.model ?? '(auto)';\n console.log(` ${chalk.cyan(label)} ${chalk.dim(provider)} ${model}`);\n }\n\n // Connectivity checks\n console.log(chalk.bold('\\nšŸ”Œ Connectivity:\\n'));\n\n if (process.env.ANTHROPIC_API_KEY) {\n console.log(` ${chalk.green('āœ…')} Claude (ANTHROPIC_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} Claude (ANTHROPIC_API_KEY not set)`);\n }\n\n if (process.env.OPENROUTER_API_KEY) {\n console.log(` ${chalk.green('āœ…')} OpenRouter (OPENROUTER_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} OpenRouter (OPENROUTER_API_KEY not set)`);\n }\n\n if (process.env.DEEPSEEK_API_KEY) {\n console.log(` ${chalk.green('āœ…')} DeepSeek (DEEPSEEK_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} DeepSeek (DEEPSEEK_API_KEY not set)`);\n }\n\n try {\n const ollamaBase = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434';\n const res = await fetch(`${ollamaBase}/api/tags`, {\n signal: AbortSignal.timeout(2000),\n });\n if (res.ok) {\n const data = (await res.json()) as { models: Array<{ name: string }> };\n console.log(\n ` ${chalk.green('āœ…')} Ollama (${data.models.length} models installed at ${ollamaBase})`,\n );\n } else {\n console.log(` ${chalk.yellow('āš ļø')} Ollama (response ${res.status})`);\n }\n } catch {\n console.log(` ${chalk.dim('ā—‹')} Ollama (unreachable)`);\n }\n\n console.log();\n });\n\nprogram.parse();\n"}, "da5cd6c2ad74f61ab2c0b84f7c891e9e00dde6ab": {"t": 1785754247.3366766, "n": 18267, "h": "0c4a33129c30dbb9", "c": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport type { ExecutorResolver } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { resolveModel } from './model-resolver.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport { runInit } from './commands/init.js';\nimport { OpenRouterExecutor } from './executors/openrouter-executor.js';\nimport { DeepSeekExecutor } from './executors/deepseek-executor.js';\nimport { HermesExecutor } from './executors/hermes-executor.js';\nimport { AgentSdkExecutor } from './executors/agent-sdk-executor.js';\nimport type { WorkflowIR, AgentDef } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n if (!existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\n/** Create an ExecutorResolver that resolves the model for each agent */\nfunction createExecutorResolver(\n toolRegistry: ReturnType,\n): ExecutorResolver {\n return (agent: AgentDef) => {\n const modelConfig = resolveModel(agent.model);\n process.stderr.write(\n chalk.dim(` šŸ“¦ [${agent.id}] model: ${modelConfig.model} (${modelConfig.provider})\\n`),\n );\n\n switch (modelConfig.provider) {\n case 'claude':\n return new ClaudeExecutor({ toolRegistry });\n case 'openrouter':\n return new OpenRouterExecutor(modelConfig.model);\n case 'deepseek':\n return new DeepSeekExecutor(modelConfig.model);\n case 'hermes':\n return new HermesExecutor();\n case 'agent-sdk':\n return new AgentSdkExecutor(modelConfig.model);\n case 'ollama':\n default:\n return new OllamaExecutor(modelConfig);\n }\n };\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version('0.1.0')\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// init command\nprogram\n .command('init')\n .description('Interactive configuration wizard')\n .action(async () => {\n try {\n await runInit();\n } catch (err) {\n if ((err as Error).name === 'ExitPromptError') {\n console.log(chalk.yellow('\\nSetup cancelled.'));\n process.exit(0);\n }\n throw err;\n }\n });\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'no loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agents: ${agentCount} | Phases: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with per-agent model resolution')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option(\n '--approve-irreversible',\n 'Allow execution of phases marked irreversible (money, deploys, deletions)',\n )\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(\n async (\n file: string,\n options: {\n input?: string;\n mock?: boolean;\n outputDir?: string;\n approveIrreversible?: boolean;\n },\n ) => {\n if (!existsSync('agentflow.config.json')) {\n console.log(\n chalk.yellow('āš ļø agentflow.config.json not found — using built-in defaults.\\n'),\n );\n console.log(chalk.dim(' Run \"agentflow init\" to customize models and providers.\\n'));\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, {\n outputDir,\n approveIrreversible: options.approveIrreversible,\n });\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n if (instance.state === 'paused' && instance.execution_receipt?.resume_from_phase) {\n const gated = ir.workflow.phases.find(\n (p) => p.id === instance.execution_receipt?.resume_from_phase && p.irreversible,\n );\n if (gated) {\n console.log(\n chalk.yellow(\n `\\nšŸ›‘ Phase \"${gated.id}\" is marked irreversible — approval required.\\n` +\n ` Review the state, then resume with:\\n` +\n ` agentflow resume ${file} --instance ${instance.instance_id} --approve-irreversible`,\n ),\n );\n }\n }\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option(\n '--approve-irreversible',\n 'Allow execution of phases marked irreversible (money, deploys, deletions)',\n )\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(\n async (\n file: string,\n options: {\n instance?: string;\n mock?: boolean;\n outputDir?: string;\n approveIrreversible?: boolean;\n },\n ) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, {\n outputDir,\n approveIrreversible: options.approveIrreversible,\n });\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// mcp-config command\nprogram\n .command('mcp-config')\n .description('Print the MCP config JSON to add to Claude Code settings')\n .option('--workflows-dir ', 'Directory containing .aflow files', process.cwd())\n .action((options: { workflowsDir: string }) => {\n // Load local .env if present\n if (existsSync('.env')) {\n const envContent = readFileSync('.env', 'utf-8');\n for (const line of envContent.split('\\n')) {\n const match = line.match(/^([^=]+)=(.*)$/);\n if (match && !process.env[match[1]]) {\n process.env[match[1]] = match[2].trim();\n }\n }\n }\n\n const workflowsDir = resolve(options.workflowsDir);\n const env: Record = {\n AGENTFLOW_WORKFLOWS_DIR: workflowsDir,\n OLLAMA_BASE_URL: 'http://localhost:11434',\n };\n if (process.env['ANTHROPIC_API_KEY'])\n env['ANTHROPIC_API_KEY'] = process.env['ANTHROPIC_API_KEY'];\n if (process.env['OPENROUTER_API_KEY'])\n env['OPENROUTER_API_KEY'] = process.env['OPENROUTER_API_KEY'];\n if (process.env['DEEPSEEK_API_KEY']) env['DEEPSEEK_API_KEY'] = process.env['DEEPSEEK_API_KEY'];\n\n const config = {\n mcpServers: {\n agentflow: {\n command: 'npx',\n args: ['-y', '--package=@anhonestboy/agentflow', 'agentflow-mcp'],\n env,\n },\n },\n };\n\n console.log(chalk.bold('\\nšŸ“‹ Add this to ~/.claude/settings.json under \"mcpServers\":\\n'));\n console.log(JSON.stringify(config, null, 2));\n if (\n !process.env['ANTHROPIC_API_KEY'] &&\n !process.env['OPENROUTER_API_KEY'] &&\n !process.env['DEEPSEEK_API_KEY']\n ) {\n console.log(chalk.yellow('\\nāš ļø No API keys found in .env — run \"agentflow init\" first.'));\n }\n console.log();\n });\n\n// models command\nprogram\n .command('models')\n .description('List configured models and test connectivity')\n .action(async () => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init'));\n process.exit(1);\n }\n const raw = readFileSync('agentflow.config.json', 'utf-8');\n const config = JSON.parse(raw) as {\n models: Record;\n };\n\n console.log(chalk.bold('\\nšŸ¤– Configured models:\\n'));\n for (const [alias, cfg] of Object.entries(config.models)) {\n const label = `${alias}`.padEnd(20);\n const provider = cfg.provider.padEnd(12);\n const model = cfg.model ?? '(auto)';\n console.log(` ${chalk.cyan(label)} ${chalk.dim(provider)} ${model}`);\n }\n\n // Connectivity checks\n console.log(chalk.bold('\\nšŸ”Œ Connectivity:\\n'));\n\n if (process.env.ANTHROPIC_API_KEY) {\n console.log(` ${chalk.green('āœ…')} Claude (ANTHROPIC_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} Claude (ANTHROPIC_API_KEY not set)`);\n }\n\n if (process.env.OPENROUTER_API_KEY) {\n console.log(` ${chalk.green('āœ…')} OpenRouter (OPENROUTER_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} OpenRouter (OPENROUTER_API_KEY not set)`);\n }\n\n if (process.env.DEEPSEEK_API_KEY) {\n console.log(` ${chalk.green('āœ…')} DeepSeek (DEEPSEEK_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} DeepSeek (DEEPSEEK_API_KEY not set)`);\n }\n\n try {\n const ollamaBase = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434';\n const res = await fetch(`${ollamaBase}/api/tags`, {\n signal: AbortSignal.timeout(2000),\n });\n if (res.ok) {\n const data = (await res.json()) as { models: Array<{ name: string }> };\n console.log(\n ` ${chalk.green('āœ…')} Ollama (${data.models.length} models installed at ${ollamaBase})`,\n );\n } else {\n console.log(` ${chalk.yellow('āš ļø')} Ollama (response ${res.status})`);\n }\n } catch {\n console.log(` ${chalk.dim('ā—‹')} Ollama (unreachable)`);\n }\n\n console.log();\n });\n\nprogram.parse();\n"}, "ba9aa67f45c89f62a62278dd1b4bc18bb4c64479": {"t": 1785754248.5640671, "n": 17742, "h": "7d5b642fe9c3d4de", "c": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport type { ExecutorResolver } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { resolveModel } from './model-resolver.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport { runInit } from './commands/init.js';\nimport { OpenRouterExecutor } from './executors/openrouter-executor.js';\nimport { HermesExecutor } from './executors/hermes-executor.js';\nimport { AgentSdkExecutor } from './executors/agent-sdk-executor.js';\nimport type { WorkflowIR, AgentDef } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n if (!existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\n/** Create an ExecutorResolver that resolves the model for each agent */\nfunction createExecutorResolver(\n toolRegistry: ReturnType,\n): ExecutorResolver {\n return (agent: AgentDef) => {\n const modelConfig = resolveModel(agent.model);\n process.stderr.write(\n chalk.dim(` šŸ“¦ [${agent.id}] model: ${modelConfig.model} (${modelConfig.provider})\\n`),\n );\n\n switch (modelConfig.provider) {\n case 'claude':\n return new ClaudeExecutor({ toolRegistry });\n case 'openrouter':\n return new OpenRouterExecutor(modelConfig.model);\n case 'hermes':\n return new HermesExecutor();\n case 'agent-sdk':\n return new AgentSdkExecutor(modelConfig.model);\n case 'ollama':\n default:\n return new OllamaExecutor(modelConfig);\n }\n };\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version('0.1.0')\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// init command\nprogram\n .command('init')\n .description('Interactive configuration wizard')\n .action(async () => {\n try {\n await runInit();\n } catch (err) {\n if ((err as Error).name === 'ExitPromptError') {\n console.log(chalk.yellow('\\nSetup cancelled.'));\n process.exit(0);\n }\n throw err;\n }\n });\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'no loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agents: ${agentCount} | Phases: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with per-agent model resolution')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option(\n '--approve-irreversible',\n 'Allow execution of phases marked irreversible (money, deploys, deletions)',\n )\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(\n async (\n file: string,\n options: {\n input?: string;\n mock?: boolean;\n outputDir?: string;\n approveIrreversible?: boolean;\n },\n ) => {\n if (!existsSync('agentflow.config.json')) {\n console.log(\n chalk.yellow('āš ļø agentflow.config.json not found — using built-in defaults.\\n'),\n );\n console.log(chalk.dim(' Run \"agentflow init\" to customize models and providers.\\n'));\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, {\n outputDir,\n approveIrreversible: options.approveIrreversible,\n });\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n if (instance.state === 'paused' && instance.execution_receipt?.resume_from_phase) {\n const gated = ir.workflow.phases.find(\n (p) => p.id === instance.execution_receipt?.resume_from_phase && p.irreversible,\n );\n if (gated) {\n console.log(\n chalk.yellow(\n `\\nšŸ›‘ Phase \"${gated.id}\" is marked irreversible — approval required.\\n` +\n ` Review the state, then resume with:\\n` +\n ` agentflow resume ${file} --instance ${instance.instance_id} --approve-irreversible`,\n ),\n );\n }\n }\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option(\n '--approve-irreversible',\n 'Allow execution of phases marked irreversible (money, deploys, deletions)',\n )\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(\n async (\n file: string,\n options: {\n instance?: string;\n mock?: boolean;\n outputDir?: string;\n approveIrreversible?: boolean;\n },\n ) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, {\n outputDir,\n approveIrreversible: options.approveIrreversible,\n });\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// mcp-config command\nprogram\n .command('mcp-config')\n .description('Print the MCP config JSON to add to Claude Code settings')\n .option('--workflows-dir ', 'Directory containing .aflow files', process.cwd())\n .action((options: { workflowsDir: string }) => {\n // Load local .env if present\n if (existsSync('.env')) {\n const envContent = readFileSync('.env', 'utf-8');\n for (const line of envContent.split('\\n')) {\n const match = line.match(/^([^=]+)=(.*)$/);\n if (match && !process.env[match[1]]) {\n process.env[match[1]] = match[2].trim();\n }\n }\n }\n\n const workflowsDir = resolve(options.workflowsDir);\n const env: Record = {\n AGENTFLOW_WORKFLOWS_DIR: workflowsDir,\n OLLAMA_BASE_URL: 'http://localhost:11434',\n };\n if (process.env['ANTHROPIC_API_KEY'])\n env['ANTHROPIC_API_KEY'] = process.env['ANTHROPIC_API_KEY'];\n if (process.env['OPENROUTER_API_KEY'])\n env['OPENROUTER_API_KEY'] = process.env['OPENROUTER_API_KEY'];\n\n const config = {\n mcpServers: {\n agentflow: {\n command: 'npx',\n args: ['-y', '--package=@anhonestboy/agentflow', 'agentflow-mcp'],\n env,\n },\n },\n };\n\n console.log(chalk.bold('\\nšŸ“‹ Add this to ~/.claude/settings.json under \"mcpServers\":\\n'));\n console.log(JSON.stringify(config, null, 2));\n if (!process.env['ANTHROPIC_API_KEY'] && !process.env['OPENROUTER_API_KEY']) {\n console.log(chalk.yellow('\\nāš ļø No API keys found in .env — run \"agentflow init\" first.'));\n }\n console.log();\n });\n\n// models command\nprogram\n .command('models')\n .description('List configured models and test connectivity')\n .action(async () => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init'));\n process.exit(1);\n }\n const raw = readFileSync('agentflow.config.json', 'utf-8');\n const config = JSON.parse(raw) as {\n models: Record;\n };\n\n console.log(chalk.bold('\\nšŸ¤– Configured models:\\n'));\n for (const [alias, cfg] of Object.entries(config.models)) {\n const label = `${alias}`.padEnd(20);\n const provider = cfg.provider.padEnd(12);\n const model = cfg.model ?? '(auto)';\n console.log(` ${chalk.cyan(label)} ${chalk.dim(provider)} ${model}`);\n }\n\n // Connectivity checks\n console.log(chalk.bold('\\nšŸ”Œ Connectivity:\\n'));\n\n if (process.env.ANTHROPIC_API_KEY) {\n console.log(` ${chalk.green('āœ…')} Claude (ANTHROPIC_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} Claude (ANTHROPIC_API_KEY not set)`);\n }\n\n if (process.env.OPENROUTER_API_KEY) {\n console.log(` ${chalk.green('āœ…')} OpenRouter (OPENROUTER_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} OpenRouter (OPENROUTER_API_KEY not set)`);\n }\n\n try {\n const ollamaBase = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434';\n const res = await fetch(`${ollamaBase}/api/tags`, {\n signal: AbortSignal.timeout(2000),\n });\n if (res.ok) {\n const data = (await res.json()) as { models: Array<{ name: string }> };\n console.log(\n ` ${chalk.green('āœ…')} Ollama (${data.models.length} models installed at ${ollamaBase})`,\n );\n } else {\n console.log(` ${chalk.yellow('āš ļø')} Ollama (response ${res.status})`);\n }\n } catch {\n console.log(` ${chalk.dim('ā—‹')} Ollama (unreachable)`);\n }\n\n console.log();\n });\n\nprogram.parse();\n"}, "f2c6d6ed58440c8f1af99c83281bcf1bf5a0a48b": {"t": 1785754249.9600413, "n": 16368, "h": "5ce89f0e0e240929", "c": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport type { ExecutorResolver } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { resolveModel } from './model-resolver.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport { runInit } from './commands/init.js';\nimport { OpenRouterExecutor } from './executors/openrouter-executor.js';\nimport { HermesExecutor } from './executors/hermes-executor.js';\nimport { AgentSdkExecutor } from './executors/agent-sdk-executor.js';\nimport type { WorkflowIR, AgentDef } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n if (!existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\n/** Create an ExecutorResolver that resolves the model for each agent */\nfunction createExecutorResolver(\n toolRegistry: ReturnType,\n): ExecutorResolver {\n return (agent: AgentDef) => {\n const modelConfig = resolveModel(agent.model);\n process.stderr.write(\n chalk.dim(` šŸ“¦ [${agent.id}] model: ${modelConfig.model} (${modelConfig.provider})\\n`),\n );\n\n switch (modelConfig.provider) {\n case 'claude':\n return new ClaudeExecutor({ toolRegistry });\n case 'openrouter':\n return new OpenRouterExecutor(modelConfig.model);\n case 'hermes':\n return new HermesExecutor();\n case 'agent-sdk':\n return new AgentSdkExecutor(modelConfig.model);\n case 'ollama':\n default:\n return new OllamaExecutor(modelConfig);\n }\n };\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version('0.1.0')\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// init command\nprogram\n .command('init')\n .description('Interactive configuration wizard')\n .action(async () => {\n try {\n await runInit();\n } catch (err) {\n if ((err as Error).name === 'ExitPromptError') {\n console.log(chalk.yellow('\\nSetup cancelled.'));\n process.exit(0);\n }\n throw err;\n }\n });\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'no loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agents: ${agentCount} | Phases: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with per-agent model resolution')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(async (file: string, options: { input?: string; mock?: boolean; outputDir?: string }) => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found — using built-in defaults.\\n'));\n console.log(chalk.dim(' Run \"agentflow init\" to customize models and providers.\\n'));\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(\n async (file: string, options: { instance?: string; mock?: boolean; outputDir?: string }) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// mcp-config command\nprogram\n .command('mcp-config')\n .description('Print the MCP config JSON to add to Claude Code settings')\n .option('--workflows-dir ', 'Directory containing .aflow files', process.cwd())\n .action((options: { workflowsDir: string }) => {\n // Load local .env if present\n if (existsSync('.env')) {\n const envContent = readFileSync('.env', 'utf-8');\n for (const line of envContent.split('\\n')) {\n const match = line.match(/^([^=]+)=(.*)$/);\n if (match && !process.env[match[1]]) {\n process.env[match[1]] = match[2].trim();\n }\n }\n }\n\n const workflowsDir = resolve(options.workflowsDir);\n const env: Record = {\n AGENTFLOW_WORKFLOWS_DIR: workflowsDir,\n OLLAMA_BASE_URL: 'http://localhost:11434',\n };\n if (process.env['ANTHROPIC_API_KEY'])\n env['ANTHROPIC_API_KEY'] = process.env['ANTHROPIC_API_KEY'];\n if (process.env['OPENROUTER_API_KEY'])\n env['OPENROUTER_API_KEY'] = process.env['OPENROUTER_API_KEY'];\n\n const config = {\n mcpServers: {\n agentflow: {\n command: 'npx',\n args: ['-y', '--package=@anhonestboy/agentflow', 'agentflow-mcp'],\n env,\n },\n },\n };\n\n console.log(chalk.bold('\\nšŸ“‹ Add this to ~/.claude/settings.json under \"mcpServers\":\\n'));\n console.log(JSON.stringify(config, null, 2));\n if (!process.env['ANTHROPIC_API_KEY'] && !process.env['OPENROUTER_API_KEY']) {\n console.log(chalk.yellow('\\nāš ļø No API keys found in .env — run \"agentflow init\" first.'));\n }\n console.log();\n });\n\n// models command\nprogram\n .command('models')\n .description('List configured models and test connectivity')\n .action(async () => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init'));\n process.exit(1);\n }\n const raw = readFileSync('agentflow.config.json', 'utf-8');\n const config = JSON.parse(raw) as {\n models: Record;\n };\n\n console.log(chalk.bold('\\nšŸ¤– Configured models:\\n'));\n for (const [alias, cfg] of Object.entries(config.models)) {\n const label = `${alias}`.padEnd(20);\n const provider = cfg.provider.padEnd(12);\n const model = cfg.model ?? '(auto)';\n console.log(` ${chalk.cyan(label)} ${chalk.dim(provider)} ${model}`);\n }\n\n // Connectivity checks\n console.log(chalk.bold('\\nšŸ”Œ Connectivity:\\n'));\n\n if (process.env.ANTHROPIC_API_KEY) {\n console.log(` ${chalk.green('āœ…')} Claude (ANTHROPIC_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} Claude (ANTHROPIC_API_KEY not set)`);\n }\n\n if (process.env.OPENROUTER_API_KEY) {\n console.log(` ${chalk.green('āœ…')} OpenRouter (OPENROUTER_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} OpenRouter (OPENROUTER_API_KEY not set)`);\n }\n\n try {\n const ollamaBase = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434';\n const res = await fetch(`${ollamaBase}/api/tags`, {\n signal: AbortSignal.timeout(2000),\n });\n if (res.ok) {\n const data = (await res.json()) as { models: Array<{ name: string }> };\n console.log(\n ` ${chalk.green('āœ…')} Ollama (${data.models.length} models installed at ${ollamaBase})`,\n );\n } else {\n console.log(` ${chalk.yellow('āš ļø')} Ollama (response ${res.status})`);\n }\n } catch {\n console.log(` ${chalk.dim('ā—‹')} Ollama (unreachable)`);\n }\n\n console.log();\n });\n\nprogram.parse();\n"}, "a610c19dc813f661a978cd8c05eb7c4509ad72e9": {"t": 1785754251.2308176, "n": 16203, "h": "102760a9e80c340e", "c": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport type { ExecutorResolver } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { resolveModel } from './model-resolver.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport { runInit } from './commands/init.js';\nimport { OpenRouterExecutor } from './executors/openrouter-executor.js';\nimport { HermesExecutor } from './executors/hermes-executor.js';\nimport type { WorkflowIR, AgentDef } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n if (!existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\n/** Create an ExecutorResolver that resolves the model for each agent */\nfunction createExecutorResolver(\n toolRegistry: ReturnType,\n): ExecutorResolver {\n return (agent: AgentDef) => {\n const modelConfig = resolveModel(agent.model);\n process.stderr.write(\n chalk.dim(` šŸ“¦ [${agent.id}] model: ${modelConfig.model} (${modelConfig.provider})\\n`),\n );\n\n switch (modelConfig.provider) {\n case 'claude':\n return new ClaudeExecutor({ toolRegistry });\n case 'openrouter':\n return new OpenRouterExecutor(modelConfig.model);\n case 'hermes':\n return new HermesExecutor();\n case 'ollama':\n default:\n return new OllamaExecutor(modelConfig);\n }\n };\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version('0.1.0')\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// init command\nprogram\n .command('init')\n .description('Interactive configuration wizard')\n .action(async () => {\n try {\n await runInit();\n } catch (err) {\n if ((err as Error).name === 'ExitPromptError') {\n console.log(chalk.yellow('\\nSetup cancelled.'));\n process.exit(0);\n }\n throw err;\n }\n });\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'no loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agents: ${agentCount} | Phases: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with per-agent model resolution')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(async (file: string, options: { input?: string; mock?: boolean; outputDir?: string }) => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found — using built-in defaults.\\n'));\n console.log(chalk.dim(' Run \"agentflow init\" to customize models and providers.\\n'));\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(\n async (file: string, options: { instance?: string; mock?: boolean; outputDir?: string }) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// mcp-config command\nprogram\n .command('mcp-config')\n .description('Print the MCP config JSON to add to Claude Code settings')\n .option('--workflows-dir ', 'Directory containing .aflow files', process.cwd())\n .action((options: { workflowsDir: string }) => {\n // Load local .env if present\n if (existsSync('.env')) {\n const envContent = readFileSync('.env', 'utf-8');\n for (const line of envContent.split('\\n')) {\n const match = line.match(/^([^=]+)=(.*)$/);\n if (match && !process.env[match[1]]) {\n process.env[match[1]] = match[2].trim();\n }\n }\n }\n\n const workflowsDir = resolve(options.workflowsDir);\n const env: Record = {\n AGENTFLOW_WORKFLOWS_DIR: workflowsDir,\n OLLAMA_BASE_URL: 'http://localhost:11434',\n };\n if (process.env['ANTHROPIC_API_KEY'])\n env['ANTHROPIC_API_KEY'] = process.env['ANTHROPIC_API_KEY'];\n if (process.env['OPENROUTER_API_KEY'])\n env['OPENROUTER_API_KEY'] = process.env['OPENROUTER_API_KEY'];\n\n const config = {\n mcpServers: {\n agentflow: {\n command: 'npx',\n args: ['-y', '--package=@anhonestboy/agentflow', 'agentflow-mcp'],\n env,\n },\n },\n };\n\n console.log(chalk.bold('\\nšŸ“‹ Add this to ~/.claude/settings.json under \"mcpServers\":\\n'));\n console.log(JSON.stringify(config, null, 2));\n if (!process.env['ANTHROPIC_API_KEY'] && !process.env['OPENROUTER_API_KEY']) {\n console.log(chalk.yellow('\\nāš ļø No API keys found in .env — run \"agentflow init\" first.'));\n }\n console.log();\n });\n\n// models command\nprogram\n .command('models')\n .description('List configured models and test connectivity')\n .action(async () => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init'));\n process.exit(1);\n }\n const raw = readFileSync('agentflow.config.json', 'utf-8');\n const config = JSON.parse(raw) as {\n models: Record;\n };\n\n console.log(chalk.bold('\\nšŸ¤– Configured models:\\n'));\n for (const [alias, cfg] of Object.entries(config.models)) {\n const label = `${alias}`.padEnd(20);\n const provider = cfg.provider.padEnd(12);\n const model = cfg.model ?? '(auto)';\n console.log(` ${chalk.cyan(label)} ${chalk.dim(provider)} ${model}`);\n }\n\n // Connectivity checks\n console.log(chalk.bold('\\nšŸ”Œ Connectivity:\\n'));\n\n if (process.env.ANTHROPIC_API_KEY) {\n console.log(` ${chalk.green('āœ…')} Claude (ANTHROPIC_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} Claude (ANTHROPIC_API_KEY not set)`);\n }\n\n if (process.env.OPENROUTER_API_KEY) {\n console.log(` ${chalk.green('āœ…')} OpenRouter (OPENROUTER_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} OpenRouter (OPENROUTER_API_KEY not set)`);\n }\n\n try {\n const ollamaBase = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434';\n const res = await fetch(`${ollamaBase}/api/tags`, { signal: AbortSignal.timeout(2000) });\n if (res.ok) {\n const data = (await res.json()) as { models: Array<{ name: string }> };\n console.log(\n ` ${chalk.green('āœ…')} Ollama (${data.models.length} models installed at ${ollamaBase})`,\n );\n } else {\n console.log(` ${chalk.yellow('āš ļø')} Ollama (response ${res.status})`);\n }\n } catch {\n console.log(` ${chalk.dim('ā—‹')} Ollama (unreachable)`);\n }\n\n console.log();\n });\n\nprogram.parse();\n"}, "d104b4fbc6e8f0e4ecadf2859107e74a3c67abc4": {"t": 1785754253.05924, "n": 16080, "h": "9cf71391110b51e4", "c": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport type { ExecutorResolver } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { resolveModel } from './model-resolver.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport { runInit } from './commands/init.js';\nimport { OpenRouterExecutor } from './executors/openrouter-executor.js';\nimport type { WorkflowIR, AgentDef } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n if (!existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\n/** Create an ExecutorResolver that resolves the model for each agent */\nfunction createExecutorResolver(\n toolRegistry: ReturnType,\n): ExecutorResolver {\n return (agent: AgentDef) => {\n const modelConfig = resolveModel(agent.model);\n process.stderr.write(\n chalk.dim(` šŸ“¦ [${agent.id}] model: ${modelConfig.model} (${modelConfig.provider})\\n`),\n );\n\n switch (modelConfig.provider) {\n case 'claude':\n return new ClaudeExecutor({ toolRegistry });\n case 'openrouter':\n return new OpenRouterExecutor(modelConfig.model);\n case 'ollama':\n default:\n return new OllamaExecutor(modelConfig);\n }\n };\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version('0.1.0')\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// init command\nprogram\n .command('init')\n .description('Interactive configuration wizard')\n .action(async () => {\n try {\n await runInit();\n } catch (err) {\n if ((err as Error).name === 'ExitPromptError') {\n console.log(chalk.yellow('\\nSetup cancelled.'));\n process.exit(0);\n }\n throw err;\n }\n });\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'no loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agents: ${agentCount} | Phases: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with per-agent model resolution')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(async (file: string, options: { input?: string; mock?: boolean; outputDir?: string }) => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found — using built-in defaults.\\n'));\n console.log(chalk.dim(' Run \"agentflow init\" to customize models and providers.\\n'));\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(\n async (file: string, options: { instance?: string; mock?: boolean; outputDir?: string }) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// mcp-config command\nprogram\n .command('mcp-config')\n .description('Print the MCP config JSON to add to Claude Code settings')\n .option('--workflows-dir ', 'Directory containing .aflow files', process.cwd())\n .action((options: { workflowsDir: string }) => {\n // Load local .env if present\n if (existsSync('.env')) {\n const envContent = readFileSync('.env', 'utf-8');\n for (const line of envContent.split('\\n')) {\n const match = line.match(/^([^=]+)=(.*)$/);\n if (match && !process.env[match[1]]) {\n process.env[match[1]] = match[2].trim();\n }\n }\n }\n\n const workflowsDir = resolve(options.workflowsDir);\n const env: Record = {\n AGENTFLOW_WORKFLOWS_DIR: workflowsDir,\n OLLAMA_BASE_URL: 'http://localhost:11434',\n };\n if (process.env['ANTHROPIC_API_KEY'])\n env['ANTHROPIC_API_KEY'] = process.env['ANTHROPIC_API_KEY'];\n if (process.env['OPENROUTER_API_KEY'])\n env['OPENROUTER_API_KEY'] = process.env['OPENROUTER_API_KEY'];\n\n const config = {\n mcpServers: {\n agentflow: {\n command: 'npx',\n args: ['-y', '--package=@anhonestboy/agentflow', 'agentflow-mcp'],\n env,\n },\n },\n };\n\n console.log(chalk.bold('\\nšŸ“‹ Add this to ~/.claude/settings.json under \"mcpServers\":\\n'));\n console.log(JSON.stringify(config, null, 2));\n if (!process.env['ANTHROPIC_API_KEY'] && !process.env['OPENROUTER_API_KEY']) {\n console.log(chalk.yellow('\\nāš ļø No API keys found in .env — run \"agentflow init\" first.'));\n }\n console.log();\n });\n\n// models command\nprogram\n .command('models')\n .description('List configured models and test connectivity')\n .action(async () => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init'));\n process.exit(1);\n }\n const raw = readFileSync('agentflow.config.json', 'utf-8');\n const config = JSON.parse(raw) as {\n models: Record;\n };\n\n console.log(chalk.bold('\\nšŸ¤– Configured models:\\n'));\n for (const [alias, cfg] of Object.entries(config.models)) {\n const label = `${alias}`.padEnd(20);\n const provider = cfg.provider.padEnd(12);\n const model = cfg.model ?? '(auto)';\n console.log(` ${chalk.cyan(label)} ${chalk.dim(provider)} ${model}`);\n }\n\n // Connectivity checks\n console.log(chalk.bold('\\nšŸ”Œ Connectivity:\\n'));\n\n if (process.env.ANTHROPIC_API_KEY) {\n console.log(` ${chalk.green('āœ…')} Claude (ANTHROPIC_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} Claude (ANTHROPIC_API_KEY not set)`);\n }\n\n if (process.env.OPENROUTER_API_KEY) {\n console.log(` ${chalk.green('āœ…')} OpenRouter (OPENROUTER_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} OpenRouter (OPENROUTER_API_KEY not set)`);\n }\n\n try {\n const ollamaBase = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434';\n const res = await fetch(`${ollamaBase}/api/tags`, { signal: AbortSignal.timeout(2000) });\n if (res.ok) {\n const data = (await res.json()) as { models: Array<{ name: string }> };\n console.log(\n ` ${chalk.green('āœ…')} Ollama (${data.models.length} models installed at ${ollamaBase})`,\n );\n } else {\n console.log(` ${chalk.yellow('āš ļø')} Ollama (response ${res.status})`);\n }\n } catch {\n console.log(` ${chalk.dim('ā—‹')} Ollama (unreachable)`);\n }\n\n console.log();\n });\n\nprogram.parse();\n"}, "d5d6853e26796a7eb5992813d06424ed0a7a9a77": {"t": 1785754255.1500814, "n": 16009, "h": "3fb3da0e5850b826", "c": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport type { ExecutorResolver } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { resolveModel } from './model-resolver.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport { runInit } from './commands/init.js';\nimport { OpenRouterExecutor } from './executors/openrouter-executor.js';\nimport type { WorkflowIR, AgentDef } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n if (!existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\n/** Create an ExecutorResolver that resolves the model for each agent */\nfunction createExecutorResolver(\n toolRegistry: ReturnType,\n): ExecutorResolver {\n return (agent: AgentDef) => {\n const modelConfig = resolveModel(agent.model);\n process.stderr.write(\n chalk.dim(` šŸ“¦ [${agent.id}] model: ${modelConfig.model} (${modelConfig.provider})\\n`),\n );\n\n switch (modelConfig.provider) {\n case 'claude':\n return new ClaudeExecutor({ toolRegistry });\n case 'openrouter':\n return new OpenRouterExecutor(modelConfig.model);\n case 'ollama':\n default:\n return new OllamaExecutor(modelConfig);\n }\n };\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version('0.1.0')\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// init command\nprogram\n .command('init')\n .description('Interactive configuration wizard')\n .action(async () => {\n try {\n await runInit();\n } catch (err) {\n if ((err as Error).name === 'ExitPromptError') {\n console.log(chalk.yellow('\\nSetup cancelled.'));\n process.exit(0);\n }\n throw err;\n }\n });\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'no loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agents: ${agentCount} | Phases: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText =\n result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with per-agent model resolution')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(async (file: string, options: { input?: string; mock?: boolean; outputDir?: string }) => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init\\n'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(\n async (file: string, options: { instance?: string; mock?: boolean; outputDir?: string }) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// mcp-config command\nprogram\n .command('mcp-config')\n .description('Print the MCP config JSON to add to Claude Code settings')\n .option('--workflows-dir ', 'Directory containing .aflow files', process.cwd())\n .action((options: { workflowsDir: string }) => {\n // Load local .env if present\n if (existsSync('.env')) {\n const envContent = readFileSync('.env', 'utf-8');\n for (const line of envContent.split('\\n')) {\n const match = line.match(/^([^=]+)=(.*)$/);\n if (match && !process.env[match[1]]) {\n process.env[match[1]] = match[2].trim();\n }\n }\n }\n\n const workflowsDir = resolve(options.workflowsDir);\n const env: Record = {\n AGENTFLOW_WORKFLOWS_DIR: workflowsDir,\n OLLAMA_BASE_URL: 'http://localhost:11434',\n };\n if (process.env['ANTHROPIC_API_KEY'])\n env['ANTHROPIC_API_KEY'] = process.env['ANTHROPIC_API_KEY'];\n if (process.env['OPENROUTER_API_KEY'])\n env['OPENROUTER_API_KEY'] = process.env['OPENROUTER_API_KEY'];\n\n const config = {\n mcpServers: {\n agentflow: {\n command: 'npx',\n args: ['-y', '--package=@anhonestboy/agentflow', 'agentflow-mcp'],\n env,\n },\n },\n };\n\n console.log(chalk.bold('\\nšŸ“‹ Add this to ~/.claude/settings.json under \"mcpServers\":\\n'));\n console.log(JSON.stringify(config, null, 2));\n if (!process.env['ANTHROPIC_API_KEY'] && !process.env['OPENROUTER_API_KEY']) {\n console.log(chalk.yellow('\\nāš ļø No API keys found in .env — run \"agentflow init\" first.'));\n }\n console.log();\n });\n\n// models command\nprogram\n .command('models')\n .description('List configured models and test connectivity')\n .action(async () => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init'));\n process.exit(1);\n }\n const raw = readFileSync('agentflow.config.json', 'utf-8');\n const config = JSON.parse(raw) as {\n models: Record;\n };\n\n console.log(chalk.bold('\\nšŸ¤– Configured models:\\n'));\n for (const [alias, cfg] of Object.entries(config.models)) {\n const label = `${alias}`.padEnd(20);\n const provider = cfg.provider.padEnd(12);\n const model = cfg.model ?? '(auto)';\n console.log(` ${chalk.cyan(label)} ${chalk.dim(provider)} ${model}`);\n }\n\n // Connectivity checks\n console.log(chalk.bold('\\nšŸ”Œ Connectivity:\\n'));\n\n if (process.env.ANTHROPIC_API_KEY) {\n console.log(` ${chalk.green('āœ…')} Claude (ANTHROPIC_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} Claude (ANTHROPIC_API_KEY not set)`);\n }\n\n if (process.env.OPENROUTER_API_KEY) {\n console.log(` ${chalk.green('āœ…')} OpenRouter (OPENROUTER_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} OpenRouter (OPENROUTER_API_KEY not set)`);\n }\n\n try {\n const ollamaBase = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434';\n const res = await fetch(`${ollamaBase}/api/tags`, { signal: AbortSignal.timeout(2000) });\n if (res.ok) {\n const data = (await res.json()) as { models: Array<{ name: string }> };\n console.log(\n ` ${chalk.green('āœ…')} Ollama (${data.models.length} models installed at ${ollamaBase})`,\n );\n } else {\n console.log(` ${chalk.yellow('āš ļø')} Ollama (response ${res.status})`);\n }\n } catch {\n console.log(` ${chalk.dim('ā—‹')} Ollama (unreachable)`);\n }\n\n console.log();\n });\n\nprogram.parse();\n"}, "725d95b9c619a4095be084611f20e73deac4cf80": {"t": 1785754257.011512, "n": 15977, "h": "ab6ad86571b823ac", "c": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport type { ExecutorResolver } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { resolveModel } from './model-resolver.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport { runInit } from './commands/init.js';\nimport { OpenRouterExecutor } from './executors/openrouter-executor.js';\nimport type { WorkflowIR, AgentDef } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n if (!existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\n/** Create an ExecutorResolver that resolves the model for each agent */\nfunction createExecutorResolver(\n toolRegistry: ReturnType,\n): ExecutorResolver {\n return (agent: AgentDef) => {\n const modelConfig = resolveModel(agent.model);\n process.stderr.write(\n chalk.dim(` šŸ“¦ [${agent.id}] model: ${modelConfig.model} (${modelConfig.provider})\\n`),\n );\n\n switch (modelConfig.provider) {\n case 'claude':\n return new ClaudeExecutor({ toolRegistry });\n case 'openrouter':\n return new OpenRouterExecutor(modelConfig.model);\n case 'ollama':\n default:\n return new OllamaExecutor(modelConfig);\n }\n };\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version('0.1.0')\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// init command\nprogram\n .command('init')\n .description('Interactive configuration wizard')\n .action(async () => {\n try {\n await runInit();\n } catch (err) {\n if ((err as Error).name === 'ExitPromptError') {\n console.log(chalk.yellow('\\nSetup cancelled.'));\n process.exit(0);\n }\n throw err;\n }\n });\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText = result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'no loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agents: ${agentCount} | Phases: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText = result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with per-agent model resolution')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(async (file: string, options: { input?: string; mock?: boolean; outputDir?: string }) => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init\\n'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(\n async (file: string, options: { instance?: string; mock?: boolean; outputDir?: string }) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// mcp-config command\nprogram\n .command('mcp-config')\n .description('Print the MCP config JSON to add to Claude Code settings')\n .option('--workflows-dir ', 'Directory containing .aflow files', process.cwd())\n .action((options: { workflowsDir: string }) => {\n // Load local .env if present\n if (existsSync('.env')) {\n const envContent = readFileSync('.env', 'utf-8');\n for (const line of envContent.split('\\n')) {\n const match = line.match(/^([^=]+)=(.*)$/);\n if (match && !process.env[match[1]]) {\n process.env[match[1]] = match[2].trim();\n }\n }\n }\n\n const workflowsDir = resolve(options.workflowsDir);\n const env: Record = {\n AGENTFLOW_WORKFLOWS_DIR: workflowsDir,\n OLLAMA_BASE_URL: 'http://localhost:11434',\n };\n if (process.env['ANTHROPIC_API_KEY']) env['ANTHROPIC_API_KEY'] = process.env['ANTHROPIC_API_KEY'];\n if (process.env['OPENROUTER_API_KEY']) env['OPENROUTER_API_KEY'] = process.env['OPENROUTER_API_KEY'];\n\n const config = {\n mcpServers: {\n agentflow: {\n command: 'npx',\n args: ['-y', '--package=@anhonestboy/agentflow', 'agentflow-mcp'],\n env,\n },\n },\n };\n\n console.log(chalk.bold('\\nšŸ“‹ Add this to ~/.claude/settings.json under \"mcpServers\":\\n'));\n console.log(JSON.stringify(config, null, 2));\n if (!process.env['ANTHROPIC_API_KEY'] && !process.env['OPENROUTER_API_KEY']) {\n console.log(chalk.yellow('\\nāš ļø No API keys found in .env — run \"agentflow init\" first.'));\n }\n console.log();\n });\n\n// models command\nprogram\n .command('models')\n .description('List configured models and test connectivity')\n .action(async () => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init'));\n process.exit(1);\n }\n const raw = readFileSync('agentflow.config.json', 'utf-8');\n const config = JSON.parse(raw) as {\n models: Record;\n };\n\n console.log(chalk.bold('\\nšŸ¤– Configured models:\\n'));\n for (const [alias, cfg] of Object.entries(config.models)) {\n const label = `${alias}`.padEnd(20);\n const provider = cfg.provider.padEnd(12);\n const model = cfg.model ?? '(auto)';\n console.log(` ${chalk.cyan(label)} ${chalk.dim(provider)} ${model}`);\n }\n\n // Connectivity checks\n console.log(chalk.bold('\\nšŸ”Œ Connectivity:\\n'));\n\n if (process.env.ANTHROPIC_API_KEY) {\n console.log(` ${chalk.green('āœ…')} Claude (ANTHROPIC_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} Claude (ANTHROPIC_API_KEY not set)`);\n }\n\n if (process.env.OPENROUTER_API_KEY) {\n console.log(` ${chalk.green('āœ…')} OpenRouter (OPENROUTER_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} OpenRouter (OPENROUTER_API_KEY not set)`);\n }\n\n try {\n const ollamaBase = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434';\n const res = await fetch(`${ollamaBase}/api/tags`, { signal: AbortSignal.timeout(2000) });\n if (res.ok) {\n const data = (await res.json()) as { models: Array<{ name: string }> };\n console.log(\n ` ${chalk.green('āœ…')} Ollama (${data.models.length} models installed at ${ollamaBase})`,\n );\n } else {\n console.log(` ${chalk.yellow('āš ļø')} Ollama (response ${res.status})`);\n }\n } catch {\n console.log(` ${chalk.dim('ā—‹')} Ollama (unreachable)`);\n }\n\n console.log();\n });\n\nprogram.parse();\n"}, "1695ac06993a3b1fe3b517ed6e509cf24604e857": {"t": 1785754259.651495, "n": 15696, "h": "62d85bfa26e31a19", "c": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport type { ExecutorResolver } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { resolveModel } from './model-resolver.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport { runInit } from './commands/init.js';\nimport { OpenRouterExecutor } from './executors/openrouter-executor.js';\nimport type { WorkflowIR, AgentDef } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\n/** Crea un ExecutorResolver che risolve il modello per ogni agente */\nfunction createExecutorResolver(\n toolRegistry: ReturnType,\n): ExecutorResolver {\n return (agent: AgentDef) => {\n const modelConfig = resolveModel(agent.model);\n process.stderr.write(\n chalk.dim(` šŸ“¦ [${agent.id}] model: ${modelConfig.model} (${modelConfig.provider})\\n`),\n );\n\n switch (modelConfig.provider) {\n case 'claude':\n return new ClaudeExecutor({ toolRegistry });\n case 'openrouter':\n return new OpenRouterExecutor(modelConfig.model);\n case 'ollama':\n default:\n return new OllamaExecutor(modelConfig);\n }\n };\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version('0.1.0')\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// init command\nprogram\n .command('init')\n .description('Wizard di configurazione interattivo')\n .action(async () => {\n await runInit();\n });\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText = result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'no loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agents: ${agentCount} | Phases: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText = result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with per-agent model resolution')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(async (file: string, options: { input?: string; mock?: boolean; outputDir?: string }) => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init\\n'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(\n async (file: string, options: { instance?: string; mock?: boolean; outputDir?: string }) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// mcp-config command\nprogram\n .command('mcp-config')\n .description('Print the MCP config JSON to add to Claude Code settings')\n .option('--workflows-dir ', 'Directory containing .aflow files', process.cwd())\n .action((options: { workflowsDir: string }) => {\n // Carica .env locale se presente\n if (existsSync('.env')) {\n const envContent = readFileSync('.env', 'utf-8');\n for (const line of envContent.split('\\n')) {\n const match = line.match(/^([^=]+)=(.*)$/);\n if (match && !process.env[match[1]]) {\n process.env[match[1]] = match[2].trim();\n }\n }\n }\n\n const workflowsDir = resolve(options.workflowsDir);\n const env: Record = {\n AGENTFLOW_WORKFLOWS_DIR: workflowsDir,\n OLLAMA_BASE_URL: 'http://localhost:11434',\n };\n if (process.env['ANTHROPIC_API_KEY']) env['ANTHROPIC_API_KEY'] = process.env['ANTHROPIC_API_KEY'];\n if (process.env['OPENROUTER_API_KEY']) env['OPENROUTER_API_KEY'] = process.env['OPENROUTER_API_KEY'];\n\n const config = {\n mcpServers: {\n agentflow: {\n command: 'npx',\n args: ['-y', '--package=@anhonestboy/agentflow', 'agentflow-mcp'],\n env,\n },\n },\n };\n\n console.log(chalk.bold('\\nšŸ“‹ Add this to ~/.claude/settings.json under \"mcpServers\":\\n'));\n console.log(JSON.stringify(config, null, 2));\n if (!process.env['ANTHROPIC_API_KEY'] && !process.env['OPENROUTER_API_KEY']) {\n console.log(chalk.yellow('\\nāš ļø No API keys found in .env — run \"agentflow init\" first.'));\n }\n console.log();\n });\n\n// models command\nprogram\n .command('models')\n .description('List configured models and test connectivity')\n .action(async () => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init'));\n process.exit(1);\n }\n const raw = readFileSync('agentflow.config.json', 'utf-8');\n const config = JSON.parse(raw) as {\n models: Record;\n };\n\n console.log(chalk.bold('\\nšŸ¤– Configured models:\\n'));\n for (const [alias, cfg] of Object.entries(config.models)) {\n const label = `${alias}`.padEnd(20);\n const provider = cfg.provider.padEnd(12);\n const model = cfg.model ?? '(auto)';\n console.log(` ${chalk.cyan(label)} ${chalk.dim(provider)} ${model}`);\n }\n\n // Connectivity checks\n console.log(chalk.bold('\\nšŸ”Œ Connectivity:\\n'));\n\n if (process.env.ANTHROPIC_API_KEY) {\n console.log(` ${chalk.green('āœ…')} Claude (ANTHROPIC_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} Claude (ANTHROPIC_API_KEY not set)`);\n }\n\n if (process.env.OPENROUTER_API_KEY) {\n console.log(` ${chalk.green('āœ…')} OpenRouter (OPENROUTER_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} OpenRouter (OPENROUTER_API_KEY not set)`);\n }\n\n try {\n const ollamaBase = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434';\n const res = await fetch(`${ollamaBase}/api/tags`, { signal: AbortSignal.timeout(2000) });\n if (res.ok) {\n const data = (await res.json()) as { models: Array<{ name: string }> };\n console.log(\n ` ${chalk.green('āœ…')} Ollama (${data.models.length} models installed at ${ollamaBase})`,\n );\n } else {\n console.log(` ${chalk.yellow('āš ļø')} Ollama (response ${res.status})`);\n }\n } catch {\n console.log(` ${chalk.dim('ā—‹')} Ollama (unreachable)`);\n }\n\n console.log();\n });\n\nprogram.parse();\n"}, "1f25324ed09df5f08dce7a59d665b981e0adfffd": {"t": 1785754261.5607584, "n": 15145, "h": "d886bd02e10742aa", "c": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport type { ExecutorResolver } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { resolveModel } from './model-resolver.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport { runInit } from './commands/init.js';\nimport { OpenRouterExecutor } from './executors/openrouter-executor.js';\nimport type { WorkflowIR, AgentDef } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\n/** Crea un ExecutorResolver che risolve il modello per ogni agente */\nfunction createExecutorResolver(\n toolRegistry: ReturnType,\n): ExecutorResolver {\n return (agent: AgentDef) => {\n const modelConfig = resolveModel(agent.model);\n process.stderr.write(\n chalk.dim(` šŸ“¦ [${agent.id}] model: ${modelConfig.model} (${modelConfig.provider})\\n`),\n );\n\n switch (modelConfig.provider) {\n case 'claude':\n return new ClaudeExecutor({ toolRegistry });\n case 'openrouter':\n return new OpenRouterExecutor(modelConfig.model);\n case 'ollama':\n default:\n return new OllamaExecutor(modelConfig);\n }\n };\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version('0.1.0')\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// init command\nprogram\n .command('init')\n .description('Wizard di configurazione interattivo')\n .action(async () => {\n await runInit();\n });\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText = result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'no loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agents: ${agentCount} | Phases: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [phase: ${err.phase}]` : ''}${err.agent ? ` [agent: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [phase: ${warn.phase}]` : ''}${warn.agent ? ` [agent: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText = result.warnings.length > 0 ? ` — ${result.warnings.length} warning(s)` : '';\n console.log(chalk.green(`\\nāœ… Workflow valid${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validation failed — ${result.errors.length} error(s), ${result.warnings.length} warning(s)`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with per-agent model resolution')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(async (file: string, options: { input?: string; mock?: boolean; outputDir?: string }) => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init\\n'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(\n async (file: string, options: { instance?: string; mock?: boolean; outputDir?: string }) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\n// mcp-config command\nprogram\n .command('mcp-config')\n .description('Print the MCP config JSON to add to Claude Code settings')\n .option('--workflows-dir ', 'Directory containing .aflow files', process.cwd())\n .action((options: { workflowsDir: string }) => {\n const workflowsDir = resolve(options.workflowsDir);\n const config = {\n mcpServers: {\n agentflow: {\n command: 'npx',\n args: ['-y', '--package=@anhonestboy/agentflow', 'agentflow-mcp'],\n env: {\n AGENTFLOW_WORKFLOWS_DIR: workflowsDir,\n ANTHROPIC_API_KEY: '${ANTHROPIC_API_KEY}',\n OPENROUTER_API_KEY: '${OPENROUTER_API_KEY}',\n OLLAMA_BASE_URL: 'http://localhost:11434',\n },\n },\n },\n };\n\n console.log(chalk.bold('\\nšŸ“‹ Add this to ~/.claude/settings.json under \"mcpServers\":\\n'));\n console.log(JSON.stringify(config, null, 2));\n console.log(chalk.dim('\\nOr run: claude mcp add agentflow -- npx -y --package=@anhonestboy/agentflow agentflow-mcp\\n'));\n });\n\n// models command\nprogram\n .command('models')\n .description('List configured models and test connectivity')\n .action(async () => {\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json not found. Run first: agentflow init'));\n process.exit(1);\n }\n const raw = readFileSync('agentflow.config.json', 'utf-8');\n const config = JSON.parse(raw) as {\n models: Record;\n };\n\n console.log(chalk.bold('\\nšŸ¤– Configured models:\\n'));\n for (const [alias, cfg] of Object.entries(config.models)) {\n const label = `${alias}`.padEnd(20);\n const provider = cfg.provider.padEnd(12);\n const model = cfg.model ?? '(auto)';\n console.log(` ${chalk.cyan(label)} ${chalk.dim(provider)} ${model}`);\n }\n\n // Connectivity checks\n console.log(chalk.bold('\\nšŸ”Œ Connectivity:\\n'));\n\n if (process.env.ANTHROPIC_API_KEY) {\n console.log(` ${chalk.green('āœ…')} Claude (ANTHROPIC_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} Claude (ANTHROPIC_API_KEY not set)`);\n }\n\n if (process.env.OPENROUTER_API_KEY) {\n console.log(` ${chalk.green('āœ…')} OpenRouter (OPENROUTER_API_KEY set)`);\n } else {\n console.log(` ${chalk.dim('ā—‹')} OpenRouter (OPENROUTER_API_KEY not set)`);\n }\n\n try {\n const ollamaBase = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434';\n const res = await fetch(`${ollamaBase}/api/tags`, { signal: AbortSignal.timeout(2000) });\n if (res.ok) {\n const data = (await res.json()) as { models: Array<{ name: string }> };\n console.log(\n ` ${chalk.green('āœ…')} Ollama (${data.models.length} models installed at ${ollamaBase})`,\n );\n } else {\n console.log(` ${chalk.yellow('āš ļø')} Ollama (response ${res.status})`);\n }\n } catch {\n console.log(` ${chalk.dim('ā—‹')} Ollama (unreachable)`);\n }\n\n console.log();\n });\n\nprogram.parse();\n"}, "263ccc678897b17467b3d97d1e52e8d6a328e0b8": {"t": 1785754263.5627463, "n": 12186, "h": "a945aa49a8fcef58", "c": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport type { ExecutorResolver } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { resolveModel } from './model-resolver.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport { runInit } from './commands/init.js';\nimport { OpenRouterExecutor } from './executors/openrouter-executor.js';\nimport type { WorkflowIR, AgentDef } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\n/** Crea un ExecutorResolver che risolve il modello per ogni agente */\nfunction createExecutorResolver(\n toolRegistry: ReturnType,\n): ExecutorResolver {\n return (agent: AgentDef) => {\n const modelConfig = resolveModel(agent.model);\n process.stderr.write(\n chalk.dim(` šŸ“¦ [${agent.id}] model: ${modelConfig.model} (${modelConfig.provider})\\n`),\n );\n\n switch (modelConfig.provider) {\n case 'claude':\n return new ClaudeExecutor({ toolRegistry });\n case 'openrouter':\n return new OpenRouterExecutor(modelConfig.model);\n case 'ollama':\n default:\n return new OllamaExecutor(modelConfig);\n }\n };\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version('0.1.0')\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// init command\nprogram\n .command('init')\n .description('Wizard di configurazione interattivo')\n .action(async () => {\n await runInit();\n });\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [fase: ${err.phase}]` : ''}${err.agent ? ` [agente: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [fase: ${warn.phase}]` : ''}${warn.agent ? ` [agente: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText = result.warnings.length > 0 ? ` — ${result.warnings.length} warning` : '';\n console.log(chalk.green(`\\nāœ… Workflow valido${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validazione fallita — ${result.errors.length} errori, ${result.warnings.length} warning`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'nessun loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agenti: ${agentCount} | Fasi: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(\n chalk.red(\n `āŒ [${err.rule}]${err.phase ? ` [fase: ${err.phase}]` : ''}${err.agent ? ` [agente: ${err.agent}]` : ''} ${err.message}`,\n ),\n );\n }\n for (const warn of result.warnings) {\n console.log(\n chalk.yellow(\n `āš ļø [${warn.rule}]${warn.phase ? ` [fase: ${warn.phase}]` : ''}${warn.agent ? ` [agente: ${warn.agent}]` : ''} ${warn.message}`,\n ),\n );\n }\n\n if (result.ok) {\n const warnText = result.warnings.length > 0 ? ` — ${result.warnings.length} warning` : '';\n console.log(chalk.green(`\\nāœ… Workflow valido${warnText}`));\n } else {\n console.log(\n chalk.red(\n `\\nāŒ Validazione fallita — ${result.errors.length} errori, ${result.warnings.length} warning`,\n ),\n );\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with per-agent model resolution')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(async (file: string, options: { input?: string; mock?: boolean; outputDir?: string }) => {\n // Guard: config mancante\n if (!existsSync('agentflow.config.json')) {\n console.log(chalk.yellow('āš ļø agentflow.config.json non trovato. Esegui prima:'));\n console.log(chalk.cyan(' npx tsx src/cli.ts init\\n'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option(\n '--output-dir ',\n 'Directory for phase output files (default: ./output/)',\n )\n .action(\n async (file: string, options: { instance?: string; mock?: boolean; outputDir?: string }) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection: per-agent model resolution\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = options.mock\n ? new MockAgentExecutor()\n : createExecutorResolver(toolRegistry);\n\n if (options.mock) console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n else console.log(chalk.cyan('šŸ”€ Executor: per-agent model resolution\\n'));\n\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n },\n );\n\nprogram.parse();\n"}, "ea464c15102b0017cee8aad2265188babb3d5902": {"t": 1785754265.9251595, "n": 11058, "h": "d352121c10e2ca04", "c": "import { readFileSync } from 'node:fs';\nimport { writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { parse } from './parser.js';\nimport { compile } from './compiler.js';\nimport { validate } from './validate.js';\nimport { WorkflowRunner, MockAgentExecutor } from './runtime.js';\nimport { ClaudeExecutor } from './executors/claude-executor.js';\nimport { OllamaExecutor } from './executors/ollama-executor.js';\nimport { createBuiltinRegistry } from './tools/index.js';\nimport type { WorkflowIR } from './types.js';\n\nfunction loadAndCompile(filePath: string): WorkflowIR {\n const source = readFileSync(filePath, 'utf-8');\n const ast = parse(source);\n return compile(ast);\n}\n\nconst program = new Command();\n\nprogram\n .name('agentflow')\n .version('0.1.0')\n .description('AgentFlow DSL — declarative language for multi-agent orchestration');\n\n// compile command\nprogram\n .command('compile ')\n .description('Compile .aflow file and output IR JSON to stdout')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n console.log(JSON.stringify(ir, null, 2));\n } catch (err) {\n console.error(chalk.red(`Compilation error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// validate command\nprogram\n .command('validate ')\n .description('Validate .aflow file and show errors/warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n for (const err of result.errors) {\n console.log(chalk.red(`āŒ [${err.rule}]${err.phase ? ` [fase: ${err.phase}]` : ''}${err.agent ? ` [agente: ${err.agent}]` : ''} ${err.message}`));\n }\n for (const warn of result.warnings) {\n console.log(chalk.yellow(`āš ļø [${warn.rule}]${warn.phase ? ` [fase: ${warn.phase}]` : ''}${warn.agent ? ` [agente: ${warn.agent}]` : ''} ${warn.message}`));\n }\n\n if (result.ok) {\n const warnText = result.warnings.length > 0 ? ` — ${result.warnings.length} warning` : '';\n console.log(chalk.green(`\\nāœ… Workflow valido${warnText}`));\n } else {\n console.log(chalk.red(`\\nāŒ Validazione fallita — ${result.errors.length} errori, ${result.warnings.length} warning`));\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// check command\nprogram\n .command('check ')\n .description('Check .aflow file: summary + errors + warnings')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n const w = ir.workflow;\n\n const agentCount = Object.keys(w.agents).length;\n const phaseCount = w.phases.length;\n const loopInfo = w.loop\n ? `Loop: ${w.loop.id} (max ${w.loop.max_iterations ?? '?'} iter.)`\n : 'nessun loop';\n\n console.log(chalk.bold(`\\nšŸ“‹ ${w.id}${w.version ? ` — v${w.version}` : ''}`));\n if (w.description) console.log(` ${w.description}`);\n console.log(` Agenti: ${agentCount} | Fasi: ${phaseCount} | ${loopInfo}\\n`);\n\n for (const err of result.errors) {\n console.log(chalk.red(`āŒ [${err.rule}]${err.phase ? ` [fase: ${err.phase}]` : ''}${err.agent ? ` [agente: ${err.agent}]` : ''} ${err.message}`));\n }\n for (const warn of result.warnings) {\n console.log(chalk.yellow(`āš ļø [${warn.rule}]${warn.phase ? ` [fase: ${warn.phase}]` : ''}${warn.agent ? ` [agente: ${warn.agent}]` : ''} ${warn.message}`));\n }\n\n if (result.ok) {\n const warnText = result.warnings.length > 0 ? ` — ${result.warnings.length} warning` : '';\n console.log(chalk.green(`\\nāœ… Workflow valido${warnText}`));\n } else {\n console.log(chalk.red(`\\nāŒ Validazione fallita — ${result.errors.length} errori, ${result.warnings.length} warning`));\n process.exit(1);\n }\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// build command\nprogram\n .command('build ')\n .description('Compile and save IR JSON to disk')\n .action((file: string) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n console.error(chalk.red('\\nBuild failed due to validation errors.'));\n process.exit(1);\n }\n\n const outFile = file.replace(/\\.aflow$/, '.ir.json');\n writeFileSync(outFile, JSON.stringify(ir, null, 2));\n console.log(chalk.green(`āœ… Built: ${outFile}`));\n } catch (err) {\n console.error(chalk.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// run command\nprogram\n .command('run ')\n .description('Execute workflow with ClaudeExecutor (real) or MockExecutor (fallback)')\n .option('--input ', 'Trigger input as key=value pairs')\n .option('--mock', 'Force MockAgentExecutor even if ANTHROPIC_API_KEY is set')\n .option('--output-dir ', 'Directory for phase output files (default: ./output/)')\n .action(async (file: string, options: { input?: string; mock?: boolean; outputDir?: string }) => {\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Parse trigger input\n const triggerInput: Record = {};\n if (options.input) {\n const pairs = options.input.match(/(\\w+)=(\"[^\"]*\"|[^\\s]+)/g) ?? [];\n for (const pair of pairs) {\n const eqIdx = pair.indexOf('=');\n const key = pair.substring(0, eqIdx);\n let value: string = pair.substring(eqIdx + 1);\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n triggerInput[key] = value;\n }\n }\n\n // Executor selection\n const useClaude = !options.mock && !!process.env.ANTHROPIC_API_KEY\n const useOllama = !options.mock && !useClaude\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = useClaude\n ? new ClaudeExecutor({ toolRegistry })\n : useOllama\n ? new OllamaExecutor()\n : new MockAgentExecutor()\n\n if (useClaude) console.log(chalk.cyan('šŸ¤– Executor: Claude API (with tools)\\n'))\n else if (useOllama) console.log(chalk.cyan(`šŸ¦™ Executor: Ollama (${process.env.OLLAMA_MODEL ?? 'gemma4:e4b'})\\n`))\n else console.log(chalk.yellow('āš ļø Executor: Mock\\n'))\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`šŸš€ Running: ${ir.workflow.id}\\n`));\n\n const instance = await runner.run(triggerInput);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n\n } catch (err) {\n console.error(chalk.red(`Runtime error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\n// resume command\nprogram\n .command('resume ')\n .description('Resume a previously interrupted workflow instance from saved state')\n .option('--instance ', 'Instance ID to resume (required)')\n .option('--mock', 'Force MockAgentExecutor')\n .option('--output-dir ', 'Directory for phase output files (default: ./output/)')\n .action(async (file: string, options: { instance?: string; mock?: boolean; outputDir?: string }) => {\n if (!options.instance) {\n console.error(chalk.red('Error: --instance is required for resume'));\n process.exit(1);\n }\n\n try {\n const ir = loadAndCompile(file);\n const result = validate(ir);\n\n if (!result.ok) {\n for (const err of result.errors) {\n console.error(chalk.red(`āŒ [${err.rule}] ${err.message}`));\n }\n process.exit(1);\n }\n\n // Executor selection\n const useClaude = !options.mock && !!process.env.ANTHROPIC_API_KEY;\n const useOllama = !options.mock && !useClaude;\n const outputDir = options.outputDir ?? `./output/${ir.workflow.id}`;\n const toolRegistry = createBuiltinRegistry(resolve(outputDir));\n\n const executor = useClaude\n ? new ClaudeExecutor({ toolRegistry })\n : useOllama\n ? new OllamaExecutor()\n : new MockAgentExecutor();\n\n if (useClaude) console.log(chalk.cyan('šŸ¤– Executor: Claude API (with tools)\\n'));\n else if (useOllama) console.log(chalk.cyan(`šŸ¦™ Executor: Ollama (${process.env.OLLAMA_MODEL ?? 'gemma4:e4b'})\\n`));\n else console.log(chalk.yellow('āš ļø Executor: Mock\\n'));\n const runner = new WorkflowRunner(ir, executor, { outputDir });\n\n console.log(chalk.bold(`ā–¶ Resuming: ${ir.workflow.id} — instance ${options.instance}\\n`));\n\n const instance = await runner.resume(options.instance);\n\n // Phase results\n console.log(chalk.bold('\\nšŸ“Š Phase Results:'));\n for (const [phaseId, state] of Object.entries(instance.phase_states)) {\n const icon = state === 'completed' ? 'āœ…' : state === 'failed' ? 'āŒ' : 'ā³';\n console.log(` ${icon} ${phaseId}: ${state}`);\n }\n\n // Loop info\n for (const [loopId, iterations] of Object.entries(instance.loop_iterations)) {\n console.log(chalk.cyan(`\\nšŸ”„ Loop \"${loopId}\": ${iterations} iteration(s)`));\n }\n\n // Final state\n const stateIcon = instance.state === 'completed' ? 'āœ…' : 'āŒ';\n console.log(chalk.bold(`\\n${stateIcon} Workflow state: ${instance.state}`));\n console.log(chalk.dim(` Instance: ${instance.instance_id}`));\n console.log(chalk.dim(` State saved to: ${instance.instance_id}.state.json`));\n console.log(chalk.dim(` Outputs saved to: ${outputDir}/\\n`));\n\n } catch (err) {\n console.error(chalk.red(`Resume error: ${(err as Error).message}`));\n process.exit(1);\n }\n });\n\nprogram.parse();"}}}}