@agentgazer/cli 0.2.0

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.
Files changed (52) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +87 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +819 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config.d.ts +27 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +136 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/dashboard/assets/index-3NEZyhLk.js +135 -0
  12. package/dist/dashboard/assets/index-7vaLtwK6.js +135 -0
  13. package/dist/dashboard/assets/index-B7YewC-Z.css +1 -0
  14. package/dist/dashboard/assets/index-BE2JQXkd.js +135 -0
  15. package/dist/dashboard/assets/index-BMIiAulZ.js +127 -0
  16. package/dist/dashboard/assets/index-BRyPy_KL.css +1 -0
  17. package/dist/dashboard/assets/index-BYHG3kAu.css +1 -0
  18. package/dist/dashboard/assets/index-BYpKdiBX.js +135 -0
  19. package/dist/dashboard/assets/index-BZBLlvtd.css +1 -0
  20. package/dist/dashboard/assets/index-Bl576aEh.css +1 -0
  21. package/dist/dashboard/assets/index-Bt6Ph9iA.css +1 -0
  22. package/dist/dashboard/assets/index-Bu9Qf8LK.js +135 -0
  23. package/dist/dashboard/assets/index-Bv9VkjUK.css +1 -0
  24. package/dist/dashboard/assets/index-C5DQefrc.js +135 -0
  25. package/dist/dashboard/assets/index-C5kNlo-q.js +135 -0
  26. package/dist/dashboard/assets/index-CS7Cz-tc.css +1 -0
  27. package/dist/dashboard/assets/index-Cobhg8CQ.js +127 -0
  28. package/dist/dashboard/assets/index-DJHDvbfs.js +127 -0
  29. package/dist/dashboard/assets/index-Dit9uSMr.css +1 -0
  30. package/dist/dashboard/assets/index-DqswNtc2.css +1 -0
  31. package/dist/dashboard/assets/index-DuCLiTI-.js +127 -0
  32. package/dist/dashboard/assets/index-Dzlrp-MJ.js +135 -0
  33. package/dist/dashboard/assets/index-PGK7aC5L.js +135 -0
  34. package/dist/dashboard/assets/index-VzlmtD1J.js +135 -0
  35. package/dist/dashboard/assets/index-XbTAQwJ9.js +135 -0
  36. package/dist/dashboard/assets/index-rQZ6KUmI.js +60 -0
  37. package/dist/dashboard/assets/index-reNMLKj9.js +127 -0
  38. package/dist/dashboard/dist/assets/index-3NEZyhLk.js +135 -0
  39. package/dist/dashboard/dist/assets/index-BYHG3kAu.css +1 -0
  40. package/dist/dashboard/dist/favicon.svg +16 -0
  41. package/dist/dashboard/dist/index.html +14 -0
  42. package/dist/dashboard/favicon.svg +16 -0
  43. package/dist/dashboard/index.html +14 -0
  44. package/dist/index.d.ts +3 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +12 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/secret-store.d.ts +42 -0
  49. package/dist/secret-store.d.ts.map +1 -0
  50. package/dist/secret-store.js +414 -0
  51. package/dist/secret-store.js.map +1 -0
  52. package/package.json +61 -0
