sidecar-cli 0.1.1 → 0.1.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,16 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
+ import readline from 'node:readline/promises';
4
5
  import { Command } from 'commander';
5
6
  import Database from 'better-sqlite3';
6
7
  import { z } from 'zod';
7
8
  import { initializeSchema } from './db/schema.js';
8
- import { getSidecarPaths } from './lib/paths.js';
9
+ import { findSidecarRoot, getSidecarPaths } from './lib/paths.js';
9
10
  import { nowIso, humanTime, stringifyJson } from './lib/format.js';
10
11
  import { SidecarError } from './lib/errors.js';
11
12
  import { jsonFailure, jsonSuccess, printJsonEnvelope } from './lib/output.js';
12
13
  import { bannerDisabled, renderBanner } from './lib/banner.js';
13
14
  import { getUpdateNotice } from './lib/update-check.js';
15
+ import { ensureUiInstalled, launchUiServer } from './lib/ui.js';
14
16
  import { requireInitialized } from './db/client.js';
15
17
  import { renderAgentsMarkdown, renderClaudeMarkdown } from './templates/agents.js';
16
18
  import { refreshSummaryFile } from './services/summary-service.js';
@@ -18,13 +20,25 @@ import { buildContext } from './services/context-service.js';
18
20
  import { getCapabilitiesManifest } from './services/capabilities-service.js';
19
21
  import { addArtifact, listArtifacts } from './services/artifact-service.js';
20
22
  import { addDecision, addNote, addWorklog, getActiveSessionId, listRecentEvents } from './services/event-service.js';
21
- import { addTask, listTasks, markTaskDone } from './services/task-service.js';
22
23
  import { currentSession, endSession, startSession, verifySessionHygiene } from './services/session-service.js';
24
+ import { eventIngestSchema, ingestEvent } from './services/event-ingest-service.js';
25
+ import { buildExportJson, buildExportJsonlEvents, writeOutputFile } from './services/export-service.js';
26
+ import { createTaskPacketRecord, getTaskPacket, listTaskPackets } from './tasks/task-service.js';
27
+ import { taskPacketPrioritySchema, taskPacketStatusSchema, taskPacketTypeSchema } from './tasks/task-packet.js';
28
+ import { getRunRecord, listRunRecords, listRunRecordsForTask } from './runs/run-service.js';
29
+ import { runStatusSchema, runnerTypeSchema } from './runs/run-record.js';
30
+ import { compileTaskPrompt } from './prompts/prompt-service.js';
31
+ import { runTaskExecution } from './services/run-orchestrator-service.js';
32
+ import { loadRunnerPreferences } from './runners/config.js';
33
+ import { assignTask, queueReadyTasks } from './services/task-orchestration-service.js';
34
+ import { buildReviewSummary, createFollowupTaskFromRun, reviewRun } from './services/run-review-service.js';
23
35
  const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
24
36
  const actorSchema = z.enum(['human', 'agent']);
25
- const taskPrioritySchema = z.enum(['low', 'medium', 'high']);
26
37
  const artifactKindSchema = z.enum(['file', 'doc', 'screenshot', 'other']);
27
- const taskStatusSchema = z.enum(['open', 'done', 'all']);
38
+ const taskListStatusSchema = z.enum(['draft', 'ready', 'queued', 'running', 'review', 'blocked', 'done', 'all']);
39
+ const runListStatusSchema = runStatusSchema.or(z.literal('all'));
40
+ const agentRoleSchema = z.enum(['planner', 'builder-ui', 'builder-app', 'reviewer', 'tester']);
41
+ const exportFormatSchema = z.enum(['json', 'jsonl']);
28
42
  const NOT_INITIALIZED_MSG = 'Sidecar is not initialized in this directory or any parent directory';
29
43
  function fail(message) {
30
44
  throw new SidecarError(message);
@@ -166,6 +180,36 @@ function renderContextMarkdown(data) {
166
180
  }
167
181
  return lines.join('\n');
168
182
  }
183
+ async function readStdinText() {
184
+ const chunks = [];
185
+ for await (const chunk of process.stdin) {
186
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
187
+ }
188
+ return Buffer.concat(chunks).toString('utf8');
189
+ }
190
+ function resolveProjectRoot(projectPath) {
191
+ const basePath = projectPath ? path.resolve(projectPath) : process.cwd();
192
+ const root = findSidecarRoot(basePath);
193
+ if (!root) {
194
+ throw new SidecarError(NOT_INITIALIZED_MSG);
195
+ }
196
+ return root;
197
+ }
198
+ function parseCsvOption(input) {
199
+ if (!input)
200
+ return [];
201
+ return input
202
+ .split(',')
203
+ .map((item) => item.trim())
204
+ .filter(Boolean);
205
+ }
206
+ async function askWithDefault(rl, question, fallback) {
207
+ const suffix = fallback ? ` [${fallback}]` : '';
208
+ const answer = (await rl.question(`${question}${suffix}: `)).trim();
209
+ if (answer.length > 0)
210
+ return answer;
211
+ return fallback ?? '';
212
+ }
169
213
  const program = new Command();
170
214
  program.name('sidecar').description('Local-first project memory and recording CLI').version(pkg.version);
171
215
  program.option('--no-banner', 'Disable Sidecar banner output');
@@ -183,6 +227,53 @@ function maybePrintUpdateNotice() {
183
227
  console.log(`Update available: ${pkg.version} -> ${notice.latestVersion}`);
184
228
  console.log(`Run: npm install -g sidecar-cli@${installTag}`);
185
229
  }
