@orchagent/cli 0.3.87 → 0.3.89

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.
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.setConfigValue = setConfigValue;
4
4
  exports.registerConfigCommand = registerConfigCommand;
5
+ const output_1 = require("../lib/output");
5
6
  const config_1 = require("../lib/config");
6
7
  const errors_1 = require("../lib/errors");
7
8
  const adapters_1 = require("../adapters");
@@ -49,13 +50,17 @@ async function setConfigValue(key, value) {
49
50
  process.stdout.write(`Set default-provider to: ${value}\n`);
50
51
  }
51
52
  }
52
- async function getConfigValue(key) {
53
+ async function getConfigValue(key, options = {}) {
53
54
  if (!isValidKey(key)) {
54
55
  throw new errors_1.CliError(`Unknown config key: ${key}. Supported keys: ${SUPPORTED_KEYS.join(', ')}`);
55
56
  }
56
57
  if (key === 'default-format') {
57
58
  const resolved = await (0, config_1.getResolvedConfig)();
58
59
  const formats = await (0, config_1.getDefaultFormats)(resolved);
60
+ if (options.json) {
61
+ (0, output_1.printJson)({ key, value: formats.length > 0 ? formats : [] });
62
+ return;
63
+ }
59
64
  if (formats.length === 0) {
60
65
  process.stdout.write('(not set)\n');
61
66
  }
@@ -65,6 +70,10 @@ async function getConfigValue(key) {
65
70
  }
66
71
  if (key === 'default-scope') {
67
72
  const scope = await (0, config_1.getDefaultScope)();
73
+ if (options.json) {
74
+ (0, output_1.printJson)({ key, value: scope ?? null });
75
+ return;
76
+ }
68
77
  if (!scope) {
69
78
  process.stdout.write('(not set)\n');
70
79
  }
@@ -74,6 +83,10 @@ async function getConfigValue(key) {
74
83
  }
75
84
  if (key === 'default-provider') {
76
85
  const provider = await (0, config_1.getDefaultProvider)();
86
+ if (options.json) {
87
+ (0, output_1.printJson)({ key, value: provider ?? null });
88
+ return;
89
+ }
77
90
  if (!provider) {
78
91
  process.stdout.write('(not set)\n');
79
92
  }
@@ -89,8 +102,16 @@ async function unsetConfigValue(key) {
89
102
  await (0, config_1.unsetConfigKey)(key);
90
103
  process.stdout.write(`Unset ${key}\n`);
91
104
  }
92
- async function listConfigValues() {
105
+ async function listConfigValues(options = {}) {
93
106
  const config = await (0, config_1.loadConfig)();
107
+ if (options.json) {
108
+ (0, output_1.printJson)({
109
+ default_format: config.default_formats ?? [],
110
+ default_scope: config.default_scope ?? null,
111
+ default_provider: config.default_provider ?? null,
112
+ });
113
+ return;
114
+ }
94
115
  process.stdout.write('CLI Configuration:\n\n');
95
116
  // default-format
96
117
  const formats = config.default_formats ?? [];
@@ -131,8 +152,9 @@ function registerConfigCommand(program) {
131
152
  config
132
153
  .command('get <key>')
133
154
  .description('Get a configuration value')
134
- .action(async (key) => {
135
- await getConfigValue(key);
155
+ .option('--json', 'Output as JSON')
156
+ .action(async (key, options) => {
157
+ await getConfigValue(key, options);
136
158
  });
137
159
  config
138
160
  .command('unset <key>')
@@ -143,7 +165,8 @@ function registerConfigCommand(program) {
143
165
  config
144
166
  .command('list')
145
167
  .description('List all configuration values')
146
- .action(async () => {
147
- await listConfigValues();
168
+ .option('--json', 'Output as JSON')
169
+ .action(async (options) => {
170
+ await listConfigValues(options);
148
171
  });
149
172
  }
@@ -0,0 +1,259 @@
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.registerDagCommand = registerDagCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const config_1 = require("../lib/config");
9
+ const api_1 = require("../lib/api");
10
+ const errors_1 = require("../lib/errors");
11
+ const output_1 = require("../lib/output");
12
+ const spinner_1 = require("../lib/spinner");
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 isUuid(value) {
30
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
31
+ }
32
+ function isShortUuid(value) {
33
+ return /^[0-9a-f]{7,}$/i.test(value) && !value.includes('/');
34
+ }
35
+ async function resolveShortRunId(config, workspaceId, shortId) {
36
+ const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/runs?limit=200&run_id_prefix=${encodeURIComponent(shortId)}`);
37
+ if (result.runs.length === 0) {
38
+ throw new errors_1.CliError(`No run found matching '${shortId}'.`);
39
+ }
40
+ if (result.runs.length > 1) {
41
+ throw new errors_1.CliError(`Ambiguous run ID '${shortId}' — matches ${result.runs.length} runs. Use more characters.`);
42
+ }
43
+ return result.runs[0].id;
44
+ }
45
+ // ============================================
46
+ // FORMATTERS
47
+ // ============================================
48
+ function statusColor(status) {
49
+ switch (status) {
50
+ case 'completed':
51
+ return chalk_1.default.green(status);
52
+ case 'failed':
53
+ case 'timeout':
54
+ case 'dead_letter':
55
+ return chalk_1.default.red(status);
56
+ case 'running':
57
+ return chalk_1.default.yellow(status);
58
+ case 'queued':
59
+ case 'claimed':
60
+ return chalk_1.default.cyan(status);
61
+ default:
62
+ return chalk_1.default.gray(status);
63
+ }
64
+ }
65
+ function statusIcon(status) {
66
+ switch (status) {
67
+ case 'completed':
68
+ return chalk_1.default.green('✓');
69
+ case 'failed':
70
+ case 'timeout':
71
+ case 'dead_letter':
72
+ return chalk_1.default.red('✗');
73
+ case 'running':
74
+ return chalk_1.default.yellow('◉');
75
+ case 'queued':
76
+ case 'claimed':
77
+ return chalk_1.default.cyan('○');
78
+ default:
79
+ return chalk_1.default.gray('?');
80
+ }
81
+ }
82
+ function formatDuration(ms) {
83
+ if (ms == null)
84
+ return '-';
85
+ if (ms < 1000)
86
+ return `${ms}ms`;
87
+ if (ms < 60000)
88
+ return `${(ms / 1000).toFixed(1)}s`;
89
+ return `${(ms / 60000).toFixed(1)}m`;
90
+ }
91
+ function formatCost(usd) {
92
+ if (usd === 0)
93
+ return '-';
94
+ if (usd < 0.01)
95
+ return `$${usd.toFixed(6)}`;
96
+ return `$${usd.toFixed(4)}`;
97
+ }
98
+ function formatTokens(n) {
99
+ if (n >= 1_000_000)
100
+ return `${(n / 1_000_000).toFixed(1)}M`;
101
+ if (n >= 1_000)
102
+ return `${(n / 1_000).toFixed(1)}K`;
103
+ return String(n);
104
+ }
105
+ // ============================================
106
+ // ASCII DAG RENDERER
107
+ // ============================================
108
+ function renderDagHeader(dag) {
109
+ const liveTag = dag.is_live ? chalk_1.default.blue(' [LIVE]') : '';
110
+ process.stdout.write(chalk_1.default.bold(`\nOrchestration DAG`) + liveTag + '\n' +
111
+ ` Root: ${dag.root_run_id.slice(0, 8)}...\n` +
112
+ ` Agents: ${dag.node_count}\n` +
113
+ ` Duration: ${formatDuration(dag.total_duration_ms)}\n` +
114
+ ` Cost: ${formatCost(dag.total_cost_usd)}\n`);
115
+ if (dag.active_count > 0 || dag.failed_count > 0) {
116
+ const parts = [];
117
+ if (dag.completed_count > 0)
118
+ parts.push(chalk_1.default.green(`${dag.completed_count} completed`));
119
+ if (dag.active_count > 0)
120
+ parts.push(chalk_1.default.yellow(`${dag.active_count} active`));
121
+ if (dag.failed_count > 0)
122
+ parts.push(chalk_1.default.red(`${dag.failed_count} failed`));
123
+ process.stdout.write(` Status: ${parts.join(', ')}\n`);
124
+ }
125
+ if (dag.most_expensive_agent) {
126
+ process.stdout.write(` Costly: ${chalk_1.default.yellow(dag.most_expensive_agent)}\n`);
127
+ }
128
+ }
129
+ function renderDagTree(node, prefix, isLast, isRoot) {
130
+ // Connector characters
131
+ const connector = isRoot ? '' : (isLast ? '└── ' : '├── ');
132
+ const childPrefix = isRoot ? '' : (isLast ? ' ' : '│ ');
133
+ // Status icon + agent name
134
+ const icon = statusIcon(node.status);
135
+ const name = chalk_1.default.bold(node.agent_name);
136
+ const version = chalk_1.default.gray(node.agent_version);
137
+ // Metrics line
138
+ const metricParts = [];
139
+ metricParts.push(statusColor(node.status));
140
+ if (node.duration_ms != null)
141
+ metricParts.push(chalk_1.default.gray(formatDuration(node.duration_ms)));
142
+ if (node.self_cost_usd > 0)
143
+ metricParts.push(chalk_1.default.gray(formatCost(node.self_cost_usd)));
144
+ if (node.llm_model)
145
+ metricParts.push(chalk_1.default.gray(node.llm_model));
146
+ const metrics = metricParts.join(chalk_1.default.gray(' | '));
147
+ // Trace summary
148
+ const ts = node.trace_summary;
149
+ const traceParts = [];
150
+ if (ts.llm_calls > 0)
151
+ traceParts.push(`${ts.llm_calls} LLM`);
152
+ if (ts.tool_calls > 0)
153
+ traceParts.push(`${ts.tool_calls} tool`);
154
+ if (ts.errors > 0)
155
+ traceParts.push(chalk_1.default.red(`${ts.errors} err`));
156
+ if (ts.total_tokens > 0)
157
+ traceParts.push(`${formatTokens(ts.total_tokens)} tok`);
158
+ const traceInfo = traceParts.length > 0 ? chalk_1.default.gray(` (${traceParts.join(', ')})`) : '';
159
+ process.stdout.write(`${prefix}${connector}${icon} ${name} ${version}\n` +
160
+ `${prefix}${childPrefix} ${metrics}${traceInfo}\n`);
161
+ // Render children
162
+ for (let i = 0; i < node.children.length; i++) {
163
+ const isChildLast = i === node.children.length - 1;
164
+ renderDagTree(node.children[i], prefix + childPrefix, isChildLast, false);
165
+ }
166
+ }
167
+ function clearScreen() {
168
+ process.stdout.write('\x1b[2J\x1b[H');
169
+ }
170
+ // ============================================
171
+ // COMMAND
172
+ // ============================================
173
+ function registerDagCommand(program) {
174
+ program
175
+ .command('dag <run-id>')
176
+ .description('Visualize the orchestration call graph for a run. Shows all agents in the chain with real-time status.')
177
+ .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
178
+ .option('--live', 'Keep polling for updates while the run is active')
179
+ .option('--interval <seconds>', 'Poll interval in seconds for --live mode', '2')
180
+ .option('--json', 'Output as JSON')
181
+ .action(async (runId, options) => {
182
+ const config = await (0, config_1.getResolvedConfig)();
183
+ if (!config.apiKey) {
184
+ throw new errors_1.CliError('Missing API key. Run `orch login` first.');
185
+ }
186
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
187
+ // Resolve short run IDs
188
+ let resolvedRunId = runId;
189
+ if (isUuid(runId)) {
190
+ resolvedRunId = runId;
191
+ }
192
+ else if (isShortUuid(runId)) {
193
+ resolvedRunId = await resolveShortRunId(config, workspaceId, runId);
194
+ }
195
+ else {
196
+ throw new errors_1.CliError(`Invalid run ID '${runId}'. Provide a full UUID or a short hex prefix (7+ characters).`);
197
+ }
198
+ const interval = Math.max(1, Math.min(30, parseFloat(options.interval || '2')));
199
+ // Fetch DAG
200
+ const spinner = (0, spinner_1.createSpinner)('Fetching orchestration DAG...');
201
+ spinner.start();
202
+ let dag;
203
+ try {
204
+ dag = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/runs/${resolvedRunId}/dag`);
205
+ spinner.stop();
206
+ }
207
+ catch (e) {
208
+ spinner.stop();
209
+ const msg = e instanceof Error ? e.message : 'Unknown error';
210
+ if (msg.includes('404') || msg.includes('not found') || msg.includes('Not part of')) {
211
+ throw new errors_1.CliError(`Run ${resolvedRunId.slice(0, 8)}... is not part of an orchestration chain.`);
212
+ }
213
+ throw e;
214
+ }
215
+ if (options.json && !options.live) {
216
+ (0, output_1.printJson)(dag);
217
+ return;
218
+ }
219
+ // Render initial DAG
220
+ renderDagHeader(dag);
221
+ process.stdout.write('\n');
222
+ renderDagTree(dag.tree, ' ', true, true);
223
+ process.stdout.write('\n');
224
+ // Live mode: poll for updates
225
+ if (options.live && dag.is_live) {
226
+ process.stdout.write(chalk_1.default.gray(`Live mode: polling every ${interval}s. Press Ctrl+C to stop.\n\n`));
227
+ while (true) {
228
+ await new Promise((resolve) => setTimeout(resolve, interval * 1000));
229
+ try {
230
+ dag = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/runs/${resolvedRunId}/dag`);
231
+ }
232
+ catch {
233
+ // Ignore transient errors during polling
234
+ continue;
235
+ }
236
+ if (options.json) {
237
+ (0, output_1.printJson)(dag);
238
+ }
239
+ else {
240
+ clearScreen();
241
+ renderDagHeader(dag);
242
+ process.stdout.write('\n');
243
+ renderDagTree(dag.tree, ' ', true, true);
244
+ process.stdout.write('\n');
245
+ }
246
+ if (!dag.is_live) {
247
+ process.stdout.write(chalk_1.default.green('Execution complete.\n'));
248
+ break;
249
+ }
250
+ }
251
+ }
252
+ else if (options.live && !dag.is_live) {
253
+ process.stdout.write(chalk_1.default.gray('Run is not active. Showing final state.\n'));
254
+ }
255
+ // Footer hints
256
+ process.stdout.write(chalk_1.default.gray(`View trace: orch trace ${resolvedRunId.slice(0, 8)}\n`) +
257
+ chalk_1.default.gray(`View logs: orch logs ${resolvedRunId.slice(0, 8)}\n`));
258
+ });
259
+ }
@@ -43,6 +43,7 @@ const fs = __importStar(require("fs/promises"));
43
43
  const config_1 = require("../lib/config");
44
44
  const api_1 = require("../lib/api");
45
45
  const errors_1 = require("../lib/errors");
46
+ const output_1 = require("../lib/output");
46
47
  const analytics_1 = require("../lib/analytics");
47
48
  async function resolveWorkspaceId(config, slug) {
48
49
  const configFile = await (0, config_1.loadConfig)();
@@ -83,6 +84,10 @@ function statusColor(status) {
83
84
  async function listEnvs(config, options) {
84
85
  const workspaceId = await resolveWorkspaceId(config, options.workspace);
85
86
  const result = await (0, api_1.listEnvironments)(config, workspaceId);
87
+ if (options.json) {
88
+ (0, output_1.printJson)(result);
89
+ return;
90
+ }
86
91
  if (result.environments.length === 0) {
87
92
  console.log(chalk_1.default.gray('No environments found.'));
88
93
  console.log(chalk_1.default.gray('Use `orchagent env create` to create one, or include a Dockerfile in your agent bundle.'));
@@ -108,7 +113,7 @@ async function listEnvs(config, options) {
108
113
  statusColor(env.build?.status),
109
114
  env.agent_count.toString(),
110
115
  env.environment.is_predefined ? chalk_1.default.blue('predefined') : chalk_1.default.gray('custom'),
111
- env.environment.id.slice(0, 8),
116
+ env.environment.id,
112
117
  ]);
113
118
  }
114
119
  console.log();
@@ -123,8 +128,12 @@ async function listEnvs(config, options) {
123
128
  }
124
129
  }
125
130
  }
126
- async function getEnvStatus(config, environmentId) {
131
+ async function getEnvStatus(config, environmentId, options) {
127
132
  const result = await (0, api_1.getEnvironment)(config, environmentId);
133
+ if (options.json) {
134
+ (0, output_1.printJson)(result);
135
+ return;
136
+ }
128
137
  console.log();
129
138
  console.log(chalk_1.default.bold(`Environment: ${result.environment.name}`));
130
139
  console.log();
@@ -206,6 +215,7 @@ function registerEnvCommand(program) {
206
215
  .command('list')
207
216
  .description('List environments in workspace')
208
217
  .option('-w, --workspace <slug>', 'Workspace slug')
218
+ .option('--json', 'Output as JSON')
209
219
  .action(async (options) => {
210
220
  const config = await (0, config_1.getResolvedConfig)();
211
221
  await listEnvs(config, options);
@@ -213,9 +223,10 @@ function registerEnvCommand(program) {
213
223
  env
214
224
  .command('status <environment-id>')
215
225
  .description('Check environment build status')
216
- .action(async (environmentId) => {
226
+ .option('--json', 'Output as JSON')
227
+ .action(async (environmentId, options) => {
217
228
  const config = await (0, config_1.getResolvedConfig)();
218
- await getEnvStatus(config, environmentId);
229
+ await getEnvStatus(config, environmentId, options);
219
230
  });
220
231
  env
221
232
  .command('create')
@@ -36,16 +36,16 @@ async function resolveWorkspace(config, workspaceSlug) {
36
36
  function registerForkCommand(program) {
37
37
  program
38
38
  .command('fork <agent>')
39
- .description('Fork a public agent into your workspace')
39
+ .description('Fork an agent into your workspace')
40
40
  .option('--name <new-name>', 'Rename the forked agent')
41
41
  .option('-w, --workspace <workspace-slug>', 'Target workspace slug')
42
42
  .option('--json', 'Output raw JSON')
43
43
  .addHelpText('after', `
44
44
  Examples:
45
- orch fork orchagent/my-discord-agent
46
- orch fork orchagent/my-discord-agent --workspace acme-corp
47
- orch fork orchagent/my-discord-agent --name customer-support-bot
48
- orch fork orchagent/my-discord-agent@v2 --json
45
+ orch fork orchagent/security-agent
46
+ orch fork orchagent/security-agent --workspace acme-corp
47
+ orch fork orchagent/security-agent --name my-security-scanner
48
+ orch fork joe/my-agent --workspace acme-corp # fork your own private agent into another workspace
49
49
  `)
50
50
  .action(async (agentRef, options) => {
51
51
  const write = (message) => {
@@ -58,7 +58,7 @@ Examples:
58
58
  }
59
59
  const { org, agent, version } = (0, agent_ref_1.parseAgentRef)(agentRef);
60
60
  write('Resolving source agent...\n');
61
- const source = await (0, api_1.getPublicAgent)(config, org, agent, version);
61
+ const source = await (0, api_1.getAgentWithFallback)(config, org, agent, version);
62
62
  if (!source.id) {
63
63
  throw new errors_1.CliError(`Could not resolve source agent ID for '${org}/${agent}@${version}'.`);
64
64
  }
@@ -3,12 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerCommands = registerCommands;
4
4
  const login_1 = require("./login");
5
5
  const logout_1 = require("./logout");
6
- const call_1 = require("./call");
7
6
  const agents_1 = require("./agents");
8
7
  const init_1 = require("./init");
9
8
  const publish_1 = require("./publish");
10
9
  const whoami_1 = require("./whoami");
11
- const keys_1 = require("./keys");
12
10
  const run_1 = require("./run");
13
11
  const info_1 = require("./info");
14
12
  const skill_1 = require("./skill");
@@ -40,17 +38,19 @@ const diff_1 = require("./diff");
40
38
  const health_1 = require("./health");
41
39
  const dev_1 = require("./dev");
42
40
  const estimate_1 = require("./estimate");
41
+ const replay_1 = require("./replay");
42
+ const trace_1 = require("./trace");
43
+ const metrics_1 = require("./metrics");
44
+ const dag_1 = require("./dag");
43
45
  function registerCommands(program) {
44
46
  (0, login_1.registerLoginCommand)(program);
45
47
  (0, logout_1.registerLogoutCommand)(program);
46
48
  (0, whoami_1.registerWhoamiCommand)(program);
47
49
  (0, init_1.registerInitCommand)(program);
48
50
  (0, publish_1.registerPublishCommand)(program);
49
- (0, call_1.registerCallCommand)(program);
50
51
  (0, run_1.registerRunCommand)(program);
51
52
  (0, info_1.registerInfoCommand)(program);
52
53
  (0, agents_1.registerAgentsCommand)(program);
53
- (0, keys_1.registerKeysCommand)(program);
54
54
  (0, skill_1.registerSkillCommand)(program);
55
55
  (0, delete_1.registerDeleteCommand)(program);
56
56
  (0, fork_1.registerForkCommand)(program);
@@ -80,4 +80,8 @@ function registerCommands(program) {
80
80
  (0, health_1.registerHealthCommand)(program);
81
81
  (0, dev_1.registerDevCommand)(program);
82
82
  (0, estimate_1.registerEstimateCommand)(program);
83
+ (0, replay_1.registerReplayCommand)(program);
84
+ (0, trace_1.registerTraceCommand)(program);
85
+ (0, metrics_1.registerMetricsCommand)(program);
86
+ (0, dag_1.registerDagCommand)(program);
83
87
  }
@@ -0,0 +1,137 @@
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.registerMetricsCommand = registerMetricsCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const config_1 = require("../lib/config");
9
+ const api_1 = require("../lib/api");
10
+ const errors_1 = require("../lib/errors");
11
+ const output_1 = require("../lib/output");
12
+ // ============================================
13
+ // HELPERS
14
+ // ============================================
15
+ async function resolveWorkspaceId(config, slug) {
16
+ const configFile = await (0, config_1.loadConfig)();
17
+ const targetSlug = slug ?? configFile.workspace;
18
+ if (!targetSlug) {
19
+ throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orch workspace use <slug>` first.');
20
+ }
21
+ const response = await (0, api_1.request)(config, 'GET', '/workspaces');
22
+ const workspace = response.workspaces.find((w) => w.slug === targetSlug);
23
+ if (!workspace) {
24
+ throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
25
+ }
26
+ return workspace.id;
27
+ }
28
+ function formatDuration(ms) {
29
+ if (ms === 0)
30
+ return '-';
31
+ if (ms < 1000)
32
+ return `${ms}ms`;
33
+ return `${(ms / 1000).toFixed(1)}s`;
34
+ }
35
+ function rateColor(rate) {
36
+ if (rate >= 95)
37
+ return chalk_1.default.green(`${rate}%`);
38
+ if (rate >= 80)
39
+ return chalk_1.default.yellow(`${rate}%`);
40
+ return chalk_1.default.red(`${rate}%`);
41
+ }
42
+ function padRight(str, len) {
43
+ return str.length >= len ? str.slice(0, len) : str + ' '.repeat(len - str.length);
44
+ }
45
+ function padLeft(str, len) {
46
+ return str.length >= len ? str.slice(0, len) : ' '.repeat(len - str.length) + str;
47
+ }
48
+ // ============================================
49
+ // COMMAND
50
+ // ============================================
51
+ function registerMetricsCommand(program) {
52
+ program
53
+ .command('metrics')
54
+ .description('Show agent performance metrics for a workspace')
55
+ .option('--workspace <slug>', 'Workspace slug (default: active workspace)')
56
+ .option('--days <n>', 'Number of days to analyze', '30')
57
+ .option('--agent <name>', 'Filter to a specific agent')
58
+ .option('--json', 'Output as JSON')
59
+ .action(async (options) => {
60
+ const config = await (0, config_1.getResolvedConfig)();
61
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
62
+ const days = parseInt(options.days || '30', 10);
63
+ if (isNaN(days) || days < 1 || days > 365) {
64
+ process.stderr.write(chalk_1.default.red('Error: --days must be between 1 and 365\n'));
65
+ process.exit(1);
66
+ }
67
+ const params = new URLSearchParams({ days: String(days) });
68
+ if (options.agent)
69
+ params.set('agent_name', options.agent);
70
+ let data;
71
+ try {
72
+ data = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/metrics/dashboard?${params}`, { headers: { 'X-Workspace-Id': workspaceId } });
73
+ }
74
+ catch (err) {
75
+ if (err instanceof api_1.ApiError && err.status === 403) {
76
+ process.stderr.write(chalk_1.default.red('Error: Not a member of this workspace\n'));
77
+ process.exit(1);
78
+ }
79
+ throw err;
80
+ }
81
+ if (options.json) {
82
+ (0, output_1.printJson)(data);
83
+ return;
84
+ }
85
+ const { overview, agents } = data;
86
+ // Header
87
+ process.stdout.write('\n');
88
+ process.stdout.write(chalk_1.default.bold('Agent Metrics') + chalk_1.default.gray(` (last ${days}d)\n`));
89
+ process.stdout.write('='.repeat(50) + '\n\n');
90
+ // Overview stats
91
+ if (overview.total_runs === 0) {
92
+ process.stdout.write(chalk_1.default.yellow('No runs in this period.\n\n'));
93
+ return;
94
+ }
95
+ process.stdout.write(` Total Runs: ${chalk_1.default.bold(String(overview.total_runs))}\n`);
96
+ process.stdout.write(` Success Rate: ${rateColor(overview.success_rate)}\n`);
97
+ process.stdout.write(` Error Rate: ${overview.error_rate}% ${chalk_1.default.gray(`(${overview.failed} failed, ${overview.timeout} timeout)`)}\n`);
98
+ process.stdout.write(` p50 Latency: ${chalk_1.default.cyan(formatDuration(overview.p50_latency_ms))}\n`);
99
+ process.stdout.write(` p95 Latency: ${chalk_1.default.yellow(formatDuration(overview.p95_latency_ms))}\n`);
100
+ process.stdout.write(` Avg Latency: ${formatDuration(overview.avg_latency_ms)}\n`);
101
+ process.stdout.write(` Runs/Day: ${overview.runs_per_day}\n`);
102
+ // Per-agent table
103
+ if (agents.length > 0) {
104
+ process.stdout.write(`\n${chalk_1.default.bold('Per Agent')}\n`);
105
+ process.stdout.write('-'.repeat(90) + '\n');
106
+ // Header
107
+ process.stdout.write(padRight('Agent', 25) +
108
+ padLeft('Runs', 8) +
109
+ padLeft('Success', 10) +
110
+ padLeft('p50', 10) +
111
+ padLeft('p95', 10) +
112
+ padLeft('Errors', 8) +
113
+ ' Top Error\n');
114
+ process.stdout.write('-'.repeat(90) + '\n');
115
+ for (const agent of agents) {
116
+ const nameStr = agent.agent_name.length > 22
117
+ ? agent.agent_name.slice(0, 22) + '..'
118
+ : agent.agent_name;
119
+ const errorStr = agent.top_error
120
+ ? agent.top_error.slice(0, 20)
121
+ : '-';
122
+ process.stdout.write(padRight(nameStr, 25) +
123
+ padLeft(String(agent.total_runs), 8) +
124
+ padLeft(agent.success_rate >= 95 ? chalk_1.default.green(`${agent.success_rate}%`) :
125
+ agent.success_rate >= 80 ? chalk_1.default.yellow(`${agent.success_rate}%`) :
126
+ chalk_1.default.red(`${agent.success_rate}%`),
127
+ // chalk adds escape chars — pad the raw value, then colorize
128
+ 10) +
129
+ padLeft(formatDuration(agent.p50_latency_ms), 10) +
130
+ padLeft(formatDuration(agent.p95_latency_ms), 10) +
131
+ padLeft(String(agent.failed + agent.timeout), 8) +
132
+ ' ' + chalk_1.default.gray(errorStr) + '\n');
133
+ }
134
+ }
135
+ process.stdout.write('\n');
136
+ });
137
+ }
@@ -982,6 +982,26 @@ function registerPublishCommand(program) {
982
982
  ` the field to use the default) and republish each dependency.\n\n`);
983
983
  }
984
984
  }
985
+ // C-1: Block publish if tool/agent type has no required_secrets declared.
986
+ // Prompt and skill types are exempt (prompt agents get LLM keys from platform,
987
+ // skills don't run standalone).
988
+ // An explicit empty array (required_secrets: []) is a valid declaration
989
+ // meaning "this agent deliberately needs no secrets."
990
+ // Runs before dry-run so --dry-run catches the same errors as real publish (BUG-11).
991
+ if ((canonicalType === 'tool' || canonicalType === 'agent') &&
992
+ manifest.required_secrets === undefined &&
993
+ options.requiredSecrets !== false) {
994
+ process.stderr.write(chalk_1.default.red(`\nError: ${canonicalType} agents must declare required_secrets in orchagent.json.\n\n`) +
995
+ ` Add the env vars your code needs at runtime:\n` +
996
+ ` ${chalk_1.default.cyan('"required_secrets": ["ANTHROPIC_API_KEY", "MY_TOKEN"]')}\n\n` +
997
+ ` If this agent genuinely needs no secrets, add an empty array:\n` +
998
+ ` ${chalk_1.default.cyan('"required_secrets": []')}\n\n` +
999
+ ` These are matched by name against your workspace secrets vault.\n` +
1000
+ ` Use ${chalk_1.default.cyan('--no-required-secrets')} to skip this check.\n`);
1001
+ const err = new errors_1.CliError('Missing required_secrets declaration', errors_1.ExitCodes.INVALID_INPUT);
1002
+ err.displayed = true;
1003
+ throw err;
1004
+ }
985
1005
  // Handle dry-run for agents
986
1006
  if (options.dryRun) {
987
1007
  const preview = await (0, api_1.previewAgentVersion)(config, manifest.name, workspaceId);
@@ -1166,25 +1186,6 @@ function registerPublishCommand(program) {
1166
1186
  ` env var to detect the reserved port.\n\n`);
1167
1187
  }
1168
1188
  }
1169
- // C-1: Block publish if tool/agent type has no required_secrets declared.
1170
- // Prompt and skill types are exempt (prompt agents get LLM keys from platform,
1171
- // skills don't run standalone).
1172
- // An explicit empty array (required_secrets: []) is a valid declaration
1173
- // meaning "this agent deliberately needs no secrets."
1174
- if ((canonicalType === 'tool' || canonicalType === 'agent') &&
1175
- manifest.required_secrets === undefined &&
1176
- options.requiredSecrets !== false) {
1177
- process.stderr.write(chalk_1.default.red(`\nError: ${canonicalType} agents must declare required_secrets in orchagent.json.\n\n`) +
1178
- ` Add the env vars your code needs at runtime:\n` +
1179
- ` ${chalk_1.default.cyan('"required_secrets": ["ANTHROPIC_API_KEY", "MY_TOKEN"]')}\n\n` +
1180
- ` If this agent genuinely needs no secrets, add an empty array:\n` +
1181
- ` ${chalk_1.default.cyan('"required_secrets": []')}\n\n` +
1182
- ` These are matched by name against your workspace secrets vault.\n` +
1183
- ` Use ${chalk_1.default.cyan('--no-required-secrets')} to skip this check.\n`);
1184
- const err = new errors_1.CliError('Missing required_secrets declaration', errors_1.ExitCodes.INVALID_INPUT);
1185
- err.displayed = true;
1186
- throw err;
1187
- }
1188
1189
  // Create the agent (server auto-assigns version)
1189
1190
  let result;
1190
1191
  try {
@@ -0,0 +1,198 @@
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.registerReplayCommand = registerReplayCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const config_1 = require("../lib/config");
9
+ const api_1 = require("../lib/api");
10
+ const errors_1 = require("../lib/errors");
11
+ const output_1 = require("../lib/output");
12
+ // ============================================
13
+ // HELPERS
14
+ // ============================================
15
+ async function resolveWorkspaceId(config, slug) {
16
+ const configFile = await (0, config_1.loadConfig)();
17
+ const targetSlug = slug ?? configFile.workspace;
18
+ if (!targetSlug) {
19
+ throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orch workspace use <slug>` first.');
20
+ }
21
+ const response = await (0, api_1.request)(config, 'GET', '/workspaces');
22
+ const workspace = response.workspaces.find((w) => w.slug === targetSlug);
23
+ if (!workspace) {
24
+ throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
25
+ }
26
+ return workspace.id;
27
+ }
28
+ function isUuid(value) {
29
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
30
+ }
31
+ function isShortUuid(value) {
32
+ return /^[0-9a-f]{7,}$/i.test(value) && !value.includes('/');
33
+ }
34
+ async function resolveShortRunId(config, workspaceId, shortId) {
35
+ const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/runs?limit=200&run_id_prefix=${encodeURIComponent(shortId)}`);
36
+ if (result.runs.length === 0) {
37
+ throw new errors_1.CliError(`No run found matching '${shortId}'.`);
38
+ }
39
+ if (result.runs.length > 1) {
40
+ throw new errors_1.CliError(`Ambiguous run ID '${shortId}' — matches ${result.runs.length} runs. Use more characters to narrow it down.`);
41
+ }
42
+ return result.runs[0].id;
43
+ }
44
+ function statusColor(status) {
45
+ if (!status)
46
+ return '-';
47
+ switch (status) {
48
+ case 'completed':
49
+ return chalk_1.default.green(status);
50
+ case 'failed':
51
+ return chalk_1.default.red(status);
52
+ case 'running':
53
+ case 'queued':
54
+ case 'claimed':
55
+ return chalk_1.default.yellow(status);
56
+ case 'timeout':
57
+ return chalk_1.default.red(status);
58
+ default:
59
+ return status;
60
+ }
61
+ }
62
+ function formatDuration(ms) {
63
+ if (ms == null)
64
+ return '-';
65
+ if (ms < 1000)
66
+ return `${ms}ms`;
67
+ if (ms < 60000)
68
+ return `${(ms / 1000).toFixed(1)}s`;
69
+ return `${(ms / 60000).toFixed(1)}m`;
70
+ }
71
+ const POLL_INTERVAL_MS = 2000;
72
+ const MAX_POLL_MS = 600000; // 10 minutes
73
+ // ============================================
74
+ // COMMAND REGISTRATION
75
+ // ============================================
76
+ function registerReplayCommand(program) {
77
+ program
78
+ .command('replay <run-id>')
79
+ .description('Replay a previous run. Re-executes with the same input and config from the original snapshot.')
80
+ .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
81
+ .option('--reason <text>', 'Reason for replay (stored in audit log)')
82
+ .option('--override-policy <id>', 'Override provider policy ID for this replay')
83
+ .option('--no-wait', 'Queue the replay and return immediately without waiting for results')
84
+ .option('--json', 'Output as JSON')
85
+ .action(async (runId, options) => {
86
+ const config = await (0, config_1.getResolvedConfig)();
87
+ if (!config.apiKey) {
88
+ throw new errors_1.CliError('Missing API key. Run `orch login` first.');
89
+ }
90
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
91
+ // Resolve short run IDs
92
+ let resolvedRunId = runId;
93
+ if (isUuid(runId)) {
94
+ resolvedRunId = runId;
95
+ }
96
+ else if (isShortUuid(runId)) {
97
+ resolvedRunId = await resolveShortRunId(config, workspaceId, runId);
98
+ }
99
+ else {
100
+ throw new errors_1.CliError(`Invalid run ID '${runId}'. Provide a full UUID or a short hex prefix (7+ characters).`);
101
+ }
102
+ // Submit replay request
103
+ const body = {};
104
+ if (options.reason)
105
+ body.reason = options.reason;
106
+ if (options.overridePolicy)
107
+ body.override_provider_policy_id = options.overridePolicy;
108
+ const replay = await (0, api_1.request)(config, 'POST', `/workspaces/${workspaceId}/runs/${resolvedRunId}/replay`, {
109
+ body: JSON.stringify(body),
110
+ headers: { 'Content-Type': 'application/json' },
111
+ });
112
+ if (options.json && options.wait === false) {
113
+ (0, output_1.printJson)(replay);
114
+ return;
115
+ }
116
+ if (options.wait === false) {
117
+ process.stdout.write(chalk_1.default.green('Replay queued.\n') +
118
+ ` Run ID: ${replay.run_id}\n` +
119
+ ` Job ID: ${replay.job_id}\n` +
120
+ ` Replaying: ${replay.replay_of_run_id}\n` +
121
+ chalk_1.default.gray('\nCheck status: orch logs ' + replay.run_id.slice(0, 8) + '\n'));
122
+ return;
123
+ }
124
+ // Poll for completion
125
+ if (!options.json) {
126
+ process.stderr.write(chalk_1.default.gray(`Replaying run ${resolvedRunId.slice(0, 8)}... `));
127
+ }
128
+ const startTime = Date.now();
129
+ let lastStatus = 'queued';
130
+ let pollResult = null;
131
+ while (Date.now() - startTime < MAX_POLL_MS) {
132
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
133
+ try {
134
+ pollResult = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/runs/${replay.run_id}`);
135
+ }
136
+ catch {
137
+ // Run may not exist yet if job worker hasn't picked it up
138
+ continue;
139
+ }
140
+ lastStatus = pollResult.status;
141
+ if (!options.json) {
142
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);
143
+ process.stderr.write(`\r${chalk_1.default.gray(`Replaying run ${resolvedRunId.slice(0, 8)}... ${statusColor(lastStatus)} (${elapsed}s)`)}`);
144
+ }
145
+ if (['completed', 'failed', 'timeout', 'dead_letter', 'cancelled'].includes(lastStatus)) {
146
+ break;
147
+ }
148
+ }
149
+ if (!options.json) {
150
+ process.stderr.write('\n');
151
+ }
152
+ // Fetch final logs
153
+ const logs = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/runs/${replay.run_id}/logs`);
154
+ if (options.json) {
155
+ (0, output_1.printJson)({
156
+ replay: {
157
+ run_id: replay.run_id,
158
+ replay_of_run_id: replay.replay_of_run_id,
159
+ job_id: replay.job_id,
160
+ },
161
+ result: logs,
162
+ });
163
+ return;
164
+ }
165
+ // Render results
166
+ renderReplayResult(replay, logs);
167
+ });
168
+ }
169
+ // ============================================
170
+ // RENDER REPLAY RESULT
171
+ // ============================================
172
+ function renderReplayResult(replay, logs) {
173
+ process.stdout.write(chalk_1.default.bold(`\nReplay ${replay.run_id}\n`) +
174
+ ` Original: ${replay.replay_of_run_id.slice(0, 8)}\n` +
175
+ ` Agent: ${logs.agent_name ?? '-'}@${logs.agent_version ?? '-'}\n` +
176
+ ` Status: ${statusColor(logs.run_status)}\n` +
177
+ ` Duration: ${formatDuration(logs.execution_time_ms)}\n`);
178
+ if (logs.exit_code != null) {
179
+ const exitLabel = logs.exit_code === 0 ? chalk_1.default.green(String(logs.exit_code)) : chalk_1.default.red(String(logs.exit_code));
180
+ process.stdout.write(` Exit code: ${exitLabel}\n`);
181
+ }
182
+ if (logs.output_data != null && Object.keys(logs.output_data).length > 0) {
183
+ process.stdout.write('\n' + chalk_1.default.bold.green('--- output ---') + '\n' +
184
+ JSON.stringify(logs.output_data, null, 2) + '\n');
185
+ }
186
+ if (logs.error_message) {
187
+ process.stdout.write('\n' + chalk_1.default.red.bold('Error:\n') + chalk_1.default.red(logs.error_message) + '\n');
188
+ }
189
+ if (logs.stdout) {
190
+ process.stdout.write('\n' + chalk_1.default.bold.cyan('--- stdout ---') + '\n' + logs.stdout + '\n');
191
+ }
192
+ if (logs.stderr) {
193
+ process.stdout.write('\n' + chalk_1.default.bold.yellow('--- stderr ---') + '\n' + logs.stderr + '\n');
194
+ }
195
+ process.stdout.write('\n');
196
+ // Footer hint
197
+ process.stdout.write(chalk_1.default.gray(`View trace: orch trace ${replay.run_id.slice(0, 8)}\n`));
198
+ }
@@ -0,0 +1,311 @@
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.registerTraceCommand = registerTraceCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const config_1 = require("../lib/config");
9
+ const api_1 = require("../lib/api");
10
+ const errors_1 = require("../lib/errors");
11
+ const output_1 = require("../lib/output");
12
+ // ============================================
13
+ // HELPERS
14
+ // ============================================
15
+ async function resolveWorkspaceId(config, slug) {
16
+ const configFile = await (0, config_1.loadConfig)();
17
+ const targetSlug = slug ?? configFile.workspace;
18
+ if (!targetSlug) {
19
+ throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orch workspace use <slug>` first.');
20
+ }
21
+ const response = await (0, api_1.request)(config, 'GET', '/workspaces');
22
+ const workspace = response.workspaces.find((w) => w.slug === targetSlug);
23
+ if (!workspace) {
24
+ throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
25
+ }
26
+ return workspace.id;
27
+ }
28
+ function isUuid(value) {
29
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
30
+ }
31
+ function isShortUuid(value) {
32
+ return /^[0-9a-f]{7,}$/i.test(value) && !value.includes('/');
33
+ }
34
+ async function resolveShortRunId(config, workspaceId, shortId) {
35
+ const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/runs?limit=200&run_id_prefix=${encodeURIComponent(shortId)}`);
36
+ if (result.runs.length === 0) {
37
+ throw new errors_1.CliError(`No run found matching '${shortId}'.`);
38
+ }
39
+ if (result.runs.length > 1) {
40
+ throw new errors_1.CliError(`Ambiguous run ID '${shortId}' — matches ${result.runs.length} runs. Use more characters to narrow it down.`);
41
+ }
42
+ return result.runs[0].id;
43
+ }
44
+ function statusColor(status) {
45
+ if (!status)
46
+ return '-';
47
+ switch (status) {
48
+ case 'completed':
49
+ return chalk_1.default.green(status);
50
+ case 'failed':
51
+ return chalk_1.default.red(status);
52
+ case 'running':
53
+ return chalk_1.default.yellow(status);
54
+ case 'timeout':
55
+ return chalk_1.default.red(status);
56
+ default:
57
+ return status;
58
+ }
59
+ }
60
+ function formatDuration(ms) {
61
+ if (ms == null)
62
+ return '-';
63
+ if (ms < 1000)
64
+ return `${ms}ms`;
65
+ if (ms < 60000)
66
+ return `${(ms / 1000).toFixed(1)}s`;
67
+ return `${(ms / 60000).toFixed(1)}m`;
68
+ }
69
+ function formatCost(usd) {
70
+ if (usd == null || usd === 0)
71
+ return '-';
72
+ if (usd < 0.01)
73
+ return `$${usd.toFixed(6)}`;
74
+ return `$${usd.toFixed(4)}`;
75
+ }
76
+ function formatTokens(input, output) {
77
+ const parts = [];
78
+ if (input)
79
+ parts.push(`${input.toLocaleString()} in`);
80
+ if (output)
81
+ parts.push(`${output.toLocaleString()} out`);
82
+ return parts.length > 0 ? parts.join(', ') : '-';
83
+ }
84
+ // ============================================
85
+ // COMMAND REGISTRATION
86
+ // ============================================
87
+ function registerTraceCommand(program) {
88
+ program
89
+ .command('trace <run-id>')
90
+ .description('View the execution trace for a run. Shows LLM calls, tool calls, decisions, and errors in timeline order.')
91
+ .option('--workspace <slug>', 'Workspace slug (default: current workspace)')
92
+ .option('--json', 'Output as JSON')
93
+ .action(async (runId, options) => {
94
+ const config = await (0, config_1.getResolvedConfig)();
95
+ if (!config.apiKey) {
96
+ throw new errors_1.CliError('Missing API key. Run `orch login` first.');
97
+ }
98
+ const workspaceId = await resolveWorkspaceId(config, options.workspace);
99
+ // Resolve short run IDs
100
+ let resolvedRunId = runId;
101
+ if (isUuid(runId)) {
102
+ resolvedRunId = runId;
103
+ }
104
+ else if (isShortUuid(runId)) {
105
+ resolvedRunId = await resolveShortRunId(config, workspaceId, runId);
106
+ }
107
+ else {
108
+ throw new errors_1.CliError(`Invalid run ID '${runId}'. Provide a full UUID or a short hex prefix (7+ characters).`);
109
+ }
110
+ // Fetch run detail for context
111
+ const run = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/runs/${resolvedRunId}`);
112
+ // Fetch trace header
113
+ let trace;
114
+ try {
115
+ const traceResp = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/runs/${resolvedRunId}/trace`);
116
+ trace = traceResp.trace;
117
+ }
118
+ catch {
119
+ throw new errors_1.CliError(`No trace available for run ${resolvedRunId.slice(0, 8)}. Traces are captured for cloud runs only.`);
120
+ }
121
+ // Fetch all trace events (paginate if needed)
122
+ const allEvents = [];
123
+ let offset = 0;
124
+ const pageSize = 500;
125
+ while (true) {
126
+ const eventsResp = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/traces/${trace.id}/events?limit=${pageSize}&offset=${offset}`);
127
+ allEvents.push(...eventsResp.events);
128
+ if (eventsResp.next_cursor === null || allEvents.length >= eventsResp.total) {
129
+ break;
130
+ }
131
+ offset = parseInt(eventsResp.next_cursor, 10);
132
+ }
133
+ if (options.json) {
134
+ (0, output_1.printJson)({ run, trace, events: allEvents });
135
+ return;
136
+ }
137
+ renderTrace(run, trace, allEvents);
138
+ });
139
+ }
140
+ // ============================================
141
+ // TRACE RENDERING
142
+ // ============================================
143
+ function renderTrace(run, trace, events) {
144
+ // Header
145
+ process.stdout.write(chalk_1.default.bold(`\nTrace for run ${run.id}\n`) +
146
+ ` Agent: ${run.agent_name ?? '-'}@${run.agent_version ?? '-'}\n` +
147
+ ` Status: ${statusColor(run.status)}\n` +
148
+ ` Duration: ${formatDuration(run.duration_ms)}\n` +
149
+ ` Source: ${run.trigger_source ?? '-'}\n`);
150
+ // Aggregate stats
151
+ const stats = computeStats(events);
152
+ if (stats.totalLlmCalls > 0 || stats.totalToolCalls > 0) {
153
+ process.stdout.write('\n' + chalk_1.default.bold(' Summary\n'));
154
+ if (stats.totalLlmCalls > 0) {
155
+ process.stdout.write(` LLM calls: ${stats.totalLlmCalls}` +
156
+ ` (${formatTokens(stats.totalTokenInput, stats.totalTokenOutput)})` +
157
+ ` cost: ${formatCost(stats.totalCostUsd)}\n`);
158
+ }
159
+ if (stats.totalToolCalls > 0) {
160
+ process.stdout.write(` Tool calls: ${stats.totalToolCalls}\n`);
161
+ }
162
+ if (stats.providers.length > 0) {
163
+ process.stdout.write(` Providers: ${stats.providers.join(', ')}\n`);
164
+ }
165
+ if (stats.totalErrors > 0) {
166
+ process.stdout.write(` Errors: ${chalk_1.default.red(String(stats.totalErrors))}\n`);
167
+ }
168
+ }
169
+ if (events.length === 0) {
170
+ process.stdout.write(chalk_1.default.gray('\n No trace events recorded.\n\n'));
171
+ return;
172
+ }
173
+ // Timeline
174
+ process.stdout.write('\n' + chalk_1.default.bold(' Timeline\n'));
175
+ for (const event of events) {
176
+ renderEvent(event);
177
+ }
178
+ process.stdout.write('\n');
179
+ // Footer hint
180
+ process.stdout.write(chalk_1.default.gray(`View logs: orch logs ${run.id.slice(0, 8)}\n`));
181
+ process.stdout.write(chalk_1.default.gray(`Replay: orch replay ${run.id.slice(0, 8)}\n`));
182
+ }
183
+ function computeStats(events) {
184
+ const stats = {
185
+ totalLlmCalls: 0,
186
+ totalToolCalls: 0,
187
+ totalErrors: 0,
188
+ totalTokenInput: 0,
189
+ totalTokenOutput: 0,
190
+ totalCostUsd: 0,
191
+ providers: [],
192
+ };
193
+ const providerSet = new Set();
194
+ for (const e of events) {
195
+ if (e.event_type === 'llm_call_succeeded') {
196
+ stats.totalLlmCalls++;
197
+ stats.totalTokenInput += e.token_input ?? 0;
198
+ stats.totalTokenOutput += e.token_output ?? 0;
199
+ stats.totalCostUsd += e.cost_usd ?? 0;
200
+ if (e.provider)
201
+ providerSet.add(e.provider);
202
+ }
203
+ else if (e.event_type === 'llm_call_failed') {
204
+ stats.totalLlmCalls++;
205
+ stats.totalErrors++;
206
+ if (e.provider)
207
+ providerSet.add(e.provider);
208
+ }
209
+ else if (e.event_type === 'tool_call_succeeded') {
210
+ stats.totalToolCalls++;
211
+ }
212
+ else if (e.event_type === 'tool_call_failed') {
213
+ stats.totalToolCalls++;
214
+ stats.totalErrors++;
215
+ }
216
+ else if (e.event_type === 'error') {
217
+ stats.totalErrors++;
218
+ }
219
+ else if (e.event_type === 'policy_violation') {
220
+ stats.totalErrors++;
221
+ }
222
+ }
223
+ stats.providers = Array.from(providerSet);
224
+ return stats;
225
+ }
226
+ function renderEvent(event) {
227
+ const seq = chalk_1.default.gray(` #${String(event.sequence_no).padStart(2, ' ')} `);
228
+ switch (event.event_type) {
229
+ case 'llm_call_started': {
230
+ const provider = event.provider ?? '?';
231
+ const model = event.model ?? '?';
232
+ process.stdout.write(seq + chalk_1.default.blue('LLM ') + chalk_1.default.gray(`${provider}/${model} started\n`));
233
+ break;
234
+ }
235
+ case 'llm_call_succeeded': {
236
+ const provider = event.provider ?? '?';
237
+ const model = event.model ?? '?';
238
+ const tokens = formatTokens(event.token_input, event.token_output);
239
+ const cost = formatCost(event.cost_usd);
240
+ const dur = formatDuration(event.duration_ms);
241
+ process.stdout.write(seq + chalk_1.default.green('LLM ') +
242
+ `${provider}/${model}` +
243
+ chalk_1.default.gray(` | ${tokens} | ${cost} | ${dur}`) + '\n');
244
+ break;
245
+ }
246
+ case 'llm_call_failed': {
247
+ const provider = event.provider ?? '?';
248
+ const model = event.model ?? '?';
249
+ const errMsg = event.error_message || event.error_type || 'unknown error';
250
+ const dur = formatDuration(event.duration_ms);
251
+ process.stdout.write(seq + chalk_1.default.red('LLM ') +
252
+ `${provider}/${model} ` +
253
+ chalk_1.default.red(errMsg) +
254
+ chalk_1.default.gray(` | ${dur}`) + '\n');
255
+ break;
256
+ }
257
+ case 'tool_call_started': {
258
+ const toolName = event.payload?.tool_name || '?';
259
+ process.stdout.write(seq + chalk_1.default.cyan('TOOL ') + chalk_1.default.gray(`${toolName} started\n`));
260
+ break;
261
+ }
262
+ case 'tool_call_succeeded': {
263
+ const toolName = event.payload?.tool_name || '?';
264
+ const dur = formatDuration(event.duration_ms);
265
+ process.stdout.write(seq + chalk_1.default.green('TOOL ') + `${toolName}` +
266
+ chalk_1.default.gray(` | ${dur}`) + '\n');
267
+ break;
268
+ }
269
+ case 'tool_call_failed': {
270
+ const toolName = event.payload?.tool_name || '?';
271
+ const errMsg = event.error_message || event.error_type || 'unknown error';
272
+ const dur = formatDuration(event.duration_ms);
273
+ process.stdout.write(seq + chalk_1.default.red('TOOL ') + `${toolName} ` +
274
+ chalk_1.default.red(errMsg) +
275
+ chalk_1.default.gray(` | ${dur}`) + '\n');
276
+ break;
277
+ }
278
+ case 'decision': {
279
+ const desc = event.payload?.description || 'decision';
280
+ process.stdout.write(seq + chalk_1.default.magenta('DECIDE ') + chalk_1.default.gray(desc) + '\n');
281
+ break;
282
+ }
283
+ case 'fallback_transition': {
284
+ const from = event.payload?.from_provider || '?';
285
+ const to = event.payload?.to_provider || '?';
286
+ const reason = event.payload?.reason || '';
287
+ process.stdout.write(seq + chalk_1.default.yellow('FALLBACK ') +
288
+ `${from} -> ${to}` +
289
+ (reason ? chalk_1.default.gray(` (${reason})`) : '') + '\n');
290
+ break;
291
+ }
292
+ case 'policy_violation': {
293
+ const vType = event.payload?.violation_type || 'violation';
294
+ const detail = event.payload?.detail || '';
295
+ process.stdout.write(seq + chalk_1.default.red('POLICY ') + chalk_1.default.red(vType) +
296
+ (detail ? chalk_1.default.gray(` — ${detail}`) : '') + '\n');
297
+ break;
298
+ }
299
+ case 'error': {
300
+ const errType = event.error_type || 'error';
301
+ const errMsg = event.error_message || '';
302
+ process.stdout.write(seq + chalk_1.default.red('ERROR ') + chalk_1.default.red(errType) +
303
+ (errMsg ? chalk_1.default.gray(` — ${errMsg}`) : '') + '\n');
304
+ break;
305
+ }
306
+ default: {
307
+ // Unknown event type — show raw
308
+ process.stdout.write(seq + chalk_1.default.gray(event.event_type) + '\n');
309
+ }
310
+ }
311
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.3.87",
3
+ "version": "0.3.89",
4
4
  "description": "Command-line interface for orchagent — deploy and run AI agents for your team",
5
5
  "license": "MIT",
6
6
  "author": "orchagent <hello@orchagent.io>",
@@ -1,23 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerCallCommand = registerCallCommand;
4
- /**
5
- * Deprecated: The 'call' command has been merged into 'run'.
6
- * Cloud execution is now the default behavior of 'orchagent run'.
7
- * This file provides a thin alias that prints a deprecation notice and exits.
8
- */
9
- function registerCallCommand(program) {
10
- program
11
- .command('call')
12
- .description('Deprecated: use "orchagent run" instead')
13
- .allowUnknownOption()
14
- .allowExcessArguments()
15
- .action(() => {
16
- process.stderr.write(`The 'call' command has been merged into 'run'.\n\n` +
17
- `Cloud execution is now the default behavior of 'orchagent run':\n` +
18
- ` orchagent run <agent> --data '{...}' # runs on server (cloud)\n` +
19
- ` orchagent run <agent> --local --data '...' # runs locally\n\n` +
20
- `Replace 'orchagent call' with 'orchagent run' in your commands.\n`);
21
- process.exit(1);
22
- });
23
- }
@@ -1,22 +0,0 @@
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.registerKeysCommand = registerKeysCommand;
7
- const chalk_1 = __importDefault(require("chalk"));
8
- function registerKeysCommand(program) {
9
- program
10
- .command('keys')
11
- .description('(Deprecated) Use "orch secrets" instead')
12
- .allowUnknownOption(true)
13
- .action(() => {
14
- process.stderr.write(chalk_1.default.yellow('\nThe `orch keys` command has been removed.\n\n') +
15
- `LLM API keys are now managed through the unified workspace secrets vault.\n` +
16
- `Use ${chalk_1.default.cyan('orch secrets')} instead:\n\n` +
17
- ` ${chalk_1.default.cyan('orch secrets set ANTHROPIC_API_KEY <key>')} Add an LLM key\n` +
18
- ` ${chalk_1.default.cyan('orch secrets list')} List all secrets\n` +
19
- ` ${chalk_1.default.cyan('orch secrets delete ANTHROPIC_API_KEY')} Remove a key\n\n`);
20
- process.exit(1);
21
- });
22
- }