package/dist/cli.js ADDED
@@ -0,0 +1,819 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const fs = __importStar(require("node:fs"));
38
+ const path = __importStar(require("node:path"));
39
+ const readline = __importStar(require("node:readline"));
40
+ const config_js_1 = require("./config.js");
41
+ const secret_store_js_1 = require("./secret-store.js");
42
+ const server_1 = require("@agentgazer/server");
43
+ const proxy_1 = require("@agentgazer/proxy");
44
+ const shared_1 = require("@agentgazer/shared");
45
+ // ---------------------------------------------------------------------------
46
+ // Arg parsing
47
+ // ---------------------------------------------------------------------------
48
+ function parseFlags(argv) {
49
+ const flags = {};
50
+ for (let i = 0; i < argv.length; i++) {
51
+ const arg = argv[i];
52
+ if (arg.startsWith("--")) {
53
+ const key = arg.slice(2);
54
+ const next = argv[i + 1];
55
+ if (next !== undefined && !next.startsWith("--")) {
56
+ flags[key] = next;
57
+ i++;
58
+ }
59
+ else {
60
+ flags[key] = "";
61
+ }
62
+ }
63
+ }
64
+ return flags;
65
+ }
66
+ function parsePositional(argv) {
67
+ const positional = [];
68
+ for (let i = 0; i < argv.length; i++) {
69
+ const arg = argv[i];
70
+ if (arg.startsWith("--")) {
71
+ const next = argv[i + 1];
72
+ if (next !== undefined && !next.startsWith("--")) {
73
+ i++; // skip flag value
74
+ }
75
+ }
76
+ else {
77
+ positional.push(arg);
78
+ }
79
+ }
80
+ return positional;
81
+ }
82
+ // ---------------------------------------------------------------------------
83
+ // Help
84
+ // ---------------------------------------------------------------------------
85
+ function printUsage() {
86
+ console.log(`
87
+ AgentGazer — AI Agent Observability
88
+
89
+ Usage: agentgazer <command> [options]
90
+
91
+ Commands:
92
+ onboard First-time setup — generate token and configure providers
93
+ start Start the server, proxy, and dashboard
94
+ status Show current configuration
95
+ reset-token Generate a new auth token
96
+ providers list List configured providers
97
+ providers set-key Interactive provider key setup
98
+ providers set <name> <key> Set provider key (non-interactive)
99
+ providers remove <name> Remove a provider
100
+ version Show version
101
+ doctor Check system health
102
+ agents List registered agents
103
+ stats [agentId] Show agent statistics (auto-selects if only one agent)
104
+ uninstall Remove AgentGazer (curl-installed only)
105
+ help Show this help message
106
+
107
+ Options (for start):
108
+ --port <number> Server/dashboard port (default: 8080)
109
+ --proxy-port <number> LLM proxy port (default: 4000)
110
+ --retention-days <number> Data retention period in days (default: 30)
111
+ --no-open Don't auto-open browser
112
+
113
+ Options (for doctor, stats, agents):
114
+ --port <number> Server port to check (default: 8080)
115
+ --proxy-port <number> Proxy port to check (default: 4000)
116
+
117
+ Options (for stats):
118
+ --range <period> Time range (default: 24h)
119
+
120
+ Options (for uninstall):
121
+ --yes Skip confirmation prompts
122
+
123
+ Examples:
124
+ agentgazer onboard First-time setup
125
+ agentgazer start Start with defaults
126
+ agentgazer start --port 9090 Use custom server port
127
+ agentgazer providers set-key Interactive provider setup
128
+ agentgazer providers list List configured providers
129
+ agentgazer stats Show stats (auto-selects agent)
130
+ agentgazer stats my-agent --range 7d Show stats for specific agent
131
+ `);
132
+ }
133
+ // ---------------------------------------------------------------------------
134
+ // Subcommands
135
+ // ---------------------------------------------------------------------------
136
+ const KNOWN_PROVIDERS = shared_1.KNOWN_PROVIDER_NAMES;
137
+ function ask(rl, question) {
138
+ return new Promise((resolve) => {
139
+ rl.question(question, (answer) => resolve(answer.trim()));
140
+ });
141
+ }
142
+ async function cmdOnboard() {
143
+ const saved = (0, config_js_1.ensureConfig)();
144
+ console.log(`
145
+ AgentGazer — Setup
146
+ ───────────────────────────────────────
147
+
148
+ Token: ${saved.token}
149
+ Config: ${(0, config_js_1.getConfigDir)()}/config.json
150
+ Database: ${(0, config_js_1.getDbPath)()}
151
+ Server: http://localhost:8080
152
+ Proxy: http://localhost:4000
153
+
154
+ ───────────────────────────────────────
155
+ `);
156
+ // Initialize secret store
157
+ const { store, backendName } = await (0, secret_store_js_1.detectSecretStore)((0, config_js_1.getConfigDir)());
158
+ console.log(` Secret backend: ${backendName}\n`);
159
+ const rl = readline.createInterface({
160
+ input: process.stdin,
161
+ output: process.stdout,
162
+ });
163
+ let providerCount = 0;
164
+ try {
165
+ console.log(" Configure provider API keys (the proxy will inject these for you).");
166
+ console.log(` Available providers: ${KNOWN_PROVIDERS.join(", ")}\n`);
167
+ for (const provider of KNOWN_PROVIDERS) {
168
+ const key = await ask(rl, ` API key for ${provider} (press Enter to skip): `);
169
+ if (!key)
170
+ continue;
171
+ // Store API key in secret store
172
+ await store.set(secret_store_js_1.PROVIDER_SERVICE, provider, key);
173
+ // Store provider entry in config.json (apiKey is empty — actual key is in secret store)
174
+ const providerConfig = { apiKey: "" };
175
+ (0, config_js_1.setProvider)(provider, providerConfig);
176
+ providerCount++;
177
+ console.log(` ✓ ${provider} configured.\n`);
178
+ }
179
+ }
180
+ finally {
181
+ rl.close();
182
+ }
183
+ console.log(`
184
+ ───────────────────────────────────────
185
+ Setup complete. ${providerCount} provider(s) configured.
186
+ ───────────────────────────────────────
187
+
188
+ Add this to your project to start tracking:
189
+
190
+ ┌──────────────────────────────────────────────────────────┐
191
+ │ │
192
+ │ import { AgentGazer } from "@agentgazer/sdk"; │
193
+ │ │
194
+ │ const at = AgentGazer.init({ │
195
+ │ apiKey: "${saved.token.slice(0, 20)}...",│
196
+ │ agentId: "my-agent", │
197
+ │ }); │
198
+ │ │
199
+ │ at.track({ │
200
+ │ provider: "openai", │
201
+ │ model: "gpt-4o", │
202
+ │ tokens: { input: 150, output: 50 }, │
203
+ │ latency_ms: 1200, │
204
+ │ status: 200, │
205
+ │ }); │
206
+ │ │
207
+ └──────────────────────────────────────────────────────────┘
208
+
209
+ Or point your LLM client at the proxy (with auto API key injection):
210
+
211
+ export OPENAI_BASE_URL=http://localhost:4000/openai/v1
212
+
213
+ Next: run "agentgazer start" to launch.
214
+ `);
215
+ }
216
+ async function cmdProviders(args) {
217
+ const action = args[0];
218
+ switch (action) {
219
+ case "list": {
220
+ // List reads from config.json only — no secret store access needed.
221
+ const providers = (0, config_js_1.listProviders)();
222
+ const names = Object.keys(providers);
223
+ if (names.length === 0) {
224
+ console.log("No providers configured. Use \"agentgazer providers set-key\" to add one.");
225
+ return;
226
+ }
227
+ console.log("\n Configured providers:");
228
+ console.log(" ───────────────────────────────────────");
229
+ for (const name of names) {
230
+ const p = providers[name];
231
+ const keyStatus = p.apiKey ? "(plaintext — run \"agentgazer start\" to migrate)" : "(secured)";
232
+ const rateInfo = p.rateLimit
233
+ ? ` (rate limit: ${p.rateLimit.maxRequests} req / ${p.rateLimit.windowSeconds}s)`
234
+ : "";
235
+ console.log(` ${name}: ${keyStatus}${rateInfo}`);
236
+ }
237
+ console.log();
238
+ break;
239
+ }
240
+ case "set": {
241
+ const name = args[1];
242
+ const key = args[2];
243
+ if (!name || !key) {
244
+ console.error("Usage: agentgazer providers set <provider-name> <api-key>");
245
+ process.exit(1);
246
+ }
247
+ if (!KNOWN_PROVIDERS.includes(name)) {
248
+ console.warn(`Warning: "${name}" is not a known provider (${KNOWN_PROVIDERS.join(", ")}). Proceeding anyway.`);
249
+ }
250
+ const { store, backendName } = await (0, secret_store_js_1.detectSecretStore)((0, config_js_1.getConfigDir)());
251
+ // Store API key in secret store
252
+ await store.set(secret_store_js_1.PROVIDER_SERVICE, name, key);
253
+ // Ensure provider entry exists in config.json (for rate limits etc.)
254
+ const config = (0, config_js_1.ensureConfig)();
255
+ if (!config.providers)
256
+ config.providers = {};
257
+ if (!config.providers[name]) {
258
+ config.providers[name] = { apiKey: "" };
259
+ }
260
+ // Remove any plaintext apiKey that might be in config
261
+ config.providers[name].apiKey = "";
262
+ (0, config_js_1.saveConfig)(config);
263
+ console.log(`Provider "${name}" configured (secret stored in ${backendName}).`);
264
+ break;
265
+ }
266
+ case "set-key": {
267
+ // Interactive provider key setup
268
+ const rl = readline.createInterface({
269
+ input: process.stdin,
270
+ output: process.stdout,
271
+ });
272
+ console.log("\n Available providers:");
273
+ console.log(" ───────────────────────────────────────");
274
+ KNOWN_PROVIDERS.forEach((p, i) => {
275
+ console.log(` ${i + 1}. ${p}`);
276
+ });
277
+ console.log();
278
+ try {
279
+ const choice = await ask(rl, " Select provider (number): ");
280
+ const idx = parseInt(choice, 10) - 1;
281
+ if (isNaN(idx) || idx < 0 || idx >= KNOWN_PROVIDERS.length) {
282
+ console.error(" Invalid selection.");
283
+ process.exit(1);
284
+ }
285
+ const provider = KNOWN_PROVIDERS[idx];
286
+ const apiKey = await ask(rl, ` API key for ${provider}: `);
287
+ if (!apiKey) {
288
+ console.error(" API key is required.");
289
+ process.exit(1);
290
+ }
291
+ // Store API key in secret store
292
+ const { store, backendName: backend } = await (0, secret_store_js_1.detectSecretStore)((0, config_js_1.getConfigDir)());
293
+ await store.set(secret_store_js_1.PROVIDER_SERVICE, provider, apiKey);
294
+ // Store provider entry in config.json (apiKey is empty — actual key is in secret store)
295
+ const providerConfig = { apiKey: "" };
296
+ (0, config_js_1.setProvider)(provider, providerConfig);
297
+ console.log(`\n ✓ ${provider} configured (secret stored in ${backend}).`);
298
+ console.log(` Rate limits can be configured in the Dashboard.`);
299
+ }
300
+ finally {
301
+ rl.close();
302
+ }
303
+ break;
304
+ }
305
+ case "remove": {
306
+ const name = args[1];
307
+ if (!name) {
308
+ console.error("Usage: agentgazer providers remove <provider-name>");
309
+ process.exit(1);
310
+ }
311
+ const { store } = await (0, secret_store_js_1.detectSecretStore)((0, config_js_1.getConfigDir)());
312
+ // Delete from secret store
313
+ await store.delete(secret_store_js_1.PROVIDER_SERVICE, name);
314
+ // Remove from config.json
315
+ (0, config_js_1.removeProvider)(name);
316
+ console.log(`Provider "${name}" removed.`);
317
+ break;
318
+ }
319
+ default:
320
+ console.error("Usage: agentgazer providers <list|set-key|set|remove>");
321
+ process.exit(1);
322
+ }
323
+ }
324
+ function cmdStatus() {
325
+ const config = (0, config_js_1.readConfig)();
326
+ if (!config) {
327
+ console.log("No configuration found. Run \"agentgazer onboard\" first.");
328
+ process.exit(1);
329
+ }
330
+ console.log(`
331
+ AgentGazer — Status
332
+ ───────────────────────────────────────
333
+
334
+ Token: ${config.token}
335
+ Config: ${(0, config_js_1.getConfigDir)()}/config.json
336
+ Database: ${(0, config_js_1.getDbPath)()}
337
+ Server: http://localhost:8080 (default)
338
+ Proxy: http://localhost:4000 (default)
339
+ `);
340
+ }
341
+ function cmdResetToken() {
342
+ const config = (0, config_js_1.resetToken)();
343
+ console.log(`Token reset. New token: ${config.token}`);
344
+ }
345
+ async function cmdStart(flags) {
346
+ const config = (0, config_js_1.ensureConfig)();
347
+ const serverPort = flags["port"] ? parseInt(flags["port"], 10) : 8080;
348
+ const proxyPort = flags["proxy-port"]
349
+ ? parseInt(flags["proxy-port"], 10)
350
+ : 4000;
351
+ if (isNaN(serverPort) || serverPort < 1 || serverPort > 65535) {
352
+ console.error("Error: --port must be a valid port number (1-65535)");
353
+ process.exit(1);
354
+ }
355
+ if (isNaN(proxyPort) || proxyPort < 1 || proxyPort > 65535) {
356
+ console.error("Error: --proxy-port must be a valid port number (1-65535)");
357
+ process.exit(1);
358
+ }
359
+ if (serverPort < 1024) {
360
+ console.warn("Warning: port %d may require elevated privileges on Unix systems.", serverPort);
361
+ }
362
+ if (proxyPort < 1024) {
363
+ console.warn("Warning: proxy port %d may require elevated privileges on Unix systems.", proxyPort);
364
+ }
365
+ const retentionDays = flags["retention-days"]
366
+ ? parseInt(flags["retention-days"], 10)
367
+ : 30;
368
+ if (isNaN(retentionDays) || retentionDays < 1) {
369
+ console.error("Error: --retention-days must be a positive integer");
370
+ process.exit(1);
371
+ }
372
+ // Resolve dashboard directory
373
+ let dashboardDir;
374
+ const possibleDashboardPaths = [
375
+ path.resolve(__dirname, "dashboard"),
376
+ path.resolve(__dirname, "../../dashboard-local/dist"),
377
+ path.resolve(__dirname, "../../../apps/dashboard-local/dist"),
378
+ ];
379
+ for (const p of possibleDashboardPaths) {
380
+ try {
381
+ const fs = await import("node:fs");
382
+ if (fs.existsSync(path.join(p, "index.html"))) {
383
+ dashboardDir = p;
384
+ break;
385
+ }
386
+ }
387
+ catch {
388
+ // ignore
389
+ }
390
+ }
391
+ // Start the local server
392
+ const { db, shutdown: shutdownServer } = await (0, server_1.startServer)({
393
+ port: serverPort,
394
+ token: config.token,
395
+ dbPath: (0, config_js_1.getDbPath)(),
396
+ dashboardDir,
397
+ retentionDays,
398
+ });
399
+ // Initialize secret store
400
+ const configDir = (0, config_js_1.getConfigDir)();
401
+ const configPath = path.join(configDir, "config.json");
402
+ const { store, backendName } = await (0, secret_store_js_1.detectSecretStore)(configDir);
403
+ console.log(` Secret backend: ${backendName}`);
404
+ // Auto-migrate plaintext keys from config.json to secret store
405
+ const migratedCount = await (0, secret_store_js_1.migrateFromPlaintextConfig)(configPath, store);
406
+ if (migratedCount > 0) {
407
+ console.log(` Migrated ${migratedCount} provider key(s) from config.json to secret store.`);
408
+ }
409
+ // Load provider keys from secret store
410
+ const providerKeys = await (0, secret_store_js_1.loadProviderKeys)(store);
411
+ // Start the LLM proxy (with db for policy enforcement and rate limits)
412
+ const { shutdown: shutdownProxy } = (0, proxy_1.startProxy)({
413
+ apiKey: config.token,
414
+ agentId: "proxy",
415
+ port: proxyPort,
416
+ endpoint: `http://localhost:${serverPort}/api/events`,
417
+ providerKeys,
418
+ db, // Rate limits are loaded from db
419
+ });
420
+ console.log(`
421
+ ╔════════════════════════════════════════════════════╗
422
+ ║ AgentGazer running ║
423
+ ╠════════════════════════════════════════════════════╣
424
+ ║ ║
425
+ ║ Dashboard: http://localhost:${String(serverPort).padEnd(5)} ║
426
+ ║ Proxy: http://localhost:${String(proxyPort).padEnd(5)} ║
427
+ ║ ║
428
+ ║ Token: ${config.token.padEnd(32)} ║
429
+ ║ ║
430
+ ╚════════════════════════════════════════════════════╝
431
+
432
+ Proxy routes: http://localhost:${proxyPort}/{provider}/...
433
+ Providers: ${shared_1.KNOWN_PROVIDER_NAMES.join(", ")}
434
+ `);
435
+ // Auto-open browser unless --no-open
436
+ if (!("no-open" in flags)) {
437
+ try {
438
+ const open = await import("open");
439
+ await open.default(`http://localhost:${serverPort}`);
440
+ }
441
+ catch {
442
+ // Silently fail if browser can't be opened
443
+ }
444
+ }
445
+ // Graceful shutdown
446
+ let shuttingDown = false;
447
+ function handleShutdown() {
448
+ if (shuttingDown)
449
+ return;
450
+ shuttingDown = true;
451
+ console.log("\nShutting down...");
452
+ Promise.all([shutdownProxy(), shutdownServer()])
453
+ .then(() => {
454
+ console.log("Shutdown complete.");
455
+ process.exit(0);
456
+ })
457
+ .catch((err) => {
458
+ console.error("Error during shutdown:", err);
459
+ process.exit(1);
460
+ });
461
+ }
462
+ process.on("SIGINT", handleShutdown);
463
+ process.on("SIGTERM", handleShutdown);
464
+ // Clean up listeners once shutdown completes to prevent leaks on restart
465
+ process.once("exit", () => {
466
+ process.removeListener("SIGINT", handleShutdown);
467
+ process.removeListener("SIGTERM", handleShutdown);
468
+ });
469
+ }
470
+ // ---------------------------------------------------------------------------
471
+ // Formatting helpers
472
+ // ---------------------------------------------------------------------------
473
+ function formatNumber(n) {
474
+ return n.toLocaleString("en-US");
475
+ }
476
+ function timeAgo(iso) {
477
+ if (!iso)
478
+ return "—";
479
+ const diff = Date.now() - new Date(iso).getTime();
480
+ if (diff < 0)
481
+ return "just now";
482
+ const seconds = Math.floor(diff / 1000);
483
+ if (seconds < 60)
484
+ return `${seconds} seconds ago`;
485
+ const minutes = Math.floor(seconds / 60);
486
+ if (minutes < 60)
487
+ return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
488
+ const hours = Math.floor(minutes / 60);
489
+ if (hours < 24)
490
+ return `${hours} hour${hours === 1 ? "" : "s"} ago`;
491
+ const days = Math.floor(hours / 24);
492
+ return `${days} day${days === 1 ? "" : "s"} ago`;
493
+ }
494
+ // ---------------------------------------------------------------------------
495
+ // API helper
496
+ // ---------------------------------------------------------------------------
497
+ async function apiGet(urlPath, port) {
498
+ const config = (0, config_js_1.readConfig)();
499
+ const token = config?.token ?? "";
500
+ try {
501
+ const res = await fetch(`http://localhost:${port}${urlPath}`, {
502
+ headers: { Authorization: `Bearer ${token}` },
503
+ signal: AbortSignal.timeout(5000),
504
+ });
505
+ if (!res.ok) {
506
+ console.error(`Server responded with ${res.status} ${res.statusText}`);
507
+ process.exit(1);
508
+ }
509
+ return await res.json();
510
+ }
511
+ catch (err) {
512
+ if (err instanceof TypeError &&
513
+ err.cause
514
+ ?.code === "ECONNREFUSED") {
515
+ console.error('Server not running. Run "agentgazer start" first.');
516
+ process.exit(1);
517
+ }
518
+ throw err;
519
+ }
520
+ }
521
+ // ---------------------------------------------------------------------------
522
+ // New subcommands
523
+ // ---------------------------------------------------------------------------
524
+ function cmdVersion() {
525
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
526
+ const pkg = require(path.resolve(__dirname, "../package.json"));
527
+ console.log(`agentgazer ${pkg.version}`);
528
+ }
529
+ async function cmdDoctor(flags) {
530
+ const port = flags["port"] ? parseInt(flags["port"], 10) : 8080;
531
+ const proxyPort = flags["proxy-port"]
532
+ ? parseInt(flags["proxy-port"], 10)
533
+ : 4000;
534
+ if (isNaN(port) || port < 1 || port > 65535) {
535
+ console.error("Error: --port must be a valid port number (1-65535)");
536
+ process.exit(1);
537
+ }
538
+ if (isNaN(proxyPort) || proxyPort < 1 || proxyPort > 65535) {
539
+ console.error("Error: --proxy-port must be a valid port number (1-65535)");
540
+ process.exit(1);
541
+ }
542
+ console.log(`
543
+ AgentGazer — Doctor
544
+ ───────────────────────────────────────
545
+ `);
546
+ let passed = 0;
547
+ const total = 6;
548
+ // 1. Config file exists
549
+ const config = (0, config_js_1.readConfig)();
550
+ if (config) {
551
+ console.log(" ✓ Config file exists");
552
+ passed++;
553
+ }
554
+ else {
555
+ console.log(" ✗ Config file missing");
556
+ }
557
+ // 2. Auth token set
558
+ if (config?.token) {
559
+ console.log(" ✓ Auth token set");
560
+ passed++;
561
+ }
562
+ else {
563
+ console.log(" ✗ Auth token not set");
564
+ }
565
+ // 3. Database file exists
566
+ if (fs.existsSync((0, config_js_1.getDbPath)())) {
567
+ console.log(" ✓ Database file exists");
568
+ passed++;
569
+ }
570
+ else {
571
+ console.log(" ✗ Database file missing");
572
+ }
573
+ // 4. Secret store accessible
574
+ try {
575
+ const { store } = await (0, secret_store_js_1.detectSecretStore)((0, config_js_1.getConfigDir)());
576
+ if (await store.isAvailable()) {
577
+ console.log(" ✓ Secret store accessible");
578
+ passed++;
579
+ }
580
+ else {
581
+ console.log(" ✗ Secret store not accessible");
582
+ }
583
+ }
584
+ catch {
585
+ console.log(" ✗ Secret store not accessible");
586
+ }
587
+ // 5. Server responding
588
+ try {
589
+ const res = await fetch(`http://localhost:${port}/api/health`, {
590
+ signal: AbortSignal.timeout(3000),
591
+ });
592
+ if (res.ok) {
593
+ console.log(` ✓ Server responding (http://localhost:${port})`);
594
+ passed++;
595
+ }
596
+ else {
597
+ console.log(` ✗ Server not responding (http://localhost:${port})`);
598
+ }
599
+ }
600
+ catch {
601
+ console.log(` ✗ Server not responding (http://localhost:${port})`);
602
+ }
603
+ // 6. Proxy responding
604
+ try {
605
+ await fetch(`http://localhost:${proxyPort}/`, {
606
+ signal: AbortSignal.timeout(3000),
607
+ });
608
+ // Any response (even 4xx) means it's up
609
+ console.log(` ✓ Proxy responding (http://localhost:${proxyPort})`);
610
+ passed++;
611
+ }
612
+ catch {
613
+ console.log(` ✗ Proxy not responding (http://localhost:${proxyPort})`);
614
+ }
615
+ console.log(`\n ${passed}/${total} checks passed.`);
616
+ }
617
+ async function cmdAgents(flags) {
618
+ const port = flags["port"] ? parseInt(flags["port"], 10) : 8080;
619
+ if (isNaN(port) || port < 1 || port > 65535) {
620
+ console.error("Error: --port must be a valid port number (1-65535)");
621
+ process.exit(1);
622
+ }
623
+ const resp = (await apiGet("/api/agents", port));
624
+ const agents = resp.agents;
625
+ if (!agents || agents.length === 0) {
626
+ console.log("No agents registered yet.");
627
+ return;
628
+ }
629
+ const header = ` ${"Agent ID".padEnd(18)}${"Status".padEnd(11)}${"Events".padStart(8)} Last Heartbeat`;
630
+ console.log(header);
631
+ console.log(" " + "─".repeat(header.trimStart().length));
632
+ for (const a of agents) {
633
+ const id = (a.agent_id ?? "").padEnd(18);
634
+ const status = (a.status ?? "unknown").padEnd(11);
635
+ const events = formatNumber(a.total_events ?? 0).padStart(8);
636
+ const heartbeat = timeAgo(a.last_heartbeat);
637
+ console.log(` ${id}${status}${events} ${heartbeat}`);
638
+ }
639
+ }
640
+ async function cmdStats(flags) {
641
+ const port = flags["port"] ? parseInt(flags["port"], 10) : 8080;
642
+ const range = flags["range"] || "24h";
643
+ const positional = parsePositional(process.argv.slice(3));
644
+ let agentId = positional[0];
645
+ // Auto-select agent if not specified
646
+ if (!agentId) {
647
+ const resp = (await apiGet("/api/agents", port));
648
+ const agents = resp.agents;
649
+ if (!agents || agents.length === 0) {
650
+ console.log("No agents registered yet.");
651
+ return;
652
+ }
653
+ if (agents.length === 1) {
654
+ agentId = agents[0].agent_id;
655
+ }
656
+ else {
657
+ console.log("Multiple agents found. Please specify one:\n");
658
+ for (const a of agents) {
659
+ console.log(` agentgazer stats ${a.agent_id}`);
660
+ }
661
+ console.log();
662
+ process.exit(1);
663
+ }
664
+ }
665
+ let data;
666
+ try {
667
+ data = (await apiGet(`/api/stats/${encodeURIComponent(agentId)}?range=${encodeURIComponent(range)}`, port));
668
+ }
669
+ catch (err) {
670
+ if (err && typeof err === "object" && "message" in err) {
671
+ console.error(`Error fetching stats for "${agentId}": ${err.message}`);
672
+ }
673
+ else {
674
+ console.error(`Error fetching stats for "${agentId}".`);
675
+ }
676
+ process.exit(1);
677
+ }
678
+ const errorPct = data.total_requests > 0
679
+ ? ((data.total_errors / data.total_requests) * 100).toFixed(2)
680
+ : "0.00";
681
+ console.log(`
682
+ AgentGazer — Stats for "${agentId}" (last ${range})
683
+ ───────────────────────────────────────
684
+
685
+ Requests: ${formatNumber(data.total_requests)}
686
+ Errors: ${formatNumber(data.total_errors)} (${errorPct}%)
687
+ Cost: $${data.total_cost.toFixed(2)}
688
+ Tokens: ${formatNumber(data.total_tokens)}
689
+
690
+ Latency: p50 = ${data.p50_latency != null ? formatNumber(data.p50_latency) : "--"}ms p99 = ${data.p99_latency != null ? formatNumber(data.p99_latency) : "--"}ms`);
691
+ if (data.cost_by_model && data.cost_by_model.length > 0) {
692
+ console.log("\n Cost by model:");
693
+ for (const m of data.cost_by_model) {
694
+ const model = m.model.padEnd(16);
695
+ const cost = `$${m.cost.toFixed(2)}`;
696
+ console.log(` ${model}${cost} (${formatNumber(m.count)} calls)`);
697
+ }
698
+ }
699
+ console.log();
700
+ }
701
+ // ---------------------------------------------------------------------------
702
+ // Uninstall
703
+ // ---------------------------------------------------------------------------
704
+ async function cmdUninstall(flags) {
705
+ const home = process.env.AGENTTRACE_HOME || path.join(require("os").homedir(), ".agentgazer");
706
+ const libDir = path.join(home, "lib");
707
+ const nodeDir = path.join(home, "node");
708
+ const wrapperPath = path.join(process.env.AGENTTRACE_BIN || "/usr/local/bin", "agentgazer");
709
+ // Detect install method
710
+ if (!fs.existsSync(libDir)) {
711
+ console.log('AgentGazer was not installed via the install script.');
712
+ console.log('');
713
+ console.log(' If installed via npm:');
714
+ console.log(' npm uninstall -g agentgazer');
715
+ console.log('');
716
+ console.log(' If installed via Homebrew:');
717
+ console.log(' brew uninstall agentgazer');
718
+ console.log('');
719
+ return;
720
+ }
721
+ const skipPrompt = "yes" in flags;
722
+ console.log(`
723
+ AgentGazer — Uninstall
724
+ ───────────────────────────────────────
725
+ `);
726
+ // Remove embedded Node.js
727
+ if (fs.existsSync(nodeDir)) {
728
+ fs.rmSync(nodeDir, { recursive: true, force: true });
729
+ console.log(` ✓ Removed embedded Node.js (${nodeDir})`);
730
+ }
731
+ // Remove lib
732
+ fs.rmSync(libDir, { recursive: true, force: true });
733
+ console.log(` ✓ Removed installation (${libDir})`);
734
+ // Remove wrapper
735
+ if (fs.existsSync(wrapperPath)) {
736
+ try {
737
+ fs.unlinkSync(wrapperPath);
738
+ console.log(` ✓ Removed wrapper (${wrapperPath})`);
739
+ }
740
+ catch {
741
+ console.log(` ! Could not remove ${wrapperPath} — try: sudo rm ${wrapperPath}`);
742
+ }
743
+ }
744
+ // Handle user data
745
+ const configPath = path.join(home, "config.json");
746
+ const dbPath = path.join(home, "data.db");
747
+ const hasData = fs.existsSync(configPath) || fs.existsSync(dbPath);
748
+ if (hasData) {
749
+ let removeData = skipPrompt;
750
+ if (!skipPrompt) {
751
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
752
+ const answer = await ask(rl, "\n Remove user data (config.json, data.db)? [y/N] ");
753
+ rl.close();
754
+ removeData = /^y(es)?$/i.test(answer);
755
+ }
756
+ if (removeData) {
757
+ fs.rmSync(home, { recursive: true, force: true });
758
+ console.log(` ✓ Removed all data (${home})`);
759
+ }
760
+ else {
761
+ console.log(` → User data preserved at ${home}`);
762
+ }
763
+ }
764
+ console.log("\n ✓ AgentGazer uninstalled.\n");
765
+ }
766
+ // ---------------------------------------------------------------------------
767
+ // Main
768
+ // ---------------------------------------------------------------------------
769
+ async function main() {
770
+ const subcommand = process.argv[2];
771
+ const flags = parseFlags(process.argv.slice(3));
772
+ switch (subcommand) {
773
+ case "onboard":
774
+ await cmdOnboard();
775
+ break;
776
+ case "start":
777
+ await cmdStart(flags);
778
+ break;
779
+ case "status":
780
+ cmdStatus();
781
+ break;
782
+ case "reset-token":
783
+ cmdResetToken();
784
+ break;
785
+ case "providers":
786
+ await cmdProviders(process.argv.slice(3));
787
+ break;
788
+ case "version":
789
+ cmdVersion();
790
+ break;
791
+ case "doctor":
792
+ await cmdDoctor(flags);
793
+ break;
794
+ case "agents":
795
+ await cmdAgents(flags);
796
+ break;
797
+ case "stats":
798
+ await cmdStats(flags);
799
+ break;
800
+ case "uninstall":
801
+ await cmdUninstall(flags);
802
+ break;
803
+ case "--help":
804
+ case "-h":
805
+ case "help":
806
+ case undefined:
807
+ printUsage();
808
+ break;
809
+ default:
810
+ console.error(`Unknown command: ${subcommand}`);
811
+ printUsage();
812
+ process.exit(1);
813
+ }
814
+ }
815
+ main().catch((err) => {
816
+ console.error("Fatal error:", err);
817
+ process.exit(1);
818
+ });
819
+ //# sourceMappingURL=cli.js.map