@orchagent/cli 0.3.99 → 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.
@@ -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
@@ -656,7 +645,7 @@ async function downloadAgent(config, org, agent, version, workspaceId) {
656
645
  entrypoint: targetAgent.entrypoint,
657
646
  };
658
647
  }
659
- async function downloadBundleWithFallback(config, org, agentName, version, agentId) {
648
+ async function downloadBundleWithFallback(config, org, agentName, version, agentId, workspaceId) {
660
649
  try {
661
650
  return await (0, api_1.downloadCodeBundle)(config, org, agentName, version);
662
651
  }
@@ -667,7 +656,7 @@ async function downloadBundleWithFallback(config, org, agentName, version, agent
667
656
  if (!config.apiKey || !agentId) {
668
657
  throw new api_1.ApiError(`Bundle for '${org}/${agentName}@${version}' not found`, 404);
669
658
  }
670
- return await (0, api_1.downloadCodeBundleAuthenticated)(config, agentId);
659
+ return await (0, api_1.downloadCodeBundleAuthenticated)(config, agentId, workspaceId);
671
660
  }
672
661
  async function checkDependencies(config, dependencies) {
673
662
  const results = [];
@@ -738,7 +727,7 @@ async function downloadSkillDependency(config, ref, defaultOrg) {
738
727
  const skillData = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${parsed.skill}/${parsed.version}/download`);
739
728
  await saveAgentLocally(org, parsed.skill, skillData);
740
729
  }
741
- async function downloadDependenciesRecursively(config, depStatuses, visited = new Set()) {
730
+ async function downloadDependenciesRecursively(config, depStatuses, visited = new Set(), workspaceId) {
742
731
  for (const status of depStatuses) {
743
732
  if (!status.downloadable || !status.agentData)
744
733
  continue;
@@ -750,7 +739,7 @@ async function downloadDependenciesRecursively(config, depStatuses, visited = ne
750
739
  await (0, spinner_1.withSpinner)(`Downloading dependency: ${depRef}...`, async () => {
751
740
  await saveAgentLocally(org, agent, status.agentData);
752
741
  if (status.agentData.has_bundle) {
753
- await saveBundleLocally(config, org, agent, status.dep.version, status.agentData.id);
742
+ await saveBundleLocally(config, org, agent, status.dep.version, status.agentData.id, workspaceId);
754
743
  }
755
744
  if (resolveExecutionEngine(status.agentData) === 'code_runtime' && (status.agentData.source_url || status.agentData.pip_package)) {
756
745
  await installTool(status.agentData);
@@ -767,7 +756,7 @@ async function downloadDependenciesRecursively(config, depStatuses, visited = ne
767
756
  }
768
757
  if (status.agentData.dependencies && status.agentData.dependencies.length > 0) {
769
758
  const nestedStatuses = await checkDependencies(config, status.agentData.dependencies);
770
- await downloadDependenciesRecursively(config, nestedStatuses, visited);
759
+ await downloadDependenciesRecursively(config, nestedStatuses, visited, workspaceId);
771
760
  }
772
761
  }
773
762
  }
@@ -1168,7 +1157,7 @@ async function unzipBundle(zipPath, destDir) {
1168
1157
  });
1169
1158
  });
1170
1159
  }
1171
- async function executeBundleAgent(config, org, agentName, version, agentData, args, inputOption) {
1160
+ async function executeBundleAgent(config, org, agentName, version, agentData, args, inputOption, workspaceId) {
1172
1161
  const userCwd = process.cwd();
1173
1162
  const tempDir = path_1.default.join(os_1.default.tmpdir(), `orchagent-${agentName}-${Date.now()}`);
1174
1163
  await promises_1.default.mkdir(tempDir, { recursive: true });
@@ -1176,7 +1165,7 @@ async function executeBundleAgent(config, org, agentName, version, agentData, ar
1176
1165
  const extractDir = path_1.default.join(tempDir, 'agent');
1177
1166
  try {
1178
1167
  const bundleBuffer = await (0, spinner_1.withSpinner)(`Downloading ${org}/${agentName}@${version} bundle...`, async () => {
1179
- const buffer = await downloadBundleWithFallback(config, org, agentName, version, agentData.id);
1168
+ const buffer = await downloadBundleWithFallback(config, org, agentName, version, agentData.id, workspaceId);
1180
1169
  await promises_1.default.writeFile(bundleZip, buffer);
1181
1170
  return buffer;
1182
1171
  }, { successText: (buf) => `Downloaded bundle (${buf.length} bytes)` });
@@ -1391,7 +1380,7 @@ async function saveAgentLocally(org, agent, agentData) {
1391
1380
  }
1392
1381
  return agentDir;
1393
1382
  }
1394
- async function saveBundleLocally(config, org, agent, version, agentId) {
1383
+ async function saveBundleLocally(config, org, agent, version, agentId, workspaceId) {
1395
1384
  const agentDir = path_1.default.join(AGENTS_DIR, org, agent);
1396
1385
  const bundleDir = path_1.default.join(agentDir, 'bundle');
1397
1386
  const metaPath = path_1.default.join(agentDir, 'agent.json');
@@ -1411,7 +1400,7 @@ async function saveBundleLocally(config, org, agent, version, agentId) {
1411
1400
  catch {
1412
1401
  // Metadata doesn't exist, need to download
1413
1402
  }
1414
- const bundleBuffer = await (0, spinner_1.withSpinner)(`Downloading bundle for ${org}/${agent}@${version}...`, async () => downloadBundleWithFallback(config, org, agent, version, agentId), { successText: `Downloaded bundle for ${org}/${agent}@${version}` });
1403
+ const bundleBuffer = await (0, spinner_1.withSpinner)(`Downloading bundle for ${org}/${agent}@${version}...`, async () => downloadBundleWithFallback(config, org, agent, version, agentId, workspaceId), { successText: `Downloaded bundle for ${org}/${agent}@${version}` });
1415
1404
  const tempZip = path_1.default.join(os_1.default.tmpdir(), `bundle-${Date.now()}.zip`);
1416
1405
  await promises_1.default.writeFile(tempZip, bundleBuffer);
1417
1406
  try {
@@ -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,10 +2734,10 @@ 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
- await downloadDependenciesRecursively(resolved, depStatuses);
2740
+ await downloadDependenciesRecursively(resolved, depStatuses, new Set(), workspaceId);
2768
2741
  }
2769
2742
  // Check if user is overriding locked skills
2770
2743
  const agentSkillsLocked = agentData.skills_locked;
@@ -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);
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,13 +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}`;
177
+ const { org, agent: agentName, version, workspaceId } = await (0, resolve_agent_1.resolveAgentContext)(agentRef, resolved);
178
+ const agentId = `${org}/${agentName}/${version}`;
199
179
  // Detect LLM key for the scan
200
180
  let llmKey;
201
181
  let llmProvider;
@@ -208,7 +188,13 @@ Examples:
208
188
  llmProvider = options.provider;
209
189
  }
210
190
  else {
211
- const detected = await (0, llm_1.detectLlmKey)(['any'], resolved);
191
+ // Respect --provider preference when detecting local keys
192
+ let providersToCheck = ['any'];
193
+ if (options.provider) {
194
+ (0, llm_1.validateProvider)(options.provider);
195
+ providersToCheck = [options.provider];
196
+ }
197
+ const detected = await (0, llm_1.detectLlmKey)(providersToCheck, resolved);
212
198
  if (detected) {
213
199
  llmKey = detected.key;
214
200
  llmProvider = detected.provider;
@@ -227,6 +213,12 @@ Examples:
227
213
  if (options.maxAttacks) {
228
214
  requestBody.max_attacks = options.maxAttacks;
229
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
+ }
230
222
  const url = `${resolved.apiUrl.replace(/\/$/, '')}/security/test`;
231
223
  // Make the API call with a spinner
232
224
  const spinner = (0, spinner_1.createSpinner)(`Scanning ${agentId} for vulnerabilities...`);
@@ -236,6 +228,9 @@ Examples:
236
228
  'Content-Type': 'application/json',
237
229
  Authorization: `Bearer ${resolved.apiKey}`,
238
230
  };
231
+ if (workspaceId) {
232
+ headers['X-Workspace-Id'] = workspaceId;
233
+ }
239
234
  if (llmKey) {
240
235
  headers['X-LLM-API-Key'] = llmKey;
241
236
  }
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.NO_LLM_KEY_FIXTURE_MESSAGE = void 0;
6
7
  exports.registerTestCommand = registerTestCommand;
7
8
  const promises_1 = __importDefault(require("fs/promises"));
8
9
  const path_1 = __importDefault(require("path"));
@@ -18,6 +19,15 @@ const config_1 = require("../lib/config");
18
19
  const llm_1 = require("../lib/llm");
19
20
  const bundle_1 = require("../lib/bundle");
20
21
  const test_mock_runner_1 = require("../lib/test-mock-runner");
22
+ // ─── Constants ────────────────────────────────────────────────────────────────
23
+ exports.NO_LLM_KEY_FIXTURE_MESSAGE = 'No LLM API key found for fixture tests.\n\n' +
24
+ 'Fixture tests run locally on your machine and cannot access workspace vault keys.\n' +
25
+ 'Set a local environment variable:\n\n' +
26
+ ' export OPENAI_API_KEY=sk-...\n' +
27
+ ' export ANTHROPIC_API_KEY=sk-ant-...\n' +
28
+ ' export GEMINI_API_KEY=AI...\n\n' +
29
+ 'Or add it to a .env file in your agent directory.\n\n' +
30
+ 'To run with vault keys instead, use: orch run --cloud';
21
31
  // ─── Utility functions ───────────────────────────────────────────────────────
22
32
  function validateFixture(data, fixturePath) {
23
33
  const fileName = path_1.default.basename(fixturePath);
@@ -511,11 +521,10 @@ async function runPromptFixtureTests(agentDir, fixtures, verbose, config) {
511
521
  catch {
512
522
  // Schema is optional
513
523
  }
514
- // Detect LLM key
524
+ // Detect LLM key — fixture tests run locally, so vault keys can't be used
515
525
  const detected = await (0, llm_1.detectLlmKey)(['any'], config);
516
526
  if (!detected) {
517
- throw new errors_1.CliError('No LLM key found for fixture tests.\n' +
518
- 'Set an environment variable (e.g., OPENAI_API_KEY) or run `orch secrets set <PROVIDER>_API_KEY <key>`');
527
+ throw new errors_1.CliError(exports.NO_LLM_KEY_FIXTURE_MESSAGE);
519
528
  }
520
529
  const { provider, key, model: serverModel } = detected;
521
530
  const model = serverModel ?? (0, llm_1.getDefaultModel)(provider);