UNPKG

@agentled/cli

Version:

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

356 lines 16.2 kB
import { readFileSync } from 'node:fs'; import { AgentledClient } from '../client.js'; import { printOutput, printError } from '../utils/output.js'; function parsePatches(opts) { if (opts.patches && opts.patchesFile) { throw new Error('Use either --patches or --patches-file, not both'); } if (!opts.patches && !opts.patchesFile) { throw new Error('Provide --patches or --patches-file'); } const raw = opts.patchesFile ? readFileSync(opts.patchesFile, 'utf8') : opts.patches; const parsed = JSON.parse(raw); if (!Array.isArray(parsed)) { throw new Error('Patches must be a JSON array'); } return parsed; } export function registerExecutionCommands(program) { const executions = program .command('executions') .alias('exec') .description('Manage workflow executions'); executions .command('list <workflowId>') .description('List executions for a workflow') .option('--status <status>', 'Filter by status') .option('--limit <n>', 'Max results', parseInt) .option('--direction <dir>', 'Sort direction: asc, desc') .option('--next-token <token>', 'Pagination cursor from previous response') .option('--format <fmt>', 'Output format', 'json') .action(async (workflowId, opts) => { try { const client = new AgentledClient(); const result = await client.listExecutions(workflowId, { status: opts.status, limit: opts.limit, direction: opts.direction, nextToken: opts.nextToken, }); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); executions .command('get <workflowId> <executionId>') .description('Get execution details with step results') .option('--format <fmt>', 'Output format', 'json') .action(async (workflowId, executionId, opts) => { try { const client = new AgentledClient(); const result = await client.getExecution(workflowId, executionId); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); executions .command('step-output <workflowId> <executionId> <stepId>') .description('Read one step output payload from an execution') .option('--field <name>', 'Return only one output field') .option('--select <fields...>', 'Return only selected top-level fields') .option('--format <fmt>', 'Output format', 'json') .action(async (workflowId, executionId, stepId, opts) => { try { const client = new AgentledClient(); const result = await client.getStepOutput(workflowId, executionId, stepId, { field: opts.field, select: opts.select, }); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); executions .command('stop <workflowId> <executionId>') .description('Stop a running execution') .option('--reason <reason>', 'Optional reason shown in execution metadata/UI') .option('--format <fmt>', 'Output format', 'json') .action(async (workflowId, executionId, opts) => { try { const client = new AgentledClient(); const result = await client.stopExecution(workflowId, executionId, { reason: opts.reason, }); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); executions .command('delete <workflowId> <executionId>') .description('Admin-only delete for stopped executions. Requires admin:patch scope. Stop first with a reason, then confirm by execution id.') .requiredOption('--reason <reason>', 'Why this deletion is needed; stored in the audit log') .requiredOption('--confirm <executionId>', 'Explicit confirmation; must equal the executionId argument') .option('--format <fmt>', 'Output format', 'json') .action(async (workflowId, executionId, opts) => { try { const client = new AgentledClient(); const result = await client.deleteExecution(workflowId, executionId, { reason: opts.reason, confirmExecutionId: opts.confirm, }); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); executions .command('pause <workflowId> <executionId>') .description('Pause a running execution') .option('--format <fmt>', 'Output format', 'json') .action(async (workflowId, executionId, opts) => { try { const client = new AgentledClient(); const result = await client.pauseExecution(workflowId, executionId); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); executions .command('resume <workflowId> <executionId>') .description('Resume a paused execution') .option('--format <fmt>', 'Output format', 'json') .action(async (workflowId, executionId, opts) => { try { const client = new AgentledClient(); const result = await client.resumeExecution(workflowId, executionId); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); executions .command('rerun <timelineId>') .description('Rerun or retry a step by its timeline ID. Works for any step status (failed, completed, skipped). Bypasses cache by default.') .option('--no-cache', 'Bypass cache when rerunning (default: true — this flag has no effect unless --cache is passed)') .option('--format <fmt>', 'Output format', 'json') .action(async (timelineId, opts) => { try { const client = new AgentledClient(); const result = await client.rerun(timelineId, { forceWithoutCache: opts.cache !== false, }); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); executions .command('rerun-step <workflowId> <executionId> <stepId>') .description('Rerun a specific step in an execution') .option('--timeline-id <id>', 'Optional timeline id to target') .option('--use-cache', 'Use cache when rerunning the step (default bypasses cache)', false) .option('--format <fmt>', 'Output format', 'json') .action(async (workflowId, executionId, stepId, opts) => { try { const client = new AgentledClient(); const result = await client.rerunStep(workflowId, executionId, { stepId, timelineId: opts.timelineId, forceWithoutCache: !opts.useCache, }); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); executions .command('retry <timelineId>') .description('Retry a failed step by its timeline ID. Alias for rerun — use the timeline ID from executions timelines.') .option('--no-cache', 'Bypass cache when retrying') .option('--format <fmt>', 'Output format', 'json') .action(async (timelineId, opts) => { try { const client = new AgentledClient(); const result = await client.rerun(timelineId, { forceWithoutCache: opts.cache === false, }); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); // --- Timelines --- executions .command('timelines <workflowId> <executionId>') .description('List step execution records (timelines) for an execution') .option('--limit <n>', 'Max results', parseInt) .option('--direction <dir>', 'Sort direction: asc, desc') .option('--next-token <token>', 'Pagination cursor') .option('--format <fmt>', 'Output format', 'json') .action(async (workflowId, executionId, opts) => { try { const client = new AgentledClient(); const result = await client.listTimelines(workflowId, executionId, { limit: opts.limit, direction: opts.direction, nextToken: opts.nextToken, }); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); executions .command('timeline <workflowId> <executionId> <timelineId>') .description('Get a single timeline (step execution record) by ID') .option('--format <fmt>', 'Output format', 'json') .action(async (workflowId, executionId, timelineId, opts) => { try { const client = new AgentledClient(); const result = await client.getTimeline(workflowId, executionId, timelineId); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); executions .command('analytics <workflowId>') .description('Get aggregated workflow analytics snapshots') .option('--period <period>', 'Snapshot period: hourly, daily, lifetime', 'daily') .option('--type <type>', 'Analytics type: execution, business', 'execution') .option('--days <n>', 'Lookback days for hourly/daily periods', parseInt) .option('--format <fmt>', 'Output format', 'json') .action(async (workflowId, opts) => { try { const client = new AgentledClient(); const result = await client.getWorkflowAnalytics(workflowId, { period: opts.period, type: opts.type, days: opts.days, }); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); executions .command('tracking-events <workflowId>') .description('List workflow tracking events such as email sent/opened/clicked') .option('--event-type <type>', 'Filter by event type, e.g. sent, opened, clicked') .option('--channel <channel>', 'Filter by channel, e.g. email') .option('--execution-id <id>', 'Filter by pipeline execution ID') .option('--timeline-id <id>', 'Filter by pipeline timeline ID') .option('--since <iso>', 'Filter createdAt >= ISO timestamp') .option('--until <iso>', 'Filter createdAt <= ISO timestamp') .option('--aggregate', 'Include aggregate counts for the returned page', false) .option('--limit <n>', 'Max events', parseInt) .option('--next-token <token>', 'Pagination cursor') .option('--format <fmt>', 'Output format', 'json') .action(async (workflowId, opts) => { try { const client = new AgentledClient(); const result = await client.listTrackingEvents(workflowId, { eventType: opts.eventType, channel: opts.channel, executionId: opts.executionId, timelineId: opts.timelineId, since: opts.since, until: opts.until, aggregate: opts.aggregate, limit: opts.limit, nextToken: opts.nextToken, }); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); executions .command('patch-timeline-fields <workflowId> <executionId> <timelineId>') .description('Admin-only surgical patch for timeline eventSummary, pending fields, or confirmed terminal incident repair. Requires admin:patch scope.') .requiredOption('--reason <reason>', 'Why this patch is needed; stored in the audit record') .requiredOption('--expected-updated-at <timestamp>', 'Current timeline updatedAt value for optimistic concurrency') .option('--confirm-timeline-id <timelineId>', 'Required for terminal timelines except eventSummary-only relabels; must exactly match the target timeline ID') .option('--patches <json>', 'JSON array of replace patches') .option('--patches-file <path>', 'File containing a JSON array of replace patches') .option('--dry-run', 'Compute the diff without writing or auditing', false) .option('--format <fmt>', 'Output format', 'json') .action(async (workflowId, executionId, timelineId, opts) => { try { const patches = parsePatches(opts); const client = new AgentledClient(); const result = await client.patchTimeline(workflowId, executionId, timelineId, { reason: opts.reason, expectedUpdatedAt: opts.expectedUpdatedAt, confirmTimelineId: opts.confirmTimelineId, patches, dryRun: opts.dryRun, }); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); executions .command('patch-execution-fields <workflowId> <executionId>') .description('Admin-only surgical patch for execution metadata, currentStepId, or narrow recovery status transitions; terminal executions allow metadata.executionName only. Requires admin:patch scope.') .requiredOption('--reason <reason>', 'Why this patch is needed; stored in the audit log') .requiredOption('--expected-updated-at <timestamp>', 'Current execution updatedAt value for optimistic concurrency') .option('--patches <json>', 'JSON array of replace patches') .option('--patches-file <path>', 'File containing a JSON array of replace patches') .option('--dry-run', 'Compute the diff without writing or auditing', false) .option('--format <fmt>', 'Output format', 'json') .action(async (workflowId, executionId, opts) => { try { const patches = parsePatches(opts); const client = new AgentledClient(); const result = await client.patchExecution(workflowId, executionId, { reason: opts.reason, expectedUpdatedAt: opts.expectedUpdatedAt, patches, dryRun: opts.dryRun, }); printOutput(result, opts.format); } catch (error) { const message = error instanceof Error ? error.message : String(error); printError(message); } }); } //# sourceMappingURL=executions.js.map