@orchagent/cli 0.3.87 → 0.3.88

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.
@@ -40,6 +40,9 @@ const diff_1 = require("./diff");
40
40
  const health_1 = require("./health");
41
41
  const dev_1 = require("./dev");
42
42
  const estimate_1 = require("./estimate");
43
+ const replay_1 = require("./replay");
44
+ const trace_1 = require("./trace");
45
+ const metrics_1 = require("./metrics");
43
46
  function registerCommands(program) {
44
47
  (0, login_1.registerLoginCommand)(program);
45
48
  (0, logout_1.registerLogoutCommand)(program);
@@ -80,4 +83,7 @@ function registerCommands(program) {
80
83
  (0, health_1.registerHealthCommand)(program);
81
84
  (0, dev_1.registerDevCommand)(program);
82
85
  (0, estimate_1.registerEstimateCommand)(program);
86
+ (0, replay_1.registerReplayCommand)(program);
87
+ (0, trace_1.registerTraceCommand)(program);
88
+ (0, metrics_1.registerMetricsCommand)(program);
83
89
  }
@@ -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
+ }
@@ -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.88",
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>",