agent-dir 0.3.1 → 0.4.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 (98) hide show
  1. package/README.md +3 -2
  2. package/dist/bin/agent-dir.d.ts +0 -1
  3. package/dist/bin/agent-dir.js +11 -4
  4. package/dist/src/auth.d.ts +0 -1
  5. package/dist/src/auth.js +0 -1
  6. package/dist/src/codegraph.d.ts +0 -1
  7. package/dist/src/codegraph.js +0 -1
  8. package/dist/src/config.d.ts +0 -1
  9. package/dist/src/config.js +0 -1
  10. package/dist/src/logging.d.ts +0 -1
  11. package/dist/src/logging.js +1 -2
  12. package/dist/src/mcp.d.ts +0 -1
  13. package/dist/src/mcp.js +11 -2
  14. package/dist/src/server.d.ts +0 -1
  15. package/dist/src/server.js +42 -2
  16. package/dist/src/setup.d.ts +0 -1
  17. package/dist/src/setup.js +4 -3
  18. package/dist/src/telemetry/aggregate.d.ts +7 -1
  19. package/dist/src/telemetry/aggregate.js +39 -3
  20. package/dist/src/telemetry/config.d.ts +0 -1
  21. package/dist/src/telemetry/config.js +0 -1
  22. package/dist/src/telemetry/format.d.ts +0 -1
  23. package/dist/src/telemetry/format.js +1 -2
  24. package/dist/src/telemetry/privacy.d.ts +0 -1
  25. package/dist/src/telemetry/privacy.js +76 -3
  26. package/dist/src/telemetry/recorder.d.ts +0 -1
  27. package/dist/src/telemetry/recorder.js +0 -1
  28. package/dist/src/telemetry/types.d.ts +28 -3
  29. package/dist/src/telemetry/types.js +1 -2
  30. package/dist/src/tools/code.d.ts +0 -1
  31. package/dist/src/tools/code.js +0 -1
  32. package/dist/src/tools/commands.d.ts +0 -1
  33. package/dist/src/tools/commands.js +0 -1
  34. package/dist/src/tools/diagnostics.d.ts +0 -1
  35. package/dist/src/tools/diagnostics.js +0 -1
  36. package/dist/src/tools/files.d.ts +0 -1
  37. package/dist/src/tools/files.js +0 -1
  38. package/dist/src/tools/git.d.ts +0 -1
  39. package/dist/src/tools/git.js +0 -1
  40. package/dist/src/tools/metadata.d.ts +0 -1
  41. package/dist/src/tools/metadata.js +0 -1
  42. package/dist/src/tools/npm.d.ts +0 -1
  43. package/dist/src/tools/npm.js +0 -1
  44. package/dist/src/tools/skills.d.ts +0 -1
  45. package/dist/src/tools/skills.js +0 -1
  46. package/dist/src/tunnel.d.ts +1 -2
  47. package/dist/src/tunnel.js +243 -83
  48. package/dist/src/types.d.ts +24 -1
  49. package/dist/src/types.js +0 -1
  50. package/package.json +2 -2
  51. package/dist/bin/agent-dir.d.ts.map +0 -1
  52. package/dist/bin/agent-dir.js.map +0 -1
  53. package/dist/src/auth.d.ts.map +0 -1
  54. package/dist/src/auth.js.map +0 -1
  55. package/dist/src/codegraph.d.ts.map +0 -1
  56. package/dist/src/codegraph.js.map +0 -1
  57. package/dist/src/config.d.ts.map +0 -1
  58. package/dist/src/config.js.map +0 -1
  59. package/dist/src/logging.d.ts.map +0 -1
  60. package/dist/src/logging.js.map +0 -1
  61. package/dist/src/mcp.d.ts.map +0 -1
  62. package/dist/src/mcp.js.map +0 -1
  63. package/dist/src/server.d.ts.map +0 -1
  64. package/dist/src/server.js.map +0 -1
  65. package/dist/src/setup.d.ts.map +0 -1
  66. package/dist/src/setup.js.map +0 -1
  67. package/dist/src/telemetry/aggregate.d.ts.map +0 -1
  68. package/dist/src/telemetry/aggregate.js.map +0 -1
  69. package/dist/src/telemetry/config.d.ts.map +0 -1
  70. package/dist/src/telemetry/config.js.map +0 -1
  71. package/dist/src/telemetry/format.d.ts.map +0 -1
  72. package/dist/src/telemetry/format.js.map +0 -1
  73. package/dist/src/telemetry/privacy.d.ts.map +0 -1
  74. package/dist/src/telemetry/privacy.js.map +0 -1
  75. package/dist/src/telemetry/recorder.d.ts.map +0 -1
  76. package/dist/src/telemetry/recorder.js.map +0 -1
  77. package/dist/src/telemetry/types.d.ts.map +0 -1
  78. package/dist/src/telemetry/types.js.map +0 -1
  79. package/dist/src/tools/code.d.ts.map +0 -1
  80. package/dist/src/tools/code.js.map +0 -1
  81. package/dist/src/tools/commands.d.ts.map +0 -1
  82. package/dist/src/tools/commands.js.map +0 -1
  83. package/dist/src/tools/diagnostics.d.ts.map +0 -1
  84. package/dist/src/tools/diagnostics.js.map +0 -1
  85. package/dist/src/tools/files.d.ts.map +0 -1
  86. package/dist/src/tools/files.js.map +0 -1
  87. package/dist/src/tools/git.d.ts.map +0 -1
  88. package/dist/src/tools/git.js.map +0 -1
  89. package/dist/src/tools/metadata.d.ts.map +0 -1
  90. package/dist/src/tools/metadata.js.map +0 -1
  91. package/dist/src/tools/npm.d.ts.map +0 -1
  92. package/dist/src/tools/npm.js.map +0 -1
  93. package/dist/src/tools/skills.d.ts.map +0 -1
  94. package/dist/src/tools/skills.js.map +0 -1
  95. package/dist/src/tunnel.d.ts.map +0 -1
  96. package/dist/src/tunnel.js.map +0 -1
  97. package/dist/src/types.d.ts.map +0 -1
  98. package/dist/src/types.js.map +0 -1
