agent-insights 0.0.1 → 0.0.7

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 CHANGED
@@ -93,6 +93,7 @@ function sanitizeInner(value) {
93
93
 
94
94
  // src/adapters/claude-code.ts
95
95
  import { mkdir, readFile, writeFile } from "fs/promises";
96
+ import { homedir } from "os";
96
97
  import { dirname, join } from "path";
97
98
  var HOOK_COMMAND_NAME = "agent-insights";
98
99
  var HOOK_MAP = {
@@ -131,8 +132,8 @@ var claudeCodeAdapter = {
131
132
  ...transcriptPath !== void 0 ? { transcriptPath } : {}
132
133
  };
133
134
  },
134
- async install(repoRoot) {
135
- const path = join(repoRoot, ".claude/settings.json");
135
+ async install(repoRoot, scope = "global") {
136
+ const path = resolveClaudePath(repoRoot, scope);
136
137
  const settings = await readJson(path);
137
138
  const next = { ...settings ?? {} };
138
139
  const hooks = { ...next.hooks ?? {} };
@@ -152,8 +153,8 @@ var claudeCodeAdapter = {
152
153
  `, "utf8");
153
154
  return { written: true, path };
154
155
  },
155
- async uninstall(repoRoot) {
156
- const path = join(repoRoot, ".claude/settings.json");
156
+ async uninstall(repoRoot, scope = "global") {
157
+ const path = resolveClaudePath(repoRoot, scope);
157
158
  const settings = await readJson(path);
158
159
  if (!settings?.hooks) return { removed: false, path };
159
160
  const hooks = { ...settings.hooks };
@@ -174,9 +175,9 @@ var claudeCodeAdapter = {
174
175
  `, "utf8");
175
176
  return { removed: touched, path };
176
177
  },
177
- async isInstalled(repoRoot) {
178
+ async isInstalled(repoRoot, scope = "global") {
178
179
  const settings = await readJson(
179
- join(repoRoot, ".claude/settings.json")
180
+ resolveClaudePath(repoRoot, scope)
180
181
  );
181
182
  if (!settings?.hooks) return false;
182
183
  return Object.values(settings.hooks).some(
@@ -184,6 +185,9 @@ var claudeCodeAdapter = {
184
185
  );
185
186
  }
186
187
  };
188
+ function resolveClaudePath(repoRoot, scope) {
189
+ return scope === "global" ? join(homedir(), ".claude", "settings.json") : join(repoRoot, ".claude", "settings.json");
190
+ }
187
191
  function isAgentInsightsCommand(cmd) {
188
192
  if (!cmd) return false;
189
193
  return cmd.startsWith(`${HOOK_COMMAND_NAME} hook`);
@@ -203,15 +207,25 @@ function asString(v) {
203
207
 
204
208
  // src/adapters/cursor.ts
205
209
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
210
+ import { homedir as homedir2 } from "os";
206
211
  import { dirname as dirname2, join as join2 } from "path";
207
212
  var HOOK_COMMAND_NAME2 = "agent-insights";
208
213
  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"
214
+ sessionStart: "session.start",
215
+ sessionEnd: "session.end",
216
+ beforeSubmitPrompt: "user.prompt.submit",
217
+ preToolUse: "tool.start",
218
+ postToolUse: "tool.end",
219
+ postToolUseFailure: "tool.failure",
220
+ subagentStart: "subagent.start",
221
+ subagentStop: "subagent.end",
222
+ stop: "agent.stop",
223
+ preCompact: "context.compact.start",
224
+ // Additional events — mapped to existing schema types
225
+ beforeShellExecution: "tool.start",
226
+ afterShellExecution: "tool.end",
227
+ afterFileEdit: "tool.end",
228
+ workspaceOpen: "session.start"
215
229
  };
216
230
  var CURSOR_HOOK_EVENTS = Object.keys(HOOK_MAP2);
217
231
  var cursorAdapter = {
@@ -223,9 +237,9 @@ var cursorAdapter = {
223
237
  const type = HOOK_MAP2[payload.event];
224
238
  if (!type) return void 0;
225
239
  const data = payload.data ?? {};
226
- const sessionId = asString2(data["session_id"]) ?? asString2(data["sessionId"]);
240
+ const sessionId = asString2(data["session_id"]) ?? asString2(data["sessionId"]) ?? asString2(data["conversation_id"]);
227
241
  const toolName = asString2(data["tool_name"]) ?? asString2(data["toolName"]);
228
- const transcriptPath = asString2(data["transcript_path"]) ?? asString2(data["transcriptPath"]);
242
+ const transcriptPath = asString2(data["transcript_path"]) ?? asString2(data["transcriptPath"]) ?? asString2(process.env.CURSOR_TRANSCRIPT_PATH);
229
243
  return {
230
244
  type,
231
245
  ...sessionId !== void 0 ? { sessionId } : {},
@@ -233,10 +247,13 @@ var cursorAdapter = {
233
247
  ...transcriptPath !== void 0 ? { transcriptPath } : {}
234
248
  };
235
249
  },
236
- async install(repoRoot) {
237
- const path = join2(repoRoot, ".cursor/hooks.json");
250
+ async install(repoRoot, scope = "global") {
251
+ const path = resolveCursorPath(repoRoot, scope);
238
252
  const settings = await readJson2(path);
239
- const next = { ...settings ?? {} };
253
+ const next = {
254
+ version: 1,
255
+ ...settings ?? {}
256
+ };
240
257
  const hooks = { ...next.hooks ?? {} };
241
258
  for (const event of CURSOR_HOOK_EVENTS) {
242
259
  const existing = (hooks[event] ?? []).filter(
@@ -251,8 +268,8 @@ var cursorAdapter = {
251
268
  `, "utf8");
252
269
  return { written: true, path };
253
270
  },
254
- async uninstall(repoRoot) {
255
- const path = join2(repoRoot, ".cursor/hooks.json");
271
+ async uninstall(repoRoot, scope = "global") {
272
+ const path = resolveCursorPath(repoRoot, scope);
256
273
  const settings = await readJson2(path);
257
274
  if (!settings?.hooks) return { removed: false, path };
258
275
  const hooks = { ...settings.hooks };
@@ -273,9 +290,9 @@ var cursorAdapter = {
273
290
  `, "utf8");
274
291
  return { removed: touched, path };
275
292
  },
276
- async isInstalled(repoRoot) {
293
+ async isInstalled(repoRoot, scope = "global") {
277
294
  const settings = await readJson2(
278
- join2(repoRoot, ".cursor/hooks.json")
295
+ resolveCursorPath(repoRoot, scope)
279
296
  );
280
297
  if (!settings?.hooks) return false;
281
298
  return Object.values(settings.hooks).some(
@@ -283,6 +300,9 @@ var cursorAdapter = {
283
300
  );
284
301
  }
285
302
  };
303
+ function resolveCursorPath(repoRoot, scope) {
304
+ return scope === "global" ? join2(homedir2(), ".cursor", "hooks.json") : join2(repoRoot, ".cursor", "hooks.json");
305
+ }
286
306
  function isAgentInsightsCommand2(cmd) {
287
307
  if (!cmd) return false;
288
308
  return cmd.startsWith(`${HOOK_COMMAND_NAME2} hook`);
@@ -315,10 +335,10 @@ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from
315
335
  import { dirname as dirname3 } from "path";
316
336
 
317
337
  // src/config/paths.ts
318
- import { homedir } from "os";
338
+ import { homedir as homedir3 } from "os";
319
339
  import { join as join3 } from "path";
320
340
  function configRoot() {
321
- return process.env.AGENT_INSIGHTS_HOME ?? join3(homedir(), ".agent-insights");
341
+ return process.env.AGENT_INSIGHTS_HOME ?? join3(homedir3(), ".agent-insights");
322
342
  }
323
343
  function configFile() {
324
344
  return join3(configRoot(), "config.json");
@@ -335,6 +355,7 @@ function defaultConfig() {
335
355
  return {
336
356
  version: 1,
337
357
  enabled: true,
358
+ hooksScope: "global",
338
359
  vercel: {
339
360
  team: "vercel-labs",
340
361
  project: "agent-insights"
@@ -358,7 +379,6 @@ function defaultConfig() {
358
379
  sessionAnalysis: {
359
380
  enabled: false,
360
381
  analyzerUrl: process.env.AGENT_INSIGHTS_ANALYZER_URL ?? "",
361
- secretEnv: "AGENT_INSIGHTS_INGEST_SECRET",
362
382
  githubIssueRepo: "vercel-labs/agent-insights"
363
383
  },
364
384
  agents: {
@@ -442,7 +462,7 @@ var BlobTranscriptStore = class {
442
462
  input.sessionId
443
463
  );
444
464
  const blob = await put(requestedPath, body, {
445
- access: "public",
465
+ access: "private",
446
466
  token: this.token,
447
467
  contentType: "application/jsonl",
448
468
  addRandomSuffix: true
@@ -456,6 +476,11 @@ var BlobTranscriptStore = class {
456
476
  };
457
477
  function createTranscriptStore(cfg) {
458
478
  if (cfg.type === "none") return new NoopTranscriptStore();
479
+ if (cfg.type === "analyzer") {
480
+ throw new Error(
481
+ "transcript store: type=analyzer uploads are handled by the hook command directly"
482
+ );
483
+ }
459
484
  const token = process.env[cfg.tokenEnv];
460
485
  if (!token) {
461
486
  throw new Error(
package/dist/index.js.map CHANGED
@@ -1 +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"]}
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 { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { HooksScope } from \"../config/config.js\";\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, scope: HooksScope = \"global\") {\n const path = resolveClaudePath(repoRoot, scope);\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, scope: HooksScope = \"global\") {\n const path = resolveClaudePath(repoRoot, scope);\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, scope: HooksScope = \"global\") {\n const settings = await readJson<ClaudeSettings>(\n resolveClaudePath(repoRoot, scope),\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 resolveClaudePath(repoRoot: string, scope: HooksScope): string {\n return scope === \"global\"\n ? join(homedir(), \".claude\", \"settings.json\")\n : join(repoRoot, \".claude\", \"settings.json\");\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 { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { HooksScope } from \"../config/config.js\";\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 native hook names (camelCase) → common schema. Reference:\n * https://cursor.com/docs/hooks\n */\nconst HOOK_MAP: Record<string, AgentEventType> = {\n sessionStart: \"session.start\",\n sessionEnd: \"session.end\",\n beforeSubmitPrompt: \"user.prompt.submit\",\n preToolUse: \"tool.start\",\n postToolUse: \"tool.end\",\n postToolUseFailure: \"tool.failure\",\n subagentStart: \"subagent.start\",\n subagentStop: \"subagent.end\",\n stop: \"agent.stop\",\n preCompact: \"context.compact.start\",\n // Additional events — mapped to existing schema types\n beforeShellExecution: \"tool.start\",\n afterShellExecution: \"tool.end\",\n afterFileEdit: \"tool.end\",\n workspaceOpen: \"session.start\",\n};\n\nexport const CURSOR_HOOK_EVENTS: ReadonlyArray<string> = Object.keys(HOOK_MAP);\n\ntype CursorHookEntry = {\n command: string;\n};\n\ntype CursorHooksConfig = {\n /** Cursor expects schema version 1 at the top level. */\n version?: number;\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 // Cursor uses `session_id` in payloads and is identical to `conversation_id`.\n const sessionId =\n asString(data[\"session_id\"]) ??\n asString(data[\"sessionId\"]) ??\n asString(data[\"conversation_id\"]);\n const toolName = asString(data[\"tool_name\"]) ?? asString(data[\"toolName\"]);\n\n // transcript_path is confirmed in all Cursor hook payloads.\n const transcriptPath =\n asString(data[\"transcript_path\"]) ??\n asString(data[\"transcriptPath\"]) ??\n asString(process.env.CURSOR_TRANSCRIPT_PATH);\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, scope: HooksScope = \"global\") {\n const path = resolveCursorPath(repoRoot, scope);\n const settings = await readJson<CursorHooksConfig>(path);\n const next: CursorHooksConfig = {\n version: 1,\n ...(settings ?? {}),\n };\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, scope: HooksScope = \"global\") {\n const path = resolveCursorPath(repoRoot, scope);\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, scope: HooksScope = \"global\") {\n const settings = await readJson<CursorHooksConfig>(\n resolveCursorPath(repoRoot, scope),\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 resolveCursorPath(repoRoot: string, scope: HooksScope): string {\n return scope === \"global\"\n ? join(homedir(), \".cursor\", \"hooks.json\")\n : join(repoRoot, \".cursor\", \"hooks.json\");\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 HooksScope = \"global\" | \"local\";\n\nexport type TranscriptStoreConfig =\n | {\n /** Upload via analyzer API using AGENT_INSIGHTS_API_KEY (preferred). */\n type: \"analyzer\";\n }\n | {\n /** Direct upload with a Blob RW token (legacy / advanced). */\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 /** 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 /** Where agent hook entries are written. Default `global` (user home). */\n hooksScope: HooksScope;\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 hooksScope: \"global\",\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 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\n/** API key storage (`chmod 600`). Not checked into git. */\nexport function credentialsFile(): string {\n return join(configRoot(), \"credentials.json\");\n}\n\n/** OAuth token storage (`chmod 600`). Not checked into git. */\nexport function authFile(): string {\n return join(configRoot(), \"auth.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\n/** User-level Claude Code settings (hooks apply in every repo). */\nexport function claudeUserSettingsPath(): string {\n return join(homedir(), \".claude\", \"settings.json\");\n}\n\n/** User-level Cursor hooks (hooks apply in every project). */\nexport function cursorUserHooksPath(): string {\n return join(homedir(), \".cursor\", \"hooks.json\");\n}\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 const blob = await put(requestedPath, body, {\n access: \"private\",\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 if (cfg.type === \"analyzer\") {\n // Analyzer-proxied uploads are handled directly in hook.ts via getValidToken().\n // createTranscriptStore is not used for this path.\n throw new Error(\n \"transcript store: type=analyzer uploads are handled by the hook command directly\",\n );\n }\n // type === \"blob\"\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","homedir","dirname","join","HOOK_COMMAND_NAME","HOOK_MAP","asString","readJson","isAgentInsightsCommand","mkdir","readFile","writeFile","dirname","homedir","join","join","readFile","writeFile","mkdir","dirname","readFile"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-insights",
3
- "version": "0.0.1",
3
+ "version": "0.0.7",
4
4
  "description": "CLI for AI coding agent lifecycle observability — Claude Code and Cursor hooks → Vercel Blob + optional Slack/GitHub feedback loop.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -48,7 +48,7 @@
48
48
  "node": ">=20"
49
49
  },
50
50
  "dependencies": {
51
- "@vercel/blob": "^1.1.1",
51
+ "@vercel/blob": "^2.4.0",
52
52
  "commander": "^14.0.0",
53
53
  "kleur": "^4.1.5"
54
54
  },