agent-dir 0.3.0 → 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.
- package/README.md +37 -2
- package/dist/bin/agent-dir.d.ts +0 -1
- package/dist/bin/agent-dir.js +11 -4
- package/dist/src/auth.d.ts +0 -1
- package/dist/src/auth.js +0 -1
- package/dist/src/codegraph.d.ts +0 -1
- package/dist/src/codegraph.js +0 -1
- package/dist/src/config.d.ts +0 -1
- package/dist/src/config.js +0 -1
- package/dist/src/logging.d.ts +0 -1
- package/dist/src/logging.js +1 -2
- package/dist/src/mcp.d.ts +0 -1
- package/dist/src/mcp.js +11 -2
- package/dist/src/server.d.ts +0 -1
- package/dist/src/server.js +42 -2
- package/dist/src/setup.d.ts +0 -1
- package/dist/src/setup.js +4 -3
- package/dist/src/telemetry/aggregate.d.ts +7 -1
- package/dist/src/telemetry/aggregate.js +39 -3
- package/dist/src/telemetry/config.d.ts +0 -1
- package/dist/src/telemetry/config.js +0 -1
- package/dist/src/telemetry/format.d.ts +0 -1
- package/dist/src/telemetry/format.js +1 -2
- package/dist/src/telemetry/privacy.d.ts +0 -1
- package/dist/src/telemetry/privacy.js +76 -3
- package/dist/src/telemetry/recorder.d.ts +0 -1
- package/dist/src/telemetry/recorder.js +0 -1
- package/dist/src/telemetry/types.d.ts +28 -3
- package/dist/src/telemetry/types.js +1 -2
- package/dist/src/tools/code.d.ts +0 -1
- package/dist/src/tools/code.js +0 -1
- package/dist/src/tools/commands.d.ts +0 -1
- package/dist/src/tools/commands.js +0 -1
- package/dist/src/tools/diagnostics.d.ts +0 -1
- package/dist/src/tools/diagnostics.js +0 -1
- package/dist/src/tools/files.d.ts +0 -1
- package/dist/src/tools/files.js +0 -1
- package/dist/src/tools/git.d.ts +0 -1
- package/dist/src/tools/git.js +0 -1
- package/dist/src/tools/metadata.d.ts +0 -1
- package/dist/src/tools/metadata.js +0 -1
- package/dist/src/tools/npm.d.ts +0 -1
- package/dist/src/tools/npm.js +0 -1
- package/dist/src/tools/skills.d.ts +0 -1
- package/dist/src/tools/skills.js +0 -1
- package/dist/src/tunnel.d.ts +1 -2
- package/dist/src/tunnel.js +243 -83
- package/dist/src/types.d.ts +24 -1
- package/dist/src/types.js +0 -1
- package/package.json +6 -3
- package/dist/bin/agent-dir.d.ts.map +0 -1
- package/dist/bin/agent-dir.js.map +0 -1
- package/dist/src/auth.d.ts.map +0 -1
- package/dist/src/auth.js.map +0 -1
- package/dist/src/codegraph.d.ts.map +0 -1
- package/dist/src/codegraph.js.map +0 -1
- package/dist/src/config.d.ts.map +0 -1
- package/dist/src/config.js.map +0 -1
- package/dist/src/logging.d.ts.map +0 -1
- package/dist/src/logging.js.map +0 -1
- package/dist/src/mcp.d.ts.map +0 -1
- package/dist/src/mcp.js.map +0 -1
- package/dist/src/server.d.ts.map +0 -1
- package/dist/src/server.js.map +0 -1
- package/dist/src/setup.d.ts.map +0 -1
- package/dist/src/setup.js.map +0 -1
- package/dist/src/telemetry/aggregate.d.ts.map +0 -1
- package/dist/src/telemetry/aggregate.js.map +0 -1
- package/dist/src/telemetry/config.d.ts.map +0 -1
- package/dist/src/telemetry/config.js.map +0 -1
- package/dist/src/telemetry/format.d.ts.map +0 -1
- package/dist/src/telemetry/format.js.map +0 -1
- package/dist/src/telemetry/privacy.d.ts.map +0 -1
- package/dist/src/telemetry/privacy.js.map +0 -1
- package/dist/src/telemetry/recorder.d.ts.map +0 -1
- package/dist/src/telemetry/recorder.js.map +0 -1
- package/dist/src/telemetry/types.d.ts.map +0 -1
- package/dist/src/telemetry/types.js.map +0 -1
- package/dist/src/tools/code.d.ts.map +0 -1
- package/dist/src/tools/code.js.map +0 -1
- package/dist/src/tools/commands.d.ts.map +0 -1
- package/dist/src/tools/commands.js.map +0 -1
- package/dist/src/tools/diagnostics.d.ts.map +0 -1
- package/dist/src/tools/diagnostics.js.map +0 -1
- package/dist/src/tools/files.d.ts.map +0 -1
- package/dist/src/tools/files.js.map +0 -1
- package/dist/src/tools/git.d.ts.map +0 -1
- package/dist/src/tools/git.js.map +0 -1
- package/dist/src/tools/metadata.d.ts.map +0 -1
- package/dist/src/tools/metadata.js.map +0 -1
- package/dist/src/tools/npm.d.ts.map +0 -1
- package/dist/src/tools/npm.js.map +0 -1
- package/dist/src/tools/skills.d.ts.map +0 -1
- package/dist/src/tools/skills.js.map +0 -1
- package/dist/src/tunnel.d.ts.map +0 -1
- package/dist/src/tunnel.js.map +0 -1
- package/dist/src/types.d.ts.map +0 -1
- 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 **
|
|
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
|
-
|
|
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
|
|
|
@@ -340,6 +341,40 @@ POST /mcp
|
|
|
340
341
|
|
|
341
342
|
All HTTP endpoints are authenticated by default.
|
|
342
343
|
|
|
344
|
+
## Releases
|
|
345
|
+
|
|
346
|
+
Releases are automated with [semantic-release](https://github.com/semantic-release/semantic-release) from the `main` branch. Developers should **not** manually edit the version in `package.json` for normal releases.
|
|
347
|
+
|
|
348
|
+
Use [Conventional Commits](https://www.conventionalcommits.org/) for commits that should communicate release impact:
|
|
349
|
+
|
|
350
|
+
| Commit | Release |
|
|
351
|
+
| --- | --- |
|
|
352
|
+
| `fix: handle tunnel reconnect` | Patch (`0.3.0` → `0.3.1`) |
|
|
353
|
+
| `feat: add project snapshots` | Minor (`0.3.0` → `0.4.0`) |
|
|
354
|
+
| `feat!: change authentication protocol` | Major (`0.3.0` → `1.0.0`) |
|
|
355
|
+
| `feat: change API` with a `BREAKING CHANGE:` footer | Major |
|
|
356
|
+
| `docs:`, `test:`, `chore:`, `refactor:`, `ci:`, etc. | No release by default |
|
|
357
|
+
|
|
358
|
+
After a pull request is reviewed and merged into `main`, the release workflow runs the normal validation (`npm run check`, `npm test`, and `npm pack --dry-run`). If validation succeeds, semantic-release determines the next SemVer version from the Conventional Commit history, updates `package.json` and `package-lock.json`, creates the release commit, creates the `vX.Y.Z` Git tag, creates the GitHub Release, and publishes the package to npm.
|
|
359
|
+
|
|
360
|
+
The release commit is marked `[skip ci]`, so it does not start another release cycle. A push to `main` that contains no release-worthy Conventional Commit produces no release.
|
|
361
|
+
|
|
362
|
+
For an intentional breaking release, use the `!` marker on the commit type/scope or add a `BREAKING CHANGE:` footer, for example:
|
|
363
|
+
|
|
364
|
+
```text
|
|
365
|
+
feat!: change authentication protocol
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
or:
|
|
369
|
+
|
|
370
|
+
```text
|
|
371
|
+
feat: change authentication protocol
|
|
372
|
+
|
|
373
|
+
BREAKING CHANGE: clients must use the new authentication protocol
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Do not use arbitrary keywords such as `bump` to control releases. The existing `v0.3.0` release is the baseline for this automation.
|
|
377
|
+
|
|
343
378
|
## Contributing
|
|
344
379
|
|
|
345
380
|
Contributions are welcome. See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for development setup, testing requirements, MCP compatibility guidance, and pull-request expectations.
|
package/dist/bin/agent-dir.d.ts
CHANGED
package/dist/bin/agent-dir.js
CHANGED
|
@@ -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
|
-
|
|
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
|
package/dist/src/auth.d.ts
CHANGED
package/dist/src/auth.js
CHANGED
package/dist/src/codegraph.d.ts
CHANGED
package/dist/src/codegraph.js
CHANGED
package/dist/src/config.d.ts
CHANGED
|
@@ -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
|
package/dist/src/config.js
CHANGED
package/dist/src/logging.d.ts
CHANGED
|
@@ -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
|
package/dist/src/logging.js
CHANGED
|
@@ -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
|
package/dist/src/server.d.ts
CHANGED
package/dist/src/server.js
CHANGED
|
@@ -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
|
-
|
|
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
|
package/dist/src/setup.d.ts
CHANGED
|
@@ -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("
|
|
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
|
|
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 ||
|
|
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
|
|
@@ -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
|