@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
JavaScript
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