UNPKG

@agentled/cli

Version:

CLI for Agentled — manage workflows, apps, and knowledge from the command line. Zero context-window cost for AI agents.

155 lines 7.13 kB
/** * `agentled fixture capture <executionId> --wf <workflowId>` — save all step * outputs from an execution as local JSON fixtures for zero-credit replay. * `agentled fixture list` — list all captured fixture sets. * * Auto-updates tests/<workflowId>.test.json so the assertions reference the * captured execution as their fixture source. */ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; import { join, resolve } from 'node:path'; import { AgentledClient } from '../client.js'; import { printOutput, printError } from '../utils/output.js'; import { findWorkspaceDir } from '../utils/workspace-folder.js'; export function registerFixtureCommands(program) { const fixture = program .command('fixture') .description('Manage captured step outputs for zero-credit test replay.'); fixture .command('capture <executionId>') .description('Save all step outputs from an execution into fixtures/step-outputs/<executionId>/.') .requiredOption('--wf <workflowId>', 'Workflow ID for the execution') .option('--force', 'Overwrite existing fixture files', false) .option('--format <fmt>', 'Output format', 'json') .action(async (executionId, opts) => { try { const client = new AgentledClient(); const wfId = opts.wf; // Load execution (used to confirm workflowId for the test-file update) const execution = await client.getExecution(wfId, executionId); // Page through timelines in ascending order so dedup-by-stepId Map keeps newest const allTimelines = []; let nextToken; let page = 0; do { const result = await client.listTimelines(wfId, executionId, { limit: 50, nextToken, direction: 'asc', }); allTimelines.push(...(result.items ?? result.timelines ?? [])); nextToken = result.nextToken; page++; if (page > 20) break; // safety cap } while (nextToken); // Dedup by stepId (asc order means later set() = newer record) const byStep = new Map(); for (const tl of allTimelines) { const stepId = (tl.stepId ?? tl.step_id ?? String(tl.id ?? '')); if (stepId) byStep.set(stepId, tl); } const stepOutputs = {}; const failures = []; for (const [stepId, tl] of byStep.entries()) { try { const full = await client.getTimeline(wfId, executionId, String(tl.id ?? '')); stepOutputs[stepId] = { stepId, timelineId: tl.id, status: full.status ?? tl.status, capturedAt: new Date().toISOString(), output: full.output ?? full.stepOutput ?? full.result ?? null, }; } catch (err) { failures.push(`${stepId}: ${err instanceof Error ? err.message : String(err)}`); } } const wsDir = findWorkspaceDir(); const outDir = wsDir ? join(wsDir, 'fixtures', 'step-outputs', executionId) : resolve('fixtures', 'step-outputs', executionId); mkdirSync(outDir, { recursive: true }); let saved = 0; const skipped = []; for (const [stepId, data] of Object.entries(stepOutputs)) { const filePath = join(outDir, `${stepId}.json`); if (existsSync(filePath) && !opts.force) { skipped.push(stepId); continue; } writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n'); saved++; } // Auto-update test file fixture references let testFileUpdated = null; if (wsDir) { const wfIdFromExec = String(execution.workflowId ?? execution.workflow_id ?? wfId); const testPath = join(wsDir, 'tests', `${wfIdFromExec}.test.json`); if (existsSync(testPath)) { try { const testFile = JSON.parse(readFileSync(testPath, 'utf-8')); if (!testFile.fixtureExecutionId) { testFile.fixtureExecutionId = executionId; const testSteps = (testFile.steps ?? []); for (const ts of testSteps) { const sid = String(ts.stepId ?? ''); if (!ts.fixtureSource && stepOutputs[sid]) { ts.fixtureSource = `${executionId}/${sid}`; } } writeFileSync(testPath, JSON.stringify(testFile, null, 2) + '\n'); testFileUpdated = testPath; } } catch { /* malformed test file — skip */ } } } printOutput({ executionId, workflowId: wfId, outDir, saved, skipped, failures, testFileUpdated, steps: Object.entries(stepOutputs).map(([stepId, data]) => ({ stepId, status: data.status ?? 'unknown', })), }, (opts.format ?? 'json')); } catch (e) { printError(e instanceof Error ? e.message : String(e)); } }); fixture .command('list') .description('List all captured fixture sets in the local workspace.') .option('--format <fmt>', 'Output format', 'json') .action((opts) => { try { const wsDir = findWorkspaceDir(); const fixtureRoot = wsDir ? join(wsDir, 'fixtures', 'step-outputs') : resolve('fixtures', 'step-outputs'); if (!existsSync(fixtureRoot)) { printOutput({ fixtures: [], hint: 'Run `agentled fixture capture <executionId> --wf <workflowId>` after a test run.' }, (opts.format ?? 'json')); return; } const fixtures = readdirSync(fixtureRoot, { withFileTypes: true }) .filter((e) => e.isDirectory()) .map((e) => { const files = readdirSync(join(fixtureRoot, e.name)).filter((f) => f.endsWith('.json')); return { executionId: e.name, steps: files.map((f) => f.replace('.json', '')) }; }); printOutput({ fixtures }, (opts.format ?? 'json')); } catch (e) { printError(e instanceof Error ? e.message : String(e)); } }); } //# sourceMappingURL=fixture.js.map