230
+ program
231
+ .command('ui')
232
+ .description('Launch the optional local Sidecar UI')
233
+ .option('--no-open', 'Do not open the browser automatically')
234
+ .option('--port <port>', 'Port to run the UI on', (v) => Number.parseInt(v, 10), 4310)
235
+ .option('--install-only', 'Install/update UI package but do not launch')
236
+ .option('--project <path>', 'Project path (defaults to nearest Sidecar root)')
237
+ .option('--reinstall', 'Force reinstall UI package')
238
+ .addHelpText('after', '\nExamples:\n $ sidecar ui\n $ sidecar ui --no-open --port 4311\n $ sidecar ui --project ../my-repo --install-only')
239
+ .action((opts) => {
240
+ const command = 'ui';
241
+ try {
242
+ const projectRoot = resolveProjectRoot(opts.project);
243
+ const port = Number(opts.port);
244
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
245
+ fail('Port must be an integer between 1 and 65535');
246
+ }
247
+ if (!bannerDisabled()) {
248
+ console.log(renderBanner());
249
+ console.log('');
250
+ }
251
+ console.log('Launching Sidecar UI');
252
+ console.log(`Project: ${projectRoot}`);
253
+ const { installedVersion } = ensureUiInstalled({
254
+ cliVersion: pkg.version,
255
+ reinstall: Boolean(opts.reinstall),
256
+ onStatus: (line) => console.log(line),
257
+ });
258
+ console.log(`UI version: ${installedVersion}`);
259
+ if (opts.installOnly) {
260
+ console.log('Install-only mode complete.');
261
+ return;
262
+ }
263
+ const { url } = launchUiServer({
264
+ projectPath: projectRoot,
265
+ port,
266
+ openBrowser: opts.open !== false,
267
+ });
268
+ console.log(`URL: ${url}`);
269
+ if (opts.open === false) {
270
+ console.log('Browser auto-open disabled.');
271
+ }
272
+ }
273
+ catch (err) {
274
+ handleCommandError(command, false, err);
275
+ }
276
+ });
186
277
  program
187
278
  .command('init')
188
279
  .description('Initialize Sidecar in the current directory')
@@ -209,6 +300,9 @@ program
209
300
  fail('Sidecar is already initialized in this project. Re-run with --force to recreate .sidecar files.');
210
301
  }
211
302
  const files = [
303
+ sidecar.tasksPath,
304
+ sidecar.runsPath,
305
+ sidecar.promptsPath,
212
306
  sidecar.dbPath,
213
307
  sidecar.configPath,
214
308
  sidecar.preferencesPath,
@@ -231,9 +325,12 @@ program
231
325
  sidecar.preferencesPath,
232
326
  sidecar.agentsPath,
233
327
  sidecar.summaryPath,
328
+ sidecar.tasksPath,
329
+ sidecar.runsPath,
330
+ sidecar.promptsPath,
234
331
  ]) {
235
332
  if (fs.existsSync(file))
236
- fs.rmSync(file);
333
+ fs.rmSync(file, { recursive: true, force: true });
237
334
  }
238
335
  }
239
336
  const db = new Database(sidecar.dbPath);
@@ -249,9 +346,17 @@ program
249
346
  settings: {},
250
347
  };
251
348
  fs.writeFileSync(sidecar.configPath, stringifyJson(config));
349
+ fs.mkdirSync(sidecar.tasksPath, { recursive: true });
350
+ fs.mkdirSync(sidecar.runsPath, { recursive: true });
351
+ fs.mkdirSync(sidecar.promptsPath, { recursive: true });
252
352
  fs.writeFileSync(sidecar.preferencesPath, stringifyJson({
253
353
  summary: { format: 'markdown', recentLimit: 8 },
254
354
  output: { humanTime: true },
355
+ runner: {
356
+ defaultRunner: 'codex',
357
+ preferredRunners: ['codex', 'claude'],
358
+ defaultAgentRole: 'builder-app',
359
+ },
255
360
  }));
256
361
  fs.writeFileSync(sidecar.agentsPath, renderAgentsMarkdown(projectName));
257
362
  if (shouldWriteRootAgents) {
@@ -307,7 +412,7 @@ program
307
412
  };
308
413
  const recent = db.prepare(`SELECT type, title, created_at FROM events WHERE project_id = ? ORDER BY created_at DESC LIMIT 5`).all(projectId);
309
414
  db.close();
310
- const data = { initialized: true, project, counts, recent };
415
+ const data = { project, counts, recent_events: recent };
311
416
  respondSuccess(command, Boolean(opts.json), data, [
312
417
  `Project: ${project.name}`,
313
418
  `Root: ${project.root_path}`,
@@ -323,6 +428,24 @@ program
323
428
  handleCommandError(command, Boolean(opts.json), normalized);
324
429
  }
325
430
  });
431
+ const preferences = program.command('preferences').description('Preferences commands');
432
+ preferences
433
+ .command('show')
434
+ .description('Show project preferences')
435
+ .option('--json', 'Print machine-readable JSON output')
436
+ .addHelpText('after', '\nExamples:\n $ sidecar preferences show\n $ sidecar preferences show --json')
437
+ .action((opts) => {
438
+ const command = 'preferences show';
439
+ try {
440
+ const { rootPath } = requireInitialized();
441
+ const prefsPath = getSidecarPaths(rootPath).preferencesPath;
442
+ const preferencesData = fs.existsSync(prefsPath) ? JSON.parse(fs.readFileSync(prefsPath, 'utf8')) : {};
443
+ respondSuccess(command, Boolean(opts.json), { project: { root_path: rootPath }, preferences: preferencesData, path: prefsPath }, [`Preferences path: ${prefsPath}`, stringifyJson(preferencesData)]);
444
+ }
445
+ catch (err) {
446
+ handleCommandError(command, Boolean(opts.json), err);
447
+ }
448
+ });
326
449
  program
327
450
  .command('capabilities')
328
451
  .description('Output a machine-readable manifest of Sidecar commands')
@@ -336,6 +459,120 @@ program
336
459
  else
337
460
  console.log(stringifyJson(manifest));
338
461
  });
