/** * `inkreach secret` — manage workflow automation secrets. * `inkreach runs` — execution history. * `inkreach fire` — manual trigger testing (button-click / webhook). * `inkreach sql` — read-only SQL queries. */ import { Command } from 'commander'; import { run, requireBaseId } from '../lib/ctx'; import { printJson, printTable, printKv, truncate } from '../lib/output'; // ===================== secret ===================== export function secretCommand(): Command { const cmd = new Command('secret'); cmd.description('Manage automation script secrets'); cmd .command('list ') .description('List secrets for a workflow. Values are never returned.') .option('--base-id ', 'Base id (overrides default)') .action( run(async (ctx, args, o) => { const workflowId = args[0] as string; const opts = o as { baseId?: string }; const baseId = requireBaseId(ctx, opts.baseId); const list = (await ctx.client.listSecrets(baseId, workflowId)) as Array<{ id: string; name: string; description?: string; }>; if (ctx.json) printJson(list); else { if (!list.length) { process.stdout.write('No secrets found.\n'); return; } printTable( [ { key: 'id', header: 'ID' }, { key: 'name', header: 'NAME' }, { key: 'description', header: 'DESCRIPTION' }, ], list.map((s) => ({ id: s.id, name: truncate(s.name, 30), description: truncate(s.description, 50), })) ); } }) ); cmd .command('set ') .description('Create a new secret') .requiredOption('--name ', 'Secret name') .requiredOption('--value ', 'Secret value') .option('--description ', 'Secret description') .option('--base-id ', 'Base id (overrides default)') .action( run(async (ctx, args, o) => { const workflowId = args[0] as string; const opts = o as { name: string; value: string; description?: string; baseId?: string }; const baseId = requireBaseId(ctx, opts.baseId); const created = await ctx.client.createSecret(baseId, workflowId, { name: opts.name, value: opts.value, description: opts.description, }); const s = created as { id: string; name: string }; if (ctx.json) printJson(created); else { printKv([ ['id', s.id], ['name', s.name], ['', ''], ['note', 'Secret value is never returned. Update it if needed.'], ]); } }) ); cmd .command('update ') .description('Update a secret') .option('--name ', 'New name') .option('--value ', 'New value') .option('--description ', 'New description') .option('--base-id ', 'Base id (overrides default)') .action( run(async (ctx, args, o) => { const [workflowId, secretId] = args as [string, string]; const opts = o as { name?: string; value?: string; description?: string; baseId?: string }; const baseId = requireBaseId(ctx, opts.baseId); await ctx.client.updateSecret(baseId, workflowId, secretId, { name: opts.name, value: opts.value, description: opts.description, }); if (ctx.json) printJson({ ok: true, id: secretId }); else process.stdout.write(`✓ Secret ${secretId} updated\n`); }) ); cmd .command('delete ') .description('Delete a secret') .option('--base-id ', 'Base id (overrides default)') .action( run(async (ctx, args, o) => { const [workflowId, secretId] = args as [string, string]; const opts = o as { baseId?: string }; const baseId = requireBaseId(ctx, opts.baseId); await ctx.client.deleteSecret(baseId, workflowId, secretId); if (ctx.json) printJson({ ok: true, id: secretId }); else process.stdout.write(`✓ Secret ${secretId} deleted\n`); }) ); return cmd; } // ===================== runs ===================== export function runsCommand(): Command { const cmd = new Command('runs'); cmd.description('Execution history for automations'); cmd .command('list ') .description('List execution history') .option('--status ', 'Filter by status: failed, succeeded, or running') .option('--base-id ', 'Base id (overrides default)') .action( run(async (ctx, args, o) => { const workflowId = args[0] as string; const opts = o as { status?: string; baseId?: string }; const baseId = requireBaseId(ctx, opts.baseId); let list = (await ctx.client.listExecutions(baseId, workflowId)) as Array<{ id: string; status: string; triggerType?: string; startedAt?: string; finishedAt?: string; }>; if (opts.status) { list = list.filter((e) => e.status === opts.status); } if (ctx.json) printJson(list); else { if (!list.length) { process.stdout.write('No executions found.\n'); return; } printTable( [ { key: 'id', header: 'ID' }, { key: 'status', header: 'STATUS' }, { key: 'triggerType', header: 'TRIGGER' }, { key: 'startedAt', header: 'STARTED' }, { key: 'finishedAt', header: 'FINISHED' }, ], list.map((e) => ({ id: e.id, status: e.status, triggerType: e.triggerType || '', startedAt: truncate(e.startedAt, 25), finishedAt: truncate(e.finishedAt, 25), })) ); } }) ); cmd .command('get ') .description('Get execution detail with node results') .option('--base-id ', 'Base id (overrides default)') .action( run(async (ctx, args, o) => { const [workflowId, executionId] = args as [string, string]; const opts = o as { baseId?: string }; const baseId = requireBaseId(ctx, opts.baseId); const exec = await ctx.client.getExecution(baseId, workflowId, executionId); const ex = exec as { id: string; status: string; triggerType?: string; startedAt?: string; finishedAt?: string; nodeResults?: Array<{ nodeId: string; status: string; error?: string }>; }; if (ctx.json) printJson(exec); else { printKv([ ['id', ex.id], ['status', ex.status], ['trigger', ex.triggerType || ''], ['started', ex.startedAt || ''], ['finished', ex.finishedAt || ''], ]); if (ex.nodeResults?.length) { process.stdout.write('\nNODE RESULTS:\n'); printTable( [ { key: 'nodeId', header: 'NODE' }, { key: 'status', header: 'STATUS' }, { key: 'error', header: 'ERROR' }, ], ex.nodeResults.map((r) => ({ nodeId: r.nodeId, status: r.status, error: truncate(r.error, 50), })) ); } } }) ); cmd .command('rerun ') .description('Rerun a failed execution') .option('--base-id ', 'Base id (overrides default)') .action( run(async (ctx, args, o) => { const [workflowId, executionId] = args as [string, string]; const opts = o as { baseId?: string }; const baseId = requireBaseId(ctx, opts.baseId); const res = (await ctx.client.rerunExecution(baseId, workflowId, executionId)) as { id?: string; }; if (ctx.json) printJson(res); else { const pairs: Array<[string, unknown]> = [ ['executionId', executionId], ['status', 'rerun requested'], ]; if (res.id) pairs.push(['newExecutionId', res.id]); printKv(pairs); } }) ); return cmd; } // ===================== fire ===================== export function fireCommand(): Command { const cmd = new Command('fire'); cmd.description('Manually fire triggers for testing'); cmd .command('button-click ') .description('Simulate a button field click') .action( run(async (ctx, args) => { const [tableId, recordId, fieldId] = args as [string, string, string]; const res = await ctx.client.fireButtonClick(tableId, recordId, fieldId); if (ctx.json) printJson(res); else { printKv([ ['runId', res.runId], ['tableId', res.tableId], ['fieldId', res.fieldId], ]); } }) ); cmd .command('webhook ') .description('Simulate a webhook call') .option('--body ', 'JSON body to send') .option('--method ', 'HTTP method (informational only)', 'POST') .action( run(async (ctx, args, o) => { const [baseId, workflowId] = args as [string, string]; const opts = o as { body?: string; method?: string }; const body = opts.body ? (JSON.parse(opts.body) as unknown) : undefined; const res = await ctx.client.fireWebhook(baseId, workflowId, body); if (ctx.json) printJson(res); else { process.stdout.write(`✓ Webhook fired for workflow ${workflowId}\n`); if (typeof res === 'object' && res !== null) { printJson(res); } } }) ); return cmd; } // ===================== sql ===================== export function sqlCommand(): Command { const cmd = new Command('sql'); cmd.description('Execute a read-only SQL query (SELECT only)'); cmd.option('--base-id ', 'Base id (overrides default)'); cmd.argument('', 'SQL SELECT statement'); cmd.action( run(async (ctx, args, o) => { const sql = args[0] as string; const opts = o as { baseId?: string }; const baseId = requireBaseId(ctx, opts.baseId); const rows = (await ctx.client.sql(baseId, sql)) as unknown[]; if (ctx.json) { printJson(rows); } else { if (!rows.length) { process.stdout.write('No rows returned.\n'); return; } const first = rows[0]; if (typeof first === 'object' && first !== null && !Array.isArray(first)) { const keys = Object.keys(first as Record); printTable( keys.map((k) => ({ key: k, header: k })), rows.map((r) => { const obj = r as Record; const row: Record = {}; for (const k of keys) { row[k] = truncate(obj[k], 40); } return row; }) ); process.stdout.write(`\n${rows.length} row(s).\n`); } else { rows.forEach((r) => process.stdout.write(JSON.stringify(r) + '\n')); } } }) ); return cmd; }