@orchagent/cli 0.3.61 → 0.3.63

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.
@@ -226,13 +226,10 @@ async function importFromGitHub(config, repo, options) {
226
226
  selectedPath = selection.path;
227
227
  }
228
228
  }
229
- // Determine visibility (default to public)
230
- const isPublic = options.private ? false : true;
231
229
  const importResult = await (0, api_1.request)(config, 'POST', '/github/import', {
232
230
  body: JSON.stringify({
233
231
  repo,
234
232
  path: selectedPath,
235
- is_public: isPublic,
236
233
  name: options.name,
237
234
  }),
238
235
  headers: { 'Content-Type': 'application/json' },
@@ -240,7 +237,6 @@ async function importFromGitHub(config, repo, options) {
240
237
  await (0, analytics_1.track)('cli_github_import', {
241
238
  repo,
242
239
  path: selectedPath,
243
- is_public: isPublic,
244
240
  type: importResult.agent.type,
245
241
  });
246
242
  if (options.json) {
@@ -253,7 +249,6 @@ async function importFromGitHub(config, repo, options) {
253
249
  process.stdout.write(` Agent: ${importResult.agent.name}\n`);
254
250
  process.stdout.write(` Version: ${importResult.agent.version}\n`);
255
251
  process.stdout.write(` Type: ${importResult.agent.type}\n`);
256
- process.stdout.write(` Public: ${isPublic ? chalk_1.default.green('Yes') : chalk_1.default.yellow('No')}\n`);
257
252
  process.stdout.write('\n');
258
253
  }
259
254
  async function getSyncConfig(config, agentId, options) {
@@ -335,8 +330,6 @@ function registerGitHubCommand(program) {
335
330
  .command('import <repo>')
336
331
  .description('Import an agent or skill from GitHub')
337
332
  .option('--path <path>', 'Path to manifest within repo (scans if not specified)')
338
- .option('--public', 'Make the agent public (default)')
339
- .option('--private', 'Make the agent private')
340
333
  .option('--name <name>', 'Override agent name')
341
334
  .option('--json', 'Output raw JSON')
342
335
  .action(async (repo, options) => {
@@ -33,6 +33,7 @@ const schedule_1 = require("./schedule");
33
33
  const service_1 = require("./service");
34
34
  const transfer_1 = require("./transfer");
35
35
  const pull_1 = require("./pull");
36
+ const logs_1 = require("./logs");
36
37
  function registerCommands(program) {
37
38
  (0, login_1.registerLoginCommand)(program);
38
39
  (0, whoami_1.registerWhoamiCommand)(program);
@@ -66,4 +67,5 @@ function registerCommands(program) {
66
67
  (0, service_1.registerServiceCommand)(program);
67
68
  (0, transfer_1.registerTransferCommand)(program);
68
69
  (0, pull_1.registerPullCommand)(program);
70
+ (0, logs_1.registerLogsCommand)(program);
69
71
  }
@@ -60,6 +60,7 @@ async function getAgentInfo(config, org, agent, version) {
60
60
  version: publicMeta.version,
61
61
  description: (publicMeta.description ?? undefined),
62
62
  supported_providers: publicMeta.supported_providers || ['any'],
63
+ callable: publicMeta.callable ?? false,
63
64
  input_schema: publicMeta.input_schema,
64
65
  output_schema: publicMeta.output_schema,
65
66
  source_url: meta.source_url,
@@ -103,6 +104,7 @@ async function getAgentInfo(config, org, agent, version) {
103
104
  version: targetAgent.version,
104
105
  description: targetAgent.description,
105
106
  prompt: targetAgent.prompt,
107
+ callable: targetAgent.callable ?? false,
106
108
  input_schema: targetAgent.input_schema,
107
109
  output_schema: targetAgent.output_schema,
108
110
  supported_providers: targetAgent.supported_providers || ['any'],
@@ -140,6 +142,9 @@ function registerInfoCommand(program) {
140
142
  process.stdout.write(`${agentData.description}\n\n`);
141
143
  }
142
144
  process.stdout.write(`Type: ${agentData.type}\n`);
145
+ if (agentData.callable) {
146
+ process.stdout.write(`Callable: ${chalk_1.default.green('yes')} — other agents can invoke this via the orchagent SDK\n`);
147
+ }
143
148
  process.stdout.write(`Providers: ${agentData.supported_providers.join(', ')}\n`);
144
149
  // Display pricing information
145
150
  const priceStr = (0, pricing_1.formatPrice)(agentData);
@@ -72,6 +72,9 @@ def main():
72
72
  user_input = data.get("input", "")
73
73
 
74
74
  # --- Your logic here ---
75
+ # To use workspace secrets, add them to "required_secrets" in orchagent.json:
76
+ # "required_secrets": ["MY_API_KEY"]
77
+ # Then access via: os.environ["MY_API_KEY"]
75
78
  result = f"Received: {user_input}"
76
79
  # --- End your logic ---
77
80
 
@@ -256,10 +259,12 @@ function registerInitCommand(program) {
256
259
  manifest.description = 'An AI agent with tool use';
257
260
  manifest.supported_providers = ['anthropic'];
258
261
  manifest.loop = { max_turns: 25 };
262
+ manifest.required_secrets = [];
259
263
  }
260
264
  else if (initMode.flavor === 'code_runtime') {
261
265
  manifest.description = 'A code-runtime agent';
262
266
  manifest.runtime = { command: 'python main.py' };
267
+ manifest.required_secrets = [];
263
268
  }
264
269
  await promises_1.default.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
265
270
  if (initMode.flavor === 'code_runtime') {
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerLogsCommand = registerLogsCommand;
7
+ const cli_table3_1 = __importDefault(require("cli-table3"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const config_1 = require("../lib/config");
10
+ const api_1 = require("../lib/api");
11
+ const errors_1 = require("../lib/errors");
12
+ const output_1 = require("../lib/output");
13
+ // ============================================
14
+ // HELPERS
15
+ // ============================================
16
+ async function resolveWorkspaceId(config, slug) {
17
+ const configFile = await (0, config_1.loadConfig)();
18
+ const targetSlug = slug ?? configFile.workspace;
19
+ if (!targetSlug) {
20
+ throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orch workspace use <slug>` first.');
21
+ }
22
+ const response = await (0, api_1.request)(config, 'GET', '/workspaces');
23
+ const workspace = response.workspaces.find((w) => w.slug === targetSlug);
24
+ if (!workspace) {
25
+ throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
26
+ }
27
+ return workspace.id;
28
+ }
29
+ function formatDate(iso) {
30
+ if (!iso)
31
+ return '-';
32
+ return new Date(iso).toLocaleString();
33
+ }
34
+ function statusColor(status) {
35
+ if (!status)
36
+ return '-';
37
+ switch (status) {
38
+ case 'completed':
39
+ return chalk_1.default.green(status);
40
+ case 'failed':
41
+ return chalk_1.default.red(status);
42
+ case 'running':
43
+ return chalk_1.default.yellow(status);
44
+ case 'timeout':
45
+ return chalk_1.default.red(status);
46
+ default:
47
+ return status;
48
+ }
49
+ }
50
+ function formatDuration(ms) {
51
+ if (ms == null)
52
+ return '-';
53
+ if (ms < 1000)
54
+ return `${ms}ms`;
55
+ if (ms < 60000)
56
+ return `${(ms / 1000).toFixed(1)}s`;
57
+ return `${(ms / 60000).toFixed(1)}m`;
58
+ }
59
+ /** Detect if a string looks like a UUID (run ID) */
60
+ function isUuid(value) {
61
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
62
+ }
63
+ // ============================================
64
+ // COMMAND REGISTRATION
65
+ // ============================================
66
+ function registerLogsCommand(program) {
67
+ program
68
+ .command('logs [target]')
69
+ .description('View execution logs. Use with no args to list recent runs, an agent name to filter, or a run ID for full detail.')
70
+ .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
71
+ .option('--status <status>', 'Filter by status: running, completed, failed, timeout')
72
+ .option('--limit <n>', 'Number of runs to show (default: 20)', '20')
73
+ .option('--json', 'Output as JSON')
74
+ .action(async (target, options) => {
75
+ const config = await (0, config_1.getResolvedConfig)();
76
+ if (!config.apiKey) {
77
+ throw new errors_1.CliError('Missing API key. Run `orch login` first.');
78
+ }
79
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
80
+ // If target looks like a UUID, show detailed logs for that run
81
+ if (target && isUuid(target)) {
82
+ await showRunLogs(config, workspaceId, target, options.json);
83
+ return;
84
+ }
85
+ // Otherwise list runs, optionally filtered by agent name
86
+ await listRuns(config, workspaceId, target, options);
87
+ });
88
+ }
89
+ // ============================================
90
+ // LIST RUNS
91
+ // ============================================
92
+ async function listRuns(config, workspaceId, agentName, options) {
93
+ const params = new URLSearchParams();
94
+ if (agentName)
95
+ params.set('agent_name', agentName);
96
+ if (options.status)
97
+ params.set('status', options.status);
98
+ const limit = parseInt(options.limit ?? '20', 10);
99
+ params.set('limit', String(Math.min(Math.max(1, limit), 200)));
100
+ const qs = params.toString() ? `?${params.toString()}` : '';
101
+ const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/runs${qs}`);
102
+ if (options.json) {
103
+ (0, output_1.printJson)(result);
104
+ return;
105
+ }
106
+ if (result.runs.length === 0) {
107
+ if (agentName) {
108
+ process.stdout.write(`No runs found for agent '${agentName}'.\n`);
109
+ }
110
+ else {
111
+ process.stdout.write('No runs found in this workspace.\n');
112
+ }
113
+ return;
114
+ }
115
+ const table = new cli_table3_1.default({
116
+ head: [
117
+ chalk_1.default.bold('Run ID'),
118
+ chalk_1.default.bold('Agent'),
119
+ chalk_1.default.bold('Status'),
120
+ chalk_1.default.bold('Duration'),
121
+ chalk_1.default.bold('Source'),
122
+ chalk_1.default.bold('Started'),
123
+ chalk_1.default.bold('Error'),
124
+ ],
125
+ });
126
+ result.runs.forEach((r) => {
127
+ const errorPreview = r.error_message
128
+ ? chalk_1.default.red(r.error_message.length > 50 ? r.error_message.slice(0, 50) + '...' : r.error_message)
129
+ : chalk_1.default.gray('-');
130
+ table.push([
131
+ r.id.slice(0, 8),
132
+ `${r.agent_name}@${r.agent_version}`,
133
+ statusColor(r.status),
134
+ formatDuration(r.duration_ms),
135
+ r.trigger_source ?? '-',
136
+ formatDate(r.started_at || r.created_at),
137
+ errorPreview,
138
+ ]);
139
+ });
140
+ process.stdout.write(table.toString() + '\n');
141
+ if (result.total > result.runs.length) {
142
+ process.stdout.write(chalk_1.default.gray(`\nShowing ${result.runs.length} of ${result.total} runs. Use --limit to see more.\n`));
143
+ }
144
+ process.stdout.write(chalk_1.default.gray('\nView detailed logs for a run: orch logs <run-id>\n'));
145
+ }
146
+ // ============================================
147
+ // SHOW RUN LOGS
148
+ // ============================================
149
+ async function showRunLogs(config, workspaceId, runId, json) {
150
+ const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/runs/${runId}/logs`);
151
+ if (json) {
152
+ (0, output_1.printJson)(result);
153
+ return;
154
+ }
155
+ // Header
156
+ process.stdout.write(chalk_1.default.bold(`\nRun ${runId}\n`) +
157
+ `Agent: ${result.agent_name ?? '-'}@${result.agent_version ?? '-'}\n` +
158
+ `Status: ${statusColor(result.run_status)}\n` +
159
+ `Duration: ${formatDuration(result.execution_time_ms)}\n`);
160
+ if (result.exit_code != null) {
161
+ const exitLabel = result.exit_code === 0 ? chalk_1.default.green(String(result.exit_code)) : chalk_1.default.red(String(result.exit_code));
162
+ process.stdout.write(`Exit code: ${exitLabel}\n`);
163
+ }
164
+ // Error message
165
+ if (result.error_message) {
166
+ process.stdout.write('\n' + chalk_1.default.red.bold('Error:\n') + chalk_1.default.red(result.error_message) + '\n');
167
+ }
168
+ // Stdout
169
+ if (result.stdout) {
170
+ process.stdout.write('\n' + chalk_1.default.bold.cyan('--- stdout ---') + '\n' + result.stdout + '\n');
171
+ }
172
+ // Stderr
173
+ if (result.stderr) {
174
+ process.stdout.write('\n' + chalk_1.default.bold.yellow('--- stderr ---') + '\n' + result.stderr + '\n');
175
+ }
176
+ // No execution log available
177
+ if (!result.has_execution_log && !result.error_message) {
178
+ process.stdout.write(chalk_1.default.gray('\nNo sandbox output available for this run. ' +
179
+ 'Execution logs are captured for agents with a code runtime (tool/agent types with runtime.command).\n'));
180
+ }
181
+ process.stdout.write('\n');
182
+ }
@@ -5,6 +5,7 @@ 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;
8
9
  exports.registerPublishCommand = registerPublishCommand;
9
10
  const promises_1 = __importDefault(require("fs/promises"));
10
11
  const path_1 = __importDefault(require("path"));
@@ -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
  */
@@ -605,6 +668,26 @@ function registerPublishCommand(program) {
605
668
  process.stderr.write('No changes made (dry run)\n');
606
669
  return;
607
670
  }
671
+ // Warn if ORCHAGENT_SERVICE_KEY is in required_secrets — the gateway
672
+ // auto-injects it for agents with manifest dependencies (F-12).
673
+ if (manifest.required_secrets?.includes('ORCHAGENT_SERVICE_KEY')) {
674
+ process.stderr.write('\n⚠ Warning: ORCHAGENT_SERVICE_KEY found in required_secrets.\n' +
675
+ ' The gateway auto-injects this for agents with manifest dependencies.\n' +
676
+ ' Having it in required_secrets can override the auto-injected key and\n' +
677
+ ' break orchestration. Remove it from required_secrets in orchagent.json.\n\n');
678
+ }
679
+ // Scan code for env var references not covered by required_secrets (F-1a).
680
+ // Only relevant for agents with code (code_runtime engine).
681
+ if (executionEngine === 'code_runtime') {
682
+ const undeclared = await scanUndeclaredEnvVars(cwd, manifest.required_secrets || []);
683
+ if (undeclared.length > 0) {
684
+ process.stderr.write(chalk_1.default.yellow(`\n⚠ Your code references environment variables not in required_secrets:\n`) +
685
+ chalk_1.default.yellow(` ${undeclared.join(', ')}\n\n`) +
686
+ ` If these should be workspace secrets, add them to required_secrets\n` +
687
+ ` in orchagent.json so they're available in the sandbox at runtime.\n` +
688
+ ` (Platform-injected vars like LLM API keys are already excluded.)\n\n`);
689
+ }
690
+ }
608
691
  // Create the agent (server auto-assigns version)
609
692
  let result;
610
693
  try {
@@ -632,6 +715,7 @@ function registerPublishCommand(program) {
632
715
  sdk_compatible: sdkCompatible || undefined,
633
716
  // Orchestration manifest (includes dependencies)
634
717
  manifest: manifest.manifest,
718
+ required_secrets: manifest.required_secrets,
635
719
  default_skills: skillsFromFlag || manifest.default_skills,
636
720
  skills_locked: manifest.skills_locked || options.skillsLocked || undefined,
637
721
  allow_local_download: options.localDownload || false,
@@ -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
  }