@orchagent/cli 0.3.53 → 0.3.55

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.
@@ -80,6 +80,28 @@ function parseAgentRef(value) {
80
80
  }
81
81
  throw new errors_1.CliError('Invalid agent reference. Use org/agent or agent format.');
82
82
  }
83
+ function canonicalAgentType(typeValue) {
84
+ const normalized = (typeValue || 'agent').toLowerCase();
85
+ return normalized === 'skill' ? 'skill' : 'agent';
86
+ }
87
+ function resolveExecutionEngine(agentData) {
88
+ if (agentData.execution_engine === 'direct_llm' || agentData.execution_engine === 'managed_loop' || agentData.execution_engine === 'code_runtime') {
89
+ return agentData.execution_engine;
90
+ }
91
+ const runtimeCommand = agentData.runtime?.command;
92
+ if (runtimeCommand && runtimeCommand.trim())
93
+ return 'code_runtime';
94
+ if (agentData.loop && Object.keys(agentData.loop).length > 0)
95
+ return 'managed_loop';
96
+ const normalized = (agentData.type || '').toLowerCase();
97
+ if (normalized === 'tool' || normalized === 'code')
98
+ return 'code_runtime';
99
+ if (normalized === 'agentic')
100
+ return 'managed_loop';
101
+ if (normalized === 'skill')
102
+ return 'direct_llm';
103
+ return 'direct_llm';
104
+ }
83
105
  // ─── Validation helpers ─────────────────────────────────────────────────────