462
+ const event = program.command('event').description('Generic event ingest commands');
463
+ event
464
+ .command('add')
465
+ .description('Add a validated generic Sidecar event')
466
+ .option('--type <type>', 'note|decision|worklog|task_created|task_completed|summary_generated')
467
+ .option('--title <title>', 'Event title')
468
+ .option('--summary <summary>', 'Event summary')
469
+ .option('--details-json <json>', 'JSON object for details_json')
470
+ .option('--created-by <by>', 'human|agent|system')
471
+ .option('--source <source>', 'cli|imported|generated')
472
+ .option('--session-id <id>', 'Optional session id', (v) => Number.parseInt(v, 10))
473
+ .option('--json-input <json>', 'Raw JSON event payload')
474
+ .option('--stdin', 'Read JSON event payload from stdin')
475
+ .option('--json', 'Print machine-readable JSON output')
476
+ .addHelpText('after', '\nExamples:\n $ sidecar event add --type note --summary "Captured context"\n $ sidecar event add --json-input \'{"type":"decision","title":"Use SQLite","summary":"Local-first"}\' --json\n $ cat event.json | sidecar event add --stdin --json')
477
+ .action(async (opts) => {
478
+ const command = 'event add';
479
+ try {
480
+ const payloadSources = [Boolean(opts.jsonInput), Boolean(opts.stdin), Boolean(opts.type || opts.title || opts.summary || opts.detailsJson || opts.createdBy || opts.source || opts.sessionId)];
481
+ if (payloadSources.filter(Boolean).length !== 1) {
482
+ fail('Provide exactly one payload source: structured flags OR --json-input OR --stdin');
483
+ }
484
+ let payloadRaw;
485
+ if (opts.jsonInput) {
486
+ payloadRaw = JSON.parse(opts.jsonInput);
487
+ }
488
+ else if (opts.stdin) {
489
+ const raw = (await readStdinText()).trim();
490
+ if (!raw)
491
+ fail('STDIN payload is empty');
492
+ payloadRaw = JSON.parse(raw);
493
+ }
494
+ else {
495
+ payloadRaw = {
496
+ type: opts.type,
497
+ title: opts.title,
498
+ summary: opts.summary,
499
+ details_json: opts.detailsJson ? JSON.parse(opts.detailsJson) : undefined,
500
+ created_by: opts.createdBy,
501
+ source: opts.source,
502
+ session_id: Number.isInteger(opts.sessionId) ? opts.sessionId : undefined,
503
+ };
504
+ }
505
+ const payload = eventIngestSchema.parse(payloadRaw);
506
+ const { db, projectId } = requireInitialized();
507
+ const created = ingestEvent(db, { project_id: projectId, payload });
508
+ db.close();
509
+ respondSuccess(command, Boolean(opts.json), { event: { ...created, created_at: nowIso() } }, [`Recorded ${created.type} event #${created.id}.`]);
510
+ }
511
+ catch (err) {
512
+ handleCommandError(command, Boolean(opts.json), err);
513
+ }
514
+ });
515
+ program
516
+ .command('export')
517
+ .description('Export project memory in JSON or JSONL')
518
+ .option('--format <format>', 'json|jsonl', 'json')
519
+ .option('--limit <n>', 'Limit exported events', (v) => Number.parseInt(v, 10))
520
+ .option('--type <event-type>', 'Filter exported events by type')
521
+ .option('--since <iso-date>', 'Filter events created_at >= since')
522
+ .option('--until <iso-date>', 'Filter events created_at <= until')
523
+ .option('--output <path>', 'Write export to file path instead of stdout')
524
+ .option('--json', 'Wrap command metadata in JSON envelope when writing to file')
525
+ .addHelpText('after', '\nExamples:\n $ sidecar export --format json\n $ sidecar export --format jsonl --output sidecar-events.jsonl\n $ sidecar export --type decision --since 2026-01-01T00:00:00Z')
526
+ .action((opts) => {
527
+ const command = 'export';
528
+ try {
529
+ const format = exportFormatSchema.parse(opts.format);
530
+ if (opts.since && Number.isNaN(Date.parse(opts.since)))
531
+ fail('--since must be a valid ISO date');
532
+ if (opts.until && Number.isNaN(Date.parse(opts.until)))
533
+ fail('--until must be a valid ISO date');
534
+ const { db, projectId, rootPath } = requireInitialized();
535
+ if (format === 'json') {
536
+ const payload = buildExportJson(db, {
537
+ projectId,
538
+ rootPath,
539
+ limit: opts.limit,
540
+ type: opts.type,
541
+ since: opts.since,
542
+ until: opts.until,
543
+ });
544
+ db.close();
545
+ const rendered = stringifyJson(payload);
546
+ if (opts.output) {
547
+ const filePath = writeOutputFile(opts.output, `${rendered}\n`);
548
+ respondSuccess(command, Boolean(opts.json), { format, output_path: filePath }, [`Export written: ${filePath}`]);
549
+ }
550
+ else {
551
+ console.log(rendered);
552
+ }
553
+ return;
554
+ }
555
+ const lines = buildExportJsonlEvents(db, {
556
+ projectId,
557
+ limit: opts.limit,
558
+ type: opts.type,
559
+ since: opts.since,
560
+ until: opts.until,
561
+ });
562
+ db.close();
563
+ const rendered = `${lines.join('\n')}${lines.length > 0 ? '\n' : ''}`;
564
+ if (opts.output) {
565
+ const filePath = writeOutputFile(opts.output, rendered);
566
+ respondSuccess(command, Boolean(opts.json), { format, output_path: filePath, records: lines.length }, [`Export written: ${filePath}`]);
567
+ }
568
+ else {
569
+ process.stdout.write(rendered);
570
+ }
571
+ }
572
+ catch (err) {
573
+ handleCommandError(command, Boolean(opts.json), err);
574
+ }
575
+ });
339
576
  program
340
577
  .command('context')
341
578
  .description('Generate a compact context snapshot for a work session')
@@ -380,7 +617,7 @@ summary
380
617
  const { db, projectId, rootPath } = requireInitialized();
381
618
  const out = refreshSummaryFile(db, rootPath, projectId, Math.max(1, opts.limit));
382
619
  db.close();
383
- respondSuccess(command, Boolean(opts.json), { ...out, timestamp: nowIso() }, ['Summary refreshed.', `Path: ${out.path}`]);
620
+ respondSuccess(command, Boolean(opts.json), { summary: { path: out.path, generated_at: out.generatedAt } }, ['Summary refreshed.', `Path: ${out.path}`]);
384
621
  }