package/README.md CHANGED
@@ -51,7 +51,7 @@ The Wormhole URL is only the transport endpoint. `agent-dir` authentication is e
51
51
 
52
52
  On the first interactive run, when no `agent-dir` config exists, the CLI walks you through creating your first saved profile. It asks for the profile name, directory, port, tunnel, optional Wormhole subdomain, optional npm scripts, optional allowed commands, and telemetry preference. The generated authentication token is saved with the profile.
53
53
 
54
- Telemetry is optional and stays local. During setup, `agent-dir` explains the available levels: **none**, **anonymous**, **basic**, and **detailed**. Telemetry is disabled by default if you choose the default option. The same choices can later be changed with `agent-dir telemetry enable|disable`.
54
+ Telemetry is optional and stays local. During setup, `agent-dir` explains the available levels: **none**, **anonymous**, **basic**, **detailed**, and **diagnostic**. Telemetry is disabled by default if you choose the default option. The same choices can later be changed with `agent-dir telemetry enable|disable`.
55
55
 
56
56
  You can also run the setup wizard explicitly at any time:
57
57
 
@@ -130,6 +130,7 @@ Enable a privacy level with:
130
130
  agent-dir telemetry enable anonymous
131
131
  agent-dir telemetry enable basic
132
132
  agent-dir telemetry enable detailed
