@orchagent/cli 0.3.100 → 0.3.101

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.
@@ -9,18 +9,14 @@ const config_1 = require("../lib/config");
9
9
  const api_1 = require("../lib/api");
10
10
  const errors_1 = require("../lib/errors");
11
11
  const key_store_1 = require("../lib/key-store");
12
+ const resolve_agent_1 = require("../lib/resolve-agent");
12
13
  /**
13
14
  * Resolve an agent reference ("org/agent" or just "agent") to an agent ID.
14
- * Uses the authenticated list-agents endpoint and finds the latest version.
15
+ * Uses the shared resolveAgentContext() for parsing and org/workspace resolution,
16
+ * then finds the latest version via the authenticated list-agents endpoint.
15
17
  */
16
18
  async function resolveAgentId(config, ref) {
17
- const parts = ref.split('/');
18
- const agentName = parts.length >= 2 ? parts[1] : parts[0];
19
- const orgSlug = parts.length >= 2 ? parts[0] : undefined;
20
- // Resolve workspace context from org slug or config
21
- const configFile = await (0, config_1.loadConfig)();
22
- const resolvedOrg = orgSlug ?? configFile.workspace ?? config.defaultOrg;
23
- const workspaceId = resolvedOrg ? await (0, api_1.resolveWorkspaceIdForOrg)(config, resolvedOrg) : undefined;
19
+ const { org, agent: agentName, workspaceId } = await (0, resolve_agent_1.resolveAgentContext)(ref, config);
24
20
  const agents = await (0, api_1.listMyAgents)(config, workspaceId);
25
21
  const matching = agents.filter(a => a.name === agentName);
26
22
  if (matching.length === 0) {
@@ -28,7 +24,7 @@ async function resolveAgentId(config, ref) {
28
24
  }
29
25
  // Use the latest version
30
26
  const latest = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
31
- return { agent: latest, agentId: latest.id, orgSlug: latest.org_slug ?? resolvedOrg ?? '', workspaceId };
27
+ return { agent: latest, agentId: latest.id, orgSlug: latest.org_slug ?? org, workspaceId };
32
28
  }
33
29
  function registerAgentKeysCommand(program) {
34
30
  const agentKeys = program
@@ -246,8 +246,10 @@ function parseSecondRef(value, firstOrg, firstName) {
246
246
  const parsed = (0, agent_ref_1.parseAgentRef)(value);
247
247
  return { org: parsed.org ?? firstOrg, agent: parsed.agent, version: parsed.version };
248
248
  }
249
- // Otherwise treat as a version shorthand for the same agent
250
- return { org: firstOrg, agent: firstName, version: value };
249
+ // Otherwise treat as a version shorthand for the same agent.
250
+ // Strip leading '@' users naturally type `@v2` since the full form is `org/agent@v2`.
251
+ const version = value.startsWith('@') ? value.slice(1) : value;
252
+ return { org: firstOrg, agent: firstName, version };
251
253
  }
252
254
  // ── Command ────────────────────────────────────────────────────
253
255
  function registerDiffCommand(program) {
@@ -10,16 +10,21 @@ const DOCS_ROUTES = {
10
10
  '': '/',
11
11
  'cli': '/using-agents/cli-commands',
12
12
  'agents': '/building-agents/agent-types',
13
+ 'orchestration': '/building-agents/orchestration',
13
14
  'skills': '/building-agents/orchestration',
14
15
  'sdk': '/building-agents/sdk',
15
16
  'api': '/api-reference/overview',
16
17
  'quickstart': '/quickstart',
18
+ 'scheduling': '/using-agents/scheduling',
19
+ 'services': '/using-agents/services',
20
+ 'security': '/concepts/security',
21
+ 'billing': '/concepts/billing',
17
22
  };
18
23
  function registerDocsCommand(program) {
19
24
  program
20
25
  .command('docs')
21
26
  .description('Open documentation in browser')
22
- .argument('[topic]', 'Topic: cli, agents, skills, sdk, api, quickstart')
27
+ .argument('[topic]', 'Topic: cli, agents, orchestration, skills, sdk, api, quickstart, scheduling, services, security, billing')
23
28
  .action(async (topic) => {
24
29
  if (topic && !(topic in DOCS_ROUTES)) {
25
30
  const validTopics = Object.keys(DOCS_ROUTES).filter(k => k).join(', ');
@@ -81,8 +81,9 @@ Examples:
81
81
  throw new errors_1.CliError('Missing org. Use org/agent format or set default org.');
82
82
  }
83
83
  const { agent, version } = parsed;
84
+ const workspaceId = await (0, api_1.resolveWorkspaceIdForOrg)(config, org);
84
85
  write('Resolving source agent...\n');
85
- const source = await (0, api_1.getAgentWithFallback)(config, org, agent, version);
86
+ const source = await (0, api_1.getAgentWithFallback)(config, org, agent, version, workspaceId);
86
87
  if (!source.id) {
87
88
  throw new errors_1.CliError(`Could not resolve source agent ID for '${org}/${agent}@${version}'.`);
88
89
  }
@@ -1494,7 +1494,10 @@ function resolveInitFlavor(typeOption) {
1494
1494
  if (normalized === 'prompt') {
1495
1495
  return { type: 'prompt', flavor: 'direct_llm' };
1496
1496
  }
1497
- if (normalized === 'agent' || normalized === 'agentic') {
1497
+ if (normalized === 'agent') {
1498
+ return { type: 'agent', flavor: 'managed_loop' };
1499
+ }
1500
+ if (normalized === 'agentic') {
1498
1501
  return { type: 'agent', flavor: 'code_runtime' };
1499
1502
  }
1500
1503
  if (normalized === 'tool' || normalized === 'code') {
@@ -1511,7 +1514,7 @@ function registerInitCommand(program) {
1511
1514
  .option('--orchestrator', 'Create an orchestrator agent with dependency scaffolding and SDK boilerplate')
1512
1515
  .option('--run-mode <mode>', 'Run mode for agents: on_demand or always_on', 'on_demand')
1513
1516
  .option('--language <lang>', 'Language: python or javascript (default: python)', 'python')
1514
- .option('--loop', 'Use platform-managed LLM loop instead of code runtime (requires --type agent)')
1517
+ .option('--loop', 'Use platform-managed LLM loop execution (explicit for --type agentic; default for --type agent)')
1515
1518
  .option('--template <name>', 'Start from a template (use --list-templates to see options)')
1516
1519
  .option('--list-templates', 'Show available templates with descriptions')
1517
1520
  .action(async (name, options) => {
@@ -1617,7 +1620,7 @@ function registerInitCommand(program) {
1617
1620
  const isJavaScript = ['javascript', 'js', 'typescript', 'ts'].includes(language);
1618
1621
  // Block unsupported JS flavors
1619
1622
  if (isJavaScript && initMode.flavor === 'managed_loop') {
1620
- throw new errors_1.CliError('JavaScript is not supported for --loop (managed loop). Use --type agent without --loop for a code-runtime agent.');
1623
+ throw new errors_1.CliError('JavaScript is not supported for managed-loop agents. Use --type agentic for a code-runtime agent scaffold.');
1621
1624
  }
1622
1625
  // JS orchestrators are now supported via the orchagent-sdk npm package
1623
1626
  // Block --language for types that don't create runtime files
@@ -2037,7 +2040,7 @@ function registerInitCommand(program) {
2037
2040
  }
2038
2041
  }
2039
2042
  if (initMode.flavor !== 'code_runtime' && initMode.flavor !== 'orchestrator' && initMode.flavor !== 'discord' && runMode === 'always_on') {
2040
- throw new errors_1.CliError("run_mode=always_on requires runtime.command in orchagent.json (e.g. \"runtime\": { \"command\": \"python main.py\" }). Use --type tool for code-runtime agents.");
2043
+ throw new errors_1.CliError("run_mode=always_on requires runtime.command in orchagent.json (e.g. \"runtime\": { \"command\": \"python main.py\" }). Use --type tool or --type agentic for code-runtime agents.");
2041
2044
  }
2042
2045
  // Create manifest and type-specific files
2043
2046
  const manifest = JSON.parse(MANIFEST_TEMPLATE);
@@ -10,24 +10,12 @@ const os_1 = __importDefault(require("os"));
10
10
  const config_1 = require("../lib/config");
11
11
  const api_1 = require("../lib/api");
12
12
  const errors_1 = require("../lib/errors");
13
+ const resolve_agent_1 = require("../lib/resolve-agent");
13
14
  const analytics_1 = require("../lib/analytics");
14
15
  const adapters_1 = require("../adapters");
15
16
  const skill_resolve_1 = require("../lib/skill-resolve");
16
17
  const installed_1 = require("../lib/installed");
17
18
  const agents_md_utils_1 = require("../lib/agents-md-utils");
18
- const DEFAULT_VERSION = 'latest';
19
- function parseAgentRef(value) {
20
- const [ref, versionPart] = value.split('@');
21
- const version = versionPart?.trim() || DEFAULT_VERSION;
22
- const segments = ref.split('/');
23
- if (segments.length === 1) {
24
- return { name: segments[0], version };
25
- }
26
- if (segments.length === 2) {
27
- return { org: segments[0], name: segments[1], version };
28
- }
29
- throw new errors_1.CliError('Invalid agent reference. Use org/agent or agent format.');
30
- }
31
19
  async function downloadAgentWithFallback(config, org, name, version, workspaceId) {
32
20
  // Fetch public metadata first to check if paid
33
21
  let publicMeta;
@@ -135,19 +123,21 @@ function registerInstallCommand(program) {
135
123
  errors: [],
136
124
  };
137
125
  const resolved = await (0, config_1.getResolvedConfig)();
138
- const parsed = parseAgentRef(agentArg);
139
- const configFile = await (0, config_1.loadConfig)();
140
- const org = parsed.org ?? configFile.workspace ?? resolved.defaultOrg;
141
- if (!org) {
142
- if (jsonMode) {
143
- result.errors.push('Missing org. Use org/agent format or set default org.');
126
+ let agentCtx;
127
+ try {
128
+ agentCtx = await (0, resolve_agent_1.resolveAgentContext)(agentArg, resolved);
129
+ }
130
+ catch (err) {
131
+ if (jsonMode && err instanceof errors_1.CliError) {
132
+ result.errors.push(err.message);
144
133
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
145
134
  process.exit(errors_1.ExitCodes.INVALID_INPUT);
146
135
  }
147
- throw new errors_1.CliError('Missing org. Use org/agent format or set default org.');
136
+ throw err;
148
137
  }
149
- result.agent = `${org}/${parsed.name}`;
150
- result.version = parsed.version;
138
+ const { org, agent: agentName, version, workspaceId } = agentCtx;
139
+ result.agent = `${org}/${agentName}`;
140
+ result.version = version;
151
141
  // Determine target formats
152
142
  let targetFormats = [];
153
143
  if (options.format) {
@@ -186,13 +176,11 @@ function registerInstallCommand(program) {
186
176
  throw new errors_1.CliError(errMsg);
187
177
  }
188
178
  result.scope = scope;
189
- // Resolve workspace context for the target org
190
- const workspaceId = await (0, api_1.resolveWorkspaceIdForOrg)(resolved, org);
191
179
  // Download agent
192
- log(`Fetching ${org}/${parsed.name}@${parsed.version}...\n`);
180
+ log(`Fetching ${org}/${agentName}@${version}...\n`);
193
181
  let agent;
194
182
  try {
195
- agent = await downloadAgentWithFallback(resolved, org, parsed.name, parsed.version, workspaceId);
183
+ agent = await downloadAgentWithFallback(resolved, org, agentName, version, workspaceId);
196
184
  }
197
185
  catch (err) {
198
186
  if (jsonMode) {
@@ -273,15 +261,15 @@ function registerInstallCommand(program) {
273
261
  catch {
274
262
  // File doesn't exist, will create new
275
263
  }
276
- const agentRef = `${org}/${parsed.name}`;
264
+ const agentRef = `${org}/${agentName}`;
277
265
  file.content = (0, agents_md_utils_1.mergeAgentsMdContent)(existingContent, file.content, agentRef);
278
266
  }
279
267
  await promises_1.default.writeFile(fullPath, file.content);
280
268
  filesWritten++;
281
269
  // Track installation
282
270
  const installedAgent = {
283
- agent: `${org}/${parsed.name}`,
284
- version: parsed.version,
271
+ agent: `${org}/${agentName}`,
272
+ version: version,
285
273
  format: formatId,
286
274
  scope: effectiveScope,
287
275
  path: fullPath,
@@ -297,7 +285,7 @@ function registerInstallCommand(program) {
297
285
  if (!options.dryRun) {
298
286
  if (filesWritten > 0) {
299
287
  await (0, analytics_1.track)('cli_agent_install', {
300
- agent: `${org}/${parsed.name}`,
288
+ agent: `${org}/${agentName}`,
301
289
  formats: targetFormats,
302
290
  scope,
303
291
  });
@@ -182,7 +182,7 @@ async function listRuns(config, workspaceId, agentName, options) {
182
182
  if (result.total > result.runs.length) {
183
183
  process.stdout.write(chalk_1.default.gray(`\nShowing ${result.runs.length} of ${result.total} runs. Use --limit to see more.\n`));
184
184
  }
185
- process.stdout.write(chalk_1.default.gray('\nView detailed logs for a run: orch logs <run-id>\n'));
185
+ process.stdout.write(chalk_1.default.gray('\nView detailed logs for a run: orch logs <run-id> · Replay a run: orch replay <run-id>\n'));
186
186
  }
187
187
  // ============================================
188
188
  // SHOW RUN LOGS
@@ -229,5 +229,7 @@ async function showRunLogs(config, workspaceId, runId, json) {
229
229
  process.stdout.write(chalk_1.default.gray('\nNo sandbox output available for this run. ' +
230
230
  'Execution logs are captured for agents with a code runtime (tool/agent types with runtime.command).\n'));
231
231
  }
232
+ // Footer hints
233
+ process.stdout.write(chalk_1.default.gray(`\nReplay: orch replay ${runId.slice(0, 8)} · Trace: orch trace ${runId.slice(0, 8)}\n`));
232
234
  process.stdout.write('\n');
233
235
  }
@@ -510,6 +510,8 @@ async function batchPublish(rootDir, options) {
510
510
  forwardArgs.push('--no-local-download');
511
511
  if (options.requiredSecrets === false)
512
512
  forwardArgs.push('--no-required-secrets');
513
+ if (options.verbose)
514
+ forwardArgs.push('--verbose');
513
515
  const results = [];
514
516
  for (let i = 0; i < sorted.length; i++) {
515
517
  const agent = sorted[i];
@@ -586,6 +588,7 @@ function registerPublishCommand(program) {
586
588
  .option('--no-local-download', 'Prevent users from downloading and running locally (default: allowed)')
587
589
  .option('--no-required-secrets', '(deprecated) No longer needed — required_secrets defaults to []')
588
590
  .option('--all', 'Publish all agents in subdirectories (dependency order)')
591
+ .option('--verbose', 'Show detailed bundle contents (file list)')
589
592
  .action(async (options) => {
590
593
  const cwd = process.cwd();
591
594
  // --all: batch publish all agents in subdirectories
@@ -696,6 +699,11 @@ function registerPublishCommand(program) {
696
699
  process.stdout.write(`\n${chalk_1.default.green('✔')} Published ${org.slug}/${skillData.frontmatter.name}@${skillVersion} successfully!\n\n`);
697
700
  if (hasMultipleFiles) {
698
701
  process.stdout.write(`Files: ${skillFiles.length} files included\n`);
702
+ if (options.verbose) {
703
+ for (const f of skillFiles) {
704
+ process.stdout.write(` ${f.path}\n`);
705
+ }
706
+ }
699
707
  }
700
708
  process.stdout.write(`Visibility: private\n`);
701
709
  process.stdout.write(`\nView analytics and usage: https://orchagent.io/dashboard\n`);
@@ -1061,6 +1069,12 @@ function registerPublishCommand(program) {
1061
1069
  process.stderr.write(` Files: ${bundlePreview.fileCount} files\n`);
1062
1070
  process.stderr.write(` Size: ${(bundlePreview.totalSizeBytes / 1024).toFixed(1)} KB\n`);
1063
1071
  process.stderr.write(` Entrypoint: ${bundlePreview.entrypoint}\n`);
1072
+ if (options.verbose && bundlePreview.files?.length) {
1073
+ process.stderr.write(` Bundled files:\n`);
1074
+ for (const f of bundlePreview.files) {
1075
+ process.stderr.write(` ${f}\n`);
1076
+ }
1077
+ }
1064
1078
  }
1065
1079
  process.stderr.write('\nAgent Preview:\n');
1066
1080
  process.stderr.write(` Name: ${manifest.name}\n`);
@@ -1324,6 +1338,12 @@ function registerPublishCommand(program) {
1324
1338
  skipEntrypointCheck: false,
1325
1339
  });
1326
1340
  process.stdout.write(` Created bundle: ${bundleResult.fileCount} files, ${(bundleResult.sizeBytes / 1024).toFixed(1)}KB\n`);
1341
+ if (options.verbose && bundleResult.files?.length) {
1342
+ process.stdout.write(` Bundled files:\n`);
1343
+ for (const f of bundleResult.files) {
1344
+ process.stdout.write(` ${f}\n`);
1345
+ }
1346
+ }
1327
1347
  // Validate bundle size
1328
1348
  const validation = await (0, bundle_1.validateBundle)(bundlePath);
1329
1349
  if (!validation.valid) {
@@ -1404,14 +1424,20 @@ function registerPublishCommand(program) {
1404
1424
  // Show security review result if available
1405
1425
  const secReview = result.security_review;
1406
1426
  if (secReview?.verdict) {
1407
- if (secReview.verdict === 'passed') {
1427
+ if (secReview.verdict === 'approved') {
1408
1428
  process.stdout.write(`Security: ${chalk_1.default.green('passed')}\n`);
1409
1429
  }
1410
1430
  else if (secReview.verdict === 'flagged') {
1411
1431
  process.stdout.write(`Security: ${chalk_1.default.yellow('flagged')} — ${secReview.summary || 'review recommended'}\n`);
1412
1432
  }
1433
+ else if (secReview.verdict === 'error') {
1434
+ process.stdout.write(`Security: ${chalk_1.default.gray('review unavailable')} — publish succeeded, review will be retried\n`);
1435
+ }
1436
+ else if (secReview.verdict === 'skipped') {
1437
+ process.stdout.write(`Security: ${chalk_1.default.gray('review skipped')} — ${secReview.summary || 'content not eligible for review'}\n`);
1438
+ }
1413
1439
  else {
1414
- process.stdout.write(`Security: ${secReview.verdict}\n`);
1440
+ process.stdout.write(`Security: ${chalk_1.default.gray(secReview.verdict)} — ${secReview.summary || ''}\n`);
1415
1441
  }
1416
1442
  }
1417
1443
  if (result.service_key) {
@@ -12,21 +12,10 @@ const child_process_1 = require("child_process");
12
12
  const config_1 = require("../lib/config");
13
13
  const api_1 = require("../lib/api");
14
14
  const errors_1 = require("../lib/errors");
15
+ const resolve_agent_1 = require("../lib/resolve-agent");
15
16
  const analytics_1 = require("../lib/analytics");
16
17
  const output_1 = require("../lib/output");
17
18
  // ─── Helpers ────────────────────────────────────────────────────────────────
18
- function parsePullRef(value) {
19
- const [ref, versionPart] = value.split('@');
20
- const version = versionPart?.trim() || 'latest';
21
- const segments = ref.split('/');
22
- if (segments.length === 1) {
23
- return { agent: segments[0], version };
24
- }
25
- if (segments.length === 2) {
26
- return { org: segments[0], agent: segments[1], version };
27
- }
28
- throw new errors_1.CliError('Invalid agent reference. Use org/agent[@version] or agent[@version] format.');
29
- }
30
19
  function canonicalType(typeValue) {
31
20
  const normalized = (typeValue || 'agent').toLowerCase();
32
21
  if (['prompt', 'tool', 'agent', 'skill'].includes(normalized))
@@ -359,19 +348,12 @@ Examples:
359
348
  process.stdout.write(message);
360
349
  };
361
350
  const config = await (0, config_1.getResolvedConfig)();
362
- const parsed = parsePullRef(agentRef);
363
- // Resolve org from workspace / defaultOrg fallback
364
- const configFile = await (0, config_1.loadConfig)();
365
- const org = parsed.org ?? configFile.workspace ?? config.defaultOrg;
366
- if (!org) {
367
- throw new errors_1.CliError('Missing org. Use org/agent[@version] format, or set a default org with:\n' +
368
- ' orch config set default-org <org>');
369
- }
370
- // Resolve workspace context for the target org
371
- const workspaceId = await (0, api_1.resolveWorkspaceIdForOrg)(config, org);
372
- write(`Resolving ${org}/${parsed.agent}@${parsed.version}...\n`);
351
+ const { org, agent: agentName, version, workspaceId } = await (0, resolve_agent_1.resolveAgentContext)(agentRef, config, {
352
+ missingOrgMessage: 'Missing org. Use org/agent[@version] format, or set a default org with:\n orch config set default-org <org>',
353
+ });
354
+ write(`Resolving ${org}/${agentName}@${version}...\n`);
373
355
  // Resolve agent data
374
- const data = await resolveAgent(config, org, parsed.agent, parsed.version, workspaceId);
356
+ const data = await resolveAgent(config, org, agentName, version, workspaceId);
375
357
  // Reject skills
376
358
  if (canonicalType(data.type) === 'skill') {
377
359
  throw new errors_1.CliError("This is a skill. Use 'orch skill install <ref>' instead.");
@@ -455,7 +437,7 @@ Examples:
455
437
  // Track analytics
456
438
  await (0, analytics_1.track)('cli_pull', {
457
439
  org,
458
- agent: parsed.agent,
440
+ agent: agentName,
459
441
  version: data.version,
460
442
  engine,
461
443
  source: data.source,
@@ -465,7 +447,7 @@ Examples:
465
447
  if (options.json) {
466
448
  const result = {
467
449
  success: true,
468
- requested_ref: `${org}/${parsed.agent}@${parsed.version}`,
450
+ requested_ref: `${org}/${agentName}@${version}`,
469
451
  resolved_ref: resolvedRef,
470
452
  output_dir: outputDir,
471
453
  engine,
@@ -91,6 +91,21 @@ function registerReplayCommand(program) {
91
91
  .option('--override-policy <id>', 'Override provider policy ID for this replay')
92
92
  .option('--no-wait', 'Queue the replay and return immediately without waiting for results')
93
93
  .option('--json', 'Output as JSON')
94
+ .addHelpText('after', `
95
+ How snapshots work:
96
+ Every cloud run automatically captures a snapshot of its input, config,
97
+ secrets, and agent version before execution. This snapshot enables
98
+ deterministic replay — even if the agent has been updated since.
99
+
100
+ Local runs (--local) do not create snapshots and cannot be replayed.
101
+ Runs created before snapshot support was added also cannot be replayed.
102
+
103
+ Examples:
104
+ orch replay a1b2c3d4 Replay and wait for results
105
+ orch replay a1b2c3d4 --no-wait Queue and return immediately
106
+ orch replay a1b2c3d4 --reason "debug" Replay with audit reason
107
+ orch replay a1b2c3d4 --json Output as JSON
108
+ `)
94
109
  .action(async (runId, options) => {
95
110
  const config = await (0, config_1.getResolvedConfig)();
96
111
  if (!config.apiKey) {
@@ -109,15 +124,33 @@ function registerReplayCommand(program) {
109
124
  throw new errors_1.CliError(`Invalid run ID '${runId}'. Provide a full UUID or a short hex prefix (7+ characters).`);
110
125
  }
111
126
  // Submit replay request
112
- const body = {};
127
+ const reqBody = {};
113
128
  if (options.reason)
114
- body.reason = options.reason;
129
+ reqBody.reason = options.reason;
115
130
  if (options.overridePolicy)
116
- body.override_provider_policy_id = options.overridePolicy;
117
- const replay = await (0, api_1.request)(config, 'POST', `/workspaces/${workspaceId}/runs/${resolvedRunId}/replay`, {
118
- body: JSON.stringify(body),
119
- headers: { 'Content-Type': 'application/json' },
120
- });
131
+ reqBody.override_provider_policy_id = options.overridePolicy;
132
+ let replay;
133
+ try {
134
+ replay = await (0, api_1.request)(config, 'POST', `/workspaces/${workspaceId}/runs/${resolvedRunId}/replay`, {
135
+ body: JSON.stringify(reqBody),
136
+ headers: { 'Content-Type': 'application/json' },
137
+ });
138
+ }
139
+ catch (err) {
140
+ if (err instanceof api_1.ApiError && err.status === 404) {
141
+ const isSnapshotMissing = err.message.toLowerCase().includes('snapshot');
142
+ if (isSnapshotMissing) {
143
+ throw new errors_1.CliError(`No snapshot available for run ${resolvedRunId.slice(0, 8)}.\n\n` +
144
+ `Snapshots are captured automatically for cloud runs. This run may have been:\n` +
145
+ ` - A local run (--local), which does not create snapshots\n` +
146
+ ` - Created before snapshot support was added\n\n` +
147
+ `Run \`orch logs ${resolvedRunId.slice(0, 8)}\` to inspect the original run.`);
148
+ }
149
+ throw new errors_1.CliError(`Run ${resolvedRunId.slice(0, 8)} not found. ` +
150
+ `Check the run ID and workspace, or run \`orch logs\` to list recent runs.`);
151
+ }
152
+ throw err;
153
+ }
121
154
  if (options.json && options.wait === false) {
122
155
  (0, output_1.printJson)(replay);
123
156
  return;
@@ -54,6 +54,7 @@ const child_process_1 = require("child_process");
54
54
  const chalk_1 = __importDefault(require("chalk"));
55
55
  const dotenv_1 = require("../lib/dotenv");
56
56
  const config_1 = require("../lib/config");
57
+ const resolve_agent_1 = require("../lib/resolve-agent");
57
58
  const api_1 = require("../lib/api");
58
59
  const errors_1 = require("../lib/errors");
59
60
  const output_1 = require("../lib/output");
@@ -84,18 +85,6 @@ function localCommandForEntrypoint(entrypoint) {
84
85
  }
85
86
  return 'python3';
86
87
  }
87
- function parseAgentRef(value) {
88
- const [ref, versionPart] = value.split('@');
89
- const version = versionPart?.trim() || DEFAULT_VERSION;
90
- const segments = ref.split('/');
91
- if (segments.length === 1) {
92
- return { agent: segments[0], version };
93
- }
94
- if (segments.length === 2) {
95
- return { org: segments[0], agent: segments[1], version };
96
- }
97
- throw new errors_1.CliError('Invalid agent reference. Use org/agent or agent format.');
98
- }
99
88
  function canonicalAgentType(typeValue) {
100
89
  const normalized = (typeValue || 'agent').toLowerCase();
101
90
  // Handle legacy type names: agentic → agent, code → tool
@@ -1869,15 +1858,8 @@ async function executeCloud(agentRef, file, options) {
1869
1858
  if (!resolved.apiKey) {
1870
1859
  throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
1871
1860
  }
1872
- const parsed = parseAgentRef(agentRef);
1873
- const configFile = await (0, config_1.loadConfig)();
1874
- const org = parsed.org ?? configFile.workspace ?? resolved.defaultOrg;
1875
- if (!org) {
1876
- throw new errors_1.CliError('Missing org. Use org/agent or set default org.');
1877
- }
1878
- // Resolve workspace context for the target org
1879
- const workspaceId = await (0, api_1.resolveWorkspaceIdForOrg)(resolved, org);
1880
- const agentMeta = await (0, api_1.getAgentWithFallback)(resolved, org, parsed.agent, parsed.version, workspaceId);
1861
+ const { org, agent: agentName, version, workspaceId } = await (0, resolve_agent_1.resolveAgentContext)(agentRef, resolved);
1862
+ const agentMeta = await (0, api_1.getAgentWithFallback)(resolved, org, agentName, version, workspaceId);
1881
1863
  const cloudType = canonicalAgentType(agentMeta.type);
1882
1864
  const cloudEngine = resolveExecutionEngine({
1883
1865
  type: agentMeta.type,
@@ -1891,10 +1873,8 @@ async function executeCloud(agentRef, file, options) {
1891
1873
  const agentRequiredSecrets = agentMeta.required_secrets;
1892
1874
  if (agentRequiredSecrets?.length) {
1893
1875
  try {
1894
- // Use already-resolved workspaceId (or fall back to config workspace slug)
1895
- const wsId = workspaceId ?? (configFile.workspace ? (await (0, api_1.resolveWorkspaceIdForOrg)(resolved, configFile.workspace)) : undefined);
1896
- if (wsId) {
1897
- const secretsResult = await (0, api_1.request)(resolved, 'GET', `/workspaces/${wsId}/secrets`);
1876
+ if (workspaceId) {
1877
+ const secretsResult = await (0, api_1.request)(resolved, 'GET', `/workspaces/${workspaceId}/secrets`);
1898
1878
  const existingNames = new Set(secretsResult.secrets.map((s) => s.name));
1899
1879
  const missing = agentRequiredSecrets.filter((s) => !existingNames.has(s));
1900
1880
  if (missing.length > 0) {
@@ -1918,7 +1898,7 @@ async function executeCloud(agentRef, file, options) {
1918
1898
  // --estimate-only: show cost estimate and exit without running
1919
1899
  if (options.estimate || options.estimateOnly) {
1920
1900
  try {
1921
- const est = await (0, api_1.getAgentCostEstimate)(resolved, org, parsed.agent, parsed.version);
1901
+ const est = await (0, api_1.getAgentCostEstimate)(resolved, org, agentName, version);
1922
1902
  const e = est.estimate;
1923
1903
  if (e.sample_size === 0) {
1924
1904
  process.stderr.write(chalk_1.default.yellow('\nNo run history available for cost estimation.\n'));
@@ -2245,7 +2225,7 @@ async function executeCloud(agentRef, file, options) {
2245
2225
  }
2246
2226
  } // end of non-injection path
2247
2227
  const verboseQs = options.verbose ? '?verbose=true' : '';
2248
- const url = `${resolved.apiUrl.replace(/\/$/, '')}/${org}/${parsed.agent}/${parsed.version}/${endpoint}${verboseQs}`;
2228
+ const url = `${resolved.apiUrl.replace(/\/$/, '')}/${org}/${agentName}/${version}/${endpoint}${verboseQs}`;
2249
2229
  // Enable SSE streaming for sandbox-backed engines (unless --json or --no-stream or --output)
2250
2230
  const isManagedLoopAgent = cloudType === 'agent' && cloudEngine === 'managed_loop';
2251
2231
  const isCodeRuntimeAgent = cloudEngine === 'code_runtime';
@@ -2257,13 +2237,13 @@ async function executeCloud(agentRef, file, options) {
2257
2237
  }
2258
2238
  // Print verbose debug info before request
2259
2239
  if (options.verbose && !options.json) {
2260
- process.stderr.write(chalk_1.default.gray(`\n[verbose] ${org}/${parsed.agent}@${parsed.version}\n` +
2240
+ process.stderr.write(chalk_1.default.gray(`\n[verbose] ${org}/${agentName}@${version}\n` +
2261
2241
  `[verbose] type=${cloudType} engine=${cloudEngine} endpoint=${endpoint}\n` +
2262
2242
  `[verbose] stream=${wantStream ? 'yes' : 'no'} url=${url}\n`));
2263
2243
  }
2264
2244
  const { spinner, dispose: disposeSpinner } = options.json
2265
2245
  ? { spinner: null, dispose: () => { } }
2266
- : (0, spinner_1.createElapsedSpinner)(`Running ${org}/${parsed.agent}@${parsed.version}...`);
2246
+ : (0, spinner_1.createElapsedSpinner)(`Running ${org}/${agentName}@${version}...`);
2267
2247
  spinner?.start();
2268
2248
  // Streamed sandbox runs can take longer; use 10 min timeout (or --wait-timeout).
2269
2249
  const waitTimeoutSec = options.waitTimeout ? parseInt(options.waitTimeout, 10) : undefined;
@@ -2432,11 +2412,11 @@ async function executeCloud(agentRef, file, options) {
2432
2412
  let finalPayload = null;
2433
2413
  let hadError = false;
2434
2414
  if (options.verbose) {
2435
- process.stderr.write(chalk_1.default.gray(`\nStreaming ${org}/${parsed.agent}@${parsed.version} (verbose):\n`));
2415
+ process.stderr.write(chalk_1.default.gray(`\nStreaming ${org}/${agentName}@${version} (verbose):\n`));
2436
2416
  process.stderr.write(chalk_1.default.gray(` type=${cloudType} engine=${cloudEngine} endpoint=${endpoint}\n`));
2437
2417
  }
2438
2418
  else {
2439
- process.stderr.write(chalk_1.default.gray(`\nStreaming ${org}/${parsed.agent}@${parsed.version}:\n`));
2419
+ process.stderr.write(chalk_1.default.gray(`\nStreaming ${org}/${agentName}@${version}:\n`));
2440
2420
  }
2441
2421
  let progressErrorShown = false;
2442
2422
  let streamTimedOut = false;
@@ -2497,7 +2477,7 @@ async function executeCloud(agentRef, file, options) {
2497
2477
  ? ''
2498
2478
  : chalk_1.default.gray('Tip: Use --wait-timeout <seconds> to wait longer.\n')));
2499
2479
  await (0, analytics_1.track)('cli_run', {
2500
- agent: `${org}/${parsed.agent}@${parsed.version}`,
2480
+ agent: `${org}/${agentName}@${version}`,
2501
2481
  input_type: hasInjection ? 'file_injection' : unkeyedFileArgs.length > 0 ? 'file' : options.data ? 'json' : 'empty',
2502
2482
  mode: 'cloud',
2503
2483
  streamed: true,
@@ -2508,7 +2488,7 @@ async function executeCloud(agentRef, file, options) {
2508
2488
  throw err;
2509
2489
  }
2510
2490
  await (0, analytics_1.track)('cli_run', {
2511
- agent: `${org}/${parsed.agent}@${parsed.version}`,
2491
+ agent: `${org}/${agentName}@${version}`,
2512
2492
  input_type: hasInjection ? 'file_injection' : unkeyedFileArgs.length > 0 ? 'file' : options.data ? 'json' : 'empty',
2513
2493
  mode: 'cloud',
2514
2494
  streamed: true,
@@ -2549,14 +2529,14 @@ async function executeCloud(agentRef, file, options) {
2549
2529
  }
2550
2530
  const runId = response.headers?.get?.('x-run-id');
2551
2531
  if (runId) {
2552
- process.stderr.write(chalk_1.default.gray(`View logs: orch logs ${runId}\n`));
2532
+ process.stderr.write(chalk_1.default.gray(`Snapshot saved · Logs: orch logs ${runId} · Replay: orch replay ${runId}\n`));
2553
2533
  }
2554
2534
  }
2555
2535
  }
2556
2536
  }
2557
2537
  return;
2558
2538
  }
2559
- spinner?.succeed(`Ran ${org}/${parsed.agent}@${parsed.version}`);
2539
+ spinner?.succeed(`Ran ${org}/${agentName}@${version}`);
2560
2540
  const inputType = hasInjection
2561
2541
  ? 'file_injection'
2562
2542
  : unkeyedFileArgs.length > 0
@@ -2569,7 +2549,7 @@ async function executeCloud(agentRef, file, options) {
2569
2549
  ? 'metadata'
2570
2550
  : 'empty';
2571
2551
  await (0, analytics_1.track)('cli_run', {
2572
- agent: `${org}/${parsed.agent}@${parsed.version}`,
2552
+ agent: `${org}/${agentName}@${version}`,
2573
2553
  input_type: inputType,
2574
2554
  mode: 'cloud',
2575
2555
  });
@@ -2638,7 +2618,7 @@ async function executeCloud(agentRef, file, options) {
2638
2618
  }
2639
2619
  const runId = response.headers?.get?.('x-run-id');
2640
2620
  if (runId) {
2641
- process.stderr.write(chalk_1.default.gray(`View logs: orch logs ${runId}\n`));
2621
+ process.stderr.write(chalk_1.default.gray(`Snapshot saved · Logs: orch logs ${runId} · Replay: orch replay ${runId}\n`));
2642
2622
  }
2643
2623
  }
2644
2624
  }
@@ -2670,21 +2650,14 @@ async function executeLocal(agentRef, args, options) {
2670
2650
  }
2671
2651
  }
2672
2652
  const resolved = await (0, config_1.getResolvedConfig)();
2673
- const parsed = parseAgentRef(agentRef);
2674
- const configFile = await (0, config_1.loadConfig)();
2675
- const org = parsed.org ?? configFile.workspace ?? resolved.defaultOrg;
2676
- if (!org) {
2677
- throw new errors_1.CliError('Missing org. Use org/agent format.');
2678
- }
2679
- // Resolve workspace context for the target org
2680
- const workspaceId = await (0, api_1.resolveWorkspaceIdForOrg)(resolved, org);
2653
+ const { org, agent: localAgentName, version: localVersion, workspaceId } = await (0, resolve_agent_1.resolveAgentContext)(agentRef, resolved);
2681
2654
  // Download agent definition with spinner
2682
- const agentData = await (0, spinner_1.withSpinner)(`Downloading ${org}/${parsed.agent}@${parsed.version}...`, async () => {
2655
+ const agentData = await (0, spinner_1.withSpinner)(`Downloading ${org}/${localAgentName}@${localVersion}...`, async () => {
2683
2656
  try {
2684
- return await downloadAgent(resolved, org, parsed.agent, parsed.version, workspaceId);
2657
+ return await downloadAgent(resolved, org, localAgentName, localVersion, workspaceId);
2685
2658
  }
2686
2659
  catch (err) {
2687
- const agentMeta = await (0, api_1.getPublicAgent)(resolved, org, parsed.agent, parsed.version);
2660
+ const agentMeta = await (0, api_1.getPublicAgent)(resolved, org, localAgentName, localVersion);
2688
2661
  return {
2689
2662
  type: agentMeta.type || 'agent',
2690
2663
  run_mode: agentMeta.run_mode ?? null,
@@ -2696,7 +2669,7 @@ async function executeLocal(agentRef, args, options) {
2696
2669
  supported_providers: agentMeta.supported_providers || ['any'],
2697
2670
  };
2698
2671
  }
2699
- }, { successText: `Downloaded ${org}/${parsed.agent}@${parsed.version}` });
2672
+ }, { successText: `Downloaded ${org}/${localAgentName}@${localVersion}` });
2700
2673
  const localType = canonicalAgentType(agentData.type);
2701
2674
  const localEngine = resolveExecutionEngine(agentData);
2702
2675
  // Skills cannot be run directly
@@ -2704,8 +2677,8 @@ async function executeLocal(agentRef, args, options) {
2704
2677
  throw new errors_1.CliError('Skills cannot be run directly.\n\n' +
2705
2678
  'Skills are instructions meant to be injected into AI agent contexts.\n\n' +
2706
2679
  'Options:\n' +
2707
- ` Install for AI tools: orchagent skill install ${org}/${parsed.agent}\n` +
2708
- ` Use with an agent: orchagent run <agent> --skills ${org}/${parsed.agent}`);
2680
+ ` Install for AI tools: orchagent skill install ${org}/${localAgentName}\n` +
2681
+ ` Use with an agent: orchagent run <agent> --skills ${org}/${localAgentName}`);
2709
2682
  }
2710
2683
  // Managed-loop agents execute locally with the agent runner.
2711
2684
  if (localEngine === 'managed_loop') {
@@ -2713,11 +2686,11 @@ async function executeLocal(agentRef, args, options) {
2713
2686
  throw new errors_1.CliError('Agent prompt not available for local execution.\n\n' +
2714
2687
  'This agent may have local download disabled.\n' +
2715
2688
  'Remove the --local flag to run in the cloud:\n' +
2716
- ` orch run ${org}/${parsed.agent}@${parsed.version} --data '{"task": "..."}'`);
2689
+ ` orch run ${org}/${localAgentName}@${localVersion} --data '{"task": "..."}'`);
2717
2690
  }
2718
2691
  if (!options.input) {
2719
2692
  process.stderr.write(`\nAgent downloaded. Run with:\n`);
2720
- process.stderr.write(` orch run ${org}/${parsed.agent}@${parsed.version} --local --data '{\"task\": \"...\"}'\n`);
2693
+ process.stderr.write(` orch run ${org}/${localAgentName}@${localVersion} --local --data '{\"task\": \"...\"}'\n`);
2721
2694
  return;
2722
2695
  }
2723
2696
  // Resolve @file.json / @- stdin syntax before parsing
@@ -2731,7 +2704,7 @@ async function executeLocal(agentRef, args, options) {
2731
2704
  }
2732
2705
  warnInputSchemaErrors(agentInputData, agentData.input_schema);
2733
2706
  // Write prompt to temp dir and run
2734
- const tempAgentDir = path_1.default.join(os_1.default.tmpdir(), `orchagent-agent-${parsed.agent}-${Date.now()}`);
2707
+ const tempAgentDir = path_1.default.join(os_1.default.tmpdir(), `orchagent-agent-${localAgentName}-${Date.now()}`);
2735
2708
  await promises_1.default.mkdir(tempAgentDir, { recursive: true });
2736
2709
  try {
2737
2710
  await promises_1.default.writeFile(path_1.default.join(tempAgentDir, 'prompt.md'), agentData.prompt);
@@ -2761,7 +2734,7 @@ async function executeLocal(agentRef, args, options) {
2761
2734
  }
2762
2735
  if (choice === 'server') {
2763
2736
  process.stderr.write(`\nRun without --local for server execution:\n`);
2764
- process.stderr.write(` orch run ${org}/${parsed.agent}@${parsed.version} --data '{...}'\n\n`);
2737
+ process.stderr.write(` orch run ${org}/${localAgentName}@${localVersion} --data '{...}'\n\n`);
2765
2738
  process.exit(0);
2766
2739
  }
2767
2740
  await downloadDependenciesRecursively(resolved, depStatuses, new Set(), workspaceId);
@@ -2784,13 +2757,13 @@ async function executeLocal(agentRef, args, options) {
2784
2757
  }
2785
2758
  }
2786
2759
  // Save locally
2787
- const agentDir = await saveAgentLocally(org, parsed.agent, agentData);
2760
+ const agentDir = await saveAgentLocally(org, localAgentName, agentData);
2788
2761
  process.stderr.write(`\nAgent saved to: ${agentDir}\n`);
2789
2762
  if (localEngine === 'code_runtime') {
2790
2763
  if (agentData.has_bundle) {
2791
2764
  if (options.downloadOnly) {
2792
2765
  process.stdout.write(`\nCode runtime bundle is available for local execution.\n`);
2793
- process.stdout.write(`Run with: orch run ${org}/${parsed.agent} --local [args...]\n`);
2766
+ process.stdout.write(`Run with: orch run ${org}/${localAgentName} --local [args...]\n`);
2794
2767
  return;
2795
2768
  }
2796
2769
  // Pre-build injected payload for bundle agent if keyed files/mounts present
@@ -2806,13 +2779,13 @@ async function executeLocal(agentRef, args, options) {
2806
2779
  });
2807
2780
  bundleInput = injected.body;
2808
2781
  }
2809
- await executeBundleAgent(resolved, org, parsed.agent, parsed.version, agentData, args, bundleInput, workspaceId);
2782
+ await executeBundleAgent(resolved, org, localAgentName, localVersion, agentData, args, bundleInput, workspaceId);
2810
2783
  return;
2811
2784
  }
2812
2785
  if (agentData.run_command && (agentData.source_url || agentData.pip_package)) {
2813
2786
  if (options.downloadOnly) {
2814
2787
  process.stdout.write(`\nTool ready for local execution.\n`);
2815
- process.stdout.write(`Run with: orch run ${org}/${parsed.agent} --local [args...]\n`);
2788
+ process.stdout.write(`Run with: orch run ${org}/${localAgentName} --local [args...]\n`);
2816
2789
  return;
2817
2790
  }
2818
2791
  await executeTool(agentData, args);
@@ -2820,12 +2793,12 @@ async function executeLocal(agentRef, args, options) {
2820
2793
  }
2821
2794
  // Fallback: code runtime agent doesn't support local execution.
2822
2795
  process.stdout.write(`\nThis code runtime agent is configured for server execution.\n`);
2823
- process.stdout.write(`\nRun without --local: orch run ${org}/${parsed.agent}@${parsed.version} --data '{...}'\n`);
2796
+ process.stdout.write(`\nRun without --local: orch run ${org}/${localAgentName}@${localVersion} --data '{...}'\n`);
2824
2797
  return;
2825
2798
  }
2826
2799
  if (options.downloadOnly) {
2827
2800
  process.stdout.write(`\nAgent downloaded. Run with:\n`);
2828
- process.stdout.write(` orch run ${org}/${parsed.agent}@${parsed.version} --local --input '{...}'\n`);
2801
+ process.stdout.write(` orch run ${org}/${localAgentName}@${localVersion} --local --input '{...}'\n`);
2829
2802
  return;
2830
2803
  }
2831
2804
  // Check for keyed file/mount injection
@@ -2835,7 +2808,7 @@ async function executeLocal(agentRef, args, options) {
2835
2808
  // Direct LLM agents execute locally via prompt composition.
2836
2809
  if (!options.input && !execLocalHasInjection) {
2837
2810
  process.stdout.write(`\nAgent ready.\n`);
2838
- process.stdout.write(`Run with: orch run ${org}/${parsed.agent}@${parsed.version} --local --input '{...}'\n`);
2811
+ process.stdout.write(`Run with: orch run ${org}/${localAgentName}@${localVersion} --local --input '{...}'\n`);
2839
2812
  return;
2840
2813
  }
2841
2814
  let inputData;
@@ -162,6 +162,7 @@ function registerScheduleCommand(program) {
162
162
  .option('--alert-webhook <url>', 'Webhook URL to POST on failure (HTTPS required)')
163
163
  .option('--alert-on-failure-count <n>', 'Number of consecutive failures before alerting (default: 3)', parseInt)
164
164
  .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
165
+ .option('--json', 'Output as JSON')
165
166
  .action(async (agentArg, options) => {
166
167
  const config = await (0, config_1.getResolvedConfig)();
167
168
  if (!config.apiKey) {
@@ -217,6 +218,10 @@ function registerScheduleCommand(program) {
217
218
  body: JSON.stringify(body),
218
219
  headers: { 'Content-Type': 'application/json' },
219
220
  });
221
+ if (options.json) {
222
+ (0, output_1.printJson)(result);
223
+ return;
224
+ }
220
225
  const s = result.schedule;
221
226
  process.stdout.write(chalk_1.default.green('\u2713') + ` Schedule created\n\n`);
222
227
  process.stdout.write(` ID: ${s.id}\n`);
@@ -264,6 +269,7 @@ function registerScheduleCommand(program) {
264
269
  .option('--alert-on-failure-count <n>', 'Number of consecutive failures before alerting', parseInt)
265
270
  .option('--clear-alert-webhook', 'Remove the alert webhook URL')
266
271
  .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
272
+ .option('--json', 'Output as JSON')
267
273
  .action(async (partialScheduleId, options) => {
268
274
  const config = await (0, config_1.getResolvedConfig)();
269
275
  if (!config.apiKey) {
@@ -322,6 +328,10 @@ function registerScheduleCommand(program) {
322
328
  body: JSON.stringify(updates),
323
329
  headers: { 'Content-Type': 'application/json' },
324
330
  });
331
+ if (options.json) {
332
+ (0, output_1.printJson)(result);
333
+ return;
334
+ }
325
335
  const s = result.schedule;
326
336
  process.stdout.write(chalk_1.default.green('\u2713') + ` Schedule updated\n\n`);
327
337
  process.stdout.write(` ID: ${s.id}\n`);
@@ -349,6 +359,7 @@ function registerScheduleCommand(program) {
349
359
  .description('Delete a schedule')
350
360
  .option('-y, --yes', 'Skip confirmation prompt')
351
361
  .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
362
+ .option('--json', 'Output as JSON (implies --yes)')
352
363
  .action(async (partialScheduleId, options) => {
353
364
  const config = await (0, config_1.getResolvedConfig)();
354
365
  if (!config.apiKey) {
@@ -356,7 +367,7 @@ function registerScheduleCommand(program) {
356
367
  }
357
368
  const workspaceId = await resolveWorkspaceId(config, options.workspace);
358
369
  const scheduleId = await resolveScheduleId(config, partialScheduleId, workspaceId);
359
- if (!options.yes) {
370
+ if (!options.yes && !options.json) {
360
371
  const rl = promises_1.default.createInterface({
361
372
  input: process.stdin,
362
373
  output: process.stdout,
@@ -368,7 +379,11 @@ function registerScheduleCommand(program) {
368
379
  return;
369
380
  }
370
381
  }
371
- await (0, api_1.request)(config, 'DELETE', `/workspaces/${workspaceId}/schedules/${scheduleId}`);
382
+ const result = await (0, api_1.request)(config, 'DELETE', `/workspaces/${workspaceId}/schedules/${scheduleId}`);
383
+ if (options.json) {
384
+ (0, output_1.printJson)({ ...result, id: scheduleId });
385
+ return;
386
+ }
372
387
  process.stdout.write(chalk_1.default.green('\u2713') + ` Schedule ${scheduleId} deleted\n`);
373
388
  });
374
389
  // orch schedule trigger <schedule-id>
@@ -378,6 +393,7 @@ function registerScheduleCommand(program) {
378
393
  .option('--data <json>', 'Override input data as JSON')
379
394
  .addOption(new commander_1.Option('--input <json>').hideHelp())
380
395
  .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
396
+ .option('--json', 'Output as JSON')
381
397
  .action(async (partialScheduleId, options) => {
382
398
  const config = await (0, config_1.getResolvedConfig)();
383
399
  if (!config.apiKey) {
@@ -396,11 +412,17 @@ function registerScheduleCommand(program) {
396
412
  throw new errors_1.CliError('Invalid JSON in --data');
397
413
  }
398
414
  }
399
- process.stdout.write('Triggering schedule...\n');
415
+ if (!options.json) {
416
+ process.stdout.write('Triggering schedule...\n');
417
+ }
400
418
  const result = await (0, api_1.request)(config, 'POST', `/workspaces/${workspaceId}/schedules/${scheduleId}/trigger`, body ? {
401
419
  body,
402
420
  headers: { 'Content-Type': 'application/json' },
403
421
  } : {});
422
+ if (options.json) {
423
+ (0, output_1.printJson)(result);
424
+ return;
425
+ }
404
426
  // Status-aware header message
405
427
  const isAsync = result.status === 'queued' || result.status === 'deduplicated';
406
428
  if (isAsync) {
@@ -565,6 +587,7 @@ function registerScheduleCommand(program) {
565
587
  .command('test-alert <schedule-id>')
566
588
  .description('Send a test alert to the schedule\'s configured webhook URL')
567
589
  .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
590
+ .option('--json', 'Output as JSON')
568
591
  .action(async (partialScheduleId, options) => {
569
592
  const config = await (0, config_1.getResolvedConfig)();
570
593
  if (!config.apiKey) {
@@ -572,8 +595,14 @@ function registerScheduleCommand(program) {
572
595
  }
573
596
  const workspaceId = await resolveWorkspaceId(config, options.workspace);
574
597
  const scheduleId = await resolveScheduleId(config, partialScheduleId, workspaceId);
575
- process.stdout.write('Sending test alert...\n');
598
+ if (!options.json) {
599
+ process.stdout.write('Sending test alert...\n');
600
+ }
576
601
  const result = await (0, api_1.request)(config, 'POST', `/workspaces/${workspaceId}/schedules/${scheduleId}/test-alert`);
602
+ if (options.json) {
603
+ (0, output_1.printJson)({ ...result, schedule_id: scheduleId });
604
+ return;
605
+ }
577
606
  if (result.success) {
578
607
  process.stdout.write(chalk_1.default.green('\u2713') + ' Test alert delivered successfully\n');
579
608
  }
@@ -587,6 +616,7 @@ function registerScheduleCommand(program) {
587
616
  .description('Regenerate the webhook secret (invalidates old URL)')
588
617
  .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
589
618
  .option('-y, --yes', 'Skip confirmation prompt')
619
+ .option('--json', 'Output as JSON (implies --yes)')
590
620
  .action(async (partialScheduleId, options) => {
591
621
  const config = await (0, config_1.getResolvedConfig)();
592
622
  if (!config.apiKey) {
@@ -594,7 +624,7 @@ function registerScheduleCommand(program) {
594
624
  }
595
625
  const workspaceId = await resolveWorkspaceId(config, options.workspace);
596
626
  const scheduleId = await resolveScheduleId(config, partialScheduleId, workspaceId);
597
- if (!options.yes) {
627
+ if (!options.yes && !options.json) {
598
628
  const rl = promises_1.default.createInterface({
599
629
  input: process.stdin,
600
630
  output: process.stdout,
@@ -609,6 +639,10 @@ function registerScheduleCommand(program) {
609
639
  }
610
640
  }
611
641
  const result = await (0, api_1.request)(config, 'POST', `/workspaces/${workspaceId}/schedules/${scheduleId}/regenerate-webhook`);
642
+ if (options.json) {
643
+ (0, output_1.printJson)({ ...result, schedule_id: scheduleId });
644
+ return;
645
+ }
612
646
  process.stdout.write(chalk_1.default.green('\u2713') + ' Webhook secret regenerated\n\n');
613
647
  process.stdout.write(` ${chalk_1.default.bold('New Webhook URL')} (save this — retrieve later with ${chalk_1.default.cyan('orch schedule info --reveal')}):\n`);
614
648
  process.stdout.write(` ${result.webhook_url}\n\n`);
@@ -11,26 +11,11 @@ const chalk_1 = __importDefault(require("chalk"));
11
11
  const config_1 = require("../lib/config");
12
12
  const api_1 = require("../lib/api");
13
13
  const errors_1 = require("../lib/errors");
14
+ const resolve_agent_1 = require("../lib/resolve-agent");
14
15
  const output_1 = require("../lib/output");
15
16
  const spinner_1 = require("../lib/spinner");
16
17
  const llm_1 = require("../lib/llm");
17
18
  const analytics_1 = require("../lib/analytics");
18
- const DEFAULT_VERSION = 'latest';
19
- function parseAgentRef(value) {
20
- const [ref, versionPart] = value.split('@');
21
- const version = versionPart?.trim() || DEFAULT_VERSION;
22
- const segments = ref.split('/');
23
- if (segments.length === 1) {
24
- return { agent: segments[0], version };
25
- }
26
- if (segments.length === 2) {
27
- return { org: segments[0], agent: segments[1], version };
28
- }
29
- if (segments.length === 3) {
30
- return { org: segments[0], agent: segments[1], version: segments[2] };
31
- }
32
- throw new errors_1.CliError('Invalid agent reference. Use org/agent/version or org/agent@version format.');
33
- }
34
19
  // Severity color mapping
35
20
  function severityColor(severity) {
36
21
  switch (severity.toLowerCase()) {
@@ -189,15 +174,8 @@ Examples:
189
174
  if (!resolved.apiKey) {
190
175
  throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
191
176
  }
192
- const parsed = parseAgentRef(agentRef);
193
- const configFile = await (0, config_1.loadConfig)();
194
- const org = parsed.org ?? configFile.workspace ?? resolved.defaultOrg;
195
- if (!org) {
196
- throw new errors_1.CliError('Missing org. Use org/agent or set default org.');
197
- }
198
- const agentId = `${org}/${parsed.agent}/${parsed.version}`;
199
- // Resolve workspace context for the target org
200
- const workspaceId = await (0, api_1.resolveWorkspaceIdForOrg)(resolved, org);
177
+ const { org, agent: agentName, version, workspaceId } = await (0, resolve_agent_1.resolveAgentContext)(agentRef, resolved);
178
+ const agentId = `${org}/${agentName}/${version}`;
201
179
  // Detect LLM key for the scan
202
180
  let llmKey;
203
181
  let llmProvider;
@@ -235,6 +213,12 @@ Examples:
235
213
  if (options.maxAttacks) {
236
214
  requestBody.max_attacks = options.maxAttacks;
237
215
  }
216
+ // Send provider preference so gateway can narrow vault key search
217
+ // (even when no local key is found, the gateway resolves from vault)
218
+ const effectiveProvider = llmProvider || options.provider;
219
+ if (effectiveProvider) {
220
+ requestBody.llm_provider = effectiveProvider;
221
+ }
238
222
  const url = `${resolved.apiUrl.replace(/\/$/, '')}/security/test`;
239
223
  // Make the API call with a spinner
240
224
  const spinner = (0, spinner_1.createSpinner)(`Scanning ${agentId} for vulnerabilities...`);
@@ -8,6 +8,7 @@ const promises_1 = __importDefault(require("readline/promises"));
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
9
  const config_1 = require("../lib/config");
10
10
  const api_1 = require("../lib/api");
11
+ const agent_ref_1 = require("../lib/agent-ref");
11
12
  const errors_1 = require("../lib/errors");
12
13
  const analytics_1 = require("../lib/analytics");
13
14
  const output_1 = require("../lib/output");
@@ -34,7 +35,7 @@ async function promptText(message) {
34
35
  }
35
36
  function registerTransferCommand(program) {
36
37
  program
37
- .command('transfer <agent-name>')
38
+ .command('transfer <agent>')
38
39
  .description('Transfer an agent to another workspace')
39
40
  .requiredOption('--to <workspace-slug>', 'Target workspace slug')
40
41
  .option('-w, --workspace <workspace-slug>', 'Source workspace slug (defaults to active workspace)')
@@ -43,12 +44,13 @@ function registerTransferCommand(program) {
43
44
  .option('--json', 'Output result as JSON')
44
45
  .addHelpText('after', `
45
46
  Examples:
46
- orch transfer my-agent --to team-workspace # Transfer agent to another workspace
47
+ orch transfer my-agent --to team-workspace # Transfer by name
48
+ orch transfer my-org/my-agent --to team-workspace # Transfer using org/agent format
47
49
  orch transfer my-agent --to team-workspace --workspace my-team
48
50
  orch transfer my-agent --to team-workspace --dry-run # Preview transfer
49
51
  orch transfer my-agent --to team-workspace --yes # Skip confirmation
50
52
  `)
51
- .action(async (agentName, options) => {
53
+ .action(async (agentArg, options) => {
52
54
  const write = (message) => {
53
55
  if (!options.json)
54
56
  process.stdout.write(message);
@@ -58,6 +60,14 @@ Examples:
58
60
  if (!config.apiKey) {
59
61
  throw new errors_1.CliError('Not logged in. Run `orchagent login` first.');
60
62
  }
63
+ // Parse org/agent[@version] or bare agent name
64
+ const parsed = (0, agent_ref_1.parseAgentRef)(agentArg);
65
+ const agentName = parsed.agent;
66
+ // If org was provided via org/agent format, use it as source workspace
67
+ // --workspace flag takes precedence if both are given
68
+ if (parsed.org && options.workspace && parsed.org !== options.workspace) {
69
+ throw new errors_1.CliError(`Conflicting source workspace: '${parsed.org}' (from agent ref) vs '${options.workspace}' (from --workspace flag).`);
70
+ }
61
71
  write('Finding agent and workspaces...\n');
62
72
  // Fetch workspace list first (needed to resolve source/target IDs).
63
73
  const workspacesResponse = await (0, api_1.request)(config, 'GET', '/workspaces');
@@ -66,8 +76,8 @@ Examples:
66
76
  if (!targetWorkspace) {
67
77
  throw new errors_1.CliError(`Workspace '${options.to}' not found. Run \`orchagent workspace list\` to see available workspaces.`);
68
78
  }
69
- // Resolve source workspace (optional). If set, list agents from that workspace.
70
- const sourceWorkspaceSlug = options.workspace ?? configFile.workspace;
79
+ // Resolve source workspace: --workspace flag > org from ref > config workspace
80
+ const sourceWorkspaceSlug = options.workspace ?? parsed.org ?? configFile.workspace;
71
81
  const sourceWorkspace = sourceWorkspaceSlug
72
82
  ? workspacesResponse.workspaces.find((w) => w.slug === sourceWorkspaceSlug)
73
83
  : null;
@@ -27,9 +27,21 @@ function registerTreeCommand(program) {
27
27
  throw new errors_1.CliError('Missing org. Use org/agent format or set default org.');
28
28
  }
29
29
  const { agent, version } = parsed;
30
- // Resolve workspace context for team workspaces
31
- const workspaceId = await (0, api_1.resolveWorkspaceIdForOrg)(config, org);
32
- const tree = await (0, api_1.request)(config, 'GET', `/agents/${org}/${agent}/${version}/tree`, ...(workspaceId ? [{ headers: { 'X-Workspace-Id': workspaceId } }] : []));
30
+ // Public-first fallback: try public tree endpoint (works for any public
31
+ // agent regardless of caller context), then fall back to authenticated
32
+ // endpoint with workspace header for private agents. Matches the pattern
33
+ // used by info/fork/estimate commands. (T12-04)
34
+ let tree;
35
+ try {
36
+ tree = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${agent}/${version}/tree`);
37
+ }
38
+ catch (err) {
39
+ if (!(err instanceof api_1.ApiError) || err.status !== 404)
40
+ throw err;
41
+ // Public endpoint returned 404 — try authenticated endpoint for private agents
42
+ const workspaceId = await (0, api_1.resolveWorkspaceIdForOrg)(config, org);
43
+ tree = await (0, api_1.request)(config, 'GET', `/agents/${org}/${agent}/${version}/tree`, workspaceId ? { headers: { 'X-Workspace-Id': workspaceId } } : undefined);
44
+ }
33
45
  if (options.json) {
34
46
  console.log(JSON.stringify(tree, null, 2));
35
47
  return;
@@ -137,18 +137,23 @@ async function createCodeBundle(sourceDir, outputPath, options = {}) {
137
137
  const output = (0, fs_1.createWriteStream)(outputPath);
138
138
  const archive = (0, archiver_1.default)('zip', { zlib: { level: 9 } });
139
139
  let fileCount = 0;
140
+ const files = [];
140
141
  output.on('close', () => {
141
142
  resolve({
142
143
  path: outputPath,
143
144
  sizeBytes: archive.pointer(),
144
145
  fileCount,
146
+ files,
145
147
  });
146
148
  });
147
149
  archive.on('error', (err) => {
148
150
  reject(err);
149
151
  });
150
- archive.on('entry', () => {
152
+ archive.on('entry', (entry) => {
151
153
  fileCount++;
154
+ if (entry.name) {
155
+ files.push(entry.name);
156
+ }
152
157
  });
153
158
  archive.pipe(output);
154
159
  // Add directory contents with exclusions
@@ -235,6 +240,7 @@ async function previewBundle(sourceDir, options = {}) {
235
240
  totalSizeBytes,
236
241
  entrypoint,
237
242
  excludePatterns,
243
+ files,
238
244
  };
239
245
  }
240
246
  /**
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveOrg = resolveOrg;
4
+ exports.resolveAgentContext = resolveAgentContext;
5
+ const agent_ref_1 = require("./agent-ref");
6
+ const config_1 = require("./config");
7
+ const api_1 = require("./api");
8
+ const errors_1 = require("./errors");
9
+ /**
10
+ * Resolve the org from a parsed AgentRef using the standard fallback chain:
11
+ * 1. Explicit org from ref (org/agent@version)
12
+ * 2. Workspace from config file
13
+ * 3. defaultOrg from resolved config
14
+ *
15
+ * Throws CliError if no org can be determined.
16
+ */
17
+ async function resolveOrg(parsed, config, options) {
18
+ if (parsed.org)
19
+ return parsed.org;
20
+ const configFile = await (0, config_1.loadConfig)();
21
+ const org = configFile.workspace ?? config.defaultOrg;
22
+ if (!org) {
23
+ throw new errors_1.CliError(options?.missingOrgMessage ??
24
+ 'Missing org. Use org/agent format or set default org.');
25
+ }
26
+ return org;
27
+ }
28
+ /**
29
+ * Central agent-reference resolution pipeline used by all commands.
30
+ *
31
+ * Takes a raw agent reference string (e.g., "org/agent@v2", "agent", "agent@latest")
32
+ * and resolves it to a full context with org, agent name, version, and workspace ID.
33
+ *
34
+ * Resolution steps:
35
+ * 1. Parse the ref string into { org?, agent, version }
36
+ * 2. Resolve org via fallback chain: ref → config workspace → defaultOrg
37
+ * 3. Resolve workspace ID for team workspace context (optional)
38
+ *
39
+ * @example
40
+ * const ctx = await resolveAgentContext('acme/my-agent@v2', config)
41
+ * // ctx = { org: 'acme', agent: 'my-agent', version: 'v2', workspaceId: 'ws-123' }
42
+ *
43
+ * @example
44
+ * const ctx = await resolveAgentContext('my-agent', config)
45
+ * // ctx = { org: 'default-org', agent: 'my-agent', version: 'latest', workspaceId: undefined }
46
+ */
47
+ async function resolveAgentContext(agentRefString, config, options) {
48
+ const parsed = (0, agent_ref_1.parseAgentRef)(agentRefString);
49
+ const org = await resolveOrg(parsed, config, {
50
+ missingOrgMessage: options?.missingOrgMessage,
51
+ });
52
+ let workspaceId;
53
+ if (!options?.skipWorkspaceResolution) {
54
+ workspaceId = await (0, api_1.resolveWorkspaceIdForOrg)(config, org);
55
+ }
56
+ return {
57
+ org,
58
+ agent: parsed.agent,
59
+ version: parsed.version,
60
+ workspaceId,
61
+ };
62
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.3.100",
3
+ "version": "0.3.101",
4
4
  "description": "Command-line interface for orchagent — deploy and run AI agents for your team",
5
5
  "license": "MIT",
6
6
  "author": "orchagent <hello@orchagent.io>",