@orchagent/cli 0.3.85 → 0.3.87

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.
@@ -37,10 +37,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.localCommandForEntrypoint = localCommandForEntrypoint;
40
+ exports.validateInputSchema = validateInputSchema;
40
41
  exports.isKeyedFileArg = isKeyedFileArg;
41
42
  exports.readKeyedFiles = readKeyedFiles;
42
43
  exports.mountDirectory = mountDirectory;
43
44
  exports.buildInjectedPayload = buildInjectedPayload;
45
+ exports.renderProgress = renderProgress;
44
46
  exports.registerRunCommand = registerRunCommand;
45
47
  const promises_1 = __importDefault(require("fs/promises"));
46
48
  const path_1 = __importDefault(require("path"));
@@ -188,6 +190,69 @@ function applySchemaDefaults(body, schema) {
188
190
  }
189
191
  return body;
190
192
  }
193
+ /**
194
+ * Validates input data against an agent's input_schema before sending to the server.
195
+ * Returns an array of human-readable error strings. Empty array = valid.
196
+ */
197
+ function validateInputSchema(body, schema) {
198
+ if (!schema)
199
+ return [];
200
+ const s = schema;
201
+ const props = s.properties;
202
+ if (!props || typeof props !== 'object')
203
+ return [];
204
+ const errors = [];
205
+ const required = (s.required ?? []);
206
+ // Check required fields
207
+ for (const field of required) {
208
+ if (body[field] === undefined || body[field] === null) {
209
+ const prop = props[field];
210
+ const desc = prop?.description ? ` (${prop.description})` : '';
211
+ errors.push(`Missing required field: "${field}"${desc}`);
212
+ }
213
+ }
214
+ // Check types of provided fields
215
+ for (const [key, value] of Object.entries(body)) {
216
+ const prop = props[key];
217
+ if (!prop || value === undefined || value === null)
218
+ continue;
219
+ const expectedType = prop.type;
220
+ if (!expectedType)
221
+ continue;
222
+ const actualType = Array.isArray(value) ? 'array' : typeof value;
223
+ let mismatch = false;
224
+ switch (expectedType) {
225
+ case 'string':
226
+ mismatch = actualType !== 'string';
227
+ break;
228
+ case 'number':
229
+ case 'integer':
230
+ mismatch = actualType !== 'number';
231
+ break;
232
+ case 'boolean':
233
+ mismatch = actualType !== 'boolean';
234
+ break;
235
+ case 'array':
236
+ mismatch = actualType !== 'array';
237
+ break;
238
+ case 'object':
239
+ mismatch = actualType !== 'object';
240
+ break;
241
+ }
242
+ if (mismatch) {
243
+ errors.push(`Field "${key}" should be ${expectedType}, got ${actualType}`);
244
+ }
245
+ }
246
+ return errors;
247
+ }
248
+ function warnInputSchemaErrors(body, schema) {
249
+ const errors = validateInputSchema(body, schema);
250
+ if (errors.length === 0)
251
+ return;
252
+ process.stderr.write(chalk_1.default.yellow(`\nInput validation warning:\n`) +
253
+ errors.map(e => chalk_1.default.yellow(` - ${e}`)).join('\n') +
254
+ chalk_1.default.yellow('\n\nThe request will still be sent, but may fail server-side.\n\n'));
255
+ }
191
256
  async function readStdin() {
192
257
  if (process.stdin.isTTY)
193
258
  return null;
@@ -853,7 +918,13 @@ async function executeAgentLocally(agentDir, prompt, inputData, outputSchema, cu
853
918
  try {
854
919
  const result = JSON.parse(stdout.trim());
855
920
  if (exitCode !== 0 && typeof result === 'object' && result !== null && 'error' in result) {
856
- throw new errors_1.CliError(`Agent error: ${result.error}`);
921
+ const errorText = String(result.error);
922
+ const err = new errors_1.CliError(`Agent error: ${errorText}`);
923
+ // BUG-13: If stderr already showed this error during execution, don't print again
924
+ if (stderr.includes(errorText)) {
925
+ err.displayed = true;
926
+ }
927
+ throw err;
857
928
  }
858
929
  if (exitCode !== 0) {
859
930
  (0, output_1.printJson)(result);
@@ -871,6 +942,12 @@ async function executeAgentLocally(agentDir, prompt, inputData, outputSchema, cu
871
942
  }
872
943
  }
873
944
  else if (exitCode !== 0) {
945
+ // BUG-13: stderr was already relayed to user in real-time; don't embed it again
946
+ if (stderr.trim()) {
947
+ const err = new errors_1.CliError(`Agent exited with code ${exitCode}`);
948
+ err.displayed = true;
949
+ throw err;
950
+ }
874
951
  throw new errors_1.CliError(`Agent exited with code ${exitCode} (no output)\n\n` +
875
952
  `Common causes:\n` +
876
953
  ` - Missing LLM API key (check ${apiKeyEnvVar || 'API key env var'})\n` +
@@ -1177,7 +1254,13 @@ async function executeBundleAgent(config, org, agentName, version, agentData, ar
1177
1254
  try {
1178
1255
  const result = JSON.parse(stdout.trim());
1179
1256
  if (exitCode !== 0 && typeof result === 'object' && result !== null && 'error' in result) {
1180
- throw new errors_1.CliError(`Agent error: ${result.error}`);
1257
+ const errorText = String(result.error);
1258
+ const err = new errors_1.CliError(`Agent error: ${errorText}`);
1259
+ // BUG-13: If stderr already showed this error during execution, don't print again
1260
+ if (stderr.includes(errorText)) {
1261
+ err.displayed = true;
1262
+ }
1263
+ throw err;
1181
1264
  }
1182
1265
  if (exitCode !== 0) {
1183
1266
  (0, output_1.printJson)(result);
@@ -1195,8 +1278,11 @@ async function executeBundleAgent(config, org, agentName, version, agentData, ar
1195
1278
  }
1196
1279
  }
1197
1280
  else if (exitCode !== 0) {
1281
+ // BUG-13: stderr was already relayed to user in real-time; don't embed it again
1198
1282
  if (stderr.trim()) {
1199
- throw new errors_1.CliError(`Agent exited with code ${exitCode}\n\nError output:\n${stderr.trim()}`);
1283
+ const err = new errors_1.CliError(`Agent exited with code ${exitCode}`);
1284
+ err.displayed = true;
1285
+ throw err;
1200
1286
  }
1201
1287
  throw new errors_1.CliError(`Agent exited with code ${exitCode} (no output)\n\n` +
1202
1288
  `Common causes:\n` +
@@ -1592,7 +1678,13 @@ async function executeLocalFromDir(dirPath, args, options) {
1592
1678
  try {
1593
1679
  const result = JSON.parse(stdout.trim());
1594
1680
  if (exitCode !== 0 && typeof result === 'object' && result !== null && 'error' in result) {
1595
- throw new errors_1.CliError(`Agent error: ${result.error}`);
1681
+ const errorText = String(result.error);
1682
+ const err = new errors_1.CliError(`Agent error: ${errorText}`);
1683
+ // BUG-13: If stderr already showed this error during execution, don't print again
1684
+ if (stderr.includes(errorText)) {
1685
+ err.displayed = true;
1686
+ }
1687
+ throw err;
1596
1688
  }
1597
1689
  if (exitCode !== 0) {
1598
1690
  (0, output_1.printJson)(result);
@@ -1610,15 +1702,53 @@ async function executeLocalFromDir(dirPath, args, options) {
1610
1702
  }
1611
1703
  }
1612
1704
  else if (exitCode !== 0) {
1705
+ // BUG-13: stderr was already relayed to user in real-time; don't repeat
1706
+ if (stderr.trim()) {
1707
+ const err = new errors_1.CliError(`Agent exited with code ${exitCode}`);
1708
+ err.displayed = true;
1709
+ throw err;
1710
+ }
1613
1711
  throw new errors_1.CliError(`Agent exited with code ${exitCode}`);
1614
1712
  }
1615
1713
  }
1616
1714
  // ─── Cloud execution path ───────────────────────────────────────────────────
1617
- function renderProgress(event) {
1715
+ function renderProgress(event, verbose = false) {
1618
1716
  switch (event.type) {
1619
1717
  case 'turn_start':
1620
1718
  process.stderr.write(chalk_1.default.gray(` Turn ${event.turn}/${event.max_turns}\n`));
1621
1719
  break;
1720
+ case 'sandbox_start':
1721
+ process.stderr.write(chalk_1.default.gray(' Starting sandbox...\n'));
1722
+ break;
1723
+ case 'setup_step': {
1724
+ const name = String(event.name || 'setup');
1725
+ const status = String(event.status || 'started');
1726
+ const icon = status === 'completed' ? chalk_1.default.green('✓') : status === 'failed' ? chalk_1.default.red('x') : chalk_1.default.gray('·');
1727
+ process.stderr.write(` ${icon} ${chalk_1.default.gray(name)} ${chalk_1.default.gray(status)}\n`);
1728
+ break;
1729
+ }
1730
+ case 'stdout': {
1731
+ if (!verbose)
1732
+ break;
1733
+ const text = typeof event.text === 'string' ? event.text : '';
1734
+ if (text)
1735
+ process.stderr.write(chalk_1.default.gray(text));
1736
+ break;
1737
+ }
1738
+ case 'stderr': {
1739
+ if (!verbose)
1740
+ break;
1741
+ const text = typeof event.text === 'string' ? event.text : '';
1742
+ if (text)
1743
+ process.stderr.write(chalk_1.default.yellow(text));
1744
+ break;
1745
+ }
1746
+ case 'heartbeat':
1747
+ // Keepalive for long-running silent steps; avoid noisy output.
1748
+ break;
1749
+ case 'output_truncated':
1750
+ process.stderr.write(chalk_1.default.yellow(' Live output truncated. Use `orch logs <run-id>` for full output.\n'));
1751
+ break;
1622
1752
  case 'tool_call': {
1623
1753
  const icon = event.tool === 'bash'
1624
1754
  ? '$'
@@ -1635,9 +1765,23 @@ function renderProgress(event) {
1635
1765
  if (event.status === 'error')
1636
1766
  process.stderr.write(chalk_1.default.yellow(` (error)\n`));
1637
1767
  break;
1638
- case 'done':
1768
+ case 'done': {
1639
1769
  process.stderr.write(chalk_1.default.green(` Done\n`));
1770
+ // In verbose mode, show exit code and execution time from done event
1771
+ if (verbose) {
1772
+ const parts = [];
1773
+ if (typeof event.exit_code === 'number' && event.exit_code !== 0) {
1774
+ parts.push(`exit_code=${event.exit_code}`);
1775
+ }
1776
+ if (typeof event.execution_time_ms === 'number') {
1777
+ parts.push(`${(event.execution_time_ms / 1000).toFixed(1)}s`);
1778
+ }
1779
+ if (parts.length > 0) {
1780
+ process.stderr.write(chalk_1.default.gray(` (${parts.join(', ')})\n`));
1781
+ }
1782
+ }
1640
1783
  break;
1784
+ }
1641
1785
  case 'error':
1642
1786
  process.stderr.write(chalk_1.default.red(` Error: ${event.message}\n`));
1643
1787
  break;
@@ -1696,6 +1840,50 @@ async function executeCloud(agentRef, file, options) {
1696
1840
  }
1697
1841
  }
1698
1842
  }
1843
+ // --estimate: show cost estimate before running and ask for confirmation
1844
+ if (options.estimate) {
1845
+ try {
1846
+ const est = await (0, api_1.getAgentCostEstimate)(resolved, org, parsed.agent, parsed.version);
1847
+ const e = est.estimate;
1848
+ if (e.sample_size === 0) {
1849
+ process.stderr.write(chalk_1.default.yellow('\nNo run history available for cost estimation.\n'));
1850
+ process.stderr.write(chalk_1.default.gray('This agent has not been run before — cost data will appear after first run.\n\n'));
1851
+ }
1852
+ else {
1853
+ const fmtUsd = (v) => {
1854
+ if (v < 0.001)
1855
+ return '<$0.001';
1856
+ if (v < 0.01)
1857
+ return `$${v.toFixed(4)}`;
1858
+ if (v < 1)
1859
+ return `$${v.toFixed(3)}`;
1860
+ return `$${v.toFixed(2)}`;
1861
+ };
1862
+ process.stderr.write('\n' + chalk_1.default.bold('Cost Estimate') + chalk_1.default.gray(` (${e.sample_size} runs, last ${e.period_days}d)`) + '\n');
1863
+ process.stderr.write(` Average: ${chalk_1.default.green(fmtUsd(e.avg_cost_usd))} · Median: ${chalk_1.default.green(fmtUsd(e.p50_cost_usd))} · 95th pct: ${chalk_1.default.yellow(fmtUsd(e.p95_cost_usd))}\n`);
1864
+ if (e.provider_breakdown && e.provider_breakdown.length > 0) {
1865
+ const parts = e.provider_breakdown.map(p => `${p.provider}: ${fmtUsd(p.avg_cost_usd)}`).join(', ');
1866
+ process.stderr.write(chalk_1.default.gray(` By provider: ${parts}\n`));
1867
+ }
1868
+ process.stderr.write('\n');
1869
+ }
1870
+ // Ask for confirmation
1871
+ const rl = await Promise.resolve().then(() => __importStar(require('readline')));
1872
+ const iface = rl.createInterface({ input: process.stdin, output: process.stderr });
1873
+ const answer = await new Promise(resolve => {
1874
+ iface.question('Proceed with run? [Y/n] ', resolve);
1875
+ });
1876
+ iface.close();
1877
+ if (answer.trim().toLowerCase() === 'n') {
1878
+ process.stderr.write('Run cancelled.\n');
1879
+ return;
1880
+ }
1881
+ }
1882
+ catch {
1883
+ // Non-fatal: if estimate fails, proceed with the run
1884
+ process.stderr.write(chalk_1.default.gray('Could not fetch cost estimate. Proceeding...\n\n'));
1885
+ }
1886
+ }
1699
1887
  const endpoint = options.endpoint?.trim() || agentMeta.default_endpoint || 'analyze';
1700
1888
  const headers = {
1701
1889
  Authorization: `Bearer ${resolved.apiKey}`,
@@ -1811,6 +1999,8 @@ async function executeCloud(agentRef, file, options) {
1811
1999
  mountArgs: options.mount,
1812
2000
  llmCredentials,
1813
2001
  });
2002
+ const injectedObj = JSON.parse(injected.body);
2003
+ warnInputSchemaErrors(injectedObj, agentMeta.input_schema);
1814
2004
  body = injected.body;
1815
2005
  sourceLabel = injected.sourceLabel;
1816
2006
  headers['Content-Type'] = 'application/json';
@@ -1851,6 +2041,7 @@ async function executeCloud(agentRef, file, options) {
1851
2041
  }
1852
2042
  }
1853
2043
  applySchemaDefaults(bodyObj, agentMeta.input_schema);
2044
+ warnInputSchemaErrors(bodyObj, agentMeta.input_schema);
1854
2045
  if (llmCredentials)
1855
2046
  bodyObj.llm_credentials = llmCredentials;
1856
2047
  body = JSON.stringify(bodyObj);
@@ -1872,14 +2063,12 @@ async function executeCloud(agentRef, file, options) {
1872
2063
  else if (options.data) {
1873
2064
  const resolvedBody = await resolveJsonBody(options.data);
1874
2065
  warnIfLocalPathReference(resolvedBody);
2066
+ const parsedBody = JSON.parse(resolvedBody);
2067
+ warnInputSchemaErrors(parsedBody, agentMeta.input_schema);
1875
2068
  if (llmCredentials) {
1876
- const bodyObj = JSON.parse(resolvedBody);
1877
- bodyObj.llm_credentials = llmCredentials;
1878
- body = JSON.stringify(bodyObj);
1879
- }
1880
- else {
1881
- body = resolvedBody;
2069
+ parsedBody.llm_credentials = llmCredentials;
1882
2070
  }
2071
+ body = JSON.stringify(parsedBody);
1883
2072
  headers['Content-Type'] = 'application/json';
1884
2073
  }
1885
2074
  else if ((filePaths.length > 0 || options.metadata) && cloudEngine !== 'code_runtime') {
@@ -1919,6 +2108,7 @@ async function executeCloud(agentRef, file, options) {
1919
2108
  }
1920
2109
  }
1921
2110
  applySchemaDefaults(bodyObj, agentMeta.input_schema);
2111
+ warnInputSchemaErrors(bodyObj, agentMeta.input_schema);
1922
2112
  if (llmCredentials) {
1923
2113
  bodyObj.llm_credentials = llmCredentials;
1924
2114
  }
@@ -1948,16 +2138,27 @@ async function executeCloud(agentRef, file, options) {
1948
2138
  } // end of non-injection path
1949
2139
  const verboseQs = options.verbose ? '?verbose=true' : '';
1950
2140
  const url = `${resolved.apiUrl.replace(/\/$/, '')}/${org}/${parsed.agent}/${parsed.version}/${endpoint}${verboseQs}`;
1951
- // Enable SSE streaming for managed-loop agents (unless --json or --no-stream or --output)
2141
+ // Enable SSE streaming for sandbox-backed engines (unless --json or --no-stream or --output)
1952
2142
  const isManagedLoopAgent = cloudType === 'agent' && cloudEngine === 'managed_loop';
1953
- const wantStream = isManagedLoopAgent && !options.json && !options.noStream && !options.output;
2143
+ const isCodeRuntimeAgent = cloudEngine === 'code_runtime';
2144
+ const streamCapable = isManagedLoopAgent || isCodeRuntimeAgent;
2145
+ const streamDisabled = options.stream === false || options.noStream === true;
2146
+ const wantStream = streamCapable && !options.json && !streamDisabled && !options.output;
1954
2147
  if (wantStream) {
1955
2148
  headers['Accept'] = 'text/event-stream';
1956
2149
  }
1957
- const spinner = options.json ? null : (0, spinner_1.createSpinner)(`Running ${org}/${parsed.agent}@${parsed.version}...`);
2150
+ // Print verbose debug info before request
2151
+ if (options.verbose && !options.json) {
2152
+ process.stderr.write(chalk_1.default.gray(`\n[verbose] ${org}/${parsed.agent}@${parsed.version}\n` +
2153
+ `[verbose] type=${cloudType} engine=${cloudEngine} endpoint=${endpoint}\n` +
2154
+ `[verbose] stream=${wantStream ? 'yes' : 'no'} url=${url}\n`));
2155
+ }
2156
+ const { spinner, dispose: disposeSpinner } = options.json
2157
+ ? { spinner: null, dispose: () => { } }
2158
+ : (0, spinner_1.createElapsedSpinner)(`Running ${org}/${parsed.agent}@${parsed.version}...`);
1958
2159
  spinner?.start();
1959
- // Managed-loop runs can take longer; use 10 min timeout for streaming.
1960
- const timeoutMs = isManagedLoopAgent ? 600000 : undefined;
2160
+ // Streamed sandbox runs can take longer; use 10 min timeout.
2161
+ const timeoutMs = wantStream ? 600000 : undefined;
1961
2162
  let response;
1962
2163
  try {
1963
2164
  response = await (0, api_1.safeFetchWithRetryForCalls)(url, {
@@ -1990,7 +2191,7 @@ async function executeCloud(agentRef, file, options) {
1990
2191
  : undefined;
1991
2192
  throw new errors_1.CliError(`Your CLI version (${package_json_1.default.version}) is too old.\n\n` +
1992
2193
  (minVersion ? `Minimum required: ${minVersion}\n` : '') +
1993
- 'Update with: npm update -g @orchagent/cli');
2194
+ 'Update with: npm install -g @orchagent/cli@latest');
1994
2195
  }
1995
2196
  if (errorCode === 'LLM_KEY_REQUIRED') {
1996
2197
  spinner?.stop();
@@ -2021,6 +2222,14 @@ async function executeCloud(agentRef, file, options) {
2021
2222
  const hint = typeof payload === 'object' && payload
2022
2223
  ? payload.error?.hint
2023
2224
  : undefined;
2225
+ // Read error category from gateway (UX-7)
2226
+ const errorCategory = typeof payload === 'object' && payload
2227
+ ? payload.error?.category
2228
+ : undefined;
2229
+ // Read sandbox_exit_code for backward compat with older gateways
2230
+ const sandboxExitCode = typeof payload === 'object' && payload
2231
+ ? payload.metadata?.sandbox_exit_code
2232
+ : undefined;
2024
2233
  // Detect platform errors that surface as SANDBOX_ERROR (BUG-11)
2025
2234
  const lowerMessage = (message || '').toLowerCase();
2026
2235
  const isPlatformError = /\b403\b/.test(message || '') ||
@@ -2028,11 +2237,27 @@ async function executeCloud(agentRef, file, options) {
2028
2237
  lowerMessage.includes('proxy token') ||
2029
2238
  lowerMessage.includes('orchagent_service_key') ||
2030
2239
  lowerMessage.includes('orchagent_billing_org');
2031
- const attribution = isPlatformError
2032
- ? `This may be a platform configuration issue, not an error in the agent's code.\n` +
2033
- `If this persists, contact support with the ref below.`
2034
- : `This is an error in the agent's code, not the platform.\n` +
2035
- `Check the agent code and requirements, then republish.`;
2240
+ // Categorize: platform > gateway category > exit_code heuristic > neutral
2241
+ let attribution;
2242
+ if (isPlatformError) {
2243
+ attribution =
2244
+ `This may be a platform configuration issue, not an error in the agent's code.\n` +
2245
+ `If this persists, contact support with the ref below.`;
2246
+ }
2247
+ else if (errorCategory === 'code_error' || (!errorCategory && sandboxExitCode != null && sandboxExitCode !== 0)) {
2248
+ attribution =
2249
+ `This is an error in the agent's code, not the platform.\n` +
2250
+ `Check the agent code and requirements, then republish.`;
2251
+ }
2252
+ else if (errorCategory === 'setup_error') {
2253
+ attribution =
2254
+ `The agent failed during setup. This may be a dependency or configuration issue.\n` +
2255
+ `Check requirements.txt and environment configuration.`;
2256
+ }
2257
+ else {
2258
+ // No category from gateway, no exit code — can't determine blame
2259
+ attribution = `Agent execution failed. Check agent logs for details.`;
2260
+ }
2036
2261
  throw new errors_1.CliError(`${message}\n\n` +
2037
2262
  attribution +
2038
2263
  (hint ? `\n\nHint: ${hint}` : '') +
@@ -2095,11 +2320,22 @@ async function executeCloud(agentRef, file, options) {
2095
2320
  const { parseSSE } = await Promise.resolve().then(() => __importStar(require('../lib/sse.js')));
2096
2321
  let finalPayload = null;
2097
2322
  let hadError = false;
2098
- process.stderr.write(chalk_1.default.gray(`\nStreaming ${org}/${parsed.agent}@${parsed.version}:\n`));
2323
+ if (options.verbose) {
2324
+ process.stderr.write(chalk_1.default.gray(`\nStreaming ${org}/${parsed.agent}@${parsed.version} (verbose):\n`));
2325
+ process.stderr.write(chalk_1.default.gray(` type=${cloudType} engine=${cloudEngine} endpoint=${endpoint}\n`));
2326
+ }
2327
+ else {
2328
+ process.stderr.write(chalk_1.default.gray(`\nStreaming ${org}/${parsed.agent}@${parsed.version}:\n`));
2329
+ }
2330
+ let progressErrorShown = false;
2099
2331
  for await (const { event, data } of parseSSE(response.body)) {
2100
2332
  if (event === 'progress') {
2101
2333
  try {
2102
- renderProgress(JSON.parse(data));
2334
+ const parsed = JSON.parse(data);
2335
+ renderProgress(parsed, !!options.verbose);
2336
+ if (parsed.type === 'error') {
2337
+ progressErrorShown = true;
2338
+ }
2103
2339
  }
2104
2340
  catch {
2105
2341
  // ignore malformed progress events
@@ -2134,7 +2370,12 @@ async function executeCloud(agentRef, file, options) {
2134
2370
  const errMsg = typeof finalPayload === 'object' && finalPayload
2135
2371
  ? finalPayload.error?.message || 'Agent execution failed'
2136
2372
  : 'Agent execution failed';
2137
- throw new errors_1.CliError(errMsg);
2373
+ const err = new errors_1.CliError(errMsg);
2374
+ // BUG-13: Error was already displayed by renderProgress during streaming
2375
+ if (progressErrorShown) {
2376
+ err.displayed = true;
2377
+ }
2378
+ throw err;
2138
2379
  }
2139
2380
  if (finalPayload !== null) {
2140
2381
  (0, output_1.printJson)(finalPayload);
@@ -2332,13 +2573,16 @@ async function executeLocal(agentRef, args, options) {
2332
2573
  process.stderr.write(` orch run ${org}/${parsed.agent}@${parsed.version} --local --data '{\"task\": \"...\"}'\n`);
2333
2574
  return;
2334
2575
  }
2576
+ // Resolve @file.json / @- stdin syntax before parsing
2577
+ const resolvedInput = await resolveJsonBody(options.input);
2335
2578
  let agentInputData;
2336
2579
  try {
2337
- agentInputData = JSON.parse(options.input);
2580
+ agentInputData = JSON.parse(resolvedInput);
2338
2581
  }
2339
2582
  catch {
2340
2583
  throw new errors_1.CliError('Invalid JSON input');
2341
2584
  }
2585
+ warnInputSchemaErrors(agentInputData, agentData.input_schema);
2342
2586
  // Write prompt to temp dir and run
2343
2587
  const tempAgentDir = path_1.default.join(os_1.default.tmpdir(), `orchagent-agent-${parsed.agent}-${Date.now()}`);
2344
2588
  await promises_1.default.mkdir(tempAgentDir, { recursive: true });
@@ -2464,6 +2708,7 @@ async function executeLocal(agentRef, args, options) {
2464
2708
  throw new errors_1.CliError('Invalid JSON input');
2465
2709
  }
2466
2710
  }
2711
+ warnInputSchemaErrors(inputData, agentData.input_schema);
2467
2712
  // Handle skill composition
2468
2713
  let skillPrompts = [];
2469
2714
  if (!options.noSkills) {
@@ -2493,14 +2738,15 @@ function registerRunCommand(program) {
2493
2738
  .option('--data <json>', 'JSON payload (string or @file, @- for stdin)')
2494
2739
  .option('--input <json>', 'Alias for --data')
2495
2740
  .option('--json', 'Output raw JSON')
2496
- .option('--verbose', 'Show sandbox stdout/stderr output (cloud only)')
2741
+ .option('--verbose', 'Show sandbox stdout/stderr and debug info (cloud only)')
2742
+ .option('--estimate', 'Show cost estimate before running and ask for confirmation')
2497
2743
  .option('--provider <provider>', 'LLM provider (openai, anthropic, gemini, ollama)')
2498
2744
  .option('--model <model>', 'LLM model to use (overrides agent default)')
2499
2745
  .option('--key <key>', 'LLM API key (overrides env vars)')
2500
2746
  .option('--skills <skills>', 'Add skills (comma-separated)')
2501
2747
  .option('--skills-only <skills>', 'Use only these skills')
2502
2748
  .option('--no-skills', 'Ignore default skills')
2503
- .option('--no-stream', 'Disable real-time streaming for agent-type agents')
2749
+ .option('--no-stream', 'Disable real-time streaming for stream-capable sandbox runs')
2504
2750
  // Cloud-only options
2505
2751
  .option('--endpoint <endpoint>', 'Override agent endpoint (cloud only)')
2506
2752
  .option('--tenant <tenant>', 'Tenant identifier for multi-tenant callers (cloud only)')
@@ -41,6 +41,8 @@ function statusColor(status) {
41
41
  case 'completed': return chalk_1.default.green(status);
42
42
  case 'failed': return chalk_1.default.red(status);
43
43
  case 'running': return chalk_1.default.yellow(status);
44
+ case 'queued': return chalk_1.default.cyan(status);
45
+ case 'deduplicated': return chalk_1.default.dim(status);
44
46
  default: return status;
45
47
  }
46
48
  }
@@ -194,14 +196,16 @@ function registerScheduleCommand(program) {
194
196
  });
195
197
  const s = result.schedule;
196
198
  process.stdout.write(chalk_1.default.green('\u2713') + ` Schedule created\n\n`);
197
- process.stdout.write(` ID: ${s.id}\n`);
198
- process.stdout.write(` Agent: ${s.agent_name}@${s.agent_version}\n`);
199
- process.stdout.write(` Type: ${s.schedule_type}\n`);
199
+ process.stdout.write(` ID: ${s.id}\n`);
200
+ process.stdout.write(` Agent: ${s.agent_name}@${s.agent_version}\n`);
201
+ process.stdout.write(` Type: ${s.schedule_type}\n`);
202
+ process.stdout.write(` Enabled: ${chalk_1.default.green('yes')}\n`);
203
+ process.stdout.write(` Auto-update: ${s.auto_update === false ? chalk_1.default.yellow('no (pinned)') : chalk_1.default.green('yes')}\n`);
200
204
  if (s.schedule_type === 'cron') {
201
- process.stdout.write(` Cron: ${s.cron_expression}\n`);
202
- process.stdout.write(` TZ: ${s.timezone}\n`);
205
+ process.stdout.write(` Cron: ${s.cron_expression}\n`);
206
+ process.stdout.write(` Timezone: ${s.timezone}\n`);
203
207
  if (s.next_run_at) {
204
- process.stdout.write(` Next: ${formatDate(s.next_run_at)}\n`);
208
+ process.stdout.write(` Next run: ${formatDate(s.next_run_at)}\n`);
205
209
  }
206
210
  }
207
211
  else {
@@ -210,6 +214,9 @@ function registerScheduleCommand(program) {
210
214
  process.stdout.write(` ${s.webhook_url}\n`);
211
215
  }
212
216
  }
217
+ if (s.llm_provider) {
218
+ process.stdout.write(` Provider: ${s.llm_provider}\n`);
219
+ }
213
220
  process.stdout.write('\n');
214
221
  });
215
222
  // orch schedule update <schedule-id>
@@ -335,10 +342,20 @@ function registerScheduleCommand(program) {
335
342
  body,
336
343
  headers: { 'Content-Type': 'application/json' },
337
344
  } : {});
338
- process.stdout.write(chalk_1.default.green('\u2713') + ` Run completed\n\n`);
345
+ // Status-aware header message
346
+ const isAsync = result.status === 'queued' || result.status === 'deduplicated';
347
+ if (isAsync) {
348
+ process.stdout.write(chalk_1.default.cyan('\u2713') + ` Run ${result.status}\n\n`);
349
+ }
350
+ else if (result.status === 'failed' || result.status === 'timeout') {
351
+ process.stdout.write(chalk_1.default.red('\u2717') + ` Run ${result.status}\n\n`);
352
+ }
353
+ else {
354
+ process.stdout.write(chalk_1.default.green('\u2713') + ` Run completed\n\n`);
355
+ }
339
356
  process.stdout.write(` Run ID: ${result.run_id}\n`);
340
357
  process.stdout.write(` Status: ${statusColor(result.status)}\n`);
341
- process.stdout.write(` Duration: ${result.duration_ms}ms\n`);
358
+ process.stdout.write(` Duration: ${result.duration_ms != null ? `${result.duration_ms}ms` : 'pending'}\n`);
342
359
  if (result.error) {
343
360
  process.stdout.write(` Error: ${chalk_1.default.red(result.error)}\n`);
344
361
  }
@@ -211,11 +211,11 @@ function registerSkillCommand(program) {
211
211
  .description('Browse available skills')
212
212
  .option('--json', 'Output raw JSON')
213
213
  .action(async () => {
214
- process.stdout.write('Browse and discover skills at: https://orchagent.io/explore\n\n' +
215
- 'Install a skill:\n' +
214
+ process.stdout.write('Install a skill:\n' +
216
215
  ' orch skill install <org>/<skill-name>\n\n' +
217
216
  'View installed skills:\n' +
218
- ' orch update --check\n');
217
+ ' orch update --check\n\n' +
218
+ 'Learn more: https://docs.orchagent.io/skills\n');
219
219
  process.exit(0);
220
220
  });
221
221
  // orch skill create [name]