@orchagent/cli 0.3.62 → 0.3.64

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.
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.extractTemplateVariables = extractTemplateVariables;
7
7
  exports.deriveInputSchema = deriveInputSchema;
8
+ exports.scanUndeclaredEnvVars = scanUndeclaredEnvVars;
9
+ exports.checkDependencies = checkDependencies;
8
10
  exports.registerPublishCommand = registerPublishCommand;
9
11
  const promises_1 = __importDefault(require("fs/promises"));
10
12
  const path_1 = __importDefault(require("path"));
@@ -14,7 +16,6 @@ const chalk_1 = __importDefault(require("chalk"));
14
16
  const config_1 = require("../lib/config");
15
17
  const api_1 = require("../lib/api");
16
18
  const errors_1 = require("../lib/errors");
17
- const api_2 = require("../lib/api");
18
19
  const analytics_1 = require("../lib/analytics");
19
20
  const bundle_1 = require("../lib/bundle");
20
21
  /**
@@ -55,6 +56,68 @@ function deriveInputSchema(variables) {
55
56
  required: [...variables],
56
57
  };
57
58
  }
59
+ /**
60
+ * Scan Python files for environment variable references and return var names
61
+ * that aren't covered by required_secrets or auto-injected by the platform.
62
+ */
63
+ async function scanUndeclaredEnvVars(agentDir, requiredSecrets) {
64
+ // Auto-injected by the gateway — never need to be in required_secrets
65
+ const autoInjected = new Set([
66
+ 'ORCHAGENT_SERVICE_KEY', 'ORCHAGENT_GATEWAY_URL', 'ORCHAGENT_CALL_CHAIN',
67
+ 'ORCHAGENT_DEADLINE_MS', 'ORCHAGENT_MAX_HOPS', 'ORCHAGENT_DOWNSTREAM_REMAINING',
68
+ 'ORCHAGENT_SDK_REQUIRED', 'ORCHAGENT_BILLING_ORG_ID', 'ORCHAGENT_ROOT_RUN_ID',
69
+ 'ORCHAGENT_REQUEST_ID',
70
+ // LLM keys injected via the platform's credential mechanism
71
+ 'ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'GEMINI_API_KEY', 'LLM_MODEL',
72
+ // Standard system env vars
73
+ 'PATH', 'HOME', 'USER', 'LANG', 'SHELL', 'TERM', 'PWD', 'TMPDIR',
74
+ ]);
75
+ const declared = new Set(requiredSecrets);
76
+ // Python env var access patterns
77
+ const patterns = [
78
+ /os\.environ\s*\[\s*['"]([A-Z][A-Z0-9_]*)['"]\s*\]/g,
79
+ /os\.environ\.get\s*\(\s*['"]([A-Z][A-Z0-9_]*)['"]/g,
80
+ /os\.getenv\s*\(\s*['"]([A-Z][A-Z0-9_]*)['"]/g,
81
+ ];
82
+ const found = new Set();
83
+ // Scan .py files in the agent directory (up to 2 levels deep)
84
+ async function scanDir(dir, depth) {
85
+ let entries;
86
+ try {
87
+ entries = await promises_1.default.readdir(dir, { withFileTypes: true });
88
+ if (!entries || !Array.isArray(entries))
89
+ return;
90
+ }
91
+ catch {
92
+ return;
93
+ }
94
+ for (const entry of entries) {
95
+ const name = entry.name;
96
+ const fullPath = path_1.default.join(dir, name);
97
+ if (entry.isDirectory() && depth < 2 && !name.startsWith('.') && name !== 'node_modules' && name !== '__pycache__' && name !== 'venv' && name !== '.venv') {
98
+ await scanDir(fullPath, depth + 1);
99
+ }
100
+ else if (entry.isFile() && name.endsWith('.py')) {
101
+ try {
102
+ const content = await promises_1.default.readFile(fullPath, 'utf-8');
103
+ for (const re of patterns) {
104
+ re.lastIndex = 0;
105
+ let m;
106
+ while ((m = re.exec(content)) !== null) { // eslint-disable-line no-cond-assign
107
+ found.add(m[1]);
108
+ }
109
+ }
110
+ }
111
+ catch {
112
+ // Skip unreadable files
113
+ }
114
+ }
115
+ }
116
+ }
117
+ await scanDir(agentDir, 0);
118
+ // Return env vars that are referenced but not declared or auto-injected
119
+ return [...found].filter(v => !declared.has(v) && !autoInjected.has(v)).sort();
120
+ }
58
121
  /**
59
122
  * Check if orchagent-sdk is listed in requirements.txt or pyproject.toml
60
123
  */
@@ -207,6 +270,56 @@ function commandForEntrypoint(entrypoint) {
207
270
  }
208
271
  return `python ${entrypoint}`;
209
272
  }
273
+ /**
274
+ * Check if manifest dependencies are published and callable.
275
+ * Best-effort: network errors cause the check to be silently skipped
276
+ * (returns empty array) to avoid false alarms.
277
+ */
278
+ async function checkDependencies(config, dependencies, publishingOrgSlug, workspaceId) {
279
+ // Pre-fetch user's agents if any deps are in the same org (one API call)
280
+ let myAgents = null;
281
+ const hasSameOrgDeps = dependencies.some(d => {
282
+ const [org] = d.id.split('/');
283
+ return org === publishingOrgSlug;
284
+ });
285
+ if (hasSameOrgDeps) {
286
+ try {
287
+ const headers = {};
288
+ if (workspaceId)
289
+ headers['X-Workspace-Id'] = workspaceId;
290
+ myAgents = await (0, api_1.request)(config, 'GET', '/agents', { headers });
291
+ }
292
+ catch {
293
+ return []; // Can't reach API — skip check entirely
294
+ }
295
+ }
296
+ return Promise.all(dependencies.map(async (dep) => {
297
+ const parts = dep.id.split('/');
298
+ const ref = `${dep.id}@${dep.version}`;
299
+ if (parts.length !== 2)
300
+ return { ref, status: 'not_found' };
301
+ const [depOrg, depName] = parts;
302
+ // Same org: check against pre-fetched agent list
303
+ if (depOrg === publishingOrgSlug && myAgents) {
304
+ const match = myAgents.find(a => a.name === depName && a.version === dep.version);
305
+ if (!match)
306
+ return { ref, status: 'not_found' };
307
+ return { ref, status: match.callable ? 'found_callable' : 'found_not_callable' };
308
+ }
309
+ // Different org: try public endpoint
310
+ try {
311
+ const agent = await (0, api_1.getPublicAgent)(config, depOrg, depName, dep.version);
312
+ return { ref, status: agent.callable ? 'found_callable' : 'found_not_callable' };
313
+ }
314
+ catch (err) {
315
+ if (err?.status === 404) {
316
+ return { ref, status: 'not_found' };
317
+ }
318
+ // Network/unexpected error — don't false alarm
319
+ return { ref, status: 'found_callable' };
320
+ }
321
+ }));
322
+ }
210
323
  function registerPublishCommand(program) {
211
324
  program
212
325
  .command('publish')
@@ -224,18 +337,33 @@ function registerPublishCommand(program) {
224
337
  : undefined;
225
338
  const config = await (0, config_1.getResolvedConfig)({}, options.profile);
226
339
  const cwd = process.cwd();
340
+ // Resolve workspace context — if `orch workspace use` was called, publish
341
+ // to that workspace instead of the personal org (F-5)
342
+ const configFile = await (0, config_1.loadConfig)();
343
+ let workspaceId;
344
+ if (configFile.workspace) {
345
+ const { workspaces } = await (0, api_1.request)(config, 'GET', '/workspaces');
346
+ const ws = workspaces.find(w => w.slug === configFile.workspace);
347
+ if (!ws) {
348
+ throw new errors_1.CliError(`Workspace '${configFile.workspace}' not found. Run \`orch workspace list\` to see available workspaces.`);
349
+ }
350
+ workspaceId = ws.id;
351
+ }
227
352
  // Check for SKILL.md first (skills take precedence)
228
353
  const skillMdPath = path_1.default.join(cwd, 'SKILL.md');
229
354
  const skillData = await parseSkillMd(skillMdPath);
230
355
  if (skillData) {
231
356
  // Publish as a skill (server auto-assigns version)
232
- const org = await (0, api_1.getOrg)(config);
357
+ const org = await (0, api_1.getOrg)(config, workspaceId);
358
+ if (workspaceId && !options.dryRun) {
359
+ process.stdout.write(`Workspace: ${org.slug}\n`);
360
+ }
233
361
  // SC-05: Collect all files in the skill directory for multi-file support
234
362
  const skillFiles = await collectSkillFiles(cwd);
235
363
  const hasMultipleFiles = skillFiles.length > 1;
236
364
  // Handle dry-run for skills
237
365
  if (options.dryRun) {
238
- const preview = await (0, api_1.previewAgentVersion)(config, skillData.frontmatter.name);
366
+ const preview = await (0, api_1.previewAgentVersion)(config, skillData.frontmatter.name, workspaceId);
239
367
  const skillBodyBytes = Buffer.byteLength(skillData.body, 'utf-8');
240
368
  const totalFilesSize = skillFiles.reduce((sum, f) => sum + f.size, 0);
241
369
  const versionInfo = preview.existing_versions.length > 0
@@ -252,6 +380,9 @@ function registerPublishCommand(program) {
252
380
  process.stderr.write('\nSkill Preview:\n');
253
381
  process.stderr.write(` Name: ${skillData.frontmatter.name}\n`);
254
382
  process.stderr.write(` Type: skill\n`);
383
+ if (workspaceId) {
384
+ process.stderr.write(` Workspace: ${org.slug}\n`);
385
+ }
255
386
  process.stderr.write(` Version: ${versionInfo}\n`);
256
387
  process.stderr.write(` Visibility: private\n`);
257
388
  process.stderr.write(` Providers: any\n`);
@@ -282,7 +413,7 @@ function registerPublishCommand(program) {
282
413
  // SC-05: Include all skill files for UI preview
283
414
  skill_files: hasMultipleFiles ? skillFiles : undefined,
284
415
  allow_local_download: options.localDownload || false,
285
- });
416
+ }, workspaceId);
286
417
  const skillVersion = skillResult.agent?.version || 'v1';
287
418
  const skillAgentId = skillResult.agent?.id;
288
419
  await (0, analytics_1.track)('cli_publish', { agent_type: 'skill', multi_file: hasMultipleFiles });
@@ -511,8 +642,11 @@ function registerPublishCommand(program) {
511
642
  if (options.docker && executionEngine !== 'code_runtime') {
512
643
  throw new errors_1.CliError('--docker is only supported for code runtime agents');
513
644
  }
514
- // Get org info
515
- const org = await (0, api_1.getOrg)(config);
645
+ // Get org info (workspace-aware — returns workspace org if workspace is active)
646
+ const org = await (0, api_1.getOrg)(config, workspaceId);
647
+ if (workspaceId && !options.dryRun) {
648
+ process.stdout.write(`Workspace: ${org.slug}\n`);
649
+ }
516
650
  // Default to 'any' provider if not specified
517
651
  const supportedProviders = manifest.supported_providers || ['any'];
518
652
  // Detect SDK compatibility for code runtime agents
@@ -523,9 +657,33 @@ function registerPublishCommand(program) {
523
657
  process.stdout.write(`SDK detected - agent will be marked as Local Ready\n`);
524
658
  }
525
659
  }
660
+ // Check if manifest dependencies are published and callable (F-9b).
661
+ // Runs for both dry-run and normal publish so users catch issues early.
662
+ const manifestDeps = manifest.manifest?.dependencies;
663
+ if (manifestDeps?.length) {
664
+ const depResults = await checkDependencies(config, manifestDeps, org.slug, workspaceId);
665
+ const notFound = depResults.filter(r => r.status === 'not_found');
666
+ const notCallable = depResults.filter(r => r.status === 'found_not_callable');
667
+ if (notFound.length > 0) {
668
+ process.stderr.write(chalk_1.default.yellow(`\n⚠ Unpublished dependencies:\n`));
669
+ for (const dep of notFound) {
670
+ process.stderr.write(chalk_1.default.yellow(` - ${dep.ref}\n`));
671
+ }
672
+ process.stderr.write(`\n These agents must be published before this orchestrator can call them.\n` +
673
+ ` Publish each dependency first, then re-run this publish.\n\n`);
674
+ }
675
+ if (notCallable.length > 0) {
676
+ process.stderr.write(chalk_1.default.yellow(`\n⚠ Dependencies not marked as callable:\n`));
677
+ for (const dep of notCallable) {
678
+ process.stderr.write(chalk_1.default.yellow(` - ${dep.ref}\n`));
679
+ }
680
+ process.stderr.write(`\n Agents must have callable: true in orchagent.json to be invoked\n` +
681
+ ` by orchestrators. Update and republish each dependency.\n\n`);
682
+ }
683
+ }
526
684
  // Handle dry-run for agents
527
685
  if (options.dryRun) {
528
- const preview = await (0, api_1.previewAgentVersion)(config, manifest.name);
686
+ const preview = await (0, api_1.previewAgentVersion)(config, manifest.name, workspaceId);
529
687
  const versionInfo = preview.existing_versions.length > 0
530
688
  ? `${preview.next_version} (new version, ${preview.existing_versions[preview.existing_versions.length - 1]} exists)`
531
689
  : `${preview.next_version} (first version)`;
@@ -578,6 +736,9 @@ function registerPublishCommand(program) {
578
736
  process.stderr.write('\nAgent Preview:\n');
579
737
  process.stderr.write(` Name: ${manifest.name}\n`);
580
738
  process.stderr.write(` Type: ${canonicalType}\n`);
739
+ if (workspaceId) {
740
+ process.stderr.write(` Workspace: ${org.slug}\n`);
741
+ }
581
742
  process.stderr.write(` Run mode: ${runMode}\n`);
582
743
  process.stderr.write(` Engine: ${executionEngine}${shouldUploadBundle ? ' (hosted)' : ''}\n`);
583
744
  process.stderr.write(` Callable: ${callable ? 'enabled' : 'disabled'}\n`);
@@ -605,6 +766,26 @@ function registerPublishCommand(program) {
605
766
  process.stderr.write('No changes made (dry run)\n');
606
767
  return;
607
768
  }
769
+ // Warn if ORCHAGENT_SERVICE_KEY is in required_secrets — the gateway
770
+ // auto-injects it for agents with manifest dependencies (F-12).
771
+ if (manifest.required_secrets?.includes('ORCHAGENT_SERVICE_KEY')) {
772
+ process.stderr.write('\n⚠ Warning: ORCHAGENT_SERVICE_KEY found in required_secrets.\n' +
773
+ ' The gateway auto-injects this for agents with manifest dependencies.\n' +
774
+ ' Having it in required_secrets can override the auto-injected key and\n' +
775
+ ' break orchestration. Remove it from required_secrets in orchagent.json.\n\n');
776
+ }
777
+ // Scan code for env var references not covered by required_secrets (F-1a).
778
+ // Only relevant for agents with code (code_runtime engine).
779
+ if (executionEngine === 'code_runtime') {
780
+ const undeclared = await scanUndeclaredEnvVars(cwd, manifest.required_secrets || []);
781
+ if (undeclared.length > 0) {
782
+ process.stderr.write(chalk_1.default.yellow(`\n⚠ Your code references environment variables not in required_secrets:\n`) +
783
+ chalk_1.default.yellow(` ${undeclared.join(', ')}\n\n`) +
784
+ ` If these should be workspace secrets, add them to required_secrets\n` +
785
+ ` in orchagent.json so they're available in the sandbox at runtime.\n` +
786
+ ` (Platform-injected vars like LLM API keys are already excluded.)\n\n`);
787
+ }
788
+ }
608
789
  // Create the agent (server auto-assigns version)
609
790
  let result;
610
791
  try {
@@ -636,11 +817,11 @@ function registerPublishCommand(program) {
636
817
  default_skills: skillsFromFlag || manifest.default_skills,
637
818
  skills_locked: manifest.skills_locked || options.skillsLocked || undefined,
638
819
  allow_local_download: options.localDownload || false,
639
- });
820
+ }, workspaceId);
640
821
  }
641
822
  catch (err) {
642
823
  // Improve SECURITY_BLOCKED error display
643
- if (err instanceof api_2.ApiError && err.status === 422) {
824
+ if (err instanceof api_1.ApiError && err.status === 422) {
644
825
  const payload = err.payload;
645
826
  const errorCode = payload?.error?.code;
646
827
  if (errorCode === 'SECURITY_BLOCKED') {
@@ -772,6 +953,22 @@ function registerPublishCommand(program) {
772
953
  process.stdout.write(`\nService key (save this - shown only once):\n`);
773
954
  process.stdout.write(` ${result.service_key}\n`);
774
955
  }
956
+ // Show next-step CLI command based on run mode
957
+ const runRef = `${org.slug}/${manifest.name}`;
958
+ if (runMode === 'always_on') {
959
+ process.stdout.write(`\nDeploy as service:\n`);
960
+ process.stdout.write(` orch service deploy ${runRef}\n`);
961
+ }
962
+ else {
963
+ const schemaProps = inputSchema && typeof inputSchema === 'object' && 'properties' in inputSchema
964
+ ? Object.keys(inputSchema.properties).slice(0, 3)
965
+ : null;
966
+ const exampleFields = schemaProps?.length
967
+ ? schemaProps.map(k => `"${k}": "..."`).join(', ')
968
+ : '"input": "..."';
969
+ process.stdout.write(`\nRun with CLI:\n`);
970
+ process.stdout.write(` orch run ${runRef} --data '{${exampleFields}}'\n`);
971
+ }
775
972
  process.stdout.write(`\nAPI endpoint:\n`);
776
973
  process.stdout.write(` POST ${config.apiUrl}/${org.slug}/${manifest.name}/${assignedVersion}/run\n`);
777
974
  if (shouldUploadBundle) {
@@ -46,6 +46,7 @@ const path_1 = __importDefault(require("path"));
46
46
  const os_1 = __importDefault(require("os"));
47
47
  const child_process_1 = require("child_process");
48
48
  const chalk_1 = __importDefault(require("chalk"));
49
+ const dotenv_1 = require("../lib/dotenv");
49
50
  const config_1 = require("../lib/config");
50
51
  const api_1 = require("../lib/api");
51
52
  const errors_1 = require("../lib/errors");
@@ -1303,6 +1304,17 @@ async function executeLocalFromDir(dirPath, args, options) {
1303
1304
  runtime: manifest.runtime || null,
1304
1305
  loop: manifest.loop || null,
1305
1306
  });
1307
+ // Load .env from agent directory (existing env vars take precedence)
1308
+ const dotEnvVars = await (0, dotenv_1.loadDotEnv)(resolved);
1309
+ const dotEnvCount = Object.keys(dotEnvVars).length;
1310
+ if (dotEnvCount > 0) {
1311
+ for (const [key, value] of Object.entries(dotEnvVars)) {
1312
+ if (!(key in process.env) || process.env[key] === undefined) {
1313
+ process.env[key] = value;
1314
+ }
1315
+ }
1316
+ process.stderr.write(chalk_1.default.gray(`Loaded ${dotEnvCount} variable${dotEnvCount === 1 ? '' : 's'} from .env\n`));
1317
+ }
1306
1318
  if (localType === 'skill') {
1307
1319
  throw new errors_1.CliError('Skills cannot be run directly.\n\n' +
1308
1320
  'Skills are instructions meant to be injected into AI agent contexts.\n' +
@@ -1908,7 +1920,8 @@ async function executeCloud(agentRef, file, options) {
1908
1920
  sourceLabel = multipart.sourceLabel;
1909
1921
  }
1910
1922
  } // end of non-injection path
1911
- const url = `${resolved.apiUrl.replace(/\/$/, '')}/${org}/${parsed.agent}/${parsed.version}/${endpoint}`;
1923
+ const verboseQs = options.verbose ? '?verbose=true' : '';
1924
+ const url = `${resolved.apiUrl.replace(/\/$/, '')}/${org}/${parsed.agent}/${parsed.version}/${endpoint}${verboseQs}`;
1912
1925
  // Enable SSE streaming for managed-loop agents (unless --json or --no-stream or --output)
1913
1926
  const isManagedLoopAgent = cloudType === 'agent' && cloudEngine === 'managed_loop';
1914
1927
  const wantStream = isManagedLoopAgent && !options.json && !options.noStream && !options.output;
@@ -1985,14 +1998,39 @@ async function executeCloud(agentRef, file, options) {
1985
1998
  payload.message ||
1986
1999
  response.statusText
1987
2000
  : response.statusText;
2001
+ const requestId = typeof payload === 'object' && payload
2002
+ ? payload.metadata?.request_id
2003
+ : undefined;
2004
+ const refSuffix = requestId ? `\n\nref: ${requestId}` : '';
2005
+ if (errorCode === 'SANDBOX_ERROR') {
2006
+ spinner?.fail('Agent execution failed');
2007
+ const hint = typeof payload === 'object' && payload
2008
+ ? payload.error?.hint
2009
+ : undefined;
2010
+ throw new errors_1.CliError(`${message}\n\n` +
2011
+ `This is an error in the agent's code, not the platform.\n` +
2012
+ `Check the agent code and requirements, then republish.` +
2013
+ (hint ? `\n\nHint: ${hint}` : '') +
2014
+ refSuffix);
2015
+ }
2016
+ if (errorCode === 'SANDBOX_TIMEOUT') {
2017
+ spinner?.fail('Agent timed out');
2018
+ throw new errors_1.CliError(`${message}\n\n` +
2019
+ `The agent did not complete in time. Try:\n` +
2020
+ ` - Simplifying the input\n` +
2021
+ ` - Using a smaller dataset\n` +
2022
+ ` - Contacting the agent author to increase the timeout` +
2023
+ refSuffix);
2024
+ }
1988
2025
  if (response.status >= 500) {
1989
2026
  spinner?.fail(`Server error (${response.status})`);
1990
2027
  throw new errors_1.CliError(`${message}\n\n` +
1991
- `This is a server-side error. Try again in a moment.\n` +
1992
- `If it persists, check the dashboard for run logs or try a different provider.`);
2028
+ `This is a platform error try again in a moment.\n` +
2029
+ `If it persists, contact support.` +
2030
+ refSuffix);
1993
2031
  }
1994
2032
  spinner?.fail(`Run failed: ${message}`);
1995
- throw new errors_1.CliError(message);
2033
+ throw new errors_1.CliError(message + refSuffix);
1996
2034
  }
1997
2035
  // Handle SSE streaming response
1998
2036
  const contentType = response.headers?.get?.('content-type') || '';
@@ -2059,6 +2097,9 @@ async function executeCloud(agentRef, file, options) {
2059
2097
  const total = (usage.input_tokens || 0) + (usage.output_tokens || 0);
2060
2098
  parts.push(`${total.toLocaleString()} tokens (${(usage.input_tokens || 0).toLocaleString()} in, ${(usage.output_tokens || 0).toLocaleString()} out)`);
2061
2099
  }
2100
+ if (typeof meta.request_id === 'string') {
2101
+ parts.push(`ref: ${meta.request_id}`);
2102
+ }
2062
2103
  if (parts.length > 0) {
2063
2104
  process.stderr.write(chalk_1.default.gray(`${parts.join(' · ')}\n`));
2064
2105
  }
@@ -2118,6 +2159,20 @@ async function executeCloud(agentRef, file, options) {
2118
2159
  if (typeof payload === 'object' && payload !== null && 'metadata' in payload) {
2119
2160
  const meta = payload.metadata;
2120
2161
  if (meta) {
2162
+ // Show sandbox output when --verbose
2163
+ if (options.verbose) {
2164
+ const stderr = meta.stderr;
2165
+ const stdout = meta.stdout;
2166
+ if (stderr) {
2167
+ process.stderr.write(chalk_1.default.bold.yellow('\n--- stderr ---') + '\n' + stderr + '\n');
2168
+ }
2169
+ if (stdout) {
2170
+ process.stderr.write(chalk_1.default.bold.cyan('\n--- stdout ---') + '\n' + stdout + '\n');
2171
+ }
2172
+ if (!stderr && !stdout) {
2173
+ process.stderr.write(chalk_1.default.gray('\nNo sandbox output captured.\n'));
2174
+ }
2175
+ }
2121
2176
  const parts = [];
2122
2177
  if (typeof meta.processing_time_ms === 'number') {
2123
2178
  parts.push(`${(meta.processing_time_ms / 1000).toFixed(1)}s total`);
@@ -2130,6 +2185,9 @@ async function executeCloud(agentRef, file, options) {
2130
2185
  const total = (usage.input_tokens || 0) + (usage.output_tokens || 0);
2131
2186
  parts.push(`${total.toLocaleString()} tokens (${(usage.input_tokens || 0).toLocaleString()} in, ${(usage.output_tokens || 0).toLocaleString()} out)`);
2132
2187
  }
2188
+ if (typeof meta.request_id === 'string') {
2189
+ parts.push(`ref: ${meta.request_id}`);
2190
+ }
2133
2191
  if (parts.length > 0) {
2134
2192
  process.stderr.write(chalk_1.default.gray(`\n${parts.join(' · ')}\n`));
2135
2193
  }
@@ -2372,6 +2430,7 @@ function registerRunCommand(program) {
2372
2430
  .option('--data <json>', 'JSON payload (string or @file, @- for stdin)')
2373
2431
  .option('--input <json>', 'Alias for --data')
2374
2432
  .option('--json', 'Output raw JSON')
2433
+ .option('--verbose', 'Show sandbox stdout/stderr output (cloud only)')
2375
2434
  .option('--provider <provider>', 'LLM provider (openai, anthropic, gemini, ollama)')
2376
2435
  .option('--model <model>', 'LLM model to use (overrides agent default)')
2377
2436
  .option('--key <key>', 'LLM API key (overrides env vars)')
@@ -101,9 +101,12 @@ function registerScheduleCommand(program) {
101
101
  const failsLabel = s.consecutive_failures > 0
102
102
  ? chalk_1.default.red(String(s.consecutive_failures))
103
103
  : chalk_1.default.gray('0');
104
+ const agentLabel = s.auto_update === false
105
+ ? `${s.agent_name}@${s.agent_version} ${chalk_1.default.yellow('[pinned]')}`
106
+ : `${s.agent_name}@${s.agent_version}`;
104
107
  table.push([
105
108
  s.id.slice(0, 8),
106
- `${s.agent_name}@${s.agent_version}`,
109
+ agentLabel,
107
110
  s.schedule_type,
108
111
  s.schedule_type === 'cron' ? (s.cron_expression ?? '-') : 'webhook',
109
112
  enabledLabel,
@@ -125,6 +128,7 @@ function registerScheduleCommand(program) {
125
128
  .option('--timezone <tz>', 'Timezone for cron schedule (default: UTC)', 'UTC')
126
129
  .option('--input <json>', 'Input data as JSON string')
127
130
  .option('--provider <provider>', 'LLM provider (anthropic, openai, gemini)')
131
+ .option('--pin-version', 'Pin to this version (disable auto-update on publish)')
128
132
  .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
129
133
  .action(async (agentArg, options) => {
130
134
  const config = await (0, config_1.getResolvedConfig)();
@@ -165,6 +169,8 @@ function registerScheduleCommand(program) {
165
169
  body.input_data = inputData;
166
170
  if (options.provider)
167
171
  body.llm_provider = options.provider;
172
+ if (options.pinVersion)
173
+ body.auto_update = false;
168
174
  const result = await (0, api_1.request)(config, 'POST', `/workspaces/${workspaceId}/schedules`, {
169
175
  body: JSON.stringify(body),
170
176
  headers: { 'Content-Type': 'application/json' },
@@ -199,6 +205,8 @@ function registerScheduleCommand(program) {
199
205
  .option('--provider <provider>', 'New LLM provider')
200
206
  .option('--enable', 'Enable the schedule')
201
207
  .option('--disable', 'Disable the schedule')
208
+ .option('--auto-update', 'Enable auto-update on publish')
209
+ .option('--pin-version', 'Pin to current version (disable auto-update)')
202
210
  .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
203
211
  .action(async (scheduleId, options) => {
204
212
  const config = await (0, config_1.getResolvedConfig)();
@@ -208,6 +216,9 @@ function registerScheduleCommand(program) {
208
216
  if (options.enable && options.disable) {
209
217
  throw new errors_1.CliError('Cannot use both --enable and --disable');
210
218
  }
219
+ if (options.autoUpdate && options.pinVersion) {
220
+ throw new errors_1.CliError('Cannot use both --auto-update and --pin-version');
221
+ }
211
222
  const workspaceId = await resolveWorkspaceId(config, options.workspace);
212
223
  const updates = {};
213
224
  if (options.cron)
@@ -220,6 +231,10 @@ function registerScheduleCommand(program) {
220
231
  updates.enabled = true;
221
232
  if (options.disable)
222
233
  updates.enabled = false;
234
+ if (options.autoUpdate)
235
+ updates.auto_update = true;
236
+ if (options.pinVersion)
237
+ updates.auto_update = false;
223
238
  if (options.input) {
224
239
  try {
225
240
  updates.input_data = JSON.parse(options.input);
@@ -333,6 +348,7 @@ function registerScheduleCommand(program) {
333
348
  process.stdout.write(` Timezone: ${s.timezone}\n`);
334
349
  }
335
350
  process.stdout.write(` Enabled: ${s.enabled ? chalk_1.default.green('yes') : chalk_1.default.red('no')}\n`);
351
+ process.stdout.write(` Auto-update: ${s.auto_update === false ? chalk_1.default.yellow('pinned') : chalk_1.default.green('yes')}\n`);
336
352
  if (s.auto_disabled_at) {
337
353
  process.stdout.write(` ${chalk_1.default.bgRed.white(' AUTO-DISABLED ')} at ${formatDate(s.auto_disabled_at)}\n`);
338
354
  }