133
+ agent-dir telemetry enable diagnostic
133
134
  ```
134
135
 
135
136
  Levels add bounded operational context:
@@ -138,7 +139,7 @@ Levels add bounded operational context:
138
139
  - **basic** — anonymous data plus bounded Agent Dir/Node/platform and MCP client version information.
139
140
  - **detailed** — basic data plus coarse project classification such as language, package manager, Git/CodeGraph availability, and project size.
140
141
 
141
- Telemetry never records file contents, command arguments, authentication tokens, environment variables, or project paths. Session telemetry separates wall-clock session lifetime from active MCP request time and the gaps between requests; those gaps may include agent reasoning, network delay, or other idle time and are not presented as agent thinking time. Persistence failures do not affect MCP requests.
142
+ Standard telemetry levels never record file contents, command arguments, authentication tokens, environment variables, or project paths. The opt-in diagnostic level additionally records local troubleshooting metadata such as project paths, hostname, username, process identifiers, Node version, memory usage, and process uptime; it remains local and is never uploaded by Agent Dir. Session telemetry separates wall-clock session lifetime from active MCP request time and the gaps between requests; those gaps may include agent reasoning, network delay, or other idle time and are not presented as agent thinking time. Persistence failures do not affect MCP requests.
142
143
 
143
144
  Manage telemetry with:
144
145
 
@@ -1,3 +1,2 @@
1
1
  #!/usr/bin/env node
2
2
  export {};
3
- //# sourceMappingURL=agent-dir.d.ts.map
@@ -100,7 +100,7 @@ if (command === "telemetry") {
100
100
  if (action === "enable") {
101
101
  const level = positionals[2];
102
102
  if (!level || !isTelemetryLevel(level) || level === "none")
103
- fail("Usage: agent-dir telemetry enable <anonymous|basic|detailed>");
103
+ fail("Usage: agent-dir telemetry enable <anonymous|basic|detailed|diagnostic>");
104
104
  config.telemetry = { level };
105
105
  await saveConfig(config);
106
106
  console.log(`✓ Telemetry enabled at ${level} level.`);
@@ -125,7 +125,8 @@ if (command === "telemetry") {
125
125
  "commandCallCount",
126
126
  ],
127
127
  envelope: ["configId", "sessionId", "timestamp", "level", "event"],
128
- events: ["mcp_request", "tool_call", "command_call", "session"],
128
+ events: ["mcp_request", "tool_call", "command_call", "tunnel", "session"],
129
+ levels: ["none", "anonymous", "basic", "detailed", "diagnostic"],
129
130
  successMetrics: {
130
131
  mcp: "successful mcp_request events / mcp_request events",
131
132
  tool: "successful tool_call events / tool_call events",
@@ -406,6 +407,12 @@ if (tunnel !== "none") {
406
407
  tunnelResult = await startTunnel({
407
408
  provider: "wormhole",
408
409
  port,
410
+ token,
411
+ telemetry: {
412
+ ...(config.telemetry ?? { level: "none" }),
413
+ ...(telemetryConfigId ? { configId: telemetryConfigId } : {}),
414
+ persist: true,
415
+ },
409
416
  random: randomTunnel,
410
417
  ...(subdomain && !randomTunnel ? { subdomain } : {}),
411
418
  });
@@ -423,7 +430,8 @@ const shutdown = async () => {
423
430
  return;
424
431
  shuttingDown = true;
425
432
  console.log("\n Stopping agent-dir...");
426
- if (tunnelResult?.child && !tunnelResult.child.killed)
433
+ tunnelResult?.stop?.();
434
+ if (tunnelResult?.child && !tunnelResult.stop && !tunnelResult.child.killed)
427
435
  tunnelResult.child.kill("SIGTERM");
428
436
  await server.close();
429
437
  process.exit(0);
@@ -456,4 +464,3 @@ function readTelemetryEvents(path) {
456
464
  return [];
457
465
  }
458
466
  }
459
- //# sourceMappingURL=agent-dir.js.map
@@ -1,3 +1,2 @@
1
1
  export declare function authenticate(request: Request, token: string): boolean;
2
2
  export declare function unauthorized(): Response;
3
- //# sourceMappingURL=auth.d.ts.map
package/dist/src/auth.js CHANGED
@@ -23,4 +23,3 @@ export function unauthorized() {
23
23
  },
24
24
  });
25
25
  }
26
- //# sourceMappingURL=auth.js.map
@@ -30,4 +30,3 @@ export declare class CodeGraphIntegration {
30
30
  private handleProcessFailure;
31
31
  private rejectPending;
32
32
  }
33
- //# sourceMappingURL=codegraph.d.ts.map
@@ -262,4 +262,3 @@ function safeEnvironment() {
262
262
  }
263
263
  return environment;
264
264
  }
265
- //# sourceMappingURL=codegraph.js.map
@@ -3,4 +3,3 @@ export declare function loadConfig(): Promise<AgentConfig>;
3
3
  export declare function saveConfig(config: AgentConfig): Promise<void>;
4
4
  export declare function configPath(): string;
5
5
  export declare function mergeProfile(profile: Profile, overrides?: CommandConfig): CommandConfig;
6
- //# sourceMappingURL=config.d.ts.map
@@ -91,4 +91,3 @@ function isProfile(value) {
91
91
  return false;
92
92
  return true;
93
93
  }
94
- //# sourceMappingURL=config.js.map
@@ -15,4 +15,3 @@ export declare function printBanner(options: {
15
15
  export declare function printRequestLog(entry: RequestLog): void;
16
16
  export declare function logRequest(entry: RequestLog): void;
17
17
  export declare function printRequestHeader(): void;
18
- //# sourceMappingURL=logging.d.ts.map
@@ -132,7 +132,7 @@ export function printHelp() {
132
132
  writeLine("");
133
133
  writeLine("Telemetry:");
134
134
  writeLine(" agent-dir telemetry status");
135
- writeLine(" agent-dir telemetry enable <anonymous|basic|detailed>");
135
+ writeLine(" agent-dir telemetry enable <anonymous|basic|detailed|diagnostic>");
136
136
  writeLine(" agent-dir telemetry disable");
137
137
  writeLine(" agent-dir telemetry schema");
138
138
  writeLine(" agent-dir telemetry show [--follow]");
@@ -267,4 +267,3 @@ export function printRequestHeader() {
267
267
  writeLine(" [Request logs]");
268
268
  writeLine(" ──────────────────────────────────────────────────────────────");
269
269
  }
270
- //# sourceMappingURL=logging.js.map
package/dist/src/mcp.d.ts CHANGED
@@ -9,4 +9,3 @@ interface McpHandler {
9
9
  export declare function createMcpHandler(root: string, commandConfig?: CommandConfig, telemetry?: TelemetryConfig): McpHandler;
10
10
  export declare function validateMcpParamHeaders(inputSchema: Record<string, unknown>, args: Record<string, unknown>, headers: Headers): string | undefined;
11
11
  export {};
12
- //# sourceMappingURL=mcp.d.ts.map
package/dist/src/mcp.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { createRequire } from "node:module";
3
+ import { hostname, userInfo } from "node:os";
3
4
  import { CodeGraphIntegration } from "./codegraph.js";
4
5
  import { TelemetryRecorder } from "./telemetry/recorder.js";
5
6
  import { findDefinition, findExports, findFiles, findImports, findReferences, findSymbol, searchCode, searchFiles, } from "./tools/code.js";
@@ -344,7 +345,7 @@ export function createMcpHandler(root, commandConfig = {}, telemetry = { level:
344
345
  let sessionToolCallCount = 0;
345
346
  let sessionCommandCallCount = 0;
346
347
  let sessionRecorded = false;
347
- const telemetryProjectPromise = telemetry.level === "detailed"
348
+ const telemetryProjectPromise = telemetry.level === "detailed" || telemetry.level === "diagnostic"
348
349
  ? buildTelemetryProjectContext(root, codeGraph).catch(() => undefined)
349
350
  : undefined;
350
351
  const npmAllowed = commandConfig.npm?.allowedScripts ?? [];
@@ -1552,6 +1553,13 @@ function telemetryRuntime(params) {
1552
1553
  arch: process.arch,
1553
1554
  ...(typeof clientInfo?.name === "string" ? { mcpClientName: clientInfo.name } : {}),
1554
1555
  ...(typeof clientInfo?.version === "string" ? { mcpClientVersion: clientInfo.version } : {}),
1556
+ nodeVersion: process.versions.node,
1557
+ hostname: hostname(),
1558
+ username: userInfo().username,
1559
+ processId: process.pid,
1560
+ parentProcessId: process.ppid,
1561
+ processUptimeMs: Math.round(process.uptime() * 1000),
1562
+ memoryRssBytes: process.memoryUsage().rss,
1555
1563
  };
1556
1564
  }
1557
1565
  function toolTelemetryMetrics(toolName, result) {
@@ -1618,6 +1626,8 @@ async function buildTelemetryProjectContext(root, codeGraph) {
1618
1626
  const projectSize = overview.files < 100 ? "small" : overview.files < 1000 ? "medium" : "large";
1619
1627
  const capability = await codeGraph.capability();
1620
1628
  return {
1629
+ projectRoot: root,
1630
+ workingDirectory: process.cwd(),
1621
1631
  ...(language ? { language } : {}),
1622
1632
  ...(packageManager && ["npm", "pnpm", "yarn", "bun"].includes(packageManager)
1623
1633
  ? { packageManager: packageManager }
@@ -1871,4 +1881,3 @@ function json(value, status = 200) {
1871
1881
  headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" },
1872
1882
  });
1873
1883
  }
1874
- //# sourceMappingURL=mcp.js.map
@@ -8,4 +8,3 @@ export declare function getMcpLog(body: Buffer): {
8
8
  name: string;
9
9
  detail: string;
10
10
  } | undefined;
11
- //# sourceMappingURL=server.d.ts.map
@@ -2,12 +2,21 @@ import http, {} from "node:http";
2
2
  import { authenticate, unauthorized } from "./auth.js";
3
3
  import { logRequest } from "./logging.js";
4
4
  import { createMcpHandler } from "./mcp.js";
5
+ import { TelemetryRecorder } from "./telemetry/recorder.js";
5
6
  import { deleteFile, listFiles, readFile, writeFile } from "./tools/files.js";
6
7
  const MAX_BODY_BYTES = 10 * 1024 * 1024;
7
8
  export function startServer({ root, port, token, commandConfig = {}, telemetry = { level: "none" }, }) {
8
9
  const mcp = createMcpHandler(root, commandConfig, telemetry);
10
+ const httpTelemetry = new TelemetryRecorder({
11
+ level: telemetry.level,
12
+ persist: telemetry.persist ?? false,
13
+ ...(telemetry.configId ? { configId: telemetry.configId } : {}),
14
+ sessionId: `http-${Date.now()}`,
15
+ });
9
16
  const server = http.createServer(async (req, res) => {
17
+ const startedAt = performance.now();
10
18
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
19
+ let authenticated = false;
11
20
  try {
12
21
  const body = await readBody(req);
13
22
  const requestInit = {
@@ -17,7 +26,8 @@ export function startServer({ root, port, token, commandConfig = {}, telemetry =
17
26
  if (body.length)
18
27
  requestInit.body = body;
19
28
  const request = new Request(url, requestInit);
20
- if (!authenticate(request, token)) {
29
+ authenticated = authenticate(request, token);
30
+ if (!authenticated) {
21
31
  const response = unauthorized();
22
32
  await sendResponse(res, response);
23
33
  logRequest({
@@ -26,6 +36,7 @@ export function startServer({ root, port, token, commandConfig = {}, telemetry =
26
36
  status: 401,
27
37
  detail: "unauthorized",
28
38
  });
39
+ recordHttpTelemetry(httpTelemetry, req.method ?? "UNKNOWN", url.pathname, 401, authenticated, startedAt);
29
40
  return;
30
41
  }
31
42
  if (url.pathname === "/mcp") {
@@ -38,6 +49,7 @@ export function startServer({ root, port, token, commandConfig = {}, telemetry =
38
49
  status: 405,
39
50
  detail: "method not allowed",
40
51
  });
52
+ recordHttpTelemetry(httpTelemetry, req.method ?? "UNKNOWN", url.pathname, 405, authenticated, startedAt);
41
53
  return;
42
54
  }
43
55
  const headerError = validateMcpHeaders(request, messageBody(body));
@@ -50,6 +62,7 @@ export function startServer({ root, port, token, commandConfig = {}, telemetry =
50
62
  status: headerError.status,
51
63
  detail: errorDetail ? `MCP header validation: ${errorDetail}` : "MCP header validation",
52
64
  });
65
+ recordHttpTelemetry(httpTelemetry, req.method, url.pathname, headerError.status, authenticated, startedAt);
53
66
  return;
54
67
  }
55
68
  const response = await mcp(request);
@@ -66,12 +79,14 @@ export function startServer({ root, port, token, commandConfig = {}, telemetry =
66
79
  : (mcpLog?.detail ?? "MCP"),
67
80
  ...(mcpLog ? { tool: mcpLog.name } : {}),
68
81
  });
82
+ recordHttpTelemetry(httpTelemetry, req.method, url.pathname, response.status, authenticated, startedAt);
69
83
  return;
70
84
  }
71
85
  if (url.pathname === "/__tree" && req.method === "GET") {
72
86
  const tree = await listFiles(root);
73
87
  sendText(res, 200, JSON.stringify(tree, null, 2), "application/json; charset=utf-8");
74
88
  logRequest({ method: req.method, path: url.pathname, status: 200, detail: "directory" });
89
+ recordHttpTelemetry(httpTelemetry, req.method, url.pathname, 200, authenticated, startedAt);
75
90
  return;
76
91
  }
77
92
  const relative = decodeURIComponent(url.pathname.slice(1));
@@ -83,23 +98,27 @@ export function startServer({ root, port, token, commandConfig = {}, telemetry =
83
98
  status: 200,
84
99
  detail: "server",
85
100
  });
101
+ recordHttpTelemetry(httpTelemetry, req.method ?? "UNKNOWN", url.pathname, 200, authenticated, startedAt);
86
102
  return;
87
103
  }
88
104
  if (req.method === "GET") {
89
105
  sendText(res, 200, await readFile(root, relative), "text/plain; charset=utf-8");
90
106
  logRequest({ method: req.method, path: url.pathname, status: 200, detail: "REST GET" });
107
+ recordHttpTelemetry(httpTelemetry, req.method, url.pathname, 200, authenticated, startedAt);
91
108
  return;
92
109
  }
93
110
  if (req.method === "PUT") {
94
111
  await writeFile(root, relative, body.toString("utf8"));
95
112
  sendText(res, 200, "File updated successfully\n");
96
113
  logRequest({ method: req.method, path: url.pathname, status: 200, detail: "REST PUT" });
114
+ recordHttpTelemetry(httpTelemetry, req.method, url.pathname, 200, authenticated, startedAt);
97
115
  return;
98
116
  }
99
117
  if (req.method === "DELETE") {
100
118
  await deleteFile(root, relative);
101
119
  sendText(res, 200, "File deleted successfully\n");
102
120
  logRequest({ method: req.method, path: url.pathname, status: 200, detail: "REST DELETE" });
121
+ recordHttpTelemetry(httpTelemetry, req.method, url.pathname, 200, authenticated, startedAt);
103
122
  return;
104
123
  }
105
124
  sendText(res, 404, "Not found\n");
@@ -109,6 +128,7 @@ export function startServer({ root, port, token, commandConfig = {}, telemetry =
109
128
  status: 404,
110
129
  detail: "not found",
111
130
  });
131
+ recordHttpTelemetry(httpTelemetry, req.method ?? "UNKNOWN", url.pathname, 404, authenticated, startedAt);
112
132
  }
113
133
  catch (error) {
114
134
  const code = error.code;
@@ -120,6 +140,7 @@ export function startServer({ root, port, token, commandConfig = {}, telemetry =
120
140
  status,
121
141
  detail: error instanceof Error ? error.message : String(error),
122
142
  });
143
+ recordHttpTelemetry(httpTelemetry, req.method ?? "UNKNOWN", url.pathname, status, authenticated, startedAt);
123
144
  }
124
145
  });
125
146
  return new Promise((resolve, reject) => {
@@ -480,6 +501,26 @@ function formatCommand(command, args) {
480
501
  const count = Array.isArray(args) ? args.length : 0;
481
502
  return count ? `${command} (args: ${count})` : command;
482
503
  }
504
+ function recordHttpTelemetry(recorder, method, pathname, status, authenticated, startedAt) {
505
+ const route = pathname === "/mcp"
506
+ ? "mcp"
507
+ : pathname === "/__tree"
508
+ ? "tree"
509
+ : pathname === "/"
510
+ ? "root"
511
+ : pathname.startsWith("/") && pathname.length > 1
512
+ ? "file"
513
+ : "other";
514
+ recorder.record({
515
+ event: "http_request",
516
+ method,
517
+ route,
518
+ status,
519
+ success: status < 400,
520
+ durationMs: performance.now() - startedAt,
521
+ authenticated,
522
+ });
523
+ }
483
524
  function toFetchHeaders(headers) {
484
525
  const result = new Headers();
485
526
  for (const [name, value] of Object.entries(headers)) {
@@ -556,4 +597,3 @@ async function sendResponse(res, response) {
556
597
  if (!res.destroyed)
557
598
  res.end();
558
599
  }
559
- //# sourceMappingURL=server.js.map
@@ -6,4 +6,3 @@ export declare function shouldOfferSetup(configExists: boolean, options: {
6
6
  tunnel?: string | undefined;
7
7
  }): boolean;
8
8
  export declare function runFirstTimeSetup(config: AgentConfig, defaultDirectory?: string): Promise<string | null | undefined>;
9
- //# sourceMappingURL=setup.d.ts.map
package/dist/src/setup.js CHANGED
@@ -119,16 +119,18 @@ export async function runFirstTimeSetup(config, defaultDirectory = ".") {
119
119
  bold("Privacy first"),
120
120
  dim("Telemetry is optional and stays local on this machine."),
121
121
  dim("It helps us understand usage, failures, and reliability improvements."),
122
- dim("Never records file contents, command arguments, tokens,"),
122
+ dim("Standard levels never record file contents, command arguments, tokens,"),
123
123
  dim("environment variables, or project paths."),
124
+ dim("Diagnostic mode is a separate opt-in local troubleshooting mode and records identifying metadata."),
124
125
  ]);
125
126
  console.log("");
126
127
  console.log(` ${green("●")} ${white("none")} ${dim("disabled")}`);
127
128
  console.log(` ${cyan("●")} ${white("anonymous")} ${dim("usage/tool metrics, success, duration, sizes, safe errors")}`);
128
129
  console.log(` ${cyan("●")} ${white("basic")} ${dim("anonymous + Agent Dir, Node, platform, MCP client version")}`);
129
130
  console.log(` ${cyan("●")} ${white("detailed")} ${dim("basic + coarse language, package manager, Git, CodeGraph, project size")}`);
131
+ console.log(` ${cyan("●")} ${white("diagnostic")} ${dim("detailed + local paths, host/user, PID, Node version, memory/uptime")}`);
130
132
  console.log("");
131
- const telemetry = await choose(rl, "Telemetry level", ["none", "anonymous", "basic", "detailed"], "none");
133
+ const telemetry = await choose(rl, "Telemetry level", ["none", "anonymous", "basic", "detailed", "diagnostic"], "none");
132
134
  const profile = { directory, port, tunnel, token: randomBytes(24).toString("hex") };
133
135
  if (subdomain)
134
136
  profile.subdomain = subdomain;
@@ -212,4 +214,3 @@ async function choose(rl, label, choices, defaultValue) {
212
214
  console.log(` ${error("✕")} ${dim(`Choose a number from 1 to ${choices.length}.`)}`);
213
215
  }
214
216
  }
215
- //# sourceMappingURL=setup.js.map
@@ -4,9 +4,16 @@ export interface TelemetrySummary {
4
4
  totalEvents: number;
5
5
  configs: Record<string, {
6
6
  eventCount: number;
7
+ httpRequests: number;
8
+ httpAuthFailures: number;
9
+ httpErrors: number;
7
10
  mcpRequests: number;
8
11
  toolCalls: number;
9
12
  commandCalls: number;
13
+ tunnelEvents: number;
14
+ tunnelDisconnects: number;
15
+ tunnelReconnects: number;
16
+ tunnelFailures: number;
10
17
  mcpSuccessRate: number | null;
11
18
  toolSuccessRate: number | null;
12
19
  commandSuccessRate: number | null;
@@ -55,4 +62,3 @@ export interface TelemetrySummary {
55
62
  }>;
56
63
  }
57
64
  export declare function summarizeTelemetry(events: TelemetryEnvelope[]): TelemetrySummary;
58
- //# sourceMappingURL=aggregate.d.ts.map
@@ -14,12 +14,15 @@ function rateForEvents(events, kind) {
14
14
  const matching = events.filter((item) => item.event.event === kind);
15
15
  return matching.length === 0
16
16
  ? null
17
- : rate(matching.filter((item) => item.event.event !== "session" && item.event.success).length, matching.length);
17
+ : rate(matching.filter((item) => "success" in item.event && item.event.success).length, matching.length);
18
18
  }
19
19
  function collectFailures(events, kind) {
20
20
  const failures = {};
21
21
  for (const envelope of events) {
22
- if (envelope.event.event !== kind || envelope.event.success || !envelope.event.errorCategory)
22
+ if (envelope.event.event !== kind ||
23
+ !("success" in envelope.event) ||
24
+ envelope.event.success ||
25
+ !envelope.event.errorCategory)
23
26
  continue;
24
27
  failures[envelope.event.errorCategory] = (failures[envelope.event.errorCategory] ?? 0) + 1;
25
28
  }
@@ -42,9 +45,16 @@ export function summarizeTelemetry(events) {
42
45
  const commands = new Map();
43
46
  const sessions = new Map();
44
47
  let legacySessionEvents = 0;
48
+ let httpRequests = 0;
49
+ let httpAuthFailures = 0;
50
+ let httpErrors = 0;
45
51
  let mcpRequests = 0;
46
52
  let toolCalls = 0;
47
53
  let commandCalls = 0;
54
+ let tunnelEvents = 0;
55
+ let tunnelDisconnects = 0;
56
+ let tunnelReconnects = 0;
57
+ let tunnelFailures = 0;
48
58
  for (const envelope of group) {
49
59
  const event = envelope.event;
50
60
  const session = sessions.get(envelope.sessionId) ?? {
@@ -57,6 +67,26 @@ export function summarizeTelemetry(events) {
57
67
  failuresByCategory: { mcp: {}, tool: {}, command: {} },
58
68
  };
59
69
  session.eventCount += 1;
70
+ if (event.event === "http_request") {
71
+ httpRequests += 1;
72
+ if (event.status === 401)
73
+ httpAuthFailures += 1;
74
+ if (event.status >= 400)
75
+ httpErrors += 1;
76
+ sessions.set(envelope.sessionId, session);
77
+ continue;
78
+ }
79
+ if (event.event === "tunnel") {
80
+ tunnelEvents += 1;
81
+ if (event.state === "disconnected")
82
+ tunnelDisconnects += 1;
83
+ if (event.state === "reconnected")
84
+ tunnelReconnects += 1;
85
+ if (event.state === "failed")
86
+ tunnelFailures += 1;
87
+ sessions.set(envelope.sessionId, session);
88
+ continue;
89
+ }
60
90
  if (event.event === "session") {
61
91
  if (Number.isFinite(event.wallClockDurationMs) &&
62
92
  Number.isFinite(event.activeRequestDurationMs) &&
@@ -130,9 +160,16 @@ export function summarizeTelemetry(events) {
130
160
  });
131
161
  configs[configId] = {
132
162
  eventCount: group.length,
163
+ httpRequests,
164
+ httpAuthFailures,
165
+ httpErrors,
133
166
  mcpRequests,
134
167
  toolCalls,
135
168
  commandCalls,
169
+ tunnelEvents,
170
+ tunnelDisconnects,
171
+ tunnelReconnects,
172
+ tunnelFailures,
136
173
  mcpSuccessRate: rateForEvents(group, "mcp_request"),
137
174
  toolSuccessRate: rateForEvents(group, "tool_call"),
138
175
  commandSuccessRate: rateForEvents(group, "command_call"),
@@ -181,4 +218,3 @@ export function summarizeTelemetry(events) {
181
218
  configs,
182
219
  };
183
220
  }
184
- //# sourceMappingURL=aggregate.js.map
@@ -2,4 +2,3 @@ import { type TelemetryConfig, type TelemetryLevel } from "./types.js";
2
2
  export declare const DEFAULT_TELEMETRY_CONFIG: TelemetryConfig;
3
3
  export declare function isTelemetryLevel(value: unknown): value is TelemetryLevel;
4
4
  export declare function parseTelemetryConfig(value: unknown): TelemetryConfig | undefined;
5
- //# sourceMappingURL=config.d.ts.map
@@ -13,4 +13,3 @@ export function parseTelemetryConfig(value) {
13
13
  return undefined;
14
14
  return { level };
15
15
  }
16
- //# sourceMappingURL=config.js.map
@@ -1,3 +1,2 @@
1
1
  import type { TelemetrySummary } from "./aggregate.js";
2
2
  export declare function formatTelemetrySummary(summary: TelemetrySummary, level: string): string;
3
- //# sourceMappingURL=format.d.ts.map
@@ -47,7 +47,7 @@ export function formatTelemetrySummary(summary, level) {
47
47
  return output.join("\n");
48
48
  }
49
49
  for (const [configId, config] of Object.entries(summary.configs)) {
50
- output.push("", ` CONFIGURATION ${configId}`, ` ${line("─", 66)}`, metric("Events", config.eventCount), metric("MCP requests", config.mcpRequests), metric("Tool calls", config.toolCalls), metric("Command calls", config.commandCalls), metric("MCP success", rate(config.mcpSuccessRate)), metric("Tool success", rate(config.toolSuccessRate)), metric("Command success", rate(config.commandSuccessRate)), "", " SESSIONS", metric("Sessions", Object.keys(config.sessions).length));
50
+ output.push("", ` CONFIGURATION ${configId}`, ` ${line("─", 66)}`, metric("Events", config.eventCount), metric("HTTP requests", config.httpRequests), metric("HTTP auth failures", config.httpAuthFailures), metric("HTTP errors", config.httpErrors), metric("MCP requests", config.mcpRequests), metric("Tool calls", config.toolCalls), metric("Command calls", config.commandCalls), metric("Tunnel events", config.tunnelEvents), metric("Tunnel disconnects", config.tunnelDisconnects), metric("Tunnel reconnects", config.tunnelReconnects), metric("Tunnel failures", config.tunnelFailures), metric("MCP success", rate(config.mcpSuccessRate)), metric("Tool success", rate(config.toolSuccessRate)), metric("Command success", rate(config.commandSuccessRate)), "", " SESSIONS", metric("Sessions", Object.keys(config.sessions).length));
51
51
  if (config.legacySessionEvents > 0) {
52
52
  output.push(metric("Legacy sessions", config.legacySessionEvents), " Legacy session records are excluded from session averages.");
53
53
  }
@@ -84,4 +84,3 @@ export function formatTelemetrySummary(summary, level) {
84
84
  output.push("", ` ${line()}`, " Success rates are per event type; MCP and tool events are related but distinct.", " Use 'telemetry show' for the raw event stream.", "");
85
85
  return output.join("\n");
86
86
  }
87
- //# sourceMappingURL=format.js.map
@@ -1,4 +1,3 @@
1
1
  import type { AnonymousTelemetryEvent, TelemetryEnvelope, TelemetryLevel, TelemetryProjectContext, TelemetryRuntimeContext } from "./types.js";
2
2
  export declare function sanitizeAnonymousEvent(event: AnonymousTelemetryEvent): AnonymousTelemetryEvent;
3
3
  export declare function createTelemetryEnvelope(level: Exclude<TelemetryLevel, "none">, event: AnonymousTelemetryEvent, runtime?: TelemetryRuntimeContext, project?: TelemetryProjectContext, configId?: string, sessionId?: string): TelemetryEnvelope;
4
- //# sourceMappingURL=privacy.d.ts.map
@@ -14,6 +14,17 @@ const SAFE_TOOL_NAME = /^[a-z][a-z0-9_]*$/;
14
14
  const SAFE_COMMAND_FAMILY = /^[a-z][a-z0-9_-]*$/;
15
15
  const SAFE_OPERATION = /^[a-z][a-z0-9_.-]*$/;
16
16
  export function sanitizeAnonymousEvent(event) {
17
+ if (event.event === "http_request") {
18
+ return {
19
+ event: "http_request",
20
+ method: sanitizeMcpMethod(event.method),
21
+ route: event.route,
22
+ status: boundedInteger(event.status),
23
+ success: event.success === true,
24
+ durationMs: boundedNumber(event.durationMs),
25
+ authenticated: event.authenticated === true,
26
+ };
27
+ }
17
28
  if (event.event === "mcp_request") {
18
29
  return {
19
30
  event: "mcp_request",
@@ -42,6 +53,18 @@ export function sanitizeAnonymousEvent(event) {
42
53
  : { errorCategory: sanitizeErrorCategory(event.errorCategory) }),
43
54
  };
44
55
  }
56
+ if (event.event === "tunnel") {
57
+ return {
58
+ event: "tunnel",
59
+ state: event.state,
60
+ ...(event.attempt === undefined ? {} : { attempt: boundedInteger(event.attempt) }),
61
+ ...(event.attempts === undefined ? {} : { attempts: boundedInteger(event.attempts) }),
62
+ ...(event.durationMs === undefined ? {} : { durationMs: boundedNumber(event.durationMs) }),
63
+ ...(event.reasonCategory === undefined
64
+ ? {}
65
+ : { reasonCategory: sanitizeErrorCategory(event.reasonCategory) }),
66
+ };
67
+ }
45
68
  if (event.event === "command_call") {
46
69
  return {
47
70
  event: "command_call",
@@ -76,15 +99,45 @@ export function createTelemetryEnvelope(level, event, runtime, project, configId
76
99
  level,
77
100
  event: sanitizeAnonymousEvent(event),
78
101
  };
79
- if (level === "basic" || level === "detailed") {
102
+ if (level === "basic" || level === "detailed" || level === "diagnostic") {
80
103
  if (runtime)
81
104
  envelope.runtime = sanitizeRuntimeContext(runtime);
82
105
  }
83
- if (level === "detailed" && project) {
106
+ if ((level === "detailed" || level === "diagnostic") && project) {
84
107
  envelope.project = sanitizeProjectContext(project);
85
108
  }
109
+ if (level === "diagnostic" && runtime) {
110
+ envelope.runtime = sanitizeDiagnosticRuntimeContext(runtime);
111
+ }
112
+ if (level === "diagnostic" && project) {
113
+ envelope.project = sanitizeDiagnosticProjectContext(project);
114
+ }
86
115
  return envelope;
87
116
  }
117
+ function sanitizeDiagnosticRuntimeContext(context) {
118
+ return {
119
+ ...sanitizeRuntimeContext(context),
120
+ ...(context.nodeVersion === undefined
121
+ ? {}
122
+ : { nodeVersion: sanitizeVersion(context.nodeVersion) }),
123
+ ...(context.hostname === undefined
124
+ ? {}
125
+ : { hostname: sanitizeSensitiveIdentifier(context.hostname) }),
126
+ ...(context.username === undefined
127
+ ? {}
128
+ : { username: sanitizeSensitiveIdentifier(context.username) }),
129
+ ...(context.processId === undefined ? {} : { processId: boundedInteger(context.processId) }),
130
+ ...(context.parentProcessId === undefined
131
+ ? {}
132
+ : { parentProcessId: boundedInteger(context.parentProcessId) }),
133
+ ...(context.processUptimeMs === undefined
134
+ ? {}
135
+ : { processUptimeMs: boundedNumber(context.processUptimeMs) }),
136
+ ...(context.memoryRssBytes === undefined
137
+ ? {}
138
+ : { memoryRssBytes: boundedNumber(context.memoryRssBytes) }),
139
+ };
140
+ }
88
141
  function sanitizeRuntimeContext(context) {
89
142
  return {
90
143
  agentDirVersion: sanitizeVersion(context.agentDirVersion),
@@ -99,6 +152,17 @@ function sanitizeRuntimeContext(context) {
99
152
  : { mcpClientVersion: sanitizeVersion(context.mcpClientVersion) }),
100
153
  };
101
154
  }
155
+ function sanitizeDiagnosticProjectContext(context) {
156
+ return {
157
+ ...sanitizeProjectContext(context),
158
+ ...(context.projectRoot === undefined
159
+ ? {}
160
+ : { projectRoot: sanitizeLocalPath(context.projectRoot) }),
161
+ ...(context.workingDirectory === undefined
162
+ ? {}
163
+ : { workingDirectory: sanitizeLocalPath(context.workingDirectory) }),
164
+ };
165
+ }
102
166
  function sanitizeProjectContext(context) {
103
167
  return {
104
168
  ...(context.language ? { language: context.language } : {}),
@@ -117,6 +181,16 @@ function sanitizeIdentifier(value, pattern) {
117
181
  const normalized = value.trim().toLowerCase();
118
182
  return pattern.test(normalized) ? normalized : "unknown";
119
183
  }
184
+ function sanitizeSensitiveIdentifier(value) {
185
+ const normalized = value.trim().slice(0, 128);
186
+ return /^[a-z0-9][a-z0-9 ._@-]*$/i.test(normalized) ? normalized : "unknown";
187
+ }
188
+ function sanitizeLocalPath(value) {
189
+ const normalized = value.trim().slice(0, 2048);
190
+ return normalized.startsWith("/") || /^[A-Za-z]:[\\\\/]/.test(normalized)
191
+ ? normalized
192
+ : "unknown";
193
+ }
120
194
  function sanitizeFreeIdentifier(value) {
121
195
  const normalized = value.trim().slice(0, 64);
122
196
  return /^[a-z0-9][a-z0-9 ._-]*$/i.test(normalized) ? normalized : "unknown";
@@ -142,4 +216,3 @@ function boundedInteger(value) {
142
216
  function optionalNumber(key, value) {
143
217
  return value === undefined ? {} : { [key]: boundedNumber(value) };
144
218
  }
145
- //# sourceMappingURL=privacy.js.map
@@ -27,4 +27,3 @@ export declare class TelemetryRecorder {
27
27
  private loadPersisted;
28
28
  private persistEvent;
29
29
  }
30
- //# sourceMappingURL=recorder.d.ts.map
@@ -95,4 +95,3 @@ export class TelemetryRecorder {
95
95
  }
96
96
  }
97
97
  }
98
- //# sourceMappingURL=recorder.js.map