84
106
  async function validateFilePath(filePath) {
85
107
  const stat = await promises_1.default.stat(filePath);
@@ -471,6 +493,9 @@ async function downloadAgent(config, org, agent, version) {
471
493
  return {
472
494
  id: targetAgent.id,
473
495
  type: targetAgent.type,
496
+ run_mode: targetAgent.run_mode ?? null,
497
+ execution_engine: targetAgent.execution_engine ?? null,
498
+ callable: targetAgent.callable,
474
499
  name: targetAgent.name,
475
500
  version: targetAgent.version,
476
501
  description: targetAgent.description,
@@ -506,7 +531,13 @@ async function checkDependencies(config, dependencies) {
506
531
  const [org, agent] = dep.id.split('/');
507
532
  try {
508
533
  const agentData = await downloadAgent(config, org, agent, dep.version);
509
- const downloadable = !!(agentData.source_url || agentData.pip_package || agentData.has_bundle || agentData.type === 'prompt');
534
+ const canonicalType = canonicalAgentType(agentData.type);
535
+ const engine = resolveExecutionEngine(agentData);
536
+ const downloadable = Boolean(canonicalType === 'skill' ||
537
+ engine !== 'code_runtime' ||
538
+ agentData.source_url ||
539
+ agentData.pip_package ||
540
+ agentData.has_bundle);
510
541
  results.push({ dep, downloadable, agentData });
511
542
  }
512
543
  catch {
@@ -577,7 +608,7 @@ async function downloadDependenciesRecursively(config, depStatuses, visited = ne
577
608
  if (status.agentData.has_bundle) {
578
609
  await saveBundleLocally(config, org, agent, status.dep.version, status.agentData.id);
579
610
  }
580
- if (status.agentData.type === 'tool' && (status.agentData.source_url || status.agentData.pip_package)) {
611
+ if (resolveExecutionEngine(status.agentData) === 'code_runtime' && (status.agentData.source_url || status.agentData.pip_package)) {
581
612
  await installTool(status.agentData);
582
613
  }
583
614
  }, { successText: `Downloaded ${depRef}` });
@@ -1174,7 +1205,7 @@ async function saveAgentLocally(org, agent, agentData) {
1174
1205
  const agentDir = path_1.default.join(AGENTS_DIR, org, agent);
1175
1206
  await promises_1.default.mkdir(agentDir, { recursive: true });
1176
1207
  await promises_1.default.writeFile(path_1.default.join(agentDir, 'agent.json'), JSON.stringify(agentData, null, 2));
1177
- if (agentData.type === 'prompt' && agentData.prompt) {
1208
+ if (resolveExecutionEngine(agentData) !== 'code_runtime' && agentData.prompt) {
1178
1209
  await promises_1.default.writeFile(path_1.default.join(agentDir, 'prompt.md'), agentData.prompt);
1179
1210
  }
1180
1211
  if (agentData.files) {
@@ -1264,13 +1295,20 @@ async function executeLocalFromDir(dirPath, args, options) {
1264
1295
  `To run a local agent, the directory must contain orchagent.json.\n` +
1265
1296
  `Create one with: orch init`);
1266
1297
  }
1267
- const agentType = manifest.type || 'tool';
1268
- if (agentType === 'skill') {
1298
+ const manifestType = manifest.type || 'agent';
1299
+ const localType = canonicalAgentType(manifestType);
1300
+ const localEngine = resolveExecutionEngine({
1301
+ type: manifestType,
1302
+ execution_engine: manifest.execution_engine || null,
1303
+ runtime: manifest.runtime || null,
1304
+ loop: manifest.loop || null,
1305
+ });
1306
+ if (localType === 'skill') {
1269
1307
  throw new errors_1.CliError('Skills cannot be run directly.\n\n' +
1270
1308
  'Skills are instructions meant to be injected into AI agent contexts.\n' +
1271
1309
  `Install with: orchagent skill install <org>/<skill>`);
1272
1310
  }
1273
- if (agentType === 'agent') {
1311
+ if (localEngine === 'managed_loop') {
1274
1312
  // Read prompt.md
1275
1313
  const promptPath = path_1.default.join(resolved, 'prompt.md');
1276
1314
  let agentPrompt;
@@ -1323,7 +1361,7 @@ async function executeLocalFromDir(dirPath, args, options) {
1323
1361
  await executeAgentLocally(resolved, agentPrompt, agentInputData, agentOutputSchema, customTools, manifest, config, options.provider, options.model);
1324
1362
  return;
1325
1363
  }
1326
- if (agentType === 'prompt') {
1364
+ if (localEngine === 'direct_llm') {
1327
1365
  // Read prompt.md
1328
1366
  const promptPath = path_1.default.join(resolved, 'prompt.md');
1329
1367
  let prompt;
@@ -1347,7 +1385,9 @@ async function executeLocalFromDir(dirPath, args, options) {
1347
1385
  }
1348
1386
  // Build AgentDownload from local files
1349
1387
  const agentData = {
1350
- type: 'prompt',
1388
+ type: 'agent',
1389
+ execution_engine: 'direct_llm',
1390
+ run_mode: manifest.run_mode || 'on_demand',
1351
1391
  name: manifest.name || path_1.default.basename(resolved),
1352
1392
  version: manifest.version || 'local',
1353
1393
  description: manifest.description,
@@ -1389,7 +1429,7 @@ async function executeLocalFromDir(dirPath, args, options) {
1389
1429
  (0, output_1.printJson)(result);
1390
1430
  return;
1391
1431
  }
1392
- // Tool agents with bundle
1432
+ // Code runtime agents with bundle
1393
1433
  const entrypoint = manifest.entrypoint || 'sandbox_main.py';
1394
1434
  const entrypointPath = path_1.default.join(resolved, entrypoint);
1395
1435
  try {
@@ -1399,7 +1439,9 @@ async function executeLocalFromDir(dirPath, args, options) {
1399
1439
  // No local entrypoint — try run_command
1400
1440
  if (manifest.run_command) {
1401
1441
  const agentData = {
1402
- type: 'tool',
1442
+ type: 'agent',
1443
+ execution_engine: 'code_runtime',
1444
+ run_mode: manifest.run_mode || 'on_demand',
1403
1445
  name: manifest.name || path_1.default.basename(resolved),
1404
1446
  version: manifest.version || 'local',
1405
1447
  supported_providers: manifest.supported_providers || ['any'],
@@ -1412,13 +1454,15 @@ async function executeLocalFromDir(dirPath, args, options) {
1412
1454
  }
1413
1455
  throw new errors_1.CliError(`No entrypoint found in ${resolved}\n\n` +
1414
1456
  `Expected: ${entrypoint}\n` +
1415
- `For tool agents, ensure the directory contains the entrypoint file\n` +
1457
+ `For code runtime agents, ensure the directory contains the entrypoint file\n` +
1416
1458
  `or has run_command set in orchagent.json.`);
1417
1459
  }
1418
1460
  // Execute bundle-style from local directory
1419
1461
  const config = await (0, config_1.getResolvedConfig)();
1420
1462
  const agentData = {
1421
- type: 'tool',
1463
+ type: 'agent',
1464
+ execution_engine: 'code_runtime',
1465
+ run_mode: manifest.run_mode || 'on_demand',
1422
1466
  name: manifest.name || path_1.default.basename(resolved),
1423
1467
  version: manifest.version || 'local',
1424
1468
  supported_providers: manifest.supported_providers || ['any'],
@@ -1561,6 +1605,13 @@ async function executeCloud(agentRef, file, options) {
1561
1605
  throw new errors_1.CliError('Missing org. Use org/agent or set default org.');
1562
1606
  }
1563
1607
  const agentMeta = await (0, api_1.getAgentWithFallback)(resolved, org, parsed.agent, parsed.version);
1608
+ const cloudType = canonicalAgentType(agentMeta.type);
1609
+ const cloudEngine = resolveExecutionEngine({
1610
+ type: agentMeta.type,
1611
+ execution_engine: agentMeta.execution_engine ?? null,
1612
+ runtime: agentMeta.runtime ?? null,
1613
+ loop: agentMeta.loop ?? null,
1614
+ });
1564
1615
  // Pre-call balance check for paid agents
1565
1616
  let pricingInfo;
1566
1617
  if ((0, pricing_1.isPaidAgent)(agentMeta)) {
@@ -1680,7 +1731,7 @@ async function executeCloud(agentRef, file, options) {
1680
1731
  ...(options.model && { model: options.model }),
1681
1732
  };
1682
1733
  }
1683
- else if (agentMeta.type === 'prompt') {
1734
+ else if (cloudEngine !== 'code_runtime') {
1684
1735
  const searchedProviders = effectiveProvider ? [effectiveProvider] : supportedProviders;
1685
1736
  const providerList = searchedProviders.join(', ');
1686
1737
  process.stderr.write(`Warning: No LLM key found for provider(s): ${providerList}\n` +
@@ -1736,7 +1787,7 @@ async function executeCloud(agentRef, file, options) {
1736
1787
  // Merge file content into --data
1737
1788
  const resolvedBody = await resolveJsonBody(options.data);
1738
1789
  const bodyObj = JSON.parse(resolvedBody);
1739
- if (agentMeta.type === 'prompt') {
1790
+ if (cloudEngine !== 'code_runtime') {
1740
1791
  const fieldName = options.fileField || inferFileField(agentMeta.input_schema);
1741
1792
  if (filePaths.length === 1) {
1742
1793
  await validateFilePath(filePaths[0]);
@@ -1768,7 +1819,7 @@ async function executeCloud(agentRef, file, options) {
1768
1819
  headers['Content-Type'] = 'application/json';
1769
1820
  }
1770
1821
  else {
1771
- // Tool agents: send files as multipart, --data as metadata
1822
+ // Code-runtime agents: send files as multipart, --data as metadata
1772
1823
  let metadata = resolvedBody;
1773
1824
  if (llmCredentials) {
1774
1825
  const metaObj = JSON.parse(metadata);
@@ -1793,7 +1844,7 @@ async function executeCloud(agentRef, file, options) {
1793
1844
  }
1794
1845
  headers['Content-Type'] = 'application/json';
1795
1846
  }
1796
- else if ((filePaths.length > 0 || options.metadata) && agentMeta.type === 'prompt') {
1847
+ else if ((filePaths.length > 0 || options.metadata) && cloudEngine !== 'code_runtime') {
1797
1848
  const fieldName = options.fileField || inferFileField(agentMeta.input_schema);
1798
1849
  let bodyObj = {};
1799
1850
  if (options.metadata) {
@@ -1858,16 +1909,16 @@ async function executeCloud(agentRef, file, options) {
1858
1909
  }
1859
1910
  } // end of non-injection path
1860
1911
  const url = `${resolved.apiUrl.replace(/\/$/, '')}/${org}/${parsed.agent}/${parsed.version}/${endpoint}`;
1861
- // Enable SSE streaming for agent-type agents (unless --json or --no-stream or --output)
1862
- const isAgentType = agentMeta.type === 'agent';
1863
- const wantStream = isAgentType && !options.json && !options.noStream && !options.output;
1912
+ // Enable SSE streaming for managed-loop agents (unless --json or --no-stream or --output)
1913
+ const isManagedLoopAgent = cloudType === 'agent' && cloudEngine === 'managed_loop';
1914
+ const wantStream = isManagedLoopAgent && !options.json && !options.noStream && !options.output;
1864
1915
  if (wantStream) {
1865
1916
  headers['Accept'] = 'text/event-stream';
1866
1917
  }
1867
1918
  const spinner = options.json ? null : (0, spinner_1.createSpinner)(`Running ${org}/${parsed.agent}@${parsed.version}...`);
1868
1919
  spinner?.start();
1869
- // Agent-type runs can take much longer; use 10 min timeout for streaming
1870
- const timeoutMs = isAgentType ? 600000 : undefined;
1920
+ // Managed-loop runs can take longer; use 10 min timeout for streaming.
1921
+ const timeoutMs = isManagedLoopAgent ? 600000 : undefined;
1871
1922
  let response;
1872
1923
  try {
1873
1924
  response = await (0, api_1.safeFetchWithRetryForCalls)(url, {
@@ -2126,7 +2177,10 @@ async function executeLocal(agentRef, args, options) {
2126
2177
  catch (err) {
2127
2178
  const agentMeta = await (0, api_1.getPublicAgent)(resolved, org, parsed.agent, parsed.version);
2128
2179
  return {
2129
- type: agentMeta.type || 'tool',
2180
+ type: agentMeta.type || 'agent',
2181
+ run_mode: agentMeta.run_mode ?? null,
2182
+ execution_engine: agentMeta.execution_engine ?? null,
2183
+ callable: agentMeta.callable,
2130
2184
  name: agentMeta.name,
2131
2185
  version: agentMeta.version,
2132
2186
  description: agentMeta.description || undefined,
@@ -2134,16 +2188,18 @@ async function executeLocal(agentRef, args, options) {
2134
2188
  };
2135
2189
  }
2136
2190
  }, { successText: `Downloaded ${org}/${parsed.agent}@${parsed.version}` });
2191
+ const localType = canonicalAgentType(agentData.type);
2192
+ const localEngine = resolveExecutionEngine(agentData);
2137
2193
  // Skills cannot be run directly
2138
- if (agentData.type === 'skill') {
2194
+ if (localType === 'skill') {
2139
2195
  throw new errors_1.CliError('Skills cannot be run directly.\n\n' +
2140
2196
  'Skills are instructions meant to be injected into AI agent contexts.\n\n' +
2141
2197
  'Options:\n' +
2142
2198
  ` Install for AI tools: orchagent skill install ${org}/${parsed.agent}\n` +
2143
2199
  ` Use with an agent: orchagent run <agent> --skills ${org}/${parsed.agent}`);
2144
2200
  }
2145
- // Agent type: execute locally with the agent runner
2146
- if (agentData.type === 'agent') {
2201
+ // Managed-loop agents execute locally with the agent runner.
2202
+ if (localEngine === 'managed_loop') {
2147
2203
  if (!agentData.prompt) {
2148
2204
  throw new errors_1.CliError('Agent prompt not available for local execution.\n\n' +
2149
2205
  'This agent may have local download disabled.\n' +
@@ -2218,10 +2274,10 @@ async function executeLocal(agentRef, args, options) {
2218
2274
  // Save locally
2219
2275
  const agentDir = await saveAgentLocally(org, parsed.agent, agentData);
2220
2276
  process.stderr.write(`\nAgent saved to: ${agentDir}\n`);
2221
- if (agentData.type === 'tool') {
2277
+ if (localEngine === 'code_runtime') {
2222
2278
  if (agentData.has_bundle) {
2223
2279
  if (options.downloadOnly) {
2224
- process.stdout.write(`\nTool has bundle available for local execution.\n`);
2280
+ process.stdout.write(`\nCode runtime bundle is available for local execution.\n`);
2225
2281
  process.stdout.write(`Run with: orch run ${org}/${parsed.agent} --local [args...]\n`);
2226
2282
  return;
2227
2283
  }
@@ -2250,8 +2306,8 @@ async function executeLocal(agentRef, args, options) {
2250
2306
  await executeTool(agentData, args);
2251
2307
  return;
2252
2308
  }
2253
- // Fallback: agent doesn't support local execution
2254
- process.stdout.write(`\nThis is a tool-based agent that runs on the server.\n`);
2309
+ // Fallback: code runtime agent doesn't support local execution.
2310
+ process.stdout.write(`\nThis code runtime agent is configured for server execution.\n`);
2255
2311
  process.stdout.write(`\nRun without --local: orch run ${org}/${parsed.agent}@${parsed.version} --data '{...}'\n`);
2256
2312
  return;
2257
2313
  }
@@ -2264,9 +2320,9 @@ async function executeLocal(agentRef, args, options) {
2264
2320
  const execLocalFileArgs = options.file ?? [];
2265
2321
  const execLocalKeyedFiles = execLocalFileArgs.filter(a => isKeyedFileArg(a) !== null);
2266
2322
  const execLocalHasInjection = execLocalKeyedFiles.length > 0 || (options.mount ?? []).length > 0;
2267
- // For prompt-based agents, execute locally
2323
+ // Direct LLM agents execute locally via prompt composition.
2268
2324
  if (!options.input && !execLocalHasInjection) {
2269
- process.stdout.write(`\nPrompt-based agent ready.\n`);
2325
+ process.stdout.write(`\nAgent ready.\n`);
2270
2326
  process.stdout.write(`Run with: orch run ${org}/${parsed.agent}@${parsed.version} --local --input '{...}'\n`);
2271
2327
  return;
2272
2328
  }
@@ -66,7 +66,7 @@ function registerScheduleCommand(program) {
66
66
  const workspaceId = await resolveWorkspaceId(config, options.workspace);
67
67
  const params = new URLSearchParams();
68
68
  if (options.agent)
69
- params.set('agent_id', options.agent);
69
+ params.set('agent_name', options.agent);
70
70
  if (options.type)
71
71
  params.set('schedule_type', options.type);
72
72
  params.set('limit', '100');
@@ -88,18 +88,26 @@ function registerScheduleCommand(program) {
88
88
  chalk_1.default.bold('Type'),
89
89
  chalk_1.default.bold('Schedule'),
90
90
  chalk_1.default.bold('Enabled'),
91
+ chalk_1.default.bold('Fails'),
91
92
  chalk_1.default.bold('Last Run'),
92
93
  chalk_1.default.bold('Status'),
93
94
  chalk_1.default.bold('Runs'),
94
95
  ],
95
96
  });
96
97
  result.schedules.forEach((s) => {
98
+ const enabledLabel = s.auto_disabled_at
99
+ ? chalk_1.default.bgRed.white(' AUTO-DISABLED ')
100
+ : s.enabled ? chalk_1.default.green('yes') : chalk_1.default.red('no');
101
+ const failsLabel = s.consecutive_failures > 0
102
+ ? chalk_1.default.red(String(s.consecutive_failures))
103
+ : chalk_1.default.gray('0');
97
104
  table.push([
98
105
  s.id.slice(0, 8),
99
106
  `${s.agent_name}@${s.agent_version}`,
100
107
  s.schedule_type,
101
108
  s.schedule_type === 'cron' ? (s.cron_expression ?? '-') : 'webhook',
102
- s.enabled ? chalk_1.default.green('yes') : chalk_1.default.red('no'),
109
+ enabledLabel,
110
+ failsLabel,
103
111
  formatDate(s.last_run_at),
104
112
  statusColor(s.last_run_status),
105
113
  s.run_count.toString(),
@@ -294,4 +302,123 @@ function registerScheduleCommand(program) {
294
302
  }
295
303
  process.stdout.write('\n');
296
304
  });
305
+ // orch schedule info <schedule-id>
306
+ schedule
307
+ .command('info <schedule-id>')
308
+ .description('Show detailed schedule information with recent runs and events')
309
+ .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
310
+ .option('--json', 'Output as JSON')
311
+ .action(async (scheduleId, options) => {
312
+ const config = await (0, config_1.getResolvedConfig)();
313
+ if (!config.apiKey) {
314
+ throw new errors_1.CliError('Missing API key. Run `orch login` first.');
315
+ }
316
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
317
+ const [scheduleRes, runsRes, eventsRes] = await Promise.all([
318
+ (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/schedules/${scheduleId}`),
319
+ (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/schedules/${scheduleId}/runs?limit=5`),
320
+ (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/schedules/${scheduleId}/events?limit=5`),
321
+ ]);
322
+ if (options.json) {
323
+ (0, output_1.printJson)({ schedule: scheduleRes.schedule, runs: runsRes.runs, events: eventsRes.events });
324
+ return;
325
+ }
326
+ const s = scheduleRes.schedule;
327
+ process.stdout.write(`\n${chalk_1.default.bold('Schedule Details')}\n\n`);
328
+ process.stdout.write(` ID: ${s.id}\n`);
329
+ process.stdout.write(` Agent: ${s.agent_name}@${s.agent_version}\n`);
330
+ process.stdout.write(` Type: ${s.schedule_type}\n`);
331
+ if (s.cron_expression) {
332
+ process.stdout.write(` Cron: ${s.cron_expression}\n`);
333
+ process.stdout.write(` Timezone: ${s.timezone}\n`);
334
+ }
335
+ process.stdout.write(` Enabled: ${s.enabled ? chalk_1.default.green('yes') : chalk_1.default.red('no')}\n`);
336
+ if (s.auto_disabled_at) {
337
+ process.stdout.write(` ${chalk_1.default.bgRed.white(' AUTO-DISABLED ')} at ${formatDate(s.auto_disabled_at)}\n`);
338
+ }
339
+ process.stdout.write(` Runs: ${s.run_count}\n`);
340
+ process.stdout.write(` Failures: ${s.consecutive_failures > 0 ? chalk_1.default.red(String(s.consecutive_failures)) : '0'} / ${s.max_consecutive_failures}\n`);
341
+ if (s.next_run_at) {
342
+ process.stdout.write(` Next Run: ${formatDate(s.next_run_at)}\n`);
343
+ }
344
+ if (s.alert_webhook_url) {
345
+ process.stdout.write(` Alert URL: ${s.alert_webhook_url.slice(0, 50)}...\n`);
346
+ }
347
+ // Recent runs
348
+ if (runsRes.runs.length > 0) {
349
+ process.stdout.write(`\n${chalk_1.default.bold('Recent Runs')}\n`);
350
+ const runsTable = new cli_table3_1.default({
351
+ head: [chalk_1.default.bold('Status'), chalk_1.default.bold('Duration'), chalk_1.default.bold('Error'), chalk_1.default.bold('Started')],
352
+ });
353
+ for (const r of runsRes.runs) {
354
+ runsTable.push([
355
+ statusColor(r.status),
356
+ r.duration_ms != null ? `${(r.duration_ms / 1000).toFixed(1)}s` : '-',
357
+ r.error_message ? chalk_1.default.red(r.error_message.slice(0, 60)) : '-',
358
+ formatDate(r.started_at),
359
+ ]);
360
+ }
361
+ process.stdout.write(`${runsTable.toString()}\n`);
362
+ }
363
+ // Recent events
364
+ if (eventsRes.events.length > 0) {
365
+ process.stdout.write(`\n${chalk_1.default.bold('Recent Events')}\n`);
366
+ for (const e of eventsRes.events) {
367
+ const color = e.event_type.includes('fail') || e.event_type.includes('disabled') ? chalk_1.default.red : chalk_1.default.gray;
368
+ process.stdout.write(` ${chalk_1.default.gray(formatDate(e.created_at))} ${color(e.event_type)} ${e.message || ''}\n`);
369
+ }
370
+ }
371
+ process.stdout.write('\n');
372
+ });
373
+ // orch schedule runs <schedule-id>
374
+ schedule
375
+ .command('runs <schedule-id>')
376
+ .description('List run history for a schedule')
377
+ .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
378
+ .option('--status <status>', 'Filter by status (completed, failed, running, timeout)')
379
+ .option('--limit <n>', 'Number of runs to show (default: 20)', '20')
380
+ .option('--json', 'Output as JSON')
381
+ .action(async (scheduleId, options) => {
382
+ const config = await (0, config_1.getResolvedConfig)();
383
+ if (!config.apiKey) {
384
+ throw new errors_1.CliError('Missing API key. Run `orch login` first.');
385
+ }
386
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
387
+ const params = new URLSearchParams();
388
+ params.set('limit', options.limit);
389
+ if (options.status)
390
+ params.set('status', options.status);
391
+ const qs = `?${params.toString()}`;
392
+ const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/schedules/${scheduleId}/runs${qs}`);
393
+ if (options.json) {
394
+ (0, output_1.printJson)(result);
395
+ return;
396
+ }
397
+ if (!result.runs.length) {
398
+ process.stdout.write('No runs found.\n');
399
+ return;
400
+ }
401
+ const table = new cli_table3_1.default({
402
+ head: [
403
+ chalk_1.default.bold('Run ID'),
404
+ chalk_1.default.bold('Status'),
405
+ chalk_1.default.bold('Source'),
406
+ chalk_1.default.bold('Duration'),
407
+ chalk_1.default.bold('Error'),
408
+ chalk_1.default.bold('Started'),
409
+ ],
410
+ });
411
+ for (const r of result.runs) {
412
+ table.push([
413
+ r.id.slice(0, 8),
414
+ statusColor(r.status),
415
+ r.trigger_source || '-',
416
+ r.duration_ms != null ? `${(r.duration_ms / 1000).toFixed(1)}s` : '-',
417
+ r.error_message ? chalk_1.default.red(r.error_message.slice(0, 50)) : '-',
418
+ formatDate(r.started_at),
419
+ ]);
420
+ }
421
+ process.stdout.write(`${table.toString()}\n`);
422
+ process.stdout.write(chalk_1.default.gray(`\n${result.total} run${result.total !== 1 ? 's' : ''} total\n`));
423
+ });
297
424
  }