385
622
  catch (err) {
386
623
  handleCommandError(command, Boolean(opts.json), err);
@@ -400,7 +637,7 @@ program
400
637
  const rows = listRecentEvents(db, { projectId, type: opts.type, limit: Math.max(1, opts.limit) });
401
638
  db.close();
402
639
  if (opts.json)
403
- printJsonEnvelope(jsonSuccess(command, rows));
640
+ printJsonEnvelope(jsonSuccess(command, { events: rows }));
404
641
  else {
405
642
  if (rows.length === 0) {
406
643
  console.log('No events found.');
@@ -432,7 +669,7 @@ program
432
669
  const sessionId = maybeSessionId(db, projectId, opts.session);
433
670
  const eventId = addNote(db, { projectId, text, title: opts.title, by, sessionId });
434
671
  db.close();
435
- respondSuccess(command, Boolean(opts.json), { eventId, timestamp: nowIso() }, [`Recorded note event #${eventId}.`]);
672
+ respondSuccess(command, Boolean(opts.json), { event: { id: eventId, type: 'note', title: opts.title?.trim() || 'Note', summary: text, created_by: by, session_id: sessionId, created_at: nowIso() } }, [`Recorded note event #${eventId}.`]);
436
673
  }
437
674
  catch (err) {
438
675
  handleCommandError(command, Boolean(opts.json), err);
@@ -457,7 +694,7 @@ decision
457
694
  const sessionId = maybeSessionId(db, projectId, opts.session);
458
695
  const eventId = addDecision(db, { projectId, title: opts.title, summary: opts.summary, details: opts.details, by, sessionId });
459
696
  db.close();
460
- respondSuccess(command, Boolean(opts.json), { eventId, timestamp: nowIso() }, [`Recorded decision event #${eventId}.`]);
697
+ respondSuccess(command, Boolean(opts.json), { event: { id: eventId, type: 'decision', title: opts.title, summary: opts.summary, created_by: by, session_id: sessionId, created_at: nowIso() } }, [`Recorded decision event #${eventId}.`]);
461
698
  }
462
699
  catch (err) {
463
700
  handleCommandError(command, Boolean(opts.json), err);
@@ -496,7 +733,7 @@ worklog
496
733
  addArtifact(db, { projectId, path: filePath, kind: 'file' });
497
734
  }
498
735
  db.close();
499
- respondSuccess(command, Boolean(opts.json), { ...result, timestamp: nowIso() }, [
736
+ respondSuccess(command, Boolean(opts.json), { event: { id: result.eventId, type: 'worklog', summary: opts.done, created_by: by, session_id: sessionId, created_at: nowIso() }, artifacts: result.files.map((p) => ({ path: p, kind: 'file' })) }, [
500
737
  `Recorded worklog event #${result.eventId}.`,
501
738
  `Artifacts linked: ${result.files.length}`,
502
739
  ]);
@@ -507,46 +744,98 @@ worklog
507
744
  });
508
745
  const task = program.command('task').description('Task commands');
509
746
  task
510
- .command('add <title>')
511
- .description('Create an open task')
512
- .option('--description <text>', 'Description')
747
+ .command('create')
748
+ .description('Create a structured task packet')
749
+ .option('--title <title>', 'Task title')
750
+ .option('--type <type>', 'feature|bug|chore|research', 'chore')
751
+ .option('--status <status>', 'draft|ready|queued|running|review|blocked|done', 'draft')
513
752
  .option('--priority <priority>', 'low|medium|high', 'medium')
514
- .option('--by <actor>', 'human|agent', 'human')
753
+ .option('--summary <summary>', 'Task summary')
754
+ .option('--goal <goal>', 'Task goal')
755
+ .option('--dependencies <task-ids>', 'Comma-separated dependency task IDs')
756
+ .option('--tags <tags>', 'Comma-separated tags')
757
+ .option('--target-areas <areas>', 'Comma-separated target areas')
758
+ .option('--scope-in <items>', 'Comma-separated in-scope items')
759
+ .option('--scope-out <items>', 'Comma-separated out-of-scope items')
760
+ .option('--related-decisions <items>', 'Comma-separated related decision IDs/titles')
761
+ .option('--related-notes <items>', 'Comma-separated related notes')
762
+ .option('--files-read <paths>', 'Comma-separated files to read')
763
+ .option('--files-avoid <paths>', 'Comma-separated files to avoid')
764
+ .option('--constraint-tech <items>', 'Comma-separated technical constraints')
765
+ .option('--constraint-design <items>', 'Comma-separated design constraints')
766
+ .option('--validate-cmds <commands>', 'Comma-separated validation commands')
767
+ .option('--dod <items>', 'Comma-separated definition-of-done checks')
768
+ .option('--branch <name>', 'Branch name')
769
+ .option('--worktree <path>', 'Worktree path')
515
770
  .option('--json', 'Print machine-readable JSON output')
516
- .addHelpText('after', '\nExamples:\n $ sidecar task add "Ship v0.1"\n $ sidecar task add "Add tests" --priority high --by agent')
517
- .action((title, opts) => {
518
- const command = 'task add';
771
+ .addHelpText('after', '\nExamples:\n $ sidecar task create\n $ sidecar task create --title "Add import support" --summary "Support JSON import" --goal "Enable scripted import flow" --priority high\n $ sidecar task create --title "Refactor parser" --files-read src/parser.ts,src/types.ts --dod "Tests pass,Docs updated"')
772
+ .action(async (opts) => {
773
+ const command = 'task create';
519
774
  try {
520
- const priority = taskPrioritySchema.parse(opts.priority);
521
- const by = actorSchema.parse(opts.by);
522
- const { db, projectId } = requireInitialized();
523
- const result = addTask(db, { projectId, title, description: opts.description, priority, by });
524
- db.close();
525
- respondSuccess(command, Boolean(opts.json), { ...result, timestamp: nowIso() }, [`Added task #${result.taskId}.`]);
775
+ const rootPath = resolveProjectRoot();
776
+ let title = opts.title?.trim() ?? '';
777
+ let summary = opts.summary?.trim() ?? '';
778
+ let goal = opts.goal?.trim() ?? '';
779
+ if (!title || !summary || !goal) {
780
+ if (!process.stdin.isTTY) {
781
+ fail('Missing required fields. Provide --title, --summary, and --goal when not running interactively.');
782
+ }
783
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
784
+ try {
785
+ title = title || (await askWithDefault(rl, 'Title'));
786
+ summary = summary || (await askWithDefault(rl, 'Summary', title));
787
+ goal = goal || (await askWithDefault(rl, 'Goal', `Complete: ${title}`));
788
+ }
789
+ finally {
790
+ rl.close();
791
+ }
792
+ }
793
+ const type = taskPacketTypeSchema.parse(opts.type);
794
+ const status = taskPacketStatusSchema.parse(opts.status);
795
+ const priority = taskPacketPrioritySchema.parse(opts.priority);
796
+ const created = createTaskPacketRecord(rootPath, {
797
+ title,
798
+ summary,
799
+ goal,
800
+ type,
801
+ status,
802
+ priority,
803
+ scope_in_scope: parseCsvOption(opts.scopeIn),
804
+ scope_out_of_scope: parseCsvOption(opts.scopeOut),
805
+ related_decisions: parseCsvOption(opts.relatedDecisions),
806
+ related_notes: parseCsvOption(opts.relatedNotes),
807
+ files_to_read: parseCsvOption(opts.filesRead),
808
+ files_to_avoid: parseCsvOption(opts.filesAvoid),
809
+ technical_constraints: parseCsvOption(opts.constraintTech),
810
+ design_constraints: parseCsvOption(opts.constraintDesign),
811
+ validation_commands: parseCsvOption(opts.validateCmds),
812
+ dependencies: parseCsvOption(opts.dependencies).map((v) => v.toUpperCase()),
813
+ tags: parseCsvOption(opts.tags),
814
+ target_areas: parseCsvOption(opts.targetAreas),
815
+ definition_of_done: parseCsvOption(opts.dod),
816
+ branch: opts.branch?.trim(),
817
+ worktree: opts.worktree?.trim(),
818
+ });
819
+ respondSuccess(command, Boolean(opts.json), { task: created.task, path: created.path }, [`Created task ${created.task.task_id}.`, `Path: ${created.path}`]);
526
820
  }
527
821
  catch (err) {
528
822
  handleCommandError(command, Boolean(opts.json), err);
529
823
  }
530
824
  });
531
825
  task
532
- .command('done <task-id>')
533
- .description('Mark a task as done')
534
- .option('--by <actor>', 'human|agent', 'human')
826
+ .command('show <task-id>')
827
+ .description('Show a task packet by id')
535
828
  .option('--json', 'Print machine-readable JSON output')
536
- .addHelpText('after', '\nExamples:\n $ sidecar task done 3\n $ sidecar task done 3 --json')
829
+ .addHelpText('after', '\nExamples:\n $ sidecar task show T-001\n $ sidecar task show T-001 --json')
537
830
  .action((taskIdText, opts) => {
538
- const command = 'task done';
831
+ const command = 'task show';
539
832
  try {
540
- const taskId = Number.parseInt(taskIdText, 10);
541
- if (!Number.isInteger(taskId) || taskId <= 0)
542
- fail('Task id must be a positive integer');
543
- const by = actorSchema.parse(opts.by);
544
- const { db, projectId } = requireInitialized();
545
- const result = markTaskDone(db, { projectId, taskId, by });
546
- db.close();
547
- if (!result.ok)
548
- fail(result.reason);
549
- respondSuccess(command, Boolean(opts.json), { taskId, eventId: result.eventId, timestamp: nowIso() }, [`Completed task #${taskId}.`]);
833
+ const task = getTaskPacket(resolveProjectRoot(), taskIdText.trim().toUpperCase());
834
+ if (opts.json) {
835
+ respondSuccess(command, true, { task }, []);
836
+ return;
837
+ }
838
+ console.log(stringifyJson(task));
550
839
  }
551
840
  catch (err) {
552
841
  handleCommandError(command, Boolean(opts.json), err);
@@ -554,40 +843,327 @@ task
554
843
  });
555
844
  task
556
845
  .command('list')
557
- .description('List tasks by status')
558
- .option('--status <status>', 'open|done|all', 'open')
559
- .option('--format <format>', 'table|json', 'table')
846
+ .description('List task packets')
847
+ .option('--status <status>', 'draft|ready|queued|running|review|blocked|done|all', 'all')
560
848
  .option('--json', 'Print machine-readable JSON output')
561
- .addHelpText('after', '\nExamples:\n $ sidecar task list\n $ sidecar task list --status all --format json')
849
+ .addHelpText('after', '\nExamples:\n $ sidecar task list\n $ sidecar task list --status open\n $ sidecar task list --json')
562
850
  .action((opts) => {
563
851
  const command = 'task list';
564
852
  try {
565
- const status = taskStatusSchema.parse(opts.status);
566
- const { db, projectId } = requireInitialized();
567
- const rows = listTasks(db, { projectId, status });
568
- db.close();
569
- if (opts.format === 'json' || opts.json) {
570
- if (opts.json)
571
- printJsonEnvelope(jsonSuccess(command, rows));
572
- else
573
- console.log(stringifyJson(rows));
853
+ const status = taskListStatusSchema.parse(opts.status);
854
+ const tasks = listTaskPackets(resolveProjectRoot());
855
+ const rows = status === 'all' ? tasks : tasks.filter((task) => task.status === status);
856
+ if (opts.json) {
857
+ respondSuccess(command, true, { status, tasks: rows }, []);
574
858
  return;
575
859
  }
576
- const taskRows = rows;
577
- if (taskRows.length === 0) {
860
+ if (rows.length === 0) {
578
861
  console.log('No tasks found.');
579
862
  return;
580
863
  }
581
- const idWidth = Math.max(2, ...taskRows.map((r) => String(r.id).length));
582
- const statusWidth = Math.max(6, ...taskRows.map((r) => r.status.length));
583
- const priorityWidth = Math.max(8, ...taskRows.map((r) => (r.priority ?? 'n/a').length));
584
- console.log(`${'ID'.padEnd(idWidth)} ${'STATUS'.padEnd(statusWidth)} ${'PRIORITY'.padEnd(priorityWidth)} TITLE`);
585
- for (const row of taskRows) {
586
- const id = String(row.id).padEnd(idWidth);
587
- const statusLabel = row.status.padEnd(statusWidth);
588
- const prio = (row.priority ?? 'n/a').padEnd(priorityWidth);
589
- console.log(`${id} ${statusLabel} ${prio} ${row.title}`);
864
+ const idWidth = Math.max(6, ...rows.map((r) => r.task_id.length));
865
+ const statusWidth = Math.max(11, ...rows.map((r) => r.status.length));
866
+ const priorityWidth = Math.max(8, ...rows.map((r) => r.priority.length));
867
+ console.log(`${'TASK ID'.padEnd(idWidth)} ${'STATUS'.padEnd(statusWidth)} ${'PRIORITY'.padEnd(priorityWidth)} TITLE`);
868
+ for (const row of rows) {
869
+ console.log(`${row.task_id.padEnd(idWidth)} ${row.status.padEnd(statusWidth)} ${row.priority.padEnd(priorityWidth)} ${row.title}`);
870
+ }
871
+ }
872
+ catch (err) {
873
+ handleCommandError(command, Boolean(opts.json), err);
874
+ }
875
+ });
876
+ task
877
+ .command('assign <task-id>')
878
+ .description('Auto-assign agent role and runner for a task')
879
+ .option('--agent-role <role>', 'planner|builder-ui|builder-app|reviewer|tester')
880
+ .option('--runner <runner>', 'codex|claude')
881
+ .option('--json', 'Print machine-readable JSON output')
882
+ .addHelpText('after', '\nExamples:\n $ sidecar task assign T-001\n $ sidecar task assign T-001 --agent-role builder-ui --runner codex\n $ sidecar task assign T-001 --json')
883
+ .action((taskIdText, opts) => {
884
+ const command = 'task assign';
885
+ try {
886
+ const rootPath = resolveProjectRoot();
887
+ const result = assignTask(rootPath, taskIdText.trim().toUpperCase(), {
888
+ role: opts.agentRole ? agentRoleSchema.parse(opts.agentRole) : undefined,
889
+ runner: opts.runner ? runnerTypeSchema.parse(opts.runner) : undefined,
890
+ });
891
+ respondSuccess(command, Boolean(opts.json), result, [
892
+ `Assigned ${result.task_id}.`,
893
+ `Role: ${result.agent_role}`,
894
+ `Runner: ${result.runner}`,
895
+ `Reason: ${result.reason}`,
896
+ ]);
897
+ }
898
+ catch (err) {
899
+ handleCommandError(command, Boolean(opts.json), err);
900
+ }
901
+ });
902
+ task
903
+ .command('create-followup <run-id>')
904
+ .description('Create a follow-up task packet from a run report')
905
+ .option('--json', 'Print machine-readable JSON output')
906
+ .addHelpText('after', '\nExamples:\n $ sidecar task create-followup R-010\n $ sidecar task create-followup R-010 --json')
907
+ .action((runIdText, opts) => {
908
+ const command = 'task create-followup';
909
+ try {
910
+ const result = createFollowupTaskFromRun(resolveProjectRoot(), runIdText.trim().toUpperCase());
911
+ respondSuccess(command, Boolean(opts.json), result, [
912
+ `Created follow-up task ${result.task_id}.`,
913
+ `Source run: ${result.source_run_id}`,
914
+ `Title: ${result.title}`,
915
+ ]);
916
+ }
917
+ catch (err) {
918
+ handleCommandError(command, Boolean(opts.json), err);
919
+ }
920
+ });
921
+ const prompt = program.command('prompt').description('Prompt compilation commands');
922
+ prompt
923
+ .command('compile <task-id>')
924
+ .description('Compile a markdown execution brief from a task packet')
925
+ .requiredOption('--runner <runner>', 'codex|claude')
926
+ .requiredOption('--agent-role <role>', 'Agent role, for example builder')
927
+ .option('--preview', 'Print compiled prompt content after writing file')
928
+ .option('--json', 'Print machine-readable JSON output')
929
+ .addHelpText('after', '\nExamples:\n $ sidecar prompt compile T-001 --runner codex --agent-role builder\n $ sidecar prompt compile T-001 --runner claude --agent-role builder --preview\n $ sidecar prompt compile T-001 --runner codex --agent-role reviewer --json')
930
+ .action((taskIdText, opts) => {
931
+ const command = 'prompt compile';
932
+ try {
933
+ const rootPath = resolveProjectRoot();
934
+ const taskId = taskIdText.trim().toUpperCase();
935
+ const runner = runnerTypeSchema.parse(opts.runner);
936
+ const agentRole = String(opts.agentRole ?? '').trim();
937
+ if (!agentRole)
938
+ fail('Agent role is required');
939
+ const compiled = compileTaskPrompt({
940
+ rootPath,
941
+ taskId,
942
+ runner,
943
+ agentRole,
944
+ });
945
+ respondSuccess(command, Boolean(opts.json), {
946
+ run_id: compiled.run_id,
947
+ task_id: compiled.task_id,
948
+ runner_type: compiled.runner_type,
949
+ agent_role: compiled.agent_role,
950
+ prompt_path: compiled.prompt_path,
951
+ preview: opts.preview ? compiled.prompt_markdown : null,
952
+ }, [
953
+ `Compiled prompt for ${compiled.task_id}.`,
954
+ `Run: ${compiled.run_id}`,
955
+ `Path: ${compiled.prompt_path}`,
956
+ ...(opts.preview ? ['', compiled.prompt_markdown] : []),
957
+ ]);
958
+ }
959
+ catch (err) {
960
+ handleCommandError(command, Boolean(opts.json), err);
961
+ }
962
+ });
963
+ program
964
+ .command('run-exec <task-id>')
965
+ .description('Internal command backing `sidecar run <task-id>`')
966
+ .option('--runner <runner>', 'codex|claude')
967
+ .option('--agent-role <role>', 'planner|builder-ui|builder-app|reviewer|tester')
968
+ .option('--dry-run', 'Prepare and compile only without executing external runner')
969
+ .option('--json', 'Print machine-readable JSON output')
970
+ .action((taskIdText, opts) => {
971
+ const command = 'run';
972
+ try {
973
+ const rootPath = resolveProjectRoot();
974
+ const defaults = loadRunnerPreferences(rootPath);
975
+ const selectedRunner = opts.runner ? runnerTypeSchema.parse(opts.runner) : defaults.default_runner;
976
+ const selectedAgentRole = opts.agentRole
977
+ ? agentRoleSchema.parse(opts.agentRole)
978
+ : defaults.default_agent_role;
979
+ const result = runTaskExecution({
980
+ rootPath,
981
+ taskId: String(taskIdText).trim().toUpperCase(),
982
+ runner: selectedRunner,
983
+ agentRole: selectedAgentRole,
984
+ dryRun: Boolean(opts.dryRun),
985
+ });
986
+ respondSuccess(command, Boolean(opts.json), result, [
987
+ `Prepared run ${result.run_id} for ${result.task_id}.`,
988
+ `Runner: ${result.runner_type} (${result.agent_role})`,
989
+ `Prompt: ${result.prompt_path}`,
990
+ `Command: ${result.shell_command}`,
991
+ `Status: ${result.status}`,
992
+ `Summary: ${result.summary}`,
993
+ ]);
994
+ }
995
+ catch (err) {
996
+ handleCommandError(command, Boolean(opts.json), err);
997
+ }
998
+ });
999
+ const run = program
1000
+ .command('run')
1001
+ .description('Run task execution or inspect run records')
1002
+ .addHelpText('after', '\nExamples:\n $ sidecar run T-001 --dry-run\n $ sidecar run T-001 --runner claude --agent-role reviewer\n $ sidecar run queue\n $ sidecar run start-ready --dry-run\n $ sidecar run list --task T-001\n $ sidecar run show R-001');
1003
+ run
1004
+ .command('queue')
1005
+ .description('Queue all ready tasks with satisfied dependencies')
1006
+ .option('--json', 'Print machine-readable JSON output')
1007
+ .addHelpText('after', '\nExamples:\n $ sidecar run queue\n $ sidecar run queue --json')
1008
+ .action((opts) => {
1009
+ const command = 'run queue';
1010
+ try {
1011
+ const rootPath = resolveProjectRoot();
1012
+ const decisions = queueReadyTasks(rootPath);
1013
+ respondSuccess(command, Boolean(opts.json), { decisions }, [
1014
+ `Processed ${decisions.length} ready task(s).`,
1015
+ ...decisions.map((d) => `- ${d.task_id}: ${d.reason}`),
1016
+ ]);
1017
+ }
1018
+ catch (err) {
1019
+ handleCommandError(command, Boolean(opts.json), err);
1020
+ }
1021
+ });
1022
+ run
1023
+ .command('start-ready')
1024
+ .description('Queue and start all runnable ready tasks')
1025
+ .option('--dry-run', 'Prepare and compile only without executing external runners')
1026
+ .option('--json', 'Print machine-readable JSON output')
1027
+ .addHelpText('after', '\nExamples:\n $ sidecar run start-ready\n $ sidecar run start-ready --dry-run --json')
1028
+ .action((opts) => {
1029
+ const command = 'run start-ready';
1030
+ try {
1031
+ const rootPath = resolveProjectRoot();
1032
+ const queueDecisions = queueReadyTasks(rootPath);
1033
+ const queuedTasks = listTaskPackets(rootPath).filter((task) => task.status === 'queued');
1034
+ const results = queuedTasks.map((task) => runTaskExecution({ rootPath, taskId: task.task_id, dryRun: Boolean(opts.dryRun) }));
1035
+ respondSuccess(command, Boolean(opts.json), { queued: queueDecisions, results }, [
1036
+ `Queued in this pass: ${queueDecisions.filter((d) => d.queued).length}`,
1037
+ `Started: ${results.length}`,
1038
+ ...results.map((r) => `- ${r.task_id} -> ${r.run_id} (${r.status})`),
1039
+ ]);
1040
+ }
1041
+ catch (err) {
1042
+ handleCommandError(command, Boolean(opts.json), err);
1043
+ }
1044
+ });
1045
+ run
1046
+ .command('approve <run-id>')
1047
+ .description('Review a completed run as approved, needs changes, or merged')
1048
+ .option('--state <state>', 'approved|needs_changes|merged', 'approved')
1049
+ .option('--note <text>', 'Review note')
1050
+ .option('--by <name>', 'Reviewer name', 'human')
1051
+ .option('--json', 'Print machine-readable JSON output')
1052
+ .addHelpText('after', '\nExamples:\n $ sidecar run approve R-010\n $ sidecar run approve R-010 --state needs_changes --note "Address test failures"\n $ sidecar run approve R-010 --state merged --json')
1053
+ .action((runIdText, opts) => {
1054
+ const command = 'run approve';
1055
+ try {
1056
+ const state = String(opts.state);
1057
+ if (state !== 'approved' && state !== 'needs_changes' && state !== 'merged') {
1058
+ fail('State must be one of: approved, needs_changes, merged');
1059
+ }
1060
+ const result = reviewRun(resolveProjectRoot(), runIdText.trim().toUpperCase(), state, {
1061
+ note: opts.note,
1062
+ by: opts.by,
1063
+ });
1064
+ respondSuccess(command, Boolean(opts.json), result, [
1065
+ `Run ${result.run_id} marked ${result.review_state}.`,
1066
+ `Task ${result.task_id} -> ${result.task_status}`,
1067
+ ]);
1068
+ }
1069
+ catch (err) {
1070
+ handleCommandError(command, Boolean(opts.json), err);
1071
+ }
1072
+ });
1073
+ run
1074
+ .command('block <run-id>')
1075
+ .description('Mark a completed run as blocked and set linked task blocked')
1076
+ .option('--note <text>', 'Blocking reason')
1077
+ .option('--by <name>', 'Reviewer name', 'human')
1078
+ .option('--json', 'Print machine-readable JSON output')
1079
+ .addHelpText('after', '\nExamples:\n $ sidecar run block R-010 --note "Migration failed"\n $ sidecar run block R-010 --json')
1080
+ .action((runIdText, opts) => {
1081
+ const command = 'run block';
1082
+ try {
1083
+ const result = reviewRun(resolveProjectRoot(), runIdText.trim().toUpperCase(), 'blocked', {
1084
+ note: opts.note,
1085
+ by: opts.by,
1086
+ });
1087
+ respondSuccess(command, Boolean(opts.json), result, [
1088
+ `Run ${result.run_id} marked blocked.`,
1089
+ `Task ${result.task_id} -> ${result.task_status}`,
1090
+ ]);
1091
+ }
1092
+ catch (err) {
1093
+ handleCommandError(command, Boolean(opts.json), err);
1094
+ }
1095
+ });
1096
+ run
1097
+ .command('list')
1098
+ .description('List execution run records')
1099
+ .option('--task <task-id>', 'Filter by task id (for example T-001)')
1100
+ .option('--status <status>', 'queued|preparing|running|review|blocked|completed|failed|all', 'all')
1101
+ .option('--json', 'Print machine-readable JSON output')
1102
+ .addHelpText('after', '\nExamples:\n $ sidecar run list\n $ sidecar run list --task T-001\n $ sidecar run list --status completed --json')
1103
+ .action((opts) => {
1104
+ const command = 'run list';
1105
+ try {
1106
+ const rootPath = resolveProjectRoot();
1107
+ const status = runListStatusSchema.parse(opts.status);
1108
+ const base = opts.task ? listRunRecordsForTask(rootPath, String(opts.task).trim().toUpperCase()) : listRunRecords(rootPath);
1109
+ const rows = status === 'all' ? base : base.filter((entry) => entry.status === status);
1110
+ if (opts.json) {
1111
+ respondSuccess(command, true, { status, task_id: opts.task ? String(opts.task).trim().toUpperCase() : null, runs: rows }, []);
1112
+ return;
1113
+ }
1114
+ if (rows.length === 0) {
1115
+ console.log('No run records found.');
1116
+ return;
1117
+ }
1118
+ const idWidth = Math.max(6, ...rows.map((r) => r.run_id.length));
1119
+ const taskWidth = Math.max(7, ...rows.map((r) => r.task_id.length));
1120
+ const statusWidth = Math.max(10, ...rows.map((r) => r.status.length));
1121
+ console.log(`${'RUN ID'.padEnd(idWidth)} ${'TASK ID'.padEnd(taskWidth)} ${'STATUS'.padEnd(statusWidth)} STARTED`);
1122
+ for (const row of rows) {
1123
+ console.log(`${row.run_id.padEnd(idWidth)} ${row.task_id.padEnd(taskWidth)} ${row.status.padEnd(statusWidth)} ${humanTime(row.started_at)}`);
1124
+ }
1125
+ }
1126
+ catch (err) {
1127
+ handleCommandError(command, Boolean(opts.json), err);
1128
+ }
1129
+ });
1130
+ run
1131
+ .command('summary')
1132
+ .description('Show project-level run review summary')
1133
+ .option('--json', 'Print machine-readable JSON output')
1134
+ .addHelpText('after', '\nExamples:\n $ sidecar run summary\n $ sidecar run summary --json')
1135
+ .action((opts) => {
1136
+ const command = 'run summary';
1137
+ try {
1138
+ const data = buildReviewSummary(resolveProjectRoot());
1139
+ respondSuccess(command, Boolean(opts.json), data, [
1140
+ `Completed runs: ${data.completed_runs}`,
1141
+ `Blocked runs: ${data.blocked_runs}`,
1142
+ `Suggested follow-ups: ${data.suggested_follow_ups}`,
1143
+ 'Recently merged:',
1144
+ ...(data.recently_merged.length
1145
+ ? data.recently_merged.map((r) => `- ${r.run_id} (${r.task_id}) at ${humanTime(r.reviewed_at)}`)
1146
+ : ['- none']),
1147
+ ]);
1148
+ }
1149
+ catch (err) {
1150
+ handleCommandError(command, Boolean(opts.json), err);
1151
+ }
1152
+ });
1153
+ run
1154
+ .command('show <run-id>')
1155
+ .description('Show a run record by id')
1156
+ .option('--json', 'Print machine-readable JSON output')
1157
+ .addHelpText('after', '\nExamples:\n $ sidecar run show R-001\n $ sidecar run show R-001 --json')
1158
+ .action((runIdText, opts) => {
1159
+ const command = 'run show';
1160
+ try {
1161
+ const runRecord = getRunRecord(resolveProjectRoot(), runIdText.trim().toUpperCase());
1162
+ if (opts.json) {
1163
+ respondSuccess(command, true, { run: runRecord }, []);
1164
+ return;
590
1165
  }
1166
+ console.log(stringifyJson(runRecord));
591
1167
  }
592
1168
  catch (err) {
593
1169
  handleCommandError(command, Boolean(opts.json), err);
@@ -610,7 +1186,7 @@ session
610
1186
  db.close();
611
1187
  if (!result.ok)
612
1188
  fail(result.reason);
613
- respondSuccess(command, Boolean(opts.json), { ...result, timestamp: nowIso() }, [`Started session #${result.sessionId}.`]);
1189
+ respondSuccess(command, Boolean(opts.json), { session: { id: result.sessionId, actor_type: actor, actor_name: opts.name ?? null, started_at: nowIso() } }, [`Started session #${result.sessionId}.`]);
614
1190
  }
615
1191
  catch (err) {
616
1192
  handleCommandError(command, Boolean(opts.json), err);
@@ -630,7 +1206,7 @@ session
630
1206
  db.close();
631
1207
  if (!result.ok)
632
1208
  fail(result.reason);
633
- respondSuccess(command, Boolean(opts.json), { ...result, timestamp: nowIso() }, [`Ended session #${result.sessionId}.`]);
1209
+ respondSuccess(command, Boolean(opts.json), { session: { id: result.sessionId, ended_at: nowIso(), summary: opts.summary ?? null } }, [`Ended session #${result.sessionId}.`]);
634
1210
  }
635
1211
  catch (err) {
636
1212
  handleCommandError(command, Boolean(opts.json), err);
@@ -648,7 +1224,7 @@ session
648
1224
  const current = currentSession(db, projectId);
649
1225
  db.close();
650
1226
  if (opts.json)
651
- printJsonEnvelope(jsonSuccess(command, { current: current ?? null }));
1227
+ printJsonEnvelope(jsonSuccess(command, { session: current ?? null }));
652
1228
  else if (!current) {
653
1229
  console.log('No active session.');
654
1230
  }
@@ -712,7 +1288,7 @@ artifact
712
1288
  const { db, projectId } = requireInitialized();
713
1289
  const artifactId = addArtifact(db, { projectId, path: artifactPath, kind, note: opts.note });
714
1290
  db.close();
715
- respondSuccess(command, Boolean(opts.json), { artifactId, timestamp: nowIso() }, [`Added artifact #${artifactId}.`]);
1291
+ respondSuccess(command, Boolean(opts.json), { artifact: { id: artifactId, path: artifactPath, kind, note: opts.note ?? null, created_at: nowIso() } }, [`Added artifact #${artifactId}.`]);
716
1292
  }
717
1293
  catch (err) {
718
1294
  handleCommandError(command, Boolean(opts.json), err);
@@ -730,7 +1306,7 @@ artifact
730
1306
  const rows = listArtifacts(db, projectId);
731
1307
  db.close();
732
1308
  if (opts.json)
733
- printJsonEnvelope(jsonSuccess(command, rows));
1309
+ printJsonEnvelope(jsonSuccess(command, { artifacts: rows }));
734
1310
  else {
735
1311
  if (rows.length === 0) {
736
1312
  console.log('No artifacts found.');
@@ -754,5 +1330,17 @@ if (process.argv.length === 2) {
754
1330
  maybePrintUpdateNotice();
755
1331
  process.exit(0);
756
1332
  }
1333
+ if (process.argv[2] === 'run' &&
1334
+ process.argv[3] &&
1335
+ !process.argv[3].startsWith('-') &&
1336
+ process.argv[3] !== 'list' &&
1337
+ process.argv[3] !== 'show' &&
1338
+ process.argv[3] !== 'queue' &&
1339
+ process.argv[3] !== 'start-ready' &&
1340
+ process.argv[3] !== 'approve' &&
1341
+ process.argv[3] !== 'block' &&
1342
+ process.argv[3] !== 'summary') {
1343
+ process.argv.splice(2, 1, 'run-exec');
1344
+ }
757
1345
  program.parse(process.argv);
758
1346
  maybePrintUpdateNotice();