@agenttrace-io/cli 0.1.9

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.
package/dist/index.js ADDED
@@ -0,0 +1,1850 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentTrace CLI
4
+ * Command-line interface for querying traces, runs, stats and exports
5
+ */
6
+ import { AgentTrace, TraceStorage, } from '@agenttrace-io/sdk';
7
+ import { startDashboard } from '@agenttrace-io/dashboard';
8
+ import { existsSync, writeFileSync } from 'node:fs';
9
+ import { fileURLToPath } from 'node:url';
10
+ export const VERSION = '0.1.0';
11
+ /** Published npm package name. */
12
+ export const PACKAGE_NAME = '@agenttrace-io/cli';
13
+ function getDbPath() {
14
+ return process.env.AGENTTRACE_DB_PATH || './agenttrace.db';
15
+ }
16
+ // ANSI colors for status
17
+ const GREEN = '\x1b[32m';
18
+ const RED = '\x1b[31m';
19
+ const YELLOW = '\x1b[33m';
20
+ const RESET = '\x1b[0m';
21
+ const NO_COLOR = process.env.NO_COLOR === '1' ||
22
+ process.env.NO_COLOR === 'true' ||
23
+ process.argv.includes('--no-color');
24
+ function color(s, c) {
25
+ if (NO_COLOR)
26
+ return s;
27
+ return c + s + RESET;
28
+ }
29
+ function colorizeStatus(status) {
30
+ const s = status.toLowerCase();
31
+ const plain = status;
32
+ if (s === 'success')
33
+ return color(plain, GREEN);
34
+ if (s === 'error' || s === 'failure')
35
+ return color(plain, RED);
36
+ if (s === 'running' || s === 'timeout')
37
+ return color(plain, YELLOW);
38
+ return plain;
39
+ }
40
+ function stripAnsi(str) {
41
+ // eslint-disable-next-line no-control-regex
42
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
43
+ }
44
+ function visibleLength(str) {
45
+ return stripAnsi(str).length;
46
+ }
47
+ function pad(str, width) {
48
+ const vis = visibleLength(str);
49
+ return str + ' '.repeat(Math.max(0, width - vis));
50
+ }
51
+ function fmtNum(n) {
52
+ if (n == null)
53
+ return '0';
54
+ return Math.round(Number(n)).toLocaleString();
55
+ }
56
+ function fmtCost(c) {
57
+ if (c == null)
58
+ return '$0.0000';
59
+ return '$' + Number(c).toFixed(4);
60
+ }
61
+ function fmtLatency(ms) {
62
+ if (ms == null)
63
+ return '0ms';
64
+ if (ms < 1000)
65
+ return Math.round(ms) + 'ms';
66
+ return (ms / 1000).toFixed(1) + 's';
67
+ }
68
+ function _asciiSpark(values, width = 10) {
69
+ if (!values || values.length === 0)
70
+ return '';
71
+ const max = Math.max(...values, 1);
72
+ const blocks = ' ▁▂▃▄▅▆▇█';
73
+ let out = '';
74
+ for (let i = 0; i < Math.min(width, values.length); i++) {
75
+ const v = values[values.length - Math.min(width, values.length) + i] || 0;
76
+ const idx = Math.min(blocks.length - 1, Math.max(0, Math.floor((v / max) * (blocks.length - 1))));
77
+ out += blocks[idx];
78
+ }
79
+ return out;
80
+ }
81
+ function printTable(headers, rows) {
82
+ if (rows.length === 0)
83
+ return;
84
+ const widths = headers.map((h, i) => {
85
+ const rowMax = Math.max(0, ...rows.map((r) => visibleLength(r[i] ?? '')));
86
+ return Math.max(h.length, rowMax);
87
+ });
88
+ // header
89
+ console.log(headers.map((h, i) => pad(h, widths[i] ?? 0)).join(' '));
90
+ // separator
91
+ console.log(widths.map((w) => '-'.repeat(w ?? 0)).join(' '));
92
+ // rows
93
+ for (const row of rows) {
94
+ console.log(row.map((c, i) => pad(c, widths[i] ?? 0)).join(' '));
95
+ }
96
+ }
97
+ function printRunsTable(runs) {
98
+ const headers = ['ID', 'Name', 'Status', 'Traces', 'Tokens', 'Cost', '▂', 'Started'];
99
+ // build cost spark history per run from recent context (simple per-row mini bar)
100
+ const maxCost = Math.max(0.0001, ...runs.map((r) => r.totalCostUsd || 0));
101
+ const rows = runs.map((r) => {
102
+ const cost = r.totalCostUsd || 0;
103
+ const barLen = Math.max(1, Math.min(8, Math.round((cost / maxCost) * 8)));
104
+ const bar = '█'.repeat(barLen) + ' '.repeat(8 - barLen);
105
+ return [
106
+ (r.id || '').substring(0, 8),
107
+ r.name || '',
108
+ colorizeStatus(r.status || ''),
109
+ fmtNum(r.traceCount ?? 0),
110
+ fmtNum(r.totalTokens?.totalTokens ?? 0),
111
+ fmtCost(cost),
112
+ bar,
113
+ r.startedAt ? new Date(r.startedAt).toISOString().slice(0, 19).replace('T', ' ') : '',
114
+ ];
115
+ });
116
+ printTable(headers, rows);
117
+ }
118
+ function printTracesTable(traces) {
119
+ const headers = ['ID', 'Name', 'Status', 'Latency', 'Tokens', 'Cost', 'Created'];
120
+ const rows = traces.map((t) => [
121
+ (t.id || '').substring(0, 8),
122
+ t.name || '',
123
+ colorizeStatus(t.status || ''),
124
+ fmtLatency(t.latencyMs ?? 0),
125
+ fmtNum(t.tokens?.totalTokens ?? 0),
126
+ fmtCost(t.costUsd ?? 0),
127
+ t.createdAt ? new Date(t.createdAt).toISOString().slice(0, 19).replace('T', ' ') : '',
128
+ ]);
129
+ printTable(headers, rows);
130
+ }
131
+ function printStats(stats) {
132
+ console.log('AgentTrace Statistics');
133
+ console.log('=====================');
134
+ console.log(`Total Runs: ${fmtNum(stats.totalRuns ?? 0)}`);
135
+ console.log(`Total Traces: ${fmtNum(stats.totalTraces ?? 0)}`);
136
+ const rate = ((stats.successRate ?? 0) * 100).toFixed(1);
137
+ console.log(`Success Rate: ${rate}%`);
138
+ console.log(`Avg Latency: ${fmtLatency(stats.avgLatencyMs ?? 0)}`);
139
+ console.log(`Total Cost: ${fmtCost(stats.totalCostUsd ?? 0)}`);
140
+ console.log(`Total Tokens: ${fmtNum(stats.totalTokens ?? 0)}`);
141
+ console.log(`Avg Tokens: ${fmtNum(stats.avgTokensPerTrace ?? 0)}`);
142
+ // Simple trend hint (computed if we can infer from SDK; otherwise neutral)
143
+ const trendNote = stats.totalCostUsd && stats.totalCostUsd > 0 ? ' (↑ track daily with `costs --daily`)' : '';
144
+ console.log(`Cost trend:${trendNote}`);
145
+ if (stats.topTools && stats.topTools.length > 0) {
146
+ console.log('\nTop Tools:');
147
+ for (const t of stats.topTools.slice(0, 5)) {
148
+ console.log(` ${t.name}: ${fmtNum(t.count)} (avg ${t.avgLatencyMs}ms)`);
149
+ }
150
+ }
151
+ if (stats.topErrors && stats.topErrors.length > 0) {
152
+ console.log('\nTop Errors:');
153
+ for (const e of stats.topErrors.slice(0, 5)) {
154
+ console.log(` ${e.error}: ${fmtNum(e.count)}`);
155
+ }
156
+ }
157
+ }
158
+ function printCosts(breakdown, daily) {
159
+ const title = daily ? 'Daily Cost Breakdown' : 'Cost Breakdown by Model';
160
+ console.log(title);
161
+ console.log('='.repeat(title.length));
162
+ const data = daily ? breakdown.costByDay : breakdown.costByModel;
163
+ const entries = Object.entries(data);
164
+ if (entries.length === 0) {
165
+ console.log('No costs recorded.');
166
+ }
167
+ else {
168
+ // sort by cost desc for models, chrono for days
169
+ const sorted = daily
170
+ ? entries.sort(([a], [b]) => a.localeCompare(b))
171
+ : entries.sort(([, c1], [, c2]) => c2 - c1);
172
+ for (const [key, cost] of sorted) {
173
+ console.log(` ${key}: $${cost.toFixed(4)}`);
174
+ }
175
+ }
176
+ console.log(`\nTotal: $${breakdown.totalCostUsd.toFixed(4)}`);
177
+ }
178
+ function printTraceTree(node, prefix = '', isLast = true) {
179
+ if (!node || !node.trace)
180
+ return;
181
+ const t = node.trace;
182
+ const branch = prefix + (isLast ? '└── ' : '├── ');
183
+ const status = colorizeStatus(t.status || '');
184
+ const shortId = (t.id || '').substring(0, 8);
185
+ console.log(`${branch}${shortId} ${t.name || ''} ${status} ${t.latencyMs ?? 0}ms $${(t.costUsd ?? 0).toFixed(4)}`);
186
+ const childPrefix = prefix + (isLast ? ' ' : '│ ');
187
+ const children = (node.children || []);
188
+ children.forEach((child, idx) => {
189
+ const lastChild = idx === children.length - 1;
190
+ printTraceTree(child, childPrefix, lastChild);
191
+ });
192
+ }
193
+ function printUsage() {
194
+ console.log(`Usage: agenttrace-io <command> [options] (alias: agenttrace)
195
+
196
+ Commands:
197
+ init Create empty agenttrace.db in current dir
198
+ wrap Trace any CLI command (zero-config)
199
+ dashboard Start the local dashboard server
200
+ runs List recent runs (most recent first)
201
+ traces List traces (most recent first)
202
+ stats Show summary statistics
203
+ costs Show cost breakdown by model (or --daily)
204
+ export Export traces to JSON or CSV
205
+ benchmark Run performance benchmark suite (prints JSON results)
206
+ tree Show parent/child/related trace tree (multi-agent)
207
+ alerts Manage alerts: list | test --name N | history
208
+ health Check health of gateway, dashboard, and database
209
+ self-stats Show self-tracked usage stats
210
+ budget Manage per-agent token budgets: set | list | status | check
211
+ who Show active agents (usage tracking)
212
+ cost Show agent cost breakdown (periods + by agent/model)
213
+ sessions List agent sessions with aggregates
214
+ activity Show recent agent activity timeline
215
+ webhook Manage webhooks: add <url> <events...> | list | remove <id> | test <id>
216
+ cleanup Manually run data retention cleanup (deletes expired traces, runs, usage)
217
+ retention Manage data retention policy: show | set <days> [--interval H]
218
+ version Show CLI version
219
+
220
+ Options (by command):
221
+ runs, traces:
222
+ --limit N Number of results (default: runs=20, traces=50)
223
+ --status FILTER Comma-separated statuses (success,error,failure,running,timeout)
224
+ traces, export, costs:
225
+ --run-id ID Filter by run ID
226
+ export:
227
+ --format json|csv Output format (default: json)
228
+ --output FILE Write to file instead of stdout
229
+ costs:
230
+ --daily Breakdown costs by day instead of by model
231
+ tree:
232
+ --trace-id ID Trace ID to display tree for (required)
233
+ alerts:
234
+ list List configured alerts
235
+ test --name NAME Test delivery for alert (forces condition + ignores cooldown)
236
+ history Show alert trigger history
237
+ who:
238
+ --active Only agents active in last 30min
239
+ --type TYPE Filter by agent type
240
+ --limit N Max agents to show (default 50)
241
+ cost:
242
+ --from DATE Start date (YYYY-MM-DD or ISO)
243
+ --to DATE End date (YYYY-MM-DD or ISO)
244
+ --agent NAME Filter to specific agent
245
+ --format json|table Output format (default: table)
246
+ sessions:
247
+ --agent NAME Filter by agent name
248
+ --active Only sessions with recent activity (30min)
249
+ --limit N Max sessions (default 20)
250
+ activity:
251
+ --agent NAME Filter by agent
252
+ --type ACTION Filter by action type
253
+ --limit N Max entries (default 30)
254
+ --since DURATION e.g. 1h, 30m, 2d (from now backwards)
255
+ webhook:
256
+ add --url <url> --events <e1,e2,...>
257
+ Register a webhook for the given event types
258
+ list List all configured webhooks
259
+ remove --id <id> Remove a webhook by ID (prefix match)
260
+ test --id <id> Send a test payload to a webhook by ID
261
+ Events: trace.complete, trace.error, run.complete, run.error, cost.threshold, agent.inactive
262
+ cleanup:
263
+ --days N Override retention days (default: use policy setting)
264
+ --dry-run Show what would be deleted without deleting
265
+ retention:
266
+ show Show current retention policy and storage stats
267
+ set <days> Set retention policy (days); optional --interval H
268
+
269
+ Global:
270
+ --json Emit machine-readable JSON (for runs, traces, stats, costs, export, self-stats)
271
+ --help Show this help
272
+
273
+ Examples:
274
+ agenttrace-io init
275
+ agenttrace-io wrap claude "Write a hello world function"
276
+ agenttrace-io runs --limit 5 --status success,running
277
+ agenttrace-io traces --run-id 123e4567 --json
278
+ agenttrace-io export --format csv --output out.csv --run-id abc
279
+ agenttrace-io dashboard
280
+ agenttrace-io costs
281
+ agenttrace-io costs --daily --json
282
+ agenttrace-io costs --run-id abc123
283
+ agenttrace-io alerts list
284
+ agenttrace-io alerts test --name high-error-rate
285
+ agenttrace-io alerts history
286
+ agenttrace-io tree --trace-id abc123def
287
+ agenttrace-io self-stats
288
+ agenttrace-io self-stats --json
289
+ agenttrace-io who --active --limit 10
290
+ agenttrace-io cost --format table
291
+ agenttrace-io cost --agent researcher-1 --from 2026-01-01
292
+ agenttrace-io sessions --active
293
+ agenttrace-io activity --since 2h --limit 20
294
+ agenttrace-io cleanup
295
+ agenttrace-io cleanup --days 7 --dry-run
296
+ agenttrace-io retention show
297
+ agenttrace-io retention set 60
298
+ agenttrace-io retention set 90 --interval 12
299
+ agenttrace-io webhook add https://example.com/hook trace.complete run.complete
300
+ agenttrace-io webhook list
301
+ agenttrace-io webhook test abc12345
302
+ agenttrace-io webhook remove abc12345
303
+ npx agenttrace-io version
304
+ # alias also works: npx agenttrace ...
305
+ `);
306
+ }
307
+ function isSelfTracked(t) {
308
+ const meta = t.metadata || t.metadata || {};
309
+ return meta.selfTracked === true || t.name?.startsWith?.('self:') === true;
310
+ }
311
+ function getDayStart(ts) {
312
+ const d = new Date(ts);
313
+ d.setHours(0, 0, 0, 0);
314
+ return d.getTime();
315
+ }
316
+ function getWeekStart(ts) {
317
+ const d = new Date(ts);
318
+ const day = d.getDay();
319
+ const diff = d.getDate() - day + (day === 0 ? -6 : 1); // monday
320
+ d.setDate(diff);
321
+ d.setHours(0, 0, 0, 0);
322
+ return d.getTime();
323
+ }
324
+ function printSelfStats(storage, useJson) {
325
+ // Fetch recent data (no hard limit to include historical self usage)
326
+ const runs = storage.getRuns(5000);
327
+ const traces = storage.getTraces({ limit: 20000 });
328
+ const selfRuns = runs.filter((r) => isSelfTracked(r));
329
+ const selfTraces = traces.filter((t) => isSelfTracked(t));
330
+ const now = Date.now();
331
+ const todayStart = getDayStart(now);
332
+ const weekStart = getWeekStart(now);
333
+ const todayTraces = selfTraces.filter((t) => (t.createdAt || 0) >= todayStart);
334
+ const weekTraces = selfTraces.filter((t) => (t.createdAt || 0) >= weekStart);
335
+ // Today's summary
336
+ const todayActions = todayTraces.length;
337
+ const todayTokens = todayTraces.reduce((s, t) => s + (t.tokens?.totalTokens || 0), 0);
338
+ const todayCost = todayTraces.reduce((s, t) => s + (t.costUsd || 0), 0);
339
+ const todaySessions = new Set(todayTraces.map((t) => t.runId)).size;
340
+ // Week summary
341
+ const weekActions = weekTraces.length;
342
+ const weekTokens = weekTraces.reduce((s, t) => s + (t.tokens?.totalTokens || 0), 0);
343
+ const weekCost = weekTraces.reduce((s, t) => s + (t.costUsd || 0), 0);
344
+ const weekSessions = new Set(weekTraces.map((t) => t.runId)).size;
345
+ // Top actions by type (use actionType from meta or derive from name)
346
+ const actionCounts = {};
347
+ for (const t of selfTraces) {
348
+ const meta = t.metadata || {};
349
+ const at = meta.actionType ||
350
+ (t.name || '').replace(/^self:/, '').split(':')[0] ||
351
+ 'unknown';
352
+ actionCounts[at] = (actionCounts[at] || 0) + 1;
353
+ }
354
+ const topActions = Object.entries(actionCounts)
355
+ .sort(([, a], [, b]) => b - a)
356
+ .slice(0, 10)
357
+ .map(([type, count]) => ({ type, count }));
358
+ // Cost breakdown (self only) - by day for recent
359
+ const costByDay = {};
360
+ for (const t of selfTraces) {
361
+ if (!t.createdAt)
362
+ continue;
363
+ const day = new Date(t.createdAt).toISOString().slice(0, 10);
364
+ costByDay[day] = (costByDay[day] || 0) + (t.costUsd || 0);
365
+ }
366
+ const totalSelfCost = Object.values(costByDay).reduce((s, v) => s + v, 0);
367
+ // Active sessions
368
+ const activeSessions = selfRuns.filter((r) => r.status === 'running').length;
369
+ const activeSessionIds = selfRuns
370
+ .filter((r) => r.status === 'running')
371
+ .map((r) => (r.id || '').substring(0, 8));
372
+ const summary = {
373
+ today: {
374
+ actions: todayActions,
375
+ tokens: todayTokens,
376
+ costUsd: Number(todayCost.toFixed(6)),
377
+ sessions: todaySessions,
378
+ },
379
+ week: {
380
+ actions: weekActions,
381
+ tokens: weekTokens,
382
+ costUsd: Number(weekCost.toFixed(6)),
383
+ sessions: weekSessions,
384
+ },
385
+ topActions,
386
+ costBreakdown: {
387
+ totalCostUsd: Number(totalSelfCost.toFixed(6)),
388
+ costByDay,
389
+ },
390
+ activeSessions,
391
+ activeSessionIds,
392
+ totalSelfTraces: selfTraces.length,
393
+ totalSelfRuns: selfRuns.length,
394
+ };
395
+ if (useJson) {
396
+ console.log(JSON.stringify(summary, null, 2));
397
+ return;
398
+ }
399
+ console.log('AgentTrace Self-Tracking Stats');
400
+ console.log('==============================================');
401
+ console.log('');
402
+ console.log("Today's Activity:");
403
+ console.log(` Actions: ${summary.today.actions}`);
404
+ console.log(` Tokens: ${summary.today.tokens}`);
405
+ console.log(` Cost: $${summary.today.costUsd}`);
406
+ console.log(` Sessions: ${summary.today.sessions}`);
407
+ console.log('');
408
+ console.log('This Week:');
409
+ console.log(` Actions: ${summary.week.actions}`);
410
+ console.log(` Tokens: ${summary.week.tokens}`);
411
+ console.log(` Cost: $${summary.week.costUsd}`);
412
+ console.log(` Sessions: ${summary.week.sessions}`);
413
+ console.log('');
414
+ if (topActions.length > 0) {
415
+ console.log('Top Actions by Type:');
416
+ const maxA = Math.max(...topActions.map((a) => a.count), 1);
417
+ for (const a of topActions.slice(0, 8)) {
418
+ const bar = '█'.repeat(Math.max(1, Math.round((a.count / maxA) * 12)));
419
+ console.log(` ${a.type.padEnd(14)} ${String(a.count).padStart(4)} ${bar}`);
420
+ }
421
+ console.log('');
422
+ }
423
+ console.log('Cost Breakdown (self-tracked):');
424
+ console.log(` Total: $${summary.costBreakdown.totalCostUsd}`);
425
+ const sortedDays = Object.keys(costByDay).sort();
426
+ if (sortedDays.length > 0) {
427
+ for (const d of sortedDays.slice(-7)) {
428
+ const c = costByDay[d] ?? 0;
429
+ console.log(` ${d}: $${c.toFixed(6)}`);
430
+ }
431
+ }
432
+ console.log('');
433
+ console.log(`Active Sessions: ${activeSessions}${activeSessionIds.length ? ' (' + activeSessionIds.join(', ') + ')' : ''}`);
434
+ if (summary.totalSelfTraces === 0) {
435
+ console.log('\n(No self-tracked data yet. Use SelfTracker in your agent to record actions.)');
436
+ }
437
+ }
438
+ // ---- Agent usage CLI helpers (who, cost, sessions, activity) ----
439
+ function parseSinceDuration(s) {
440
+ if (!s || typeof s !== 'string')
441
+ return undefined;
442
+ const m = s.trim().match(/^(\d+)([smhd])$/i);
443
+ if (!m)
444
+ return undefined;
445
+ const n = parseInt(m[1], 10);
446
+ if (!Number.isFinite(n) || n <= 0)
447
+ return undefined;
448
+ const unit = m[2].toLowerCase();
449
+ const mult = unit === 's' ? 1000 : unit === 'm' ? 60000 : unit === 'h' ? 3600000 : 86400000;
450
+ return Date.now() - n * mult;
451
+ }
452
+ function parseDateInput(d) {
453
+ if (!d || typeof d !== 'string')
454
+ return undefined;
455
+ const t = Date.parse(d);
456
+ if (Number.isFinite(t))
457
+ return t;
458
+ return undefined;
459
+ }
460
+ function formatDuration(ms) {
461
+ if (ms < 1000)
462
+ return `${ms}ms`;
463
+ const totalSec = Math.floor(ms / 1000);
464
+ if (totalSec < 60)
465
+ return `${totalSec}s`;
466
+ const m = Math.floor(totalSec / 60);
467
+ const s = totalSec % 60;
468
+ return `${m}m${s}s`;
469
+ }
470
+ function formatDateShort(ts) {
471
+ if (!ts)
472
+ return '';
473
+ return new Date(ts).toISOString().slice(0, 19).replace('T', ' ');
474
+ }
475
+ function getModelFromRec(r) {
476
+ const meta = r.metadata || {};
477
+ const m = meta.model;
478
+ return typeof m === 'string' ? m : 'unknown';
479
+ }
480
+ function computeAgentCostBreakdown(recs) {
481
+ let total = 0;
482
+ const byAgent = {};
483
+ const byModel = {};
484
+ for (const r of recs) {
485
+ const c = r.costUsd || 0;
486
+ total += c;
487
+ byAgent[r.agentName] = (byAgent[r.agentName] || 0) + c;
488
+ const mod = getModelFromRec(r);
489
+ byModel[mod] = (byModel[mod] || 0) + c;
490
+ }
491
+ return { totalCostUsd: total, costByAgent: byAgent, costByModel: byModel };
492
+ }
493
+ function getPeriodStarts() {
494
+ const now = Date.now();
495
+ const dToday = new Date(now);
496
+ dToday.setHours(0, 0, 0, 0);
497
+ const today = dToday.getTime();
498
+ // week start (monday) reuse logic similar to self-stats
499
+ const dWeek = new Date(now);
500
+ const day = dWeek.getDay();
501
+ const diff = dWeek.getDate() - day + (day === 0 ? -6 : 1);
502
+ dWeek.setDate(diff);
503
+ dWeek.setHours(0, 0, 0, 0);
504
+ const week = dWeek.getTime();
505
+ const dMonth = new Date(now);
506
+ dMonth.setDate(1);
507
+ dMonth.setHours(0, 0, 0, 0);
508
+ const month = dMonth.getTime();
509
+ return { today, week, month, all: 0 };
510
+ }
511
+ function printAgentCostSection(title, bd) {
512
+ console.log(title);
513
+ console.log('='.repeat(title.length));
514
+ console.log(`Total: $${bd.totalCostUsd.toFixed(4)}`);
515
+ // by agent
516
+ const agents = Object.entries(bd.costByAgent).sort(([, a], [, b]) => b - a);
517
+ if (agents.length > 0) {
518
+ console.log('By Agent:');
519
+ for (const [name, c] of agents.slice(0, 10)) {
520
+ console.log(` ${name}: $${c.toFixed(4)}`);
521
+ }
522
+ }
523
+ // by model
524
+ const models = Object.entries(bd.costByModel).sort(([, a], [, b]) => b - a);
525
+ if (models.length > 0) {
526
+ console.log('By Model:');
527
+ for (const [m, c] of models.slice(0, 10)) {
528
+ console.log(` ${m}: $${c.toFixed(4)}`);
529
+ }
530
+ }
531
+ console.log('');
532
+ }
533
+ function printWhoTable(who) {
534
+ const headers = ['Agent', 'Type', 'Session', 'Last Action', 'Actions', 'Tokens', 'Cost'];
535
+ const rows = who.map((w) => [
536
+ w.agentName,
537
+ w.agentType || '',
538
+ w.sessionId ? w.sessionId.substring(0, 8) : '',
539
+ w.lastAction,
540
+ fmtNum(w.actions),
541
+ fmtNum(w.tokens),
542
+ fmtCost(w.costUsd || 0),
543
+ ]);
544
+ printTable(headers, rows);
545
+ }
546
+ function printSessionsTable(sessions) {
547
+ const headers = [
548
+ 'Session ID',
549
+ 'Agent',
550
+ 'Started',
551
+ 'Duration',
552
+ 'Actions',
553
+ 'Tokens',
554
+ 'Cost',
555
+ 'Status',
556
+ ];
557
+ const rows = sessions.map((s) => [
558
+ s.sessionId.substring(0, 12),
559
+ s.agentName,
560
+ formatDateShort(s.startedAt),
561
+ formatDuration(s.durationMs),
562
+ fmtNum(s.actions),
563
+ fmtNum(s.tokens),
564
+ fmtCost(s.costUsd || 0),
565
+ colorizeStatus(s.status),
566
+ ]);
567
+ printTable(headers, rows);
568
+ }
569
+ function printActivityTimeline(recs) {
570
+ if (recs.length === 0) {
571
+ console.log('No activity found.');
572
+ return;
573
+ }
574
+ const headers = ['Time', 'Agent', 'Action', 'Tokens', 'Cost', 'Status'];
575
+ const rows = recs.map((r) => [
576
+ formatDateShort(r.createdAt),
577
+ r.agentName,
578
+ r.action + (r.target ? `:${r.target}` : ''),
579
+ fmtNum(r.tokensUsed || 0),
580
+ fmtCost(r.costUsd || 0),
581
+ colorizeStatus(r.status),
582
+ ]);
583
+ printTable(headers, rows);
584
+ }
585
+ function printWebhooksTable(webhooks) {
586
+ if (webhooks.length === 0) {
587
+ console.log('No webhooks configured.');
588
+ return;
589
+ }
590
+ const headers = ['ID', 'URL', 'Events', 'Enabled', 'Last Triggered', 'Failures'];
591
+ const rows = webhooks.map((w) => [
592
+ w.id.substring(0, 8),
593
+ w.url,
594
+ (w.events || []).join(','),
595
+ w.enabled ? 'enabled' : 'disabled',
596
+ w.lastTriggeredAt
597
+ ? new Date(w.lastTriggeredAt).toISOString().slice(0, 19).replace('T', ' ')
598
+ : 'never',
599
+ String(w.failureCount || 0),
600
+ ]);
601
+ printTable(headers, rows);
602
+ }
603
+ function parseArgs(argv) {
604
+ const args = argv.slice(2);
605
+ // Command is the first non-flag argument
606
+ const command = args.find((a) => typeof a === 'string' && !a.startsWith('-')) || 'help';
607
+ const flags = {};
608
+ for (let i = 0; i < args.length; i++) {
609
+ const arg = args[i];
610
+ if (typeof arg !== 'string' || !arg.startsWith('--'))
611
+ continue;
612
+ // flag
613
+ const eqIdx = arg.indexOf('=');
614
+ if (eqIdx !== -1) {
615
+ const key = arg.slice(2, eqIdx);
616
+ const val = arg.slice(eqIdx + 1);
617
+ flags[key] = val;
618
+ }
619
+ else {
620
+ const key = arg.slice(2);
621
+ const next = args[i + 1];
622
+ const val = i + 1 < args.length && typeof next === 'string' && !next.startsWith('-')
623
+ ? (i++, next)
624
+ : true;
625
+ flags[key] = val;
626
+ }
627
+ }
628
+ return { command, flags };
629
+ }
630
+ function getAgentTrace(requireDb = true) {
631
+ const dbp = getDbPath();
632
+ if (requireDb && !existsSync(dbp)) {
633
+ console.error(`No ${dbp} found in current directory.`);
634
+ console.error('Run "agenttrace-io init" to create one.');
635
+ process.exit(1);
636
+ }
637
+ return new AgentTrace({ dbPath: dbp, silent: true });
638
+ }
639
+ async function runMain() {
640
+ const { command, flags } = parseArgs(process.argv);
641
+ if (flags.help || command === 'help') {
642
+ printUsage();
643
+ return;
644
+ }
645
+ const useJson = !!flags.json;
646
+ // detect subcommand for alerts (e.g. alerts list, alerts test)
647
+ const alertsSub = (() => {
648
+ if (command !== 'alerts')
649
+ return undefined;
650
+ const argvArgs = process.argv.slice(2);
651
+ const idx = argvArgs.indexOf('alerts');
652
+ if (idx === -1)
653
+ return undefined;
654
+ for (let k = idx + 1; k < argvArgs.length; k++) {
655
+ const c = argvArgs[k];
656
+ if (typeof c === 'string' && !c.startsWith('-')) {
657
+ return c;
658
+ }
659
+ }
660
+ return undefined;
661
+ })();
662
+ // detect subcommand for webhooks (e.g. webhook add, webhook list, webhook remove, webhook test)
663
+ const webhookSub = (() => {
664
+ if (command !== 'webhook')
665
+ return undefined;
666
+ const argvArgs = process.argv.slice(2);
667
+ const idx = argvArgs.indexOf('webhook');
668
+ if (idx === -1)
669
+ return undefined;
670
+ for (let k = idx + 1; k < argvArgs.length; k++) {
671
+ const c = argvArgs[k];
672
+ if (typeof c === 'string' && !c.startsWith('-')) {
673
+ return c;
674
+ }
675
+ }
676
+ return undefined;
677
+ })();
678
+ // capture positional args for webhook commands (used in webhook subcommand handling below)
679
+ const _webhookPositionals = (() => {
680
+ if (command !== 'webhook')
681
+ return [];
682
+ const argvArgs = process.argv.slice(2);
683
+ const idx = argvArgs.indexOf('webhook');
684
+ if (idx === -1)
685
+ return [];
686
+ const result = [];
687
+ for (let k = idx + 1; k < argvArgs.length; k++) {
688
+ const c = argvArgs[k];
689
+ if (typeof c === 'string' && !c.startsWith('-')) {
690
+ result.push(c);
691
+ }
692
+ }
693
+ // First positional is the subcommand; rest are args
694
+ return result.slice(1);
695
+ })();
696
+ void _webhookPositionals;
697
+ // detect subcommand for retention (e.g. retention show, retention set <days>)
698
+ const _retentionSub = (() => {
699
+ if (command !== 'retention')
700
+ return undefined;
701
+ const argvArgs = process.argv.slice(2);
702
+ const idx = argvArgs.indexOf('retention');
703
+ if (idx === -1)
704
+ return undefined;
705
+ for (let k = idx + 1; k < argvArgs.length; k++) {
706
+ const c = argvArgs[k];
707
+ if (typeof c === 'string' && !c.startsWith('-')) {
708
+ return c;
709
+ }
710
+ }
711
+ return undefined;
712
+ })();
713
+ void _retentionSub;
714
+ switch (command) {
715
+ case 'init': {
716
+ const dbp = getDbPath();
717
+ if (existsSync(dbp)) {
718
+ console.log(`${dbp} already exists.`);
719
+ }
720
+ else {
721
+ const trace = new AgentTrace({ dbPath: dbp, silent: true });
722
+ trace.close();
723
+ console.log(`Created ${dbp}`);
724
+ }
725
+ break;
726
+ }
727
+ case 'wrap': {
728
+ const argvArgs = process.argv.slice(2);
729
+ const cmd = argvArgs[1];
730
+ if (!cmd) {
731
+ console.error('Usage: agenttrace-io wrap <command> [args...]');
732
+ process.exit(1);
733
+ }
734
+ const cmdArgs = argvArgs.slice(2);
735
+ const agenttrace = new AgentTrace({ dbPath: getDbPath(), silent: true });
736
+ const runId = agenttrace.startRun(`wrap:${cmd}`);
737
+ void runId;
738
+ const inputStr = `${cmd} ${cmdArgs.join(' ')}`.trim();
739
+ let stdout = '';
740
+ let stderr = '';
741
+ let exitCode;
742
+ try {
743
+ const { spawn } = await import('node:child_process');
744
+ const result = await new Promise((resolve, reject) => {
745
+ const child = spawn(cmd, cmdArgs, { stdio: 'pipe', shell: true });
746
+ child.stdout.on('data', (d) => {
747
+ stdout += d.toString();
748
+ });
749
+ child.stderr.on('data', (d) => {
750
+ stderr += d.toString();
751
+ });
752
+ child.on('close', (code) => {
753
+ exitCode = code ?? 0;
754
+ if (exitCode !== 0) {
755
+ const e = new Error(stderr.slice(0, 500) || `exited with code ${exitCode}`);
756
+ e.exitCode = exitCode;
757
+ reject(e);
758
+ }
759
+ else {
760
+ resolve(stdout.slice(0, 2000));
761
+ }
762
+ });
763
+ child.on('error', (err) => {
764
+ exitCode = 1;
765
+ reject(err);
766
+ });
767
+ });
768
+ await agenttrace.trace(`wrap:${cmd}`, async () => result, { input: inputStr });
769
+ agenttrace.completeRun('success');
770
+ }
771
+ catch (e) {
772
+ agenttrace.completeRun('error');
773
+ const err = e;
774
+ exitCode = err?.exitCode ?? 1;
775
+ if (stderr)
776
+ process.stderr.write(stderr.slice(0, 500));
777
+ agenttrace.close();
778
+ process.exit(exitCode);
779
+ }
780
+ agenttrace.close();
781
+ process.exit(0);
782
+ break;
783
+ }
784
+ case 'dashboard': {
785
+ const rawPort = flags.port ? parseInt(String(flags.port), 10) : NaN;
786
+ const port = Number.isFinite(rawPort) && rawPort > 0 ? rawPort : undefined;
787
+ const host = typeof flags.host === 'string' ? String(flags.host) : undefined;
788
+ startDashboard({ dbPath: getDbPath(), port, host });
789
+ // server keeps process alive
790
+ return;
791
+ }
792
+ case 'runs': {
793
+ const trace = getAgentTrace();
794
+ const rawLimit = flags.limit ? parseInt(String(flags.limit), 10) : NaN;
795
+ const lim = Number.isFinite(rawLimit) && rawLimit > 0 ? rawLimit : 20;
796
+ const allRuns = trace.getRuns(Math.max(1, Math.min(1000, lim)));
797
+ const statusRaw = flags.status ? String(flags.status) : '';
798
+ const runs = statusRaw
799
+ ? (() => {
800
+ const allowed = statusRaw
801
+ .split(',')
802
+ .map((s) => s.trim())
803
+ .filter(Boolean);
804
+ return allowed.length ? allRuns.filter((r) => allowed.includes(r.status)) : allRuns;
805
+ })()
806
+ : allRuns;
807
+ trace.close();
808
+ if (useJson) {
809
+ console.log(JSON.stringify(runs, null, 2));
810
+ }
811
+ else if (runs.length === 0) {
812
+ console.log('No runs found.');
813
+ }
814
+ else {
815
+ printRunsTable(runs);
816
+ }
817
+ break;
818
+ }
819
+ case 'traces': {
820
+ const trace = getAgentTrace();
821
+ const rawLimit = flags.limit ? parseInt(String(flags.limit), 10) : NaN;
822
+ const lim = Number.isFinite(rawLimit) && rawLimit > 0 ? rawLimit : 50;
823
+ const runId = (flags['run-id'] || flags.runId || flags['runId']);
824
+ const filter = {
825
+ limit: Math.max(1, Math.min(1000, lim)),
826
+ };
827
+ if (runId) {
828
+ filter.runId = String(runId);
829
+ }
830
+ const statusRaw = flags.status ? String(flags.status) : '';
831
+ if (statusRaw) {
832
+ const statuses = statusRaw
833
+ .split(',')
834
+ .map((s) => s.trim())
835
+ .filter(Boolean);
836
+ if (statuses.length) {
837
+ filter.status = statuses;
838
+ }
839
+ }
840
+ const traces = trace.getTraces(filter);
841
+ trace.close();
842
+ if (useJson) {
843
+ console.log(JSON.stringify(traces, null, 2));
844
+ }
845
+ else if (traces.length === 0) {
846
+ console.log('No traces found.');
847
+ }
848
+ else {
849
+ printTracesTable(traces);
850
+ }
851
+ break;
852
+ }
853
+ case 'stats': {
854
+ const trace = getAgentTrace();
855
+ const stats = trace.getStats();
856
+ trace.close();
857
+ if (useJson) {
858
+ console.log(JSON.stringify(stats, null, 2));
859
+ }
860
+ else {
861
+ printStats(stats);
862
+ }
863
+ break;
864
+ }
865
+ case 'costs': {
866
+ const trace = getAgentTrace();
867
+ const runId = (flags['run-id'] || flags.runId || flags['runId']);
868
+ const daily = !!flags.daily;
869
+ const breakdown = trace.getCostBreakdown({ runId: runId ? String(runId) : undefined });
870
+ trace.close();
871
+ if (useJson) {
872
+ console.log(JSON.stringify(breakdown, null, 2));
873
+ }
874
+ else {
875
+ printCosts(breakdown, daily);
876
+ }
877
+ break;
878
+ }
879
+ case 'export': {
880
+ const trace = getAgentTrace();
881
+ const format = flags.format === 'csv' ? 'csv' : 'json';
882
+ const runId = (flags['run-id'] || flags.runId || flags['runId']);
883
+ const filter = {};
884
+ if (runId) {
885
+ filter.runId = String(runId);
886
+ }
887
+ const data = trace.export(format, filter);
888
+ trace.close();
889
+ const outFile = flags.output ? String(flags.output) : '';
890
+ if (outFile) {
891
+ writeFileSync(outFile, data, 'utf8');
892
+ console.log(`Exported ${format.toUpperCase()} to ${outFile}`);
893
+ }
894
+ else {
895
+ console.log(data);
896
+ }
897
+ break;
898
+ }
899
+ case 'tree': {
900
+ const traceId = (flags['trace-id'] || flags.traceId || flags['traceId']);
901
+ if (!traceId) {
902
+ console.error('Usage: agenttrace-io tree --trace-id <id>');
903
+ process.exit(1);
904
+ }
905
+ const tr = getAgentTrace();
906
+ const tree = (() => {
907
+ try {
908
+ return tr.getTraceTree(String(traceId));
909
+ }
910
+ catch (e) {
911
+ console.error('Error:', e instanceof Error ? e.message : String(e));
912
+ tr.close();
913
+ process.exit(1);
914
+ }
915
+ })();
916
+ tr.close();
917
+ if (useJson) {
918
+ console.log(JSON.stringify(tree, null, 2));
919
+ }
920
+ else if (!tree || !tree.trace) {
921
+ console.log('Trace not found.');
922
+ }
923
+ else {
924
+ console.log('Trace Tree:');
925
+ printTraceTree(tree);
926
+ }
927
+ break;
928
+ }
929
+ case 'alerts': {
930
+ const sub = alertsSub || 'list';
931
+ const dbp = getDbPath();
932
+ const dbExists = existsSync(dbp);
933
+ if (!dbExists && (sub === 'test' || sub === 'history')) {
934
+ console.error(`No ${dbp} found in current directory.`);
935
+ console.error('Run "agenttrace-io init" to create one.');
936
+ process.exit(1);
937
+ }
938
+ if (sub === 'list' || sub === '') {
939
+ if (!dbExists) {
940
+ if (useJson) {
941
+ console.log('[]');
942
+ }
943
+ else {
944
+ console.log('No alerts configured.');
945
+ }
946
+ break;
947
+ }
948
+ const agent = new AgentTrace({ dbPath: dbp, silent: true });
949
+ const alerts = agent.getAlerts();
950
+ agent.close();
951
+ if (useJson) {
952
+ console.log(JSON.stringify(alerts, null, 2));
953
+ }
954
+ else if (alerts.length === 0) {
955
+ console.log('No alerts configured.');
956
+ }
957
+ else {
958
+ console.log('Configured alerts:');
959
+ for (const a of alerts) {
960
+ const parts = [`cooldown=${a.cooldown}s`];
961
+ if (a.webhook)
962
+ parts.push('webhook');
963
+ if (a.email)
964
+ parts.push('email');
965
+ const last = a.lastTriggered
966
+ ? new Date(a.lastTriggered).toISOString().slice(0, 19)
967
+ : 'never';
968
+ console.log(` ${a.name} (${parts.join(', ')}) lastTriggered=${last}`);
969
+ }
970
+ }
971
+ break;
972
+ }
973
+ const agent = new AgentTrace({ dbPath: dbp, silent: true });
974
+ if (sub === 'history') {
975
+ const history = agent.getAlertHistory();
976
+ agent.close();
977
+ if (useJson) {
978
+ console.log(JSON.stringify(history, null, 2));
979
+ }
980
+ else if (history.length === 0) {
981
+ console.log('No alert history.');
982
+ }
983
+ else {
984
+ console.log('Alert history (newest first):');
985
+ for (const h of history.slice(0, 100)) {
986
+ const t = new Date(h.triggeredAt).toISOString().slice(0, 19).replace('T', ' ');
987
+ const del = h.delivered ? 'delivered' : `failed${h.error ? ' (' + h.error + ')' : ''}`;
988
+ console.log(` ${t} ${h.alertName} ${del}`);
989
+ }
990
+ }
991
+ break;
992
+ }
993
+ if (sub === 'test') {
994
+ const name = flags.name || flags['name'] ? String(flags.name || flags['name']) : '';
995
+ if (!name) {
996
+ console.error('Usage: agenttrace-io alerts test --name <name>');
997
+ agent.close();
998
+ process.exit(1);
999
+ }
1000
+ const alerts = agent.getAlerts();
1001
+ const def = alerts.find((a) => a.name === name);
1002
+ if (!def) {
1003
+ console.error(`Alert '${name}' not found. Register it via the SDK first.`);
1004
+ agent.close();
1005
+ process.exit(1);
1006
+ }
1007
+ // Force a test by registering a temp always-true version (bypasses cooldown + uses stored config)
1008
+ const testAlert = {
1009
+ name: def.name,
1010
+ condition: () => true,
1011
+ webhook: def.webhook,
1012
+ email: def.email,
1013
+ cooldown: 0,
1014
+ lastTriggered: 0,
1015
+ };
1016
+ agent.registerAlert(testAlert);
1017
+ const fired = await agent.checkAlerts();
1018
+ agent.close();
1019
+ if (fired.length > 0) {
1020
+ const f = fired[0];
1021
+ const outcome = f.delivered ? 'delivered' : `failed${f.error ? ': ' + f.error : ''}`;
1022
+ console.log(`Test-fired alert '${name}'. ${outcome}`);
1023
+ }
1024
+ else {
1025
+ console.log(`Alert '${name}' test did not fire.`);
1026
+ }
1027
+ break;
1028
+ }
1029
+ console.error(`Unknown alerts subcommand: ${sub}`);
1030
+ printUsage();
1031
+ agent.close();
1032
+ process.exit(1);
1033
+ break;
1034
+ }
1035
+ case 'health': {
1036
+ const dbp = getDbPath();
1037
+ const useJsonLocal = useJson;
1038
+ const GREEN = '\x1b[32m';
1039
+ const RED = '\x1b[31m';
1040
+ const YELLOW = '\x1b[33m';
1041
+ const RESET = '\x1b[0m';
1042
+ const checks = {};
1043
+ // Database check (always; creates empty db if absent like init)
1044
+ let dbOk = false;
1045
+ let dbTraceCount = 0;
1046
+ let dbSize = 0;
1047
+ let dbIntegrity;
1048
+ try {
1049
+ const tr = new AgentTrace({ dbPath: dbp, silent: true });
1050
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1051
+ const h = tr.getHealth();
1052
+ tr.close();
1053
+ dbOk = h.status === 'ok';
1054
+ dbTraceCount = h.traceCount;
1055
+ dbSize = h.dbSize;
1056
+ dbIntegrity = h.integrity;
1057
+ checks.database = {
1058
+ ok: dbOk,
1059
+ dbPath: dbp,
1060
+ traceCount: dbTraceCount,
1061
+ dbSize,
1062
+ integrity: dbIntegrity,
1063
+ };
1064
+ }
1065
+ catch (e) {
1066
+ checks.database = { ok: false, dbPath: dbp, error: String(e) };
1067
+ }
1068
+ // Dashboard check (local default)
1069
+ let dashOk = false;
1070
+ const dashPort = 4317;
1071
+ const dashUrl = `http://127.0.0.1:${dashPort}/api/health`;
1072
+ try {
1073
+ const controller = new AbortController();
1074
+ const timeout = setTimeout(() => controller.abort(), 1500);
1075
+ const resp = await fetch(dashUrl, { signal: controller.signal, method: 'GET' });
1076
+ clearTimeout(timeout);
1077
+ if (resp.ok) {
1078
+ const data = (await resp.json());
1079
+ dashOk = data && data.status === 'ok';
1080
+ }
1081
+ }
1082
+ catch (_) {
1083
+ // not reachable
1084
+ }
1085
+ checks.dashboard = { ok: dashOk, url: dashUrl };
1086
+ // Gateway: treat as core data access (db/sdk layer)
1087
+ const gatewayOk = dbOk;
1088
+ checks.gateway = { ok: gatewayOk };
1089
+ const overallOk = gatewayOk && dbOk;
1090
+ if (useJsonLocal) {
1091
+ console.log(JSON.stringify({
1092
+ status: overallOk ? 'ok' : 'degraded',
1093
+ checks,
1094
+ }, null, 2));
1095
+ }
1096
+ else {
1097
+ console.log('AgentTrace Health');
1098
+ console.log('=================');
1099
+ const gStr = gatewayOk ? `${GREEN}ok${RESET}` : `${RED}fail${RESET}`;
1100
+ console.log(`gateway: ${gStr}`);
1101
+ const dStr = dashOk ? `${GREEN}ok${RESET}` : `${YELLOW}not running${RESET}`;
1102
+ console.log(`dashboard: ${dStr} (${dashUrl})`);
1103
+ const dbStr = dbOk ? `${GREEN}ok${RESET}` : `${RED}fail${RESET}`;
1104
+ const extra = dbOk ? ` (traces=${dbTraceCount}, size=${dbSize}B)` : '';
1105
+ console.log(`database: ${dbStr} (${dbp})${extra}`);
1106
+ if (dbOk && dbIntegrity && (!dbIntegrity.tablesExist || !dbIntegrity.noOrphans)) {
1107
+ console.log(` ${YELLOW}integrity: tablesExist=${dbIntegrity.tablesExist}, noOrphans=${dbIntegrity.noOrphans}${dbIntegrity.details ? ' ' + dbIntegrity.details : ''}${RESET}`);
1108
+ }
1109
+ }
1110
+ if (!overallOk) {
1111
+ process.exit(1);
1112
+ }
1113
+ break;
1114
+ }
1115
+ case 'version': {
1116
+ console.log(`${PACKAGE_NAME} ${VERSION}`);
1117
+ break;
1118
+ }
1119
+ case 'self-stats': {
1120
+ const dbp = getDbPath();
1121
+ // self-stats creates db on demand (no error if missing; will just show zeros)
1122
+ const storage = new TraceStorage(dbp);
1123
+ try {
1124
+ printSelfStats(storage, useJson);
1125
+ }
1126
+ finally {
1127
+ storage.close();
1128
+ }
1129
+ break;
1130
+ }
1131
+ case 'who': {
1132
+ const dbp = getDbPath();
1133
+ const storage = new TraceStorage(dbp);
1134
+ try {
1135
+ const activeOnly = !!flags.active;
1136
+ const typeF = flags.type ? String(flags.type) : undefined;
1137
+ const rawLim = flags.limit ? parseInt(String(flags.limit), 10) : NaN;
1138
+ const lim = Number.isFinite(rawLim) && rawLim > 0 ? rawLim : 50;
1139
+ const who = storage.getAgentWho({ activeOnly, agentType: typeF, limit: lim });
1140
+ if (useJson) {
1141
+ console.log(JSON.stringify(who, null, 2));
1142
+ }
1143
+ else if (who.length === 0) {
1144
+ console.log('No agents found.');
1145
+ }
1146
+ else {
1147
+ printWhoTable(who);
1148
+ }
1149
+ }
1150
+ finally {
1151
+ storage.close();
1152
+ }
1153
+ break;
1154
+ }
1155
+ case 'cost': {
1156
+ const dbp = getDbPath();
1157
+ const storage = new TraceStorage(dbp);
1158
+ try {
1159
+ const agentF = flags.agent ? String(flags.agent) : undefined;
1160
+ const from = parseDateInput(flags.from);
1161
+ const to = parseDateInput(flags.to);
1162
+ const fmt = (flags.format ? String(flags.format) : 'table').toLowerCase();
1163
+ const isJson = fmt === 'json' || useJson;
1164
+ const allRecs = storage.getAgentUsage({
1165
+ agentName: agentF,
1166
+ limit: 50000,
1167
+ });
1168
+ if (from || to) {
1169
+ const filtered = allRecs.filter((r) => {
1170
+ if (from && r.createdAt < from)
1171
+ return false;
1172
+ if (to && r.createdAt > to)
1173
+ return false;
1174
+ return true;
1175
+ });
1176
+ const bd = computeAgentCostBreakdown(filtered);
1177
+ if (isJson) {
1178
+ console.log(JSON.stringify({
1179
+ range: {
1180
+ from: from ? new Date(from).toISOString() : null,
1181
+ to: to ? new Date(to).toISOString() : null,
1182
+ },
1183
+ agent: agentF || null,
1184
+ ...bd,
1185
+ }, null, 2));
1186
+ }
1187
+ else {
1188
+ const title = `Agent Cost Breakdown (custom range)${agentF ? ` for ${agentF}` : ''}`;
1189
+ printAgentCostSection(title, bd);
1190
+ }
1191
+ }
1192
+ else {
1193
+ // show 4 periods + breakdowns
1194
+ const periods = getPeriodStarts();
1195
+ const todayRecs = allRecs.filter((r) => r.createdAt >= periods.today);
1196
+ const weekRecs = allRecs.filter((r) => r.createdAt >= periods.week);
1197
+ const monthRecs = allRecs.filter((r) => r.createdAt >= periods.month);
1198
+ const allBd = computeAgentCostBreakdown(allRecs);
1199
+ const todayBd = computeAgentCostBreakdown(todayRecs);
1200
+ const weekBd = computeAgentCostBreakdown(weekRecs);
1201
+ const monthBd = computeAgentCostBreakdown(monthRecs);
1202
+ if (isJson) {
1203
+ console.log(JSON.stringify({
1204
+ agent: agentF || null,
1205
+ today: todayBd,
1206
+ week: weekBd,
1207
+ month: monthBd,
1208
+ allTime: allBd,
1209
+ }, null, 2));
1210
+ }
1211
+ else {
1212
+ console.log('Agent Cost Breakdown');
1213
+ console.log('====================');
1214
+ if (agentF)
1215
+ console.log(`Filter: agent=${agentF}`);
1216
+ console.log('');
1217
+ printAgentCostSection('Today', todayBd);
1218
+ printAgentCostSection('This Week', weekBd);
1219
+ printAgentCostSection('This Month', monthBd);
1220
+ printAgentCostSection('All Time', allBd);
1221
+ }
1222
+ }
1223
+ }
1224
+ finally {
1225
+ storage.close();
1226
+ }
1227
+ break;
1228
+ }
1229
+ case 'sessions': {
1230
+ const dbp = getDbPath();
1231
+ const storage = new TraceStorage(dbp);
1232
+ try {
1233
+ const agentF = flags.agent ? String(flags.agent) : undefined;
1234
+ const activeOnly = !!flags.active;
1235
+ const rawLim = flags.limit ? parseInt(String(flags.limit), 10) : NaN;
1236
+ const lim = Number.isFinite(rawLim) && rawLim > 0 ? rawLim : 20;
1237
+ const sessions = storage.getAgentSessions({ agentName: agentF, activeOnly, limit: lim });
1238
+ if (useJson) {
1239
+ console.log(JSON.stringify(sessions, null, 2));
1240
+ }
1241
+ else if (sessions.length === 0) {
1242
+ console.log('No sessions found.');
1243
+ }
1244
+ else {
1245
+ printSessionsTable(sessions);
1246
+ }
1247
+ }
1248
+ finally {
1249
+ storage.close();
1250
+ }
1251
+ break;
1252
+ }
1253
+ case 'activity': {
1254
+ const dbp = getDbPath();
1255
+ const storage = new TraceStorage(dbp);
1256
+ try {
1257
+ const agentF = flags.agent ? String(flags.agent) : undefined;
1258
+ const actionF = flags.type ? String(flags.type) : undefined; // --type means action
1259
+ const rawLim = flags.limit ? parseInt(String(flags.limit), 10) : NaN;
1260
+ const lim = Number.isFinite(rawLim) && rawLim > 0 ? rawLim : 30;
1261
+ const sinceFrom = parseSinceDuration(flags.since);
1262
+ const f = { limit: lim };
1263
+ if (agentF)
1264
+ f.agentName = agentF;
1265
+ if (actionF)
1266
+ f.action = actionF;
1267
+ if (sinceFrom)
1268
+ f.fromDate = sinceFrom;
1269
+ const recs = storage.getAgentUsage(f);
1270
+ if (useJson) {
1271
+ console.log(JSON.stringify(recs, null, 2));
1272
+ }
1273
+ else if (recs.length === 0) {
1274
+ console.log('No activity found.');
1275
+ }
1276
+ else {
1277
+ printActivityTimeline(recs);
1278
+ }
1279
+ }
1280
+ finally {
1281
+ storage.close();
1282
+ }
1283
+ break;
1284
+ }
1285
+ case 'webhook': {
1286
+ const sub = webhookSub || 'list';
1287
+ const dbp = getDbPath();
1288
+ const dbExists = existsSync(dbp);
1289
+ if (sub === 'list' || sub === '') {
1290
+ if (!dbExists) {
1291
+ if (useJson) {
1292
+ console.log('[]');
1293
+ }
1294
+ else {
1295
+ console.log('No webhooks configured.');
1296
+ }
1297
+ break;
1298
+ }
1299
+ const agent = new AgentTrace({ dbPath: dbp, silent: true });
1300
+ try {
1301
+ const webhooks = agent.getWebhooks();
1302
+ if (useJson) {
1303
+ console.log(JSON.stringify(webhooks, null, 2));
1304
+ }
1305
+ else if (webhooks.length === 0) {
1306
+ console.log('No webhooks configured.');
1307
+ }
1308
+ else {
1309
+ printWebhooksTable(webhooks);
1310
+ }
1311
+ }
1312
+ finally {
1313
+ agent.close();
1314
+ }
1315
+ break;
1316
+ }
1317
+ // For add/remove/test, require existing db
1318
+ if (!dbExists) {
1319
+ console.error(`No ${dbp} found in current directory.`);
1320
+ console.error('Run "agenttrace-io init" to create one.');
1321
+ process.exit(1);
1322
+ }
1323
+ const agent = new AgentTrace({ dbPath: dbp, silent: true });
1324
+ try {
1325
+ if (sub === 'add') {
1326
+ const url = flags.url ? String(flags.url) : '';
1327
+ const eventsRaw = flags.events ? String(flags.events) : '';
1328
+ if (!url) {
1329
+ console.error('Usage: agenttrace-io webhook add --url <url> [--events <event1,event2,...>]');
1330
+ console.error('Events: trace.complete, trace.error, run.complete, run.error, cost.threshold, agent.inactive');
1331
+ process.exit(1);
1332
+ }
1333
+ const defaultEvents = [
1334
+ 'trace.complete',
1335
+ 'trace.error',
1336
+ 'run.complete',
1337
+ 'run.error',
1338
+ 'cost.threshold',
1339
+ 'agent.inactive',
1340
+ ];
1341
+ const events = eventsRaw
1342
+ ? eventsRaw
1343
+ .split(',')
1344
+ .map((e) => e.trim())
1345
+ .filter(Boolean)
1346
+ : defaultEvents;
1347
+ const id = agent.addWebhook(url, events);
1348
+ if (useJson) {
1349
+ console.log(JSON.stringify({ id, url, events }, null, 2));
1350
+ }
1351
+ else {
1352
+ console.log(`Registered webhook: ${id.substring(0, 8)} -> ${url} (${events.join(',')})`);
1353
+ }
1354
+ break;
1355
+ }
1356
+ if (sub === 'remove') {
1357
+ const id = flags.id ? String(flags.id) : '';
1358
+ if (!id) {
1359
+ console.error('Usage: agenttrace-io webhook remove --id <id>');
1360
+ process.exit(1);
1361
+ }
1362
+ const webhooks = agent.getWebhooks();
1363
+ const match = webhooks.find((w) => w.id.startsWith(id) || w.id === id);
1364
+ if (!match) {
1365
+ console.error(`Webhook '${id}' not found.`);
1366
+ process.exit(1);
1367
+ }
1368
+ agent.removeWebhook(match.id);
1369
+ if (useJson) {
1370
+ console.log(JSON.stringify({ id: match.id, removed: true }, null, 2));
1371
+ }
1372
+ else {
1373
+ console.log(`Removed webhook ${match.id.substring(0, 8)}.`);
1374
+ }
1375
+ break;
1376
+ }
1377
+ if (sub === 'test') {
1378
+ const id = flags.id ? String(flags.id) : '';
1379
+ if (!id) {
1380
+ console.error('Usage: agenttrace-io webhook test --id <id>');
1381
+ process.exit(1);
1382
+ }
1383
+ const webhooks = agent.getWebhooks();
1384
+ const match = webhooks.find((w) => w.id.startsWith(id) || w.id === id);
1385
+ if (!match) {
1386
+ console.error(`Webhook '${id}' not found.`);
1387
+ process.exit(1);
1388
+ }
1389
+ const result = await agent.testWebhook(match.id);
1390
+ if (useJson) {
1391
+ console.log(JSON.stringify(result, null, 2));
1392
+ }
1393
+ else if (result.ok) {
1394
+ console.log(`Webhook ${match.id.substring(0, 8)} delivered (HTTP ${result.status}).`);
1395
+ }
1396
+ else {
1397
+ console.log(`Webhook ${match.id.substring(0, 8)} failed: ${result.error || `HTTP ${result.status}`}`);
1398
+ }
1399
+ break;
1400
+ }
1401
+ console.error(`Unknown webhook subcommand: ${sub}`);
1402
+ printUsage();
1403
+ process.exit(1);
1404
+ }
1405
+ finally {
1406
+ agent.close();
1407
+ }
1408
+ break;
1409
+ }
1410
+ case 'cleanup': {
1411
+ const dbp = getDbPath();
1412
+ if (!existsSync(dbp)) {
1413
+ console.error(`No ${dbp} found in current directory.`);
1414
+ console.error('Run "agenttrace-io init" to create one.');
1415
+ process.exit(1);
1416
+ }
1417
+ const storage = new TraceStorage(dbp);
1418
+ const rawDays = flags.days ? parseInt(String(flags.days), 10) : NaN;
1419
+ const days = Number.isFinite(rawDays) && rawDays > 0 ? rawDays : 30;
1420
+ const cutoff = Date.now() - days * 86400000;
1421
+ const tracesDeleted = storage.cleanupOldTraces(cutoff);
1422
+ const runsDeleted = storage.cleanupOldRuns(cutoff);
1423
+ const usageDeleted = storage.cleanupOldAgentUsage(cutoff);
1424
+ storage.close();
1425
+ if (useJson) {
1426
+ console.log(JSON.stringify({ tracesDeleted, runsDeleted, usageDeleted, days }, null, 2));
1427
+ }
1428
+ else {
1429
+ console.log(`Cleanup complete (older than ${days} days):`);
1430
+ console.log(` Traces deleted: ${tracesDeleted}`);
1431
+ console.log(` Runs deleted: ${runsDeleted}`);
1432
+ console.log(` Usage deleted: ${usageDeleted}`);
1433
+ }
1434
+ break;
1435
+ }
1436
+ case 'retention': {
1437
+ const dbp = getDbPath();
1438
+ const dbExists = existsSync(dbp);
1439
+ const argvArgs2 = process.argv.slice(2);
1440
+ const idx2 = argvArgs2.indexOf('retention');
1441
+ let sub = 'show';
1442
+ if (idx2 !== -1) {
1443
+ for (let k = idx2 + 1; k < argvArgs2.length; k++) {
1444
+ const c = argvArgs2[k];
1445
+ if (typeof c === 'string' && !c.startsWith('-')) {
1446
+ sub = c;
1447
+ break;
1448
+ }
1449
+ }
1450
+ }
1451
+ if (sub === 'show' || sub === 'stats') {
1452
+ if (!dbExists) {
1453
+ if (useJson) {
1454
+ console.log(JSON.stringify({ error: 'no database' }, null, 2));
1455
+ }
1456
+ else {
1457
+ console.log('No database found. Run "agenttrace-io init" first.');
1458
+ }
1459
+ break;
1460
+ }
1461
+ const storage = new TraceStorage(dbp);
1462
+ const policy = storage.getRetentionPolicy();
1463
+ const stats = storage.getStorageStats();
1464
+ storage.close();
1465
+ if (useJson) {
1466
+ console.log(JSON.stringify({ policy, stats }, null, 2));
1467
+ }
1468
+ else {
1469
+ console.log('Retention Policy:');
1470
+ console.log(` Retention days: ${policy.retentionDays}`);
1471
+ console.log(` Cleanup interval (hrs): ${policy.cleanupIntervalHours}`);
1472
+ console.log('');
1473
+ console.log('Storage Stats:');
1474
+ console.log(` DB size: ${stats.totalSizeBytes} bytes`);
1475
+ console.log(` Trace count: ${stats.traceCount}`);
1476
+ console.log(` Run count: ${stats.runCount}`);
1477
+ if (stats.oldestTrace) {
1478
+ console.log(` Oldest trace: ${new Date(stats.oldestTrace).toISOString().slice(0, 19)}`);
1479
+ }
1480
+ if (stats.newestTrace) {
1481
+ console.log(` Newest trace: ${new Date(stats.newestTrace).toISOString().slice(0, 19)}`);
1482
+ }
1483
+ }
1484
+ break;
1485
+ }
1486
+ if (!dbExists) {
1487
+ console.error(`No ${dbp} found in current directory.`);
1488
+ console.error('Run "agenttrace-io init" to create one.');
1489
+ process.exit(1);
1490
+ }
1491
+ const storage = new TraceStorage(dbp);
1492
+ if (sub === 'set') {
1493
+ const rawDays = flags.days ? parseInt(String(flags.days), 10) : NaN;
1494
+ if (!Number.isFinite(rawDays) || rawDays < 0) {
1495
+ console.error('Usage: agenttrace-io retention set --days <N> [--interval <H>]');
1496
+ storage.close();
1497
+ process.exit(1);
1498
+ }
1499
+ const interval = flags.interval ? parseInt(String(flags.interval), 10) : undefined;
1500
+ storage.setRetentionPolicy(rawDays, Number.isFinite(interval) && interval > 0 ? interval : undefined);
1501
+ storage.close();
1502
+ if (useJson) {
1503
+ console.log(JSON.stringify({ retentionDays: rawDays, cleanupIntervalHours: interval || 24 }, null, 2));
1504
+ }
1505
+ else {
1506
+ console.log(`Retention policy set: ${rawDays} days, cleanup every ${interval || 24}h`);
1507
+ }
1508
+ break;
1509
+ }
1510
+ console.error(`Unknown retention subcommand: ${sub}`);
1511
+ printUsage();
1512
+ storage.close();
1513
+ process.exit(1);
1514
+ break;
1515
+ }
1516
+ case 'key': {
1517
+ const dbp = getDbPath();
1518
+ const dbExists = existsSync(dbp);
1519
+ const argvArgs3 = process.argv.slice(2);
1520
+ const idx3 = argvArgs3.indexOf('key');
1521
+ let sub = 'list';
1522
+ if (idx3 !== -1) {
1523
+ for (let k = idx3 + 1; k < argvArgs3.length; k++) {
1524
+ const c = argvArgs3[k];
1525
+ if (typeof c === 'string' && !c.startsWith('-')) {
1526
+ sub = c;
1527
+ break;
1528
+ }
1529
+ }
1530
+ }
1531
+ if (sub === 'list') {
1532
+ if (!dbExists) {
1533
+ if (useJson) {
1534
+ console.log('[]');
1535
+ }
1536
+ else {
1537
+ console.log('No API keys found.');
1538
+ }
1539
+ break;
1540
+ }
1541
+ const storage = new TraceStorage(dbp);
1542
+ const keys = storage.getApiKeys();
1543
+ storage.close();
1544
+ if (useJson) {
1545
+ console.log(JSON.stringify(keys, null, 2));
1546
+ }
1547
+ else if (keys.length === 0) {
1548
+ console.log('No API keys found.');
1549
+ }
1550
+ else {
1551
+ console.log('API Keys:');
1552
+ for (const k of keys) {
1553
+ const status = k.enabled ? 'enabled' : 'disabled';
1554
+ const last = k.lastUsedAt ? new Date(k.lastUsedAt).toISOString().slice(0, 19) : 'never';
1555
+ console.log(` ${k.id.substring(0, 8)} ${k.name} [${status}] last_used=${last}`);
1556
+ }
1557
+ }
1558
+ break;
1559
+ }
1560
+ if (!dbExists) {
1561
+ console.error(`No ${dbp} found in current directory.`);
1562
+ console.error('Run "agenttrace-io init" to create one.');
1563
+ process.exit(1);
1564
+ }
1565
+ const storage = new TraceStorage(dbp);
1566
+ if (sub === 'create') {
1567
+ const name = flags.name ? String(flags.name) : '';
1568
+ if (!name) {
1569
+ console.error('Usage: agenttrace-io key create --name <name>');
1570
+ storage.close();
1571
+ process.exit(1);
1572
+ }
1573
+ const created = storage.createApiKey(name);
1574
+ storage.close();
1575
+ if (useJson) {
1576
+ console.log(JSON.stringify({
1577
+ id: created.id,
1578
+ name: created.name,
1579
+ key: created.key,
1580
+ preview: created.preview,
1581
+ createdAt: created.createdAt,
1582
+ }, null, 2));
1583
+ }
1584
+ else {
1585
+ console.log(`Created API key: ${created.name}`);
1586
+ console.log(` ID: ${created.id.substring(0, 8)}`);
1587
+ console.log(` Key: ${created.key}`);
1588
+ console.log(` Preview: ${created.preview}`);
1589
+ console.log(' (Store this key — it will not be shown again)');
1590
+ }
1591
+ break;
1592
+ }
1593
+ if (sub === 'revoke' || sub === 'delete' || sub === 'remove') {
1594
+ const id = flags.id ? String(flags.id) : '';
1595
+ if (!id) {
1596
+ console.error('Usage: agenttrace-io key revoke --id <id>');
1597
+ storage.close();
1598
+ process.exit(1);
1599
+ }
1600
+ storage.revokeApiKey(id);
1601
+ storage.close();
1602
+ if (useJson) {
1603
+ console.log(JSON.stringify({ revoked: true, id }, null, 2));
1604
+ }
1605
+ else {
1606
+ console.log(`Revoked API key ${id.substring(0, 8)}`);
1607
+ }
1608
+ break;
1609
+ }
1610
+ console.error(`Unknown key subcommand: ${sub}`);
1611
+ printUsage();
1612
+ storage.close();
1613
+ process.exit(1);
1614
+ break;
1615
+ }
1616
+ case 'benchmark': {
1617
+ const trace = getAgentTrace();
1618
+ const results = [];
1619
+ // Write benchmark
1620
+ {
1621
+ const start = Date.now();
1622
+ const ops = 100;
1623
+ for (let i = 0; i < ops; i++) {
1624
+ trace.startRun(`bench-${i}`);
1625
+ trace.completeRun();
1626
+ }
1627
+ const durationMs = Date.now() - start;
1628
+ results.push({
1629
+ name: 'write',
1630
+ ops,
1631
+ durationMs,
1632
+ opsPerSec: Math.round((ops / durationMs) * 1000),
1633
+ });
1634
+ }
1635
+ // Read benchmark
1636
+ {
1637
+ const start = Date.now();
1638
+ const runs = trace.getRuns(1000);
1639
+ const durationMs = Date.now() - start;
1640
+ results.push({
1641
+ name: 'read',
1642
+ ops: runs.length,
1643
+ durationMs,
1644
+ opsPerSec: Math.round((runs.length / Math.max(1, durationMs)) * 1000),
1645
+ });
1646
+ }
1647
+ // Stats benchmark
1648
+ {
1649
+ const start = Date.now();
1650
+ const iterations = 10;
1651
+ for (let i = 0; i < iterations; i++) {
1652
+ trace.getStats();
1653
+ }
1654
+ const durationMs = Date.now() - start;
1655
+ results.push({
1656
+ name: 'stats',
1657
+ ops: iterations,
1658
+ durationMs,
1659
+ opsPerSec: Math.round((iterations / durationMs) * 1000),
1660
+ });
1661
+ }
1662
+ trace.close();
1663
+ if (useJson) {
1664
+ console.log(JSON.stringify({ results }, null, 2));
1665
+ }
1666
+ else {
1667
+ console.log('Benchmark Results:');
1668
+ for (const r of results) {
1669
+ console.log(` ${r.name}: ${r.ops} ops in ${r.durationMs}ms (${r.opsPerSec} ops/sec)`);
1670
+ }
1671
+ }
1672
+ break;
1673
+ }
1674
+ case 'budget':
1675
+ case 'budget-check': {
1676
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1677
+ const budgetArgv = process.argv.slice(2);
1678
+ const budgetIdx = budgetArgv.indexOf(command);
1679
+ let budgetSub = command === 'budget-check' ? 'check' : 'list';
1680
+ let budgetAgent;
1681
+ if (budgetIdx !== -1) {
1682
+ for (let k = budgetIdx + 1; k < budgetArgv.length; k++) {
1683
+ const c = budgetArgv[k];
1684
+ if (typeof c === 'string' && !c.startsWith('-')) {
1685
+ if (budgetSub === 'list' || budgetSub === 'check') {
1686
+ budgetSub = c;
1687
+ }
1688
+ else if (!budgetAgent) {
1689
+ budgetAgent = c;
1690
+ }
1691
+ }
1692
+ }
1693
+ }
1694
+ const sub = flags.sub || budgetSub;
1695
+ const agent = (flags.agent ? String(flags.agent) : undefined) || budgetAgent || undefined;
1696
+ const dbp = getDbPath();
1697
+ const storage = new TraceStorage(dbp);
1698
+ try {
1699
+ const _db = storage.db;
1700
+ if (sub === 'list' || sub === '') {
1701
+ const rows = storage.db.prepare('SELECT * FROM budgets ORDER BY agent_name').all() || [];
1702
+ if (useJson) {
1703
+ console.log(JSON.stringify(rows.map((r) => ({
1704
+ agent: r.agent_name,
1705
+ maxTokensPerDay: r.max_tokens_per_day,
1706
+ maxCostPerDay: r.max_cost_per_day,
1707
+ })), null, 2));
1708
+ }
1709
+ else if (!rows || rows.length === 0) {
1710
+ console.log('No budgets configured.');
1711
+ }
1712
+ else {
1713
+ console.log('Budgets:');
1714
+ for (const r of rows) {
1715
+ console.log(` ${r.agent_name}: tokens=${r.max_tokens_per_day || 0}/day cost=$${(r.max_cost_per_day || 0).toFixed(2)}/day`);
1716
+ }
1717
+ }
1718
+ break;
1719
+ }
1720
+ if (sub === 'set') {
1721
+ const name = agent || '';
1722
+ if (!name) {
1723
+ console.error('Usage: agenttrace-io budget set <agent-name> --tokens <N> --cost <M>');
1724
+ process.exit(1);
1725
+ }
1726
+ const maxTokens = flags.tokens ? parseInt(String(flags.tokens), 10) || 0 : 0;
1727
+ const maxCost = flags.cost ? parseFloat(String(flags.cost)) || 0 : 0;
1728
+ const now = Date.now();
1729
+ storage.db
1730
+ .prepare(`
1731
+ INSERT OR REPLACE INTO budgets (agent_name, max_tokens_per_day, max_cost_per_day, created_at)
1732
+ VALUES (?, ?, ?, ?)
1733
+ `)
1734
+ .run(name, maxTokens, maxCost, now);
1735
+ if (useJson) {
1736
+ console.log(JSON.stringify({ agent: name, maxTokensPerDay: maxTokens, maxCostPerDay: maxCost }, null, 2));
1737
+ }
1738
+ else {
1739
+ console.log(`Budget set for ${name}: ${maxTokens} tokens/day, $${maxCost.toFixed(2)} cost/day`);
1740
+ }
1741
+ break;
1742
+ }
1743
+ // status or check
1744
+ const name = agent || '';
1745
+ if (!name) {
1746
+ console.error('Usage: agenttrace-io budget status <agent-name> or budget check <agent-name>');
1747
+ process.exit(1);
1748
+ }
1749
+ const brow = storage.db
1750
+ .prepare('SELECT * FROM budgets WHERE agent_name = ?')
1751
+ .get(name);
1752
+ const maxT = brow ? Number(brow.max_tokens_per_day || 0) : 0;
1753
+ const maxC = brow ? Number(brow.max_cost_per_day || 0) : 0;
1754
+ const dayStart = getDayStart(Date.now());
1755
+ const urow = storage.db
1756
+ .prepare('SELECT COALESCE(SUM(tokens_used),0) as t, COALESCE(SUM(cost_usd),0) as c FROM agent_usage WHERE agent_name = ? AND created_at >= ?')
1757
+ .get(name, dayStart);
1758
+ const usedT = urow ? Number(urow.t || 0) : 0;
1759
+ const usedC = urow ? Number(urow.c || 0) : 0;
1760
+ // projected daily
1761
+ const elapsed = Date.now() - dayStart;
1762
+ const dayMs = 86400000;
1763
+ const frac = elapsed > 0 ? Math.min(1, elapsed / dayMs) : 1;
1764
+ const projT = frac > 0 ? Math.round(usedT / frac) : usedT;
1765
+ const projC = frac > 0 ? usedC / frac : usedC;
1766
+ const overT = maxT > 0 && usedT > maxT;
1767
+ const overC = maxC > 0 && usedC > maxC;
1768
+ const isOver = overT || overC;
1769
+ if (sub === 'check' || command === 'budget-check') {
1770
+ storage.close();
1771
+ if (isOver) {
1772
+ if (!useJson)
1773
+ console.error(`Over budget for ${name}`);
1774
+ process.exit(1);
1775
+ }
1776
+ else {
1777
+ process.exit(0);
1778
+ }
1779
+ }
1780
+ if (useJson) {
1781
+ console.log(JSON.stringify({
1782
+ agent: name,
1783
+ maxTokensPerDay: maxT,
1784
+ maxCostPerDay: maxC,
1785
+ usedTokens: usedT,
1786
+ usedCostUsd: usedC,
1787
+ projectedTokens: projT,
1788
+ projectedCostUsd: Number(projC.toFixed(4)),
1789
+ overBudget: isOver,
1790
+ overTokens: overT,
1791
+ overCost: overC,
1792
+ }, null, 2));
1793
+ }
1794
+ else {
1795
+ console.log(`Budget status for ${name}:`);
1796
+ console.log(` Tokens today: ${usedT} / ${maxT || '∞'} (projected ~${projT})`);
1797
+ console.log(` Cost today: $${usedC.toFixed(4)} / $${maxC.toFixed(2) || '∞'} (projected ~$${projC.toFixed(4)})`);
1798
+ if (isOver) {
1799
+ console.log(' *** OVER BUDGET ***');
1800
+ }
1801
+ }
1802
+ }
1803
+ finally {
1804
+ storage.close();
1805
+ }
1806
+ break;
1807
+ }
1808
+ default: {
1809
+ console.error(`Unknown command: ${command}`);
1810
+ printUsage();
1811
+ process.exit(1);
1812
+ }
1813
+ }
1814
+ }
1815
+ function main() {
1816
+ try {
1817
+ const result = runMain();
1818
+ if (result && typeof result.then === 'function') {
1819
+ return result.catch((err) => {
1820
+ console.error('Error:', err instanceof Error ? err.message : String(err));
1821
+ process.exit(1);
1822
+ });
1823
+ }
1824
+ }
1825
+ catch (err) {
1826
+ console.error('Error:', err instanceof Error ? err.message : String(err));
1827
+ process.exit(1);
1828
+ }
1829
+ }
1830
+ // Run CLI main() only when this file is executed directly (bin or `node dist/index.js`).
1831
+ // Prevents side effects (e.g. printing help) when the module is imported (tests, `require`/`import`).
1832
+ const isMain = (() => {
1833
+ try {
1834
+ const invoked = process.argv[1];
1835
+ if (!invoked)
1836
+ return false;
1837
+ const thisFile = fileURLToPath(import.meta.url);
1838
+ // Normalize for cross-platform (esp. Windows backslashes)
1839
+ return invoked === thisFile || invoked.replace(/\\/g, '/') === thisFile.replace(/\\/g, '/');
1840
+ }
1841
+ catch (_) {
1842
+ /* ignore */
1843
+ return false;
1844
+ }
1845
+ })();
1846
+ if (isMain) {
1847
+ main();
1848
+ }
1849
+ export { main };
1850
+ //# sourceMappingURL=index.js.map