agent-insights 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,484 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/privacy/hash.ts
4
+ import { createHash } from "crypto";
5
+ function sha256(value) {
6
+ return createHash("sha256").update(value, "utf8").digest("hex");
7
+ }
8
+ function shortHash(value, length = 12) {
9
+ return sha256(value).slice(0, length);
10
+ }
11
+
12
+ // src/privacy/sanitize.ts
13
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
14
+ "prompt",
15
+ "prompts",
16
+ "message",
17
+ "messages",
18
+ "content",
19
+ "text",
20
+ "transcript",
21
+ "toolinput",
22
+ "input",
23
+ "arguments",
24
+ "args",
25
+ "params",
26
+ "file",
27
+ "filecontent",
28
+ "filecontents",
29
+ "diff",
30
+ "patch",
31
+ "secret",
32
+ "secrets",
33
+ "token",
34
+ "apikey",
35
+ "authorization",
36
+ "password",
37
+ "credentials"
38
+ ]);
39
+ var MAX_STRING_LENGTH = 256;
40
+ function normalizeKey(key) {
41
+ return key.toLowerCase().replace(/[_\-\s]/g, "");
42
+ }
43
+ function truncate(value) {
44
+ if (value.length <= MAX_STRING_LENGTH) return value;
45
+ return `${value.slice(0, MAX_STRING_LENGTH)}\u2026[truncated]`;
46
+ }
47
+ function sanitizePrimitive(value) {
48
+ if (value === null) return null;
49
+ switch (typeof value) {
50
+ case "string":
51
+ return truncate(value);
52
+ case "number":
53
+ return Number.isFinite(value) ? value : null;
54
+ case "boolean":
55
+ return value;
56
+ default:
57
+ return void 0;
58
+ }
59
+ }
60
+ function sanitize(input) {
61
+ return sanitizeInner(input);
62
+ }
63
+ function sanitizeInner(value) {
64
+ if (Array.isArray(value)) {
65
+ const out = [];
66
+ for (const item of value) {
67
+ const sanitizedItem = sanitizeInner(item);
68
+ if (sanitizedItem !== void 0) {
69
+ out.push(sanitizedItem);
70
+ }
71
+ }
72
+ return out;
73
+ }
74
+ if (value && typeof value === "object") {
75
+ const out = {};
76
+ for (const [rawKey, rawValue] of Object.entries(value)) {
77
+ if (FORBIDDEN_KEYS.has(normalizeKey(rawKey))) {
78
+ continue;
79
+ }
80
+ if (rawValue && typeof rawValue === "object") {
81
+ out[rawKey] = sanitizeInner(rawValue);
82
+ } else {
83
+ const primitive = sanitizePrimitive(rawValue);
84
+ if (primitive !== void 0) {
85
+ out[rawKey] = primitive;
86
+ }
87
+ }
88
+ }
89
+ return out;
90
+ }
91
+ return sanitizePrimitive(value);
92
+ }
93
+
94
+ // src/adapters/claude-code.ts
95
+ import { mkdir, readFile, writeFile } from "fs/promises";
96
+ import { dirname, join } from "path";
97
+ var HOOK_COMMAND_NAME = "agent-insights";
98
+ var HOOK_MAP = {
99
+ SessionStart: "session.start",
100
+ SessionEnd: "session.end",
101
+ UserPromptSubmit: "user.prompt.submit",
102
+ PreToolUse: "tool.start",
103
+ PostToolUse: "tool.end",
104
+ PostToolUseFailure: "tool.failure",
105
+ PermissionRequest: "permission.request",
106
+ PermissionDenied: "permission.denied",
107
+ SubagentStart: "subagent.start",
108
+ SubagentStop: "subagent.end",
109
+ Stop: "agent.stop",
110
+ Notification: "notification",
111
+ PreCompact: "context.compact.start",
112
+ PostCompact: "context.compact.end"
113
+ };
114
+ var CLAUDE_HOOK_EVENTS = Object.keys(HOOK_MAP);
115
+ var claudeCodeAdapter = {
116
+ id: "claude-code",
117
+ agent: "claude-code",
118
+ label: "Claude Code",
119
+ settingsFile: ".claude/settings.json",
120
+ map(payload) {
121
+ const type = HOOK_MAP[payload.event];
122
+ if (!type) return void 0;
123
+ const data = payload.data ?? {};
124
+ const sessionId = asString(data["session_id"]) ?? asString(data["sessionId"]);
125
+ const toolName = asString(data["tool_name"]) ?? asString(data["toolName"]) ?? asString(data["tool"]?.["name"]);
126
+ const transcriptPath = asString(data["transcript_path"]) ?? asString(data["transcriptPath"]);
127
+ return {
128
+ type,
129
+ ...sessionId !== void 0 ? { sessionId } : {},
130
+ ...toolName !== void 0 ? { toolName } : {},
131
+ ...transcriptPath !== void 0 ? { transcriptPath } : {}
132
+ };
133
+ },
134
+ async install(repoRoot) {
135
+ const path = join(repoRoot, ".claude/settings.json");
136
+ const settings = await readJson(path);
137
+ const next = { ...settings ?? {} };
138
+ const hooks = { ...next.hooks ?? {} };
139
+ for (const event of CLAUDE_HOOK_EVENTS) {
140
+ const existing = (hooks[event] ?? []).filter(
141
+ (h) => !isAgentInsightsCommand(h.command)
142
+ );
143
+ existing.push({
144
+ type: "command",
145
+ command: `${HOOK_COMMAND_NAME} hook ${event}`
146
+ });
147
+ hooks[event] = existing;
148
+ }
149
+ next.hooks = hooks;
150
+ await mkdir(dirname(path), { recursive: true });
151
+ await writeFile(path, `${JSON.stringify(next, null, 2)}
152
+ `, "utf8");
153
+ return { written: true, path };
154
+ },
155
+ async uninstall(repoRoot) {
156
+ const path = join(repoRoot, ".claude/settings.json");
157
+ const settings = await readJson(path);
158
+ if (!settings?.hooks) return { removed: false, path };
159
+ const hooks = { ...settings.hooks };
160
+ let touched = false;
161
+ for (const [event, entries] of Object.entries(hooks)) {
162
+ const filtered = entries.filter(
163
+ (h) => !isAgentInsightsCommand(h.command)
164
+ );
165
+ if (filtered.length !== entries.length) touched = true;
166
+ if (filtered.length === 0) {
167
+ delete hooks[event];
168
+ } else {
169
+ hooks[event] = filtered;
170
+ }
171
+ }
172
+ settings.hooks = hooks;
173
+ await writeFile(path, `${JSON.stringify(settings, null, 2)}
174
+ `, "utf8");
175
+ return { removed: touched, path };
176
+ },
177
+ async isInstalled(repoRoot) {
178
+ const settings = await readJson(
179
+ join(repoRoot, ".claude/settings.json")
180
+ );
181
+ if (!settings?.hooks) return false;
182
+ return Object.values(settings.hooks).some(
183
+ (entries) => entries.some((h) => isAgentInsightsCommand(h.command))
184
+ );
185
+ }
186
+ };
187
+ function isAgentInsightsCommand(cmd) {
188
+ if (!cmd) return false;
189
+ return cmd.startsWith(`${HOOK_COMMAND_NAME} hook`);
190
+ }
191
+ async function readJson(path) {
192
+ try {
193
+ const raw = await readFile(path, "utf8");
194
+ return JSON.parse(raw);
195
+ } catch (err) {
196
+ if (err.code === "ENOENT") return void 0;
197
+ throw err;
198
+ }
199
+ }
200
+ function asString(v) {
201
+ return typeof v === "string" && v.length > 0 ? v : void 0;
202
+ }
203
+
204
+ // src/adapters/cursor.ts
205
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
206
+ import { dirname as dirname2, join as join2 } from "path";
207
+ var HOOK_COMMAND_NAME2 = "agent-insights";
208
+ var HOOK_MAP2 = {
209
+ SessionStart: "session.start",
210
+ SessionEnd: "session.end",
211
+ UserPromptSubmit: "user.prompt.submit",
212
+ PreToolUse: "tool.start",
213
+ PostToolUse: "tool.end",
214
+ Stop: "agent.stop"
215
+ };
216
+ var CURSOR_HOOK_EVENTS = Object.keys(HOOK_MAP2);
217
+ var cursorAdapter = {
218
+ id: "cursor",
219
+ agent: "cursor",
220
+ label: "Cursor",
221
+ settingsFile: ".cursor/hooks.json",
222
+ map(payload) {
223
+ const type = HOOK_MAP2[payload.event];
224
+ if (!type) return void 0;
225
+ const data = payload.data ?? {};
226
+ const sessionId = asString2(data["session_id"]) ?? asString2(data["sessionId"]);
227
+ const toolName = asString2(data["tool_name"]) ?? asString2(data["toolName"]);
228
+ const transcriptPath = asString2(data["transcript_path"]) ?? asString2(data["transcriptPath"]);
229
+ return {
230
+ type,
231
+ ...sessionId !== void 0 ? { sessionId } : {},
232
+ ...toolName !== void 0 ? { toolName } : {},
233
+ ...transcriptPath !== void 0 ? { transcriptPath } : {}
234
+ };
235
+ },
236
+ async install(repoRoot) {
237
+ const path = join2(repoRoot, ".cursor/hooks.json");
238
+ const settings = await readJson2(path);
239
+ const next = { ...settings ?? {} };
240
+ const hooks = { ...next.hooks ?? {} };
241
+ for (const event of CURSOR_HOOK_EVENTS) {
242
+ const existing = (hooks[event] ?? []).filter(
243
+ (h) => !isAgentInsightsCommand2(h.command)
244
+ );
245
+ existing.push({ command: `${HOOK_COMMAND_NAME2} hook ${event}` });
246
+ hooks[event] = existing;
247
+ }
248
+ next.hooks = hooks;
249
+ await mkdir2(dirname2(path), { recursive: true });
250
+ await writeFile2(path, `${JSON.stringify(next, null, 2)}
251
+ `, "utf8");
252
+ return { written: true, path };
253
+ },
254
+ async uninstall(repoRoot) {
255
+ const path = join2(repoRoot, ".cursor/hooks.json");
256
+ const settings = await readJson2(path);
257
+ if (!settings?.hooks) return { removed: false, path };
258
+ const hooks = { ...settings.hooks };
259
+ let touched = false;
260
+ for (const [event, entries] of Object.entries(hooks)) {
261
+ const filtered = entries.filter(
262
+ (h) => !isAgentInsightsCommand2(h.command)
263
+ );
264
+ if (filtered.length !== entries.length) touched = true;
265
+ if (filtered.length === 0) {
266
+ delete hooks[event];
267
+ } else {
268
+ hooks[event] = filtered;
269
+ }
270
+ }
271
+ settings.hooks = hooks;
272
+ await writeFile2(path, `${JSON.stringify(settings, null, 2)}
273
+ `, "utf8");
274
+ return { removed: touched, path };
275
+ },
276
+ async isInstalled(repoRoot) {
277
+ const settings = await readJson2(
278
+ join2(repoRoot, ".cursor/hooks.json")
279
+ );
280
+ if (!settings?.hooks) return false;
281
+ return Object.values(settings.hooks).some(
282
+ (entries) => entries.some((h) => isAgentInsightsCommand2(h.command))
283
+ );
284
+ }
285
+ };
286
+ function isAgentInsightsCommand2(cmd) {
287
+ if (!cmd) return false;
288
+ return cmd.startsWith(`${HOOK_COMMAND_NAME2} hook`);
289
+ }
290
+ async function readJson2(path) {
291
+ try {
292
+ const raw = await readFile2(path, "utf8");
293
+ return JSON.parse(raw);
294
+ } catch (err) {
295
+ if (err.code === "ENOENT") return void 0;
296
+ throw err;
297
+ }
298
+ }
299
+ function asString2(v) {
300
+ return typeof v === "string" && v.length > 0 ? v : void 0;
301
+ }
302
+
303
+ // src/adapters/registry.ts
304
+ var adapters = {
305
+ "claude-code": claudeCodeAdapter,
306
+ cursor: cursorAdapter
307
+ };
308
+ var adapterList = Object.values(adapters);
309
+ function getAdapter(id) {
310
+ return adapters[id];
311
+ }
312
+
313
+ // src/config/config.ts
314
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
315
+ import { dirname as dirname3 } from "path";
316
+
317
+ // src/config/paths.ts
318
+ import { homedir } from "os";
319
+ import { join as join3 } from "path";
320
+ function configRoot() {
321
+ return process.env.AGENT_INSIGHTS_HOME ?? join3(homedir(), ".agent-insights");
322
+ }
323
+ function configFile() {
324
+ return join3(configRoot(), "config.json");
325
+ }
326
+ function sessionCacheDir() {
327
+ return join3(configRoot(), "session-cache");
328
+ }
329
+ function logsDir() {
330
+ return join3(configRoot(), "logs");
331
+ }
332
+
333
+ // src/config/config.ts
334
+ function defaultConfig() {
335
+ return {
336
+ version: 1,
337
+ enabled: true,
338
+ vercel: {
339
+ team: "vercel-labs",
340
+ project: "agent-insights"
341
+ },
342
+ userConsent: {
343
+ eventTelemetry: false,
344
+ transcriptSync: false,
345
+ sessionAnalysis: false
346
+ },
347
+ exporter: {
348
+ type: "otlp",
349
+ endpoint: process.env.AGENT_INSIGHTS_OTEL_ENDPOINT ?? "",
350
+ headers: {},
351
+ dataset: "agent-insights"
352
+ },
353
+ transcriptStore: {
354
+ type: "blob",
355
+ tokenEnv: "BLOB_READ_WRITE_TOKEN",
356
+ prefix: "sessions/"
357
+ },
358
+ sessionAnalysis: {
359
+ enabled: false,
360
+ analyzerUrl: process.env.AGENT_INSIGHTS_ANALYZER_URL ?? "",
361
+ secretEnv: "AGENT_INSIGHTS_INGEST_SECRET",
362
+ githubIssueRepo: "vercel-labs/agent-insights"
363
+ },
364
+ agents: {
365
+ claudeCode: { enabled: false },
366
+ cursor: { enabled: false }
367
+ },
368
+ privacy: {
369
+ capturePrompts: false,
370
+ captureMessages: false,
371
+ captureToolInputs: false,
372
+ captureFileContents: false
373
+ }
374
+ };
375
+ }
376
+ async function loadConfig() {
377
+ try {
378
+ const raw = await readFile3(configFile(), "utf8");
379
+ const parsed = JSON.parse(raw);
380
+ return mergeConfig(defaultConfig(), parsed);
381
+ } catch (err) {
382
+ if (err.code === "ENOENT") return void 0;
383
+ throw err;
384
+ }
385
+ }
386
+ async function saveConfig(cfg) {
387
+ await ensureDirs();
388
+ await writeFile3(configFile(), `${JSON.stringify(cfg, null, 2)}
389
+ `, "utf8");
390
+ }
391
+ async function ensureDirs() {
392
+ await mkdir3(dirname3(configFile()), { recursive: true });
393
+ await mkdir3(sessionCacheDir(), { recursive: true });
394
+ await mkdir3(logsDir(), { recursive: true });
395
+ }
396
+ function mergeConfig(base, patch) {
397
+ return {
398
+ ...base,
399
+ ...patch,
400
+ vercel: { ...base.vercel, ...patch.vercel ?? {} },
401
+ userConsent: { ...base.userConsent, ...patch.userConsent ?? {} },
402
+ exporter: { ...base.exporter, ...patch.exporter ?? {} },
403
+ transcriptStore: patch.transcriptStore ?? base.transcriptStore,
404
+ sessionAnalysis: {
405
+ ...base.sessionAnalysis,
406
+ ...patch.sessionAnalysis ?? {}
407
+ },
408
+ agents: {
409
+ claudeCode: {
410
+ ...base.agents.claudeCode,
411
+ ...patch.agents?.claudeCode ?? {}
412
+ },
413
+ cursor: { ...base.agents.cursor, ...patch.agents?.cursor ?? {} }
414
+ },
415
+ privacy: base.privacy
416
+ };
417
+ }
418
+
419
+ // src/transcript/store.ts
420
+ import { readFile as readFile4 } from "fs/promises";
421
+ import { put } from "@vercel/blob";
422
+ var NoopTranscriptStore = class {
423
+ async upload() {
424
+ throw new Error("transcript store disabled (transcriptStore.type=none)");
425
+ }
426
+ async ping() {
427
+ return { ok: false, reason: "transcript store disabled" };
428
+ }
429
+ };
430
+ var BlobTranscriptStore = class {
431
+ constructor(token, prefix) {
432
+ this.token = token;
433
+ this.prefix = prefix;
434
+ }
435
+ token;
436
+ prefix;
437
+ async upload(input) {
438
+ const body = await readFile4(input.transcriptPath);
439
+ const requestedPath = buildPathname(
440
+ this.prefix,
441
+ input.userHash,
442
+ input.sessionId
443
+ );
444
+ const blob = await put(requestedPath, body, {
445
+ access: "public",
446
+ token: this.token,
447
+ contentType: "application/jsonl",
448
+ addRandomSuffix: true
449
+ });
450
+ return { pathname: blob.pathname, url: blob.url, size: body.byteLength };
451
+ }
452
+ async ping() {
453
+ if (!this.token) return { ok: false, reason: "missing token" };
454
+ return { ok: true };
455
+ }
456
+ };
457
+ function createTranscriptStore(cfg) {
458
+ if (cfg.type === "none") return new NoopTranscriptStore();
459
+ const token = process.env[cfg.tokenEnv];
460
+ if (!token) {
461
+ throw new Error(
462
+ `transcript store: env ${cfg.tokenEnv} is not set (run 'vercel env pull .env.local')`
463
+ );
464
+ }
465
+ return new BlobTranscriptStore(token, cfg.prefix);
466
+ }
467
+ function buildPathname(prefix, userHash, sessionId) {
468
+ const safePrefix = prefix.endsWith("/") ? prefix : `${prefix}/`;
469
+ const safeSession = encodeURIComponent(sessionId);
470
+ return `${safePrefix}${userHash}/${safeSession}/transcript.jsonl`;
471
+ }
472
+ export {
473
+ adapterList,
474
+ adapters,
475
+ createTranscriptStore,
476
+ defaultConfig,
477
+ getAdapter,
478
+ loadConfig,
479
+ sanitize,
480
+ saveConfig,
481
+ sha256,
482
+ shortHash
483
+ };
484
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/privacy/hash.ts","../src/privacy/sanitize.ts","../src/adapters/claude-code.ts","../src/adapters/cursor.ts","../src/adapters/registry.ts","../src/config/config.ts","../src/config/paths.ts","../src/transcript/store.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\n\n/**\n * Stable SHA-256 hash of a value, hex-encoded.\n *\n * Used to anonymize usernames, repo remote URLs, workspace paths, and\n * session identifiers before they leave the machine.\n */\nexport function sha256(value: string): string {\n return createHash(\"sha256\").update(value, \"utf8\").digest(\"hex\");\n}\n\n/** Short prefix of `sha256(value)` for human-readable dashboards. */\nexport function shortHash(value: string, length = 12): string {\n return sha256(value).slice(0, length);\n}\n","/**\n * Keys that must never appear in any payload that leaves the machine.\n *\n * Matching is case-insensitive and snake_case/camelCase agnostic — we strip a\n * key if its normalized form equals one of these values.\n */\nconst FORBIDDEN_KEYS: ReadonlySet<string> = new Set([\n \"prompt\",\n \"prompts\",\n \"message\",\n \"messages\",\n \"content\",\n \"text\",\n \"transcript\",\n \"toolinput\",\n \"input\",\n \"arguments\",\n \"args\",\n \"params\",\n \"file\",\n \"filecontent\",\n \"filecontents\",\n \"diff\",\n \"patch\",\n \"secret\",\n \"secrets\",\n \"token\",\n \"apikey\",\n \"authorization\",\n \"password\",\n \"credentials\",\n]);\n\nconst MAX_STRING_LENGTH = 256;\n\n/** Allowed primitive value types in sanitized metadata. */\nexport type SanitizedPrimitive = string | number | boolean | null;\n\nfunction normalizeKey(key: string): string {\n return key.toLowerCase().replace(/[_\\-\\s]/g, \"\");\n}\n\nfunction truncate(value: string): string {\n if (value.length <= MAX_STRING_LENGTH) return value;\n return `${value.slice(0, MAX_STRING_LENGTH)}…[truncated]`;\n}\n\nfunction sanitizePrimitive(value: unknown): SanitizedPrimitive | undefined {\n if (value === null) return null;\n switch (typeof value) {\n case \"string\":\n return truncate(value);\n case \"number\":\n return Number.isFinite(value) ? value : null;\n case \"boolean\":\n return value;\n default:\n return undefined;\n }\n}\n\n/**\n * Recursively remove forbidden keys from an arbitrary payload.\n *\n * Returned shape:\n * - Objects keep allowed primitive fields (truncated to {@link MAX_STRING_LENGTH}).\n * - Arrays keep allowed primitive elements; nested objects/arrays are recursed.\n * - Non-primitive, non-container values (functions, symbols, Buffers, …) are dropped.\n */\nexport function sanitize(input: unknown): unknown {\n return sanitizeInner(input);\n}\n\nfunction sanitizeInner(value: unknown): unknown {\n if (Array.isArray(value)) {\n const out: unknown[] = [];\n for (const item of value) {\n const sanitizedItem = sanitizeInner(item);\n if (sanitizedItem !== undefined) {\n out.push(sanitizedItem);\n }\n }\n return out;\n }\n\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [rawKey, rawValue] of Object.entries(value)) {\n if (FORBIDDEN_KEYS.has(normalizeKey(rawKey))) {\n continue;\n }\n if (rawValue && typeof rawValue === \"object\") {\n out[rawKey] = sanitizeInner(rawValue);\n } else {\n const primitive = sanitizePrimitive(rawValue);\n if (primitive !== undefined) {\n out[rawKey] = primitive;\n }\n }\n }\n return out;\n }\n\n return sanitizePrimitive(value);\n}\n\nexport const __testing = { FORBIDDEN_KEYS, normalizeKey };\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { AgentEventType } from \"../types.js\";\nimport type { Adapter, HookPayload, MappedEvent } from \"./types.js\";\n\nconst HOOK_COMMAND_NAME = \"agent-insights\";\n\nconst HOOK_MAP: Record<string, AgentEventType> = {\n SessionStart: \"session.start\",\n SessionEnd: \"session.end\",\n UserPromptSubmit: \"user.prompt.submit\",\n PreToolUse: \"tool.start\",\n PostToolUse: \"tool.end\",\n PostToolUseFailure: \"tool.failure\",\n PermissionRequest: \"permission.request\",\n PermissionDenied: \"permission.denied\",\n SubagentStart: \"subagent.start\",\n SubagentStop: \"subagent.end\",\n Stop: \"agent.stop\",\n Notification: \"notification\",\n PreCompact: \"context.compact.start\",\n PostCompact: \"context.compact.end\",\n};\n\nexport const CLAUDE_HOOK_EVENTS: ReadonlyArray<string> = Object.keys(HOOK_MAP);\n\ntype ClaudeHookEntry = {\n type: \"command\";\n command: string;\n};\n\ntype ClaudeSettings = {\n hooks?: Record<string, ClaudeHookEntry[]>;\n [key: string]: unknown;\n};\n\nexport const claudeCodeAdapter: Adapter = {\n id: \"claude-code\",\n agent: \"claude-code\",\n label: \"Claude Code\",\n settingsFile: \".claude/settings.json\",\n\n map(payload: HookPayload): MappedEvent | undefined {\n const type = HOOK_MAP[payload.event];\n if (!type) return undefined;\n\n const data = payload.data ?? {};\n const sessionId =\n asString(data[\"session_id\"]) ?? asString(data[\"sessionId\"]);\n const toolName =\n asString(data[\"tool_name\"]) ??\n asString(data[\"toolName\"]) ??\n asString((data[\"tool\"] as Record<string, unknown> | undefined)?.[\"name\"]);\n const transcriptPath =\n asString(data[\"transcript_path\"]) ?? asString(data[\"transcriptPath\"]);\n\n return {\n type,\n ...(sessionId !== undefined ? { sessionId } : {}),\n ...(toolName !== undefined ? { toolName } : {}),\n ...(transcriptPath !== undefined ? { transcriptPath } : {}),\n };\n },\n\n async install(repoRoot: string) {\n const path = join(repoRoot, \".claude/settings.json\");\n const settings = await readJson<ClaudeSettings>(path);\n const next: ClaudeSettings = { ...(settings ?? {}) };\n const hooks: Record<string, ClaudeHookEntry[]> = { ...(next.hooks ?? {}) };\n\n for (const event of CLAUDE_HOOK_EVENTS) {\n const existing = (hooks[event] ?? []).filter(\n (h) => !isAgentInsightsCommand(h.command),\n );\n existing.push({\n type: \"command\",\n command: `${HOOK_COMMAND_NAME} hook ${event}`,\n });\n hooks[event] = existing;\n }\n\n next.hooks = hooks;\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(next, null, 2)}\\n`, \"utf8\");\n return { written: true, path };\n },\n\n async uninstall(repoRoot: string) {\n const path = join(repoRoot, \".claude/settings.json\");\n const settings = await readJson<ClaudeSettings>(path);\n if (!settings?.hooks) return { removed: false, path };\n const hooks = { ...settings.hooks };\n let touched = false;\n for (const [event, entries] of Object.entries(hooks)) {\n const filtered = entries.filter(\n (h) => !isAgentInsightsCommand(h.command),\n );\n if (filtered.length !== entries.length) touched = true;\n if (filtered.length === 0) {\n delete hooks[event];\n } else {\n hooks[event] = filtered;\n }\n }\n settings.hooks = hooks;\n await writeFile(path, `${JSON.stringify(settings, null, 2)}\\n`, \"utf8\");\n return { removed: touched, path };\n },\n\n async isInstalled(repoRoot: string) {\n const settings = await readJson<ClaudeSettings>(\n join(repoRoot, \".claude/settings.json\"),\n );\n if (!settings?.hooks) return false;\n return Object.values(settings.hooks).some((entries) =>\n entries.some((h) => isAgentInsightsCommand(h.command)),\n );\n },\n};\n\nfunction isAgentInsightsCommand(cmd: string | undefined): boolean {\n if (!cmd) return false;\n return cmd.startsWith(`${HOOK_COMMAND_NAME} hook`);\n}\n\nasync function readJson<T>(path: string): Promise<T | undefined> {\n try {\n const raw = await readFile(path, \"utf8\");\n return JSON.parse(raw) as T;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw err;\n }\n}\n\nfunction asString(v: unknown): string | undefined {\n return typeof v === \"string\" && v.length > 0 ? v : undefined;\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { AgentEventType } from \"../types.js\";\nimport type { Adapter, HookPayload, MappedEvent } from \"./types.js\";\n\nconst HOOK_COMMAND_NAME = \"agent-insights\";\n\n/**\n * Cursor hook names → common schema. Confirm against current Cursor docs when\n * implementing; align with Claude where event names match.\n */\nconst HOOK_MAP: Record<string, AgentEventType> = {\n SessionStart: \"session.start\",\n SessionEnd: \"session.end\",\n UserPromptSubmit: \"user.prompt.submit\",\n PreToolUse: \"tool.start\",\n PostToolUse: \"tool.end\",\n Stop: \"agent.stop\",\n};\n\nexport const CURSOR_HOOK_EVENTS: ReadonlyArray<string> = Object.keys(HOOK_MAP);\n\ntype CursorHookEntry = {\n command: string;\n};\n\ntype CursorHooksConfig = {\n hooks?: Record<string, CursorHookEntry[]>;\n [key: string]: unknown;\n};\n\nexport const cursorAdapter: Adapter = {\n id: \"cursor\",\n agent: \"cursor\",\n label: \"Cursor\",\n settingsFile: \".cursor/hooks.json\",\n\n map(payload: HookPayload): MappedEvent | undefined {\n const type = HOOK_MAP[payload.event];\n if (!type) return undefined;\n\n const data = payload.data ?? {};\n const sessionId =\n asString(data[\"session_id\"]) ?? asString(data[\"sessionId\"]);\n const toolName = asString(data[\"tool_name\"]) ?? asString(data[\"toolName\"]);\n const transcriptPath =\n asString(data[\"transcript_path\"]) ?? asString(data[\"transcriptPath\"]);\n\n return {\n type,\n ...(sessionId !== undefined ? { sessionId } : {}),\n ...(toolName !== undefined ? { toolName } : {}),\n ...(transcriptPath !== undefined ? { transcriptPath } : {}),\n };\n },\n\n async install(repoRoot: string) {\n const path = join(repoRoot, \".cursor/hooks.json\");\n const settings = await readJson<CursorHooksConfig>(path);\n const next: CursorHooksConfig = { ...(settings ?? {}) };\n const hooks: Record<string, CursorHookEntry[]> = { ...(next.hooks ?? {}) };\n\n for (const event of CURSOR_HOOK_EVENTS) {\n const existing = (hooks[event] ?? []).filter(\n (h) => !isAgentInsightsCommand(h.command),\n );\n existing.push({ command: `${HOOK_COMMAND_NAME} hook ${event}` });\n hooks[event] = existing;\n }\n\n next.hooks = hooks;\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(next, null, 2)}\\n`, \"utf8\");\n return { written: true, path };\n },\n\n async uninstall(repoRoot: string) {\n const path = join(repoRoot, \".cursor/hooks.json\");\n const settings = await readJson<CursorHooksConfig>(path);\n if (!settings?.hooks) return { removed: false, path };\n const hooks = { ...settings.hooks };\n let touched = false;\n for (const [event, entries] of Object.entries(hooks)) {\n const filtered = entries.filter(\n (h) => !isAgentInsightsCommand(h.command),\n );\n if (filtered.length !== entries.length) touched = true;\n if (filtered.length === 0) {\n delete hooks[event];\n } else {\n hooks[event] = filtered;\n }\n }\n settings.hooks = hooks;\n await writeFile(path, `${JSON.stringify(settings, null, 2)}\\n`, \"utf8\");\n return { removed: touched, path };\n },\n\n async isInstalled(repoRoot: string) {\n const settings = await readJson<CursorHooksConfig>(\n join(repoRoot, \".cursor/hooks.json\"),\n );\n if (!settings?.hooks) return false;\n return Object.values(settings.hooks).some((entries) =>\n entries.some((h) => isAgentInsightsCommand(h.command)),\n );\n },\n};\n\nfunction isAgentInsightsCommand(cmd: string | undefined): boolean {\n if (!cmd) return false;\n return cmd.startsWith(`${HOOK_COMMAND_NAME} hook`);\n}\n\nasync function readJson<T>(path: string): Promise<T | undefined> {\n try {\n const raw = await readFile(path, \"utf8\");\n return JSON.parse(raw) as T;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw err;\n }\n}\n\nfunction asString(v: unknown): string | undefined {\n return typeof v === \"string\" && v.length > 0 ? v : undefined;\n}\n","import { claudeCodeAdapter } from \"./claude-code.js\";\nimport { cursorAdapter } from \"./cursor.js\";\nimport type { Adapter, AgentId } from \"./types.js\";\n\nexport const adapters: Record<AgentId, Adapter> = {\n \"claude-code\": claudeCodeAdapter,\n cursor: cursorAdapter,\n};\n\nexport const adapterList: ReadonlyArray<Adapter> = Object.values(adapters);\n\nexport function getAdapter(id: AgentId): Adapter {\n return adapters[id];\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { configFile, logsDir, sessionCacheDir } from \"./paths.js\";\n\nexport type ExporterConfig = {\n type: \"otlp\";\n endpoint: string;\n headers: Record<string, string>;\n dataset: string;\n};\n\nexport type TranscriptStoreConfig =\n | {\n type: \"blob\";\n tokenEnv: string;\n prefix: string;\n }\n | {\n type: \"none\";\n };\n\nexport type SessionAnalysisConfig = {\n /** Server-side enablement; user opts in via `userConsent.sessionAnalysis`. */\n enabled: boolean;\n /** Analyzer endpoint base URL. CLI POSTs to `${analyzerUrl}/api/sessions`. */\n analyzerUrl: string;\n /** Env var name holding an optional shared secret. Sent as `x-agent-insights-secret`. */\n secretEnv: string;\n /** GitHub repo for pre-filled new-issue URLs (display only; constructed by analyzer). */\n githubIssueRepo: string;\n};\n\nexport type AgentConfig = {\n enabled: boolean;\n};\n\nexport type AgentInsightsConfig = {\n version: 1;\n enabled: boolean;\n vercel: {\n team: string;\n project: string;\n };\n userConsent: {\n eventTelemetry: boolean;\n transcriptSync: boolean;\n sessionAnalysis: boolean;\n };\n exporter: ExporterConfig;\n transcriptStore: TranscriptStoreConfig;\n sessionAnalysis: SessionAnalysisConfig;\n agents: Record<\"claudeCode\" | \"cursor\", AgentConfig>;\n privacy: {\n capturePrompts: false;\n captureMessages: false;\n captureToolInputs: false;\n captureFileContents: false;\n };\n};\n\nexport function defaultConfig(): AgentInsightsConfig {\n return {\n version: 1,\n enabled: true,\n vercel: {\n team: \"vercel-labs\",\n project: \"agent-insights\",\n },\n userConsent: {\n eventTelemetry: false,\n transcriptSync: false,\n sessionAnalysis: false,\n },\n exporter: {\n type: \"otlp\",\n endpoint: process.env.AGENT_INSIGHTS_OTEL_ENDPOINT ?? \"\",\n headers: {},\n dataset: \"agent-insights\",\n },\n transcriptStore: {\n type: \"blob\",\n tokenEnv: \"BLOB_READ_WRITE_TOKEN\",\n prefix: \"sessions/\",\n },\n sessionAnalysis: {\n enabled: false,\n analyzerUrl: process.env.AGENT_INSIGHTS_ANALYZER_URL ?? \"\",\n secretEnv: \"AGENT_INSIGHTS_INGEST_SECRET\",\n githubIssueRepo: \"vercel-labs/agent-insights\",\n },\n agents: {\n claudeCode: { enabled: false },\n cursor: { enabled: false },\n },\n privacy: {\n capturePrompts: false,\n captureMessages: false,\n captureToolInputs: false,\n captureFileContents: false,\n },\n };\n}\n\nexport async function loadConfig(): Promise<AgentInsightsConfig | undefined> {\n try {\n const raw = await readFile(configFile(), \"utf8\");\n const parsed = JSON.parse(raw) as Partial<AgentInsightsConfig>;\n return mergeConfig(defaultConfig(), parsed);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw err;\n }\n}\n\nexport async function saveConfig(cfg: AgentInsightsConfig): Promise<void> {\n await ensureDirs();\n await writeFile(configFile(), `${JSON.stringify(cfg, null, 2)}\\n`, \"utf8\");\n}\n\nexport async function ensureDirs(): Promise<void> {\n await mkdir(dirname(configFile()), { recursive: true });\n await mkdir(sessionCacheDir(), { recursive: true });\n await mkdir(logsDir(), { recursive: true });\n}\n\nfunction mergeConfig(\n base: AgentInsightsConfig,\n patch: Partial<AgentInsightsConfig>,\n): AgentInsightsConfig {\n return {\n ...base,\n ...patch,\n vercel: { ...base.vercel, ...(patch.vercel ?? {}) },\n userConsent: { ...base.userConsent, ...(patch.userConsent ?? {}) },\n exporter: { ...base.exporter, ...(patch.exporter ?? {}) },\n transcriptStore: (patch.transcriptStore ??\n base.transcriptStore) as TranscriptStoreConfig,\n sessionAnalysis: {\n ...base.sessionAnalysis,\n ...(patch.sessionAnalysis ?? {}),\n },\n agents: {\n claudeCode: {\n ...base.agents.claudeCode,\n ...(patch.agents?.claudeCode ?? {}),\n },\n cursor: { ...base.agents.cursor, ...(patch.agents?.cursor ?? {}) },\n },\n privacy: base.privacy,\n };\n}\n","import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/** Root directory for agent-insights state. Honors `AGENT_INSIGHTS_HOME`. */\nexport function configRoot(): string {\n return process.env.AGENT_INSIGHTS_HOME ?? join(homedir(), \".agent-insights\");\n}\n\nexport function configFile(): string {\n return join(configRoot(), \"config.json\");\n}\n\nexport function sessionCacheDir(): string {\n return join(configRoot(), \"session-cache\");\n}\n\nexport function logsDir(): string {\n return join(configRoot(), \"logs\");\n}\n\nexport const CLAUDE_SETTINGS_FILE = \".claude/settings.json\";\nexport const CURSOR_HOOKS_FILE = \".cursor/hooks.json\";\n","import { readFile } from \"node:fs/promises\";\nimport { put } from \"@vercel/blob\";\nimport type { TranscriptStoreConfig } from \"../config/config.js\";\n\nexport type TranscriptUploadInput = {\n /** Absolute path on disk to the transcript file. */\n transcriptPath: string;\n /** SHA256-hashed user identifier. Used in the upload pathname. */\n userHash: string;\n /** Session identifier supplied by the agent. */\n sessionId: string;\n /** Agent name (e.g. \"claude-code\"). */\n agent: string;\n};\n\nexport type TranscriptUploadResult = {\n pathname: string;\n url: string;\n size: number;\n};\n\nexport interface TranscriptStore {\n upload(input: TranscriptUploadInput): Promise<TranscriptUploadResult>;\n /** Lightweight health check used by `doctor`. */\n ping(): Promise<{ ok: true } | { ok: false; reason: string }>;\n}\n\nclass NoopTranscriptStore implements TranscriptStore {\n async upload(): Promise<TranscriptUploadResult> {\n throw new Error(\"transcript store disabled (transcriptStore.type=none)\");\n }\n async ping(): Promise<{ ok: false; reason: string }> {\n return { ok: false, reason: \"transcript store disabled\" };\n }\n}\n\nclass BlobTranscriptStore implements TranscriptStore {\n constructor(\n private readonly token: string,\n private readonly prefix: string,\n ) {}\n\n async upload(input: TranscriptUploadInput): Promise<TranscriptUploadResult> {\n const body = await readFile(input.transcriptPath);\n const requestedPath = buildPathname(\n this.prefix,\n input.userHash,\n input.sessionId,\n );\n // addRandomSuffix avoids guessable URLs: transcripts contain prompts and\n // tool inputs, so URL knowledge is the only access control until we move\n // to private blobs + signed reads.\n const blob = await put(requestedPath, body, {\n access: \"public\",\n token: this.token,\n contentType: \"application/jsonl\",\n addRandomSuffix: true,\n });\n return { pathname: blob.pathname, url: blob.url, size: body.byteLength };\n }\n\n async ping(): Promise<{ ok: true } | { ok: false; reason: string }> {\n if (!this.token) return { ok: false, reason: \"missing token\" };\n return { ok: true };\n }\n}\n\nexport function createTranscriptStore(\n cfg: TranscriptStoreConfig,\n): TranscriptStore {\n if (cfg.type === \"none\") return new NoopTranscriptStore();\n const token = process.env[cfg.tokenEnv];\n if (!token) {\n throw new Error(\n `transcript store: env ${cfg.tokenEnv} is not set (run 'vercel env pull .env.local')`,\n );\n }\n return new BlobTranscriptStore(token, cfg.prefix);\n}\n\nfunction buildPathname(\n prefix: string,\n userHash: string,\n sessionId: string,\n): string {\n const safePrefix = prefix.endsWith(\"/\") ? prefix : `${prefix}/`;\n const safeSession = encodeURIComponent(sessionId);\n return `${safePrefix}${userHash}/${safeSession}/transcript.jsonl`;\n}\n"],"mappings":"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","names":["mkdir","readFile","writeFile","dirname","join","HOOK_COMMAND_NAME","HOOK_MAP","asString","readJson","isAgentInsightsCommand","mkdir","readFile","writeFile","dirname","join","readFile","writeFile","mkdir","dirname","readFile"]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "agent-insights",
3
+ "version": "0.0.1",
4
+ "description": "CLI for AI coding agent lifecycle observability — Claude Code and Cursor hooks → Vercel Blob + optional Slack/GitHub feedback loop.",
5
+ "keywords": [
6
+ "claude-code",
7
+ "cursor",
8
+ "agent",
9
+ "observability",
10
+ "vercel",
11
+ "blob"
12
+ ],
13
+ "homepage": "https://github.com/vercel-labs/agent-insights",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/vercel-labs/agent-insights.git",
17
+ "directory": "packages/cli"
18
+ },
19
+ "bugs": "https://github.com/vercel-labs/agent-insights/issues",
20
+ "license": "Apache-2.0",
21
+ "type": "module",
22
+ "bin": {
23
+ "agent-insights": "dist/cli.js"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md"
28
+ ],
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ }
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "dev": "tsup --watch",
41
+ "typecheck": "tsc --noEmit",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "cli": "node ./dist/cli.js",
45
+ "prepublishOnly": "pnpm typecheck && pnpm test && pnpm build"
46
+ },
47
+ "engines": {
48
+ "node": ">=20"
49
+ },
50
+ "dependencies": {
51
+ "@vercel/blob": "^1.1.1",
52
+ "commander": "^14.0.0",
53
+ "kleur": "^4.1.5"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^20.19.0",
57
+ "tsup": "^8.5.0",
58
+ "typescript": "^5.7.0",
59
+ "vitest": "^2.1.0"
60
+ }
61
+ }