agent-insights 0.0.16 → 0.0.18

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
@@ -95,6 +95,33 @@ function sanitizeInner(value) {
95
95
  import { mkdir, readFile, writeFile } from "fs/promises";
96
96
  import { homedir } from "os";
97
97
  import { dirname, join } from "path";
98
+
99
+ // src/util/hook-command.ts
100
+ import { realpathSync } from "fs";
101
+ function resolve() {
102
+ const node = process.execPath;
103
+ let script = process.argv[1] ?? "";
104
+ try {
105
+ script = realpathSync(script);
106
+ } catch {
107
+ }
108
+ return { node, script };
109
+ }
110
+ function quote(s) {
111
+ if (s.length === 0) return "''";
112
+ if (!/[\s'"\\$`<>|&;()*?#~]/.test(s)) return s;
113
+ return `'${s.replace(/'/g, "'\\''")}'`;
114
+ }
115
+ function hookCommandString(event) {
116
+ const { node, script } = resolve();
117
+ return `${quote(node)} ${quote(script)} hook ${event}`;
118
+ }
119
+ function hookCommandArgv(event) {
120
+ const { node, script } = resolve();
121
+ return { node, script, args: ["hook", event] };
122
+ }
123
+
124
+ // src/adapters/claude-code.ts
98
125
  var HOOK_COMMAND_NAME = "agent-insights";
99
126
  var HOOK_MAP = {
100
127
  SessionStart: "session.start",
@@ -189,7 +216,8 @@ function resolveClaudePath(repoRoot, scope) {
189
216
  }
190
217
  function isAgentInsightsCommand(cmd) {
191
218
  if (!cmd) return false;
192
- return cmd.startsWith(`${HOOK_COMMAND_NAME} hook`);
219
+ if (cmd.startsWith(`${HOOK_COMMAND_NAME} hook`)) return true;
220
+ return /\bagent-insights\b.*\bcli\.js\b.*\bhook\b/.test(cmd);
193
221
  }
194
222
  function isHookCommand(value) {
195
223
  if (!value || typeof value !== "object") return false;
@@ -232,7 +260,7 @@ function removeAgentInsightsMatchers(matchers) {
232
260
  return result;
233
261
  }
234
262
  function addAgentInsightsHook(matchers, event) {
235
- const command = `${HOOK_COMMAND_NAME} hook ${event}`;
263
+ const command = hookCommandString(event);
236
264
  const entry = { type: "command", command };
237
265
  for (const group of matchers) {
238
266
  if (group.matcher === "") {
@@ -344,7 +372,8 @@ function resolveCodexPath(repoRoot, scope) {
344
372
  }
345
373
  function isAgentInsightsCommand2(cmd) {
346
374
  if (!cmd) return false;
347
- return cmd.startsWith(`${HOOK_COMMAND_NAME2} hook`);
375
+ if (cmd.startsWith(`${HOOK_COMMAND_NAME2} hook`)) return true;
376
+ return /\bagent-insights\b.*\bcli\.js\b.*\bhook\b/.test(cmd);
348
377
  }
349
378
  function isHookCommand2(value) {
350
379
  if (!value || typeof value !== "object") return false;
@@ -382,7 +411,7 @@ function removeAgentInsightsMatchers2(matchers) {
382
411
  return result;
383
412
  }
384
413
  function addAgentInsightsHook2(matchers, event) {
385
- const command = `${HOOK_COMMAND_NAME2} hook ${event}`;
414
+ const command = hookCommandString(event);
386
415
  const entry = { type: "command", command };
387
416
  for (const group of matchers) {
388
417
  if (group.matcher === "") {
@@ -461,7 +490,7 @@ var cursorAdapter = {
461
490
  const existing = (hooks[event] ?? []).filter(
462
491
  (h) => !isAgentInsightsCommand3(h.command)
463
492
  );
464
- existing.push({ command: `${HOOK_COMMAND_NAME3} hook ${event}` });
493
+ existing.push({ command: hookCommandString(event) });
465
494
  hooks[event] = existing;
466
495
  }
467
496
  next.hooks = hooks;
@@ -507,7 +536,8 @@ function resolveCursorPath(repoRoot, scope) {
507
536
  }
508
537
  function isAgentInsightsCommand3(cmd) {
509
538
  if (!cmd) return false;
510
- return cmd.startsWith(`${HOOK_COMMAND_NAME3} hook`);
539
+ if (cmd.startsWith(`${HOOK_COMMAND_NAME3} hook`)) return true;
540
+ return /\bagent-insights\b.*\bcli\.js\b.*\bhook\b/.test(cmd);
511
541
  }
512
542
  async function readJson3(path) {
513
543
  try {
@@ -536,6 +566,7 @@ var HOOK_MAP4 = {
536
566
  var PI_HOOK_EVENTS = Object.keys(HOOK_MAP4);
537
567
  var EXTENSION_MARKER = "// installed-by: agent-insights";
538
568
  function extensionSource() {
569
+ const { node, script } = hookCommandArgv("Placeholder");
539
570
  return `${EXTENSION_MARKER}
540
571
  // Generated by \`agent-insights init\`. Edit-with-caution: it's overwritten on every install.
541
572
 
@@ -543,10 +574,12 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
543
574
  import { spawn } from "node:child_process";
544
575
 
545
576
  const EVENTS = ${JSON.stringify(PI_HOOK_EVENTS)} as const;
577
+ const NODE_BIN = ${JSON.stringify(node)};
578
+ const CLI_SCRIPT = ${JSON.stringify(script)};
546
579
 
547
580
  function emit(event: string, payload: Record<string, unknown>): void {
548
581
  try {
549
- const child = spawn("agent-insights", ["hook", event], {
582
+ const child = spawn(NODE_BIN, [CLI_SCRIPT, "hook", event], {
550
583
  stdio: ["pipe", "ignore", "ignore"],
551
584
  detached: true,
552
585
  });
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/codex.ts","../src/adapters/cursor.ts","../src/adapters/pi.ts","../src/adapters/registry.ts","../src/config/config.ts","../src/config/analyzer.ts","../src/config/paths.ts","../src/transcript/store.ts","../src/auth/store.ts","../src/auth/oauth.ts","../src/config/bypass.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 ClaudeHookCommand = {\n type: \"command\";\n command: string;\n};\n\n/** Claude Code 0.12+ matcher group (see code.claude.com/docs/en/hooks). */\ntype ClaudeHookMatcherGroup = {\n matcher: string;\n hooks: ClaudeHookCommand[];\n};\n\ntype ClaudeSettings = {\n hooks?: Record<string, unknown[]>;\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, ClaudeHookMatcherGroup[]> = {};\n\n for (const [event, raw] of Object.entries(next.hooks ?? {})) {\n hooks[event] = normalizeEventHooks(raw);\n }\n\n for (const event of CLAUDE_HOOK_EVENTS) {\n const withoutInsights = removeAgentInsightsMatchers(\n hooks[event] ?? [],\n );\n hooks[event] = addAgentInsightsHook(withoutInsights, event);\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: Record<string, ClaudeHookMatcherGroup[]> = {};\n let touched = false;\n\n for (const [event, raw] of Object.entries(settings.hooks)) {\n const before = normalizeEventHooks(raw);\n const after = removeAgentInsightsMatchers(before);\n if (after.length !== before.length) touched = true;\n else if (JSON.stringify(after) !== JSON.stringify(before)) {\n touched = true;\n }\n if (after.length > 0) hooks[event] = after;\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((raw) =>\n normalizeEventHooks(raw).some((group) =>\n group.hooks.some((h) => isAgentInsightsCommand(h.command)),\n ),\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\nfunction isHookCommand(value: unknown): value is ClaudeHookCommand {\n if (!value || typeof value !== \"object\") return false;\n const entry = value as Record<string, unknown>;\n return entry.type === \"command\" && typeof entry.command === \"string\";\n}\n\n/** Accept matcher groups and migrate legacy flat `{ type, command }` entries. */\nfunction normalizeEventHooks(raw: unknown[]): ClaudeHookMatcherGroup[] {\n const groups: ClaudeHookMatcherGroup[] = [];\n\n for (const entry of raw) {\n if (!entry || typeof entry !== \"object\") continue;\n const record = entry as Record<string, unknown>;\n\n if (Array.isArray(record.hooks)) {\n const commands = record.hooks.filter(isHookCommand);\n if (commands.length === 0) continue;\n groups.push({\n matcher: typeof record.matcher === \"string\" ? record.matcher : \"\",\n hooks: commands,\n });\n continue;\n }\n\n if (isHookCommand(record)) {\n groups.push({\n matcher: \"\",\n hooks: [record],\n });\n }\n }\n\n return groups;\n}\n\nfunction removeAgentInsightsMatchers(\n matchers: ClaudeHookMatcherGroup[],\n): ClaudeHookMatcherGroup[] {\n const result: ClaudeHookMatcherGroup[] = [];\n\n for (const group of matchers) {\n const hooks = group.hooks.filter(\n (h) => !isAgentInsightsCommand(h.command),\n );\n if (hooks.length > 0) {\n result.push({ ...group, hooks });\n }\n }\n\n return result;\n}\n\nfunction addAgentInsightsHook(\n matchers: ClaudeHookMatcherGroup[],\n event: string,\n): ClaudeHookMatcherGroup[] {\n const command = `${HOOK_COMMAND_NAME} hook ${event}`;\n const entry: ClaudeHookCommand = { type: \"command\", command };\n\n for (const group of matchers) {\n if (group.matcher === \"\") {\n if (!group.hooks.some((h) => h.command === command)) {\n group.hooks.push(entry);\n }\n return matchers;\n }\n }\n\n return [...matchers, { matcher: \"\", hooks: [entry] }];\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 * Codex (OpenAI) hooks reference:\n * https://developers.openai.com/codex/hooks\n *\n * Same JSON shape as Claude Code: a `hooks` object keyed by event name,\n * each value is an array of matcher groups `{ matcher, hooks: [{ type:\n * \"command\", command }] }`. We write `~/.codex/hooks.json` (global) or\n * `<repo>/.codex/hooks.json` (local).\n *\n * Note: Codex does not have a separate `SessionEnd` event — `Stop`\n * fires when a turn completes and ships the `transcript_path` we need,\n * so we map Stop → session.end for transcript-upload purposes.\n */\nconst HOOK_MAP: Record<string, AgentEventType> = {\n SessionStart: \"session.start\",\n UserPromptSubmit: \"user.prompt.submit\",\n PreToolUse: \"tool.start\",\n PostToolUse: \"tool.end\",\n PermissionRequest: \"permission.request\",\n Stop: \"session.end\",\n};\n\nexport const CODEX_HOOK_EVENTS: ReadonlyArray<string> = Object.keys(HOOK_MAP);\n\ntype CodexHookCommand = {\n type: \"command\";\n command: string;\n};\n\ntype CodexHookMatcherGroup = {\n matcher: string;\n hooks: CodexHookCommand[];\n};\n\ntype CodexHooksFile = {\n hooks?: Record<string, unknown[]>;\n [key: string]: unknown;\n};\n\nexport const codexAdapter: Adapter = {\n id: \"codex\",\n agent: \"codex\",\n label: \"Codex\",\n settingsFile: \".codex/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 =\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 = resolveCodexPath(repoRoot, scope);\n const settings = await readJson<CodexHooksFile>(path);\n const next: CodexHooksFile = { ...(settings ?? {}) };\n const hooks: Record<string, CodexHookMatcherGroup[]> = {};\n\n for (const [event, raw] of Object.entries(next.hooks ?? {})) {\n hooks[event] = normalizeEventHooks(raw);\n }\n\n for (const event of CODEX_HOOK_EVENTS) {\n const withoutInsights = removeAgentInsightsMatchers(hooks[event] ?? []);\n hooks[event] = addAgentInsightsHook(withoutInsights, event);\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 = resolveCodexPath(repoRoot, scope);\n const settings = await readJson<CodexHooksFile>(path);\n if (!settings?.hooks) return { removed: false, path };\n const hooks: Record<string, CodexHookMatcherGroup[]> = {};\n let touched = false;\n\n for (const [event, raw] of Object.entries(settings.hooks)) {\n const before = normalizeEventHooks(raw);\n const after = removeAgentInsightsMatchers(before);\n if (\n after.length !== before.length ||\n JSON.stringify(after) !== JSON.stringify(before)\n ) {\n touched = true;\n }\n if (after.length > 0) hooks[event] = after;\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<CodexHooksFile>(\n resolveCodexPath(repoRoot, scope),\n );\n if (!settings?.hooks) return false;\n return Object.values(settings.hooks).some((raw) =>\n normalizeEventHooks(raw).some((group) =>\n group.hooks.some((h) => isAgentInsightsCommand(h.command)),\n ),\n );\n },\n};\n\nfunction resolveCodexPath(repoRoot: string, scope: HooksScope): string {\n return scope === \"global\"\n ? join(homedir(), \".codex\", \"hooks.json\")\n : join(repoRoot, \".codex\", \"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\nfunction isHookCommand(value: unknown): value is CodexHookCommand {\n if (!value || typeof value !== \"object\") return false;\n const entry = value as Record<string, unknown>;\n return entry.type === \"command\" && typeof entry.command === \"string\";\n}\n\nfunction normalizeEventHooks(raw: unknown[]): CodexHookMatcherGroup[] {\n const groups: CodexHookMatcherGroup[] = [];\n for (const entry of raw) {\n if (!entry || typeof entry !== \"object\") continue;\n const record = entry as Record<string, unknown>;\n if (Array.isArray(record.hooks)) {\n const commands = record.hooks.filter(isHookCommand);\n if (commands.length === 0) continue;\n groups.push({\n matcher: typeof record.matcher === \"string\" ? record.matcher : \"\",\n hooks: commands,\n });\n continue;\n }\n if (isHookCommand(record)) {\n groups.push({ matcher: \"\", hooks: [record] });\n }\n }\n return groups;\n}\n\nfunction removeAgentInsightsMatchers(\n matchers: CodexHookMatcherGroup[],\n): CodexHookMatcherGroup[] {\n const result: CodexHookMatcherGroup[] = [];\n for (const group of matchers) {\n const hooks = group.hooks.filter(\n (h) => !isAgentInsightsCommand(h.command),\n );\n if (hooks.length > 0) result.push({ ...group, hooks });\n }\n return result;\n}\n\nfunction addAgentInsightsHook(\n matchers: CodexHookMatcherGroup[],\n event: string,\n): CodexHookMatcherGroup[] {\n const command = `${HOOK_COMMAND_NAME} hook ${event}`;\n const entry: CodexHookCommand = { type: \"command\", command };\n\n for (const group of matchers) {\n if (group.matcher === \"\") {\n if (!group.hooks.some((h) => h.command === command)) {\n group.hooks.push(entry);\n }\n return matchers;\n }\n }\n return [...matchers, { matcher: \"\", hooks: [entry] }];\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 { mkdir, readFile, unlink, 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 EXTENSION_FILENAME = \"agent-insights.ts\";\n\n/**\n * Pi (earendil-works/pi-mono) doesn't have a file-based hooks config\n * like Claude Code or Codex. Instead it has a TypeScript extension\n * system that subscribes to lifecycle events via `pi.on(...)`.\n *\n * We install a tiny extension at `~/.pi/agent/extensions/agent-insights.ts`\n * (or `<repo>/.pi/extensions/agent-insights.ts` for local scope) that\n * shells out to `agent-insights hook <event>` whenever a Pi lifecycle\n * event fires, with the same stdin-JSON contract the other adapters\n * use. Pi loads it automatically on next session start.\n */\nconst HOOK_MAP: Record<string, AgentEventType> = {\n session_start: \"session.start\",\n session_shutdown: \"session.end\",\n tool_call: \"tool.start\",\n tool_result: \"tool.end\",\n};\n\nexport const PI_HOOK_EVENTS: ReadonlyArray<string> = Object.keys(HOOK_MAP);\n\nconst EXTENSION_MARKER = \"// installed-by: agent-insights\";\n\nfunction extensionSource(): string {\n // Listed events run synchronously via pi.on(). We spawn `agent-insights\n // hook <event>` detached and don't block Pi — the CLI's hook command\n // is best-effort and never throws.\n return `${EXTENSION_MARKER}\n// Generated by \\`agent-insights init\\`. Edit-with-caution: it's overwritten on every install.\n\nimport type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\nimport { spawn } from \"node:child_process\";\n\nconst EVENTS = ${JSON.stringify(PI_HOOK_EVENTS)} as const;\n\nfunction emit(event: string, payload: Record<string, unknown>): void {\n try {\n const child = spawn(\"agent-insights\", [\"hook\", event], {\n stdio: [\"pipe\", \"ignore\", \"ignore\"],\n detached: true,\n });\n child.on(\"error\", () => {});\n child.stdin.end(JSON.stringify(payload));\n child.unref();\n } catch {\n // best-effort\n }\n}\n\nexport default function (pi: ExtensionAPI) {\n for (const event of EVENTS) {\n pi.on(event as never, async (raw: unknown, ctx: { sessionManager?: { getSessionFile?: () => string | undefined; getSessionId?: () => string | undefined } }) => {\n const transcript_path = ctx.sessionManager?.getSessionFile?.();\n const session_id = ctx.sessionManager?.getSessionId?.();\n const e = (raw ?? {}) as Record<string, unknown>;\n emit(event, {\n ...(session_id ? { session_id } : {}),\n ...(transcript_path ? { transcript_path } : {}),\n ...(\"toolName\" in e && typeof e.toolName === \"string\"\n ? { tool_name: e.toolName }\n : {}),\n ...(\"reason\" in e ? { reason: e.reason } : {}),\n });\n });\n }\n}\n`;\n}\n\nexport const piAdapter: Adapter = {\n id: \"pi\",\n agent: \"pi\",\n label: \"Pi\",\n settingsFile: \".pi/extensions/agent-insights.ts\",\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\"]) ?? 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, scope: HooksScope = \"global\") {\n const path = resolvePath(repoRoot, scope);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, extensionSource(), \"utf8\");\n return { written: true, path };\n },\n\n async uninstall(repoRoot: string, scope: HooksScope = \"global\") {\n const path = resolvePath(repoRoot, scope);\n try {\n const raw = await readFile(path, \"utf8\");\n if (!raw.includes(EXTENSION_MARKER)) {\n // Not ours — leave it alone.\n return { removed: false, path };\n }\n await unlink(path);\n return { removed: true, path };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { removed: false, path };\n }\n throw err;\n }\n },\n\n async isInstalled(repoRoot: string, scope: HooksScope = \"global\") {\n const path = resolvePath(repoRoot, scope);\n try {\n const raw = await readFile(path, \"utf8\");\n return raw.includes(EXTENSION_MARKER);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return false;\n throw err;\n }\n },\n};\n\nfunction resolvePath(repoRoot: string, scope: HooksScope): string {\n return scope === \"global\"\n ? join(homedir(), \".pi\", \"agent\", \"extensions\", EXTENSION_FILENAME)\n : join(repoRoot, \".pi\", \"extensions\", EXTENSION_FILENAME);\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 { codexAdapter } from \"./codex.js\";\nimport { cursorAdapter } from \"./cursor.js\";\nimport { piAdapter } from \"./pi.js\";\nimport type { Adapter, AgentId } from \"./types.js\";\n\nexport const adapters: Record<AgentId, Adapter> = {\n \"claude-code\": claudeCodeAdapter,\n cursor: cursorAdapter,\n codex: codexAdapter,\n pi: piAdapter,\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 { getAnalyzerUrl } from \"./analyzer.js\";\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\" | \"codex\" | \"pi\", 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 // Upload via the analyzer using the SiwV bearer token — no client-side\n // Blob token required. Set `transcriptStore.type` to \"blob\" in\n // config.json to switch to direct uploads with BLOB_READ_WRITE_TOKEN.\n type: \"analyzer\",\n },\n sessionAnalysis: {\n enabled: false,\n analyzerUrl: getAnalyzerUrl(),\n githubIssueRepo: \"vercel-labs/agent-insights\",\n },\n agents: {\n claudeCode: { enabled: false },\n cursor: { enabled: false },\n codex: { enabled: false },\n pi: { 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 codex: { ...base.agents.codex, ...(patch.agents?.codex ?? {}) },\n pi: { ...base.agents.pi, ...(patch.agents?.pi ?? {}) },\n },\n privacy: base.privacy,\n };\n}\n","/**\n * Production analyzer deployment. Override via AGENT_INSIGHTS_ANALYZER_URL\n * for local/dev/staging analyzers.\n */\nexport const DEFAULT_ANALYZER_URL = \"https://agent-insights.labs.vercel.dev\";\n\n/** Resolve the analyzer base URL: env var if set, otherwise the prod default. */\nexport function getAnalyzerUrl(): string {\n return process.env.AGENT_INSIGHTS_ANALYZER_URL || DEFAULT_ANALYZER_URL;\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\n/** Vercel deployment-protection bypass secret (`chmod 600`). */\nexport function bypassSecretFile(): string {\n return join(configRoot(), \"bypass-secret\");\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 { getValidToken } from \"../auth/store.js\";\nimport { getAnalyzerUrl } from \"../config/analyzer.js\";\nimport { withBypassHeader } from \"../config/bypass.js\";\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 * Vercel email captured at `agent-insights login`. Lets the analyzer\n * join sessions to real user accounts; never logged or written into\n * the transcript itself.\n */\n userEmail?: 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\n/**\n * Uploads transcripts via the analyzer's POST /api/sessions/upload endpoint\n * using the Sign-in-with-Vercel bearer token. The analyzer then proxies the\n * write to Vercel Blob with its own server-side credentials, so this client\n * does not need a Blob read/write token.\n */\nclass AnalyzerTranscriptStore implements TranscriptStore {\n constructor(private readonly analyzerUrl: string) {}\n\n async upload(input: TranscriptUploadInput): Promise<TranscriptUploadResult> {\n const token = await getValidToken();\n if (!token) {\n throw new Error(\n \"transcript store: not logged in — run `agent-insights login`\",\n );\n }\n const body = await readFile(input.transcriptPath);\n const url = new URL(\"/api/sessions/upload\", this.analyzerUrl).toString();\n const headers = await withBypassHeader({\n authorization: `Bearer ${token}`,\n \"content-type\": \"application/jsonl\",\n \"x-session-id\": input.sessionId,\n \"x-agent\": input.agent,\n \"x-user-hash\": input.userHash,\n ...(input.userEmail ? { \"x-user-email\": input.userEmail } : {}),\n });\n const res = await fetch(url, { method: \"POST\", headers, body });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\n `transcript upload failed (${res.status}): ${text || res.statusText}`,\n );\n }\n const json = (await res.json()) as {\n ok: boolean;\n blobUrl?: string;\n pathname?: string;\n size?: number;\n error?: string;\n };\n if (!json.ok || !json.blobUrl || !json.pathname) {\n throw new Error(`transcript upload error: ${json.error ?? \"unknown\"}`);\n }\n return {\n pathname: json.pathname,\n url: json.blobUrl,\n size: json.size ?? body.byteLength,\n };\n }\n\n async ping(): Promise<{ ok: true } | { ok: false; reason: string }> {\n const token = await getValidToken();\n if (!token) {\n return {\n ok: false,\n reason: \"not logged in — run `agent-insights login`\",\n };\n }\n // Try the actual upload endpoint with an intentionally bad request — a\n // healthy analyzer responds 400 (missing headers); a protection wall\n // responds 401/403 with HTML; an unreachable host throws.\n try {\n const url = new URL(\"/api/sessions/upload\", this.analyzerUrl).toString();\n const headers = await withBypassHeader({\n authorization: `Bearer ${token}`,\n });\n const res = await fetch(url, { method: \"POST\", headers });\n if (res.status === 400) return { ok: true };\n if (res.status === 401 || res.status === 403) {\n const ct = res.headers.get(\"content-type\") ?? \"\";\n if (ct.includes(\"text/html\")) {\n return {\n ok: false,\n reason:\n \"blocked by Vercel deployment protection — set a bypass secret via `agent-insights init --bypass-secret <secret>`\",\n };\n }\n return { ok: false, reason: `analyzer rejected token (${res.status})` };\n }\n return { ok: false, reason: `unexpected status ${res.status}` };\n } catch (err) {\n return {\n ok: false,\n reason: `analyzer unreachable: ${(err as Error).message}`,\n };\n }\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 return new AnalyzerTranscriptStore(getAnalyzerUrl());\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 \\`agent-insights init --force\\` to switch to analyzer-proxied uploads (no token required), or \\`vercel env pull .env.local\\` to keep direct Blob uploads`,\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","import { chmod, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { mkdir } from \"node:fs/promises\";\nimport { authFile } from \"../config/paths.js\";\nimport { refreshAccessToken } from \"./oauth.js\";\n\nexport type AuthState = {\n accessToken: string;\n refreshToken?: string;\n /** Unix epoch milliseconds when the access token expires. */\n expiresAt: number;\n /** Captured at login for per-user analytics joins on the analyzer side. */\n email?: string;\n};\n\nexport async function loadEmail(): Promise<string | undefined> {\n return (await loadAuth())?.email;\n}\n\n/** How many ms before expiry we proactively refresh. */\nconst REFRESH_BUFFER_MS = 60_000;\n\nexport async function loadAuth(): Promise<AuthState | undefined> {\n try {\n const raw = await readFile(authFile(), \"utf8\");\n const parsed = JSON.parse(raw) as Partial<AuthState>;\n if (typeof parsed.accessToken === \"string\" && parsed.accessToken.length > 0) {\n return parsed as AuthState;\n }\n return undefined;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw err;\n }\n}\n\nexport async function saveAuth(state: AuthState): Promise<void> {\n const path = authFile();\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(state, null, 2)}\\n`, {\n encoding: \"utf8\",\n mode: 0o600,\n });\n try {\n await chmod(path, 0o600);\n } catch {\n // Windows doesn't support chmod — ignore\n }\n}\n\nexport async function clearAuth(): Promise<void> {\n const { unlink } = await import(\"node:fs/promises\");\n try {\n await unlink(authFile());\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n}\n\n/**\n * Returns a valid access token, refreshing silently if needed.\n * Returns `undefined` if not logged in or refresh fails (caller should prompt login).\n */\nexport async function getValidToken(): Promise<string | undefined> {\n const auth = await loadAuth();\n if (!auth) return undefined;\n\n const needsRefresh = Date.now() >= auth.expiresAt - REFRESH_BUFFER_MS;\n if (!needsRefresh) return auth.accessToken;\n\n if (!auth.refreshToken) {\n // Access token expired, no refresh token — clear and force re-login\n await clearAuth();\n return undefined;\n }\n\n try {\n const tokens = await refreshAccessToken(auth.refreshToken);\n const refreshed: AuthState = {\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token ?? auth.refreshToken,\n expiresAt: Date.now() + tokens.expires_in * 1000,\n ...(auth.email ? { email: auth.email } : {}),\n };\n await saveAuth(refreshed);\n return refreshed.accessToken;\n } catch {\n // Refresh failed (revoked, expired beyond 30 days) — clear and force re-login\n await clearAuth();\n return undefined;\n }\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { exec } from \"node:child_process\";\nimport { createServer } from \"node:http\";\nimport { platform } from \"node:os\";\n\n// ---------------------------------------------------------------------------\n// OAuth app config — Sign-in-with-Vercel client ID (public, not a secret).\n// Override at runtime via AGENT_INSIGHTS_CLIENT_ID env var.\n// ---------------------------------------------------------------------------\nexport const CLIENT_ID =\n process.env.AGENT_INSIGHTS_CLIENT_ID ?? \"cl_TxqqNFTHcF9kEtM48LBFsYfwjhJ9T8sz\";\n\nconst AUTH_ENDPOINT = \"https://vercel.com/oauth/authorize\";\nconst TOKEN_ENDPOINT = \"https://api.vercel.com/login/oauth/token\";\nconst USERINFO_ENDPOINT = \"https://api.vercel.com/login/oauth/userinfo\";\nconst REVOKE_ENDPOINT = \"https://api.vercel.com/login/oauth/token/revoke\";\n\n// Fixed port — must match the redirect URI registered in the OAuth app.\nconst CALLBACK_PORT = 9797;\nexport const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}/callback`;\n\nconst SCOPES = \"openid email profile offline_access\";\n\nexport type TokenResponse = {\n access_token: string;\n refresh_token?: string;\n expires_in: number;\n token_type: string;\n id_token?: string;\n};\n\nexport type UserInfo = {\n sub: string;\n email: string;\n email_verified: boolean;\n name?: string;\n preferred_username?: string;\n picture?: string;\n};\n\n// ---------------------------------------------------------------------------\n// PKCE helpers\n// ---------------------------------------------------------------------------\n\nfunction generateCodeVerifier(): string {\n return randomBytes(48).toString(\"base64url\");\n}\n\nfunction generateCodeChallenge(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\nfunction generateState(): string {\n return randomBytes(16).toString(\"hex\");\n}\n\n// ---------------------------------------------------------------------------\n// Authorization Code + PKCE flow\n// ---------------------------------------------------------------------------\n\nexport type AuthCodeResult =\n | { status: \"success\"; code: string; state: string }\n | { status: \"error\"; message: string };\n\n/**\n * Starts a localhost callback server, opens the Vercel authorization URL in\n * the browser, and waits for the redirect with the auth code.\n * Resolves (or rejects after timeout) when Vercel redirects back.\n */\nexport async function startPkceFlow(timeoutMs = 120_000): Promise<{\n code: string;\n codeVerifier: string;\n}> {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n const authUrl = new URL(AUTH_ENDPOINT);\n authUrl.searchParams.set(\"client_id\", CLIENT_ID);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"redirect_uri\", REDIRECT_URI);\n authUrl.searchParams.set(\"scope\", SCOPES);\n authUrl.searchParams.set(\"code_challenge\", codeChallenge);\n authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authUrl.searchParams.set(\"state\", state);\n\n const url = authUrl.toString();\n const codePromise = listenForCallback(state, timeoutMs);\n openBrowser(url);\n // Always print the URL — browser open may fail silently\n process.stderr.write(`\\nIf the browser did not open, visit:\\n ${url}\\n\\n`);\n\n const code = await codePromise;\n return { code, codeVerifier };\n}\n\nfunction listenForCallback(\n expectedState: string,\n timeoutMs: number,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? \"/\", `http://localhost:${CALLBACK_PORT}`);\n if (url.pathname !== \"/callback\") {\n res.writeHead(404, { connection: \"close\" });\n res.end(\"Not found\");\n return;\n }\n\n const code = url.searchParams.get(\"code\");\n const returnedState = url.searchParams.get(\"state\");\n const error = url.searchParams.get(\"error\");\n\n const html = (msg: string) =>\n `<html><body style=\"font-family:sans-serif;text-align:center;padding:80px\">` +\n `<h2>${msg}</h2><p>You can close this tab.</p></body></html>`;\n\n // Tell the browser to drop the socket so the server can shut down cleanly\n // and Node's event loop can exit once the callback returns.\n const headers = { \"content-type\": \"text/html\", connection: \"close\" };\n\n const finish = (\n next: () => void,\n body: string,\n status: number,\n ): void => {\n res.writeHead(status, headers);\n res.end(body, () => {\n clearTimeout(timer);\n server.closeAllConnections?.();\n server.close();\n next();\n });\n };\n\n if (error) {\n finish(\n () => reject(new Error(`OAuth error: ${error}`)),\n html(`Login failed: ${error}`),\n 400,\n );\n return;\n }\n\n if (returnedState !== expectedState) {\n finish(\n () => reject(new Error(\"State mismatch — possible CSRF\")),\n html(\"Invalid state parameter — please try again.\"),\n 400,\n );\n return;\n }\n\n if (!code) {\n finish(\n () => reject(new Error(\"No code in callback\")),\n html(\"No authorization code received.\"),\n 400,\n );\n return;\n }\n\n finish(\n () => resolve(code),\n html(\"✓ Logged in! You can close this tab.\"),\n 200,\n );\n });\n\n const timer = setTimeout(() => {\n server.closeAllConnections?.();\n server.close();\n reject(new Error(\"Login timed out — no response within 2 minutes.\"));\n }, timeoutMs);\n timer.unref();\n\n server.listen(CALLBACK_PORT, \"127.0.0.1\", () => {\n // Server is ready; browser will be opened by caller\n });\n\n server.on(\"error\", (err: NodeJS.ErrnoException) => {\n clearTimeout(timer);\n if (err.code === \"EADDRINUSE\") {\n reject(\n new Error(\n `Port ${CALLBACK_PORT} is already in use. Close any other agent-insights login process and try again.`,\n ),\n );\n } else {\n reject(err);\n }\n });\n });\n}\n\n// ---------------------------------------------------------------------------\n// Token exchange\n// ---------------------------------------------------------------------------\n\nexport async function exchangeCodeForToken(\n code: string,\n codeVerifier: string,\n): Promise<TokenResponse> {\n const res = await fetch(TOKEN_ENDPOINT, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"authorization_code\",\n client_id: CLIENT_ID,\n code,\n code_verifier: codeVerifier,\n redirect_uri: REDIRECT_URI,\n }),\n });\n if (!res.ok) {\n const body = await res.text();\n throw new Error(`Token exchange failed (${res.status}): ${body}`);\n }\n return res.json() as Promise<TokenResponse>;\n}\n\nexport async function refreshAccessToken(\n refreshToken: string,\n): Promise<TokenResponse> {\n const res = await fetch(TOKEN_ENDPOINT, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"refresh_token\",\n client_id: CLIENT_ID,\n refresh_token: refreshToken,\n }),\n });\n if (!res.ok) {\n const body = await res.text();\n throw new Error(`Token refresh failed (${res.status}): ${body}`);\n }\n return res.json() as Promise<TokenResponse>;\n}\n\nexport async function revokeToken(token: string): Promise<void> {\n await fetch(REVOKE_ENDPOINT, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({ token }),\n }).catch(() => {\n // best-effort — don't fail logout if revocation fails\n });\n}\n\nexport async function fetchUserInfo(accessToken: string): Promise<UserInfo> {\n const res = await fetch(USERINFO_ENDPOINT, {\n headers: { authorization: `Bearer ${accessToken}` },\n });\n if (!res.ok) {\n throw new Error(`Failed to fetch user info (${res.status})`);\n }\n return res.json() as Promise<UserInfo>;\n}\n\n/** Open a URL in the system browser. Fails silently — callers always print the URL too. */\nexport function openBrowser(url: string): void {\n const os = platform();\n let cmd: string;\n if (os === \"darwin\") cmd = `open \"${url}\"`;\n else if (os === \"win32\") cmd = `start \"\" \"${url}\"`;\n else cmd = `xdg-open \"${url}\"`;\n\n // Detach + unref so the child doesn't keep the event loop alive after login.\n const child = exec(cmd);\n child.on(\"error\", () => {\n // Silently ignore — caller has already printed the URL\n });\n child.unref();\n}\n","import { chmod, mkdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { bypassSecretFile } from \"./paths.js\";\n\n/**\n * Resolve the Vercel deployment-protection bypass secret.\n * Precedence:\n * 1. `AGENT_INSIGHTS_BYPASS_SECRET` (explicit override)\n * 2. `VERCEL_AUTOMATION_BYPASS_SECRET` (Vercel's standard env)\n * 3. `~/.agent-insights/bypass-secret` (set via `agent-insights init`)\n */\nexport async function loadBypassSecret(): Promise<string | undefined> {\n const fromEnv =\n process.env.AGENT_INSIGHTS_BYPASS_SECRET ||\n process.env.VERCEL_AUTOMATION_BYPASS_SECRET;\n if (fromEnv) return fromEnv;\n\n try {\n const raw = (await readFile(bypassSecretFile(), \"utf8\")).trim();\n return raw || undefined;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw err;\n }\n}\n\nexport async function saveBypassSecret(secret: string): Promise<void> {\n const path = bypassSecretFile();\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, secret.trim(), { encoding: \"utf8\", mode: 0o600 });\n try {\n await chmod(path, 0o600);\n } catch {\n // Windows — ignore.\n }\n}\n\nexport async function clearBypassSecret(): Promise<void> {\n try {\n await unlink(bypassSecretFile());\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n}\n\n/**\n * Add the `x-vercel-protection-bypass` header onto an existing headers\n * object if a secret is configured. Mutates and returns the input.\n */\nexport async function withBypassHeader<T extends Record<string, string>>(\n headers: T,\n): Promise<T> {\n const secret = await loadBypassSecret();\n if (secret) {\n (headers as Record<string, string>)[\"x-vercel-protection-bypass\"] = secret;\n }\n return headers;\n}\n"],"mappings":"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","names":["mkdir","readFile","writeFile","homedir","dirname","join","HOOK_COMMAND_NAME","HOOK_MAP","asString","readJson","normalizeEventHooks","removeAgentInsightsMatchers","addAgentInsightsHook","isAgentInsightsCommand","isHookCommand","mkdir","readFile","writeFile","homedir","dirname","join","HOOK_COMMAND_NAME","HOOK_MAP","asString","readJson","isAgentInsightsCommand","mkdir","readFile","writeFile","homedir","dirname","join","HOOK_MAP","asString","mkdir","readFile","writeFile","dirname","homedir","join","join","readFile","writeFile","mkdir","dirname","readFile","readFile","writeFile","dirname","mkdir","createHash","readFile","mkdir","dirname","writeFile","unlink","chmod","mkdir","readFile","unlink","writeFile","dirname","readFile","readFile"]}
1
+ {"version":3,"sources":["../src/privacy/hash.ts","../src/privacy/sanitize.ts","../src/adapters/claude-code.ts","../src/util/hook-command.ts","../src/adapters/codex.ts","../src/adapters/cursor.ts","../src/adapters/pi.ts","../src/adapters/registry.ts","../src/config/config.ts","../src/config/analyzer.ts","../src/config/paths.ts","../src/transcript/store.ts","../src/auth/store.ts","../src/auth/oauth.ts","../src/config/bypass.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 { hookCommandString } from \"../util/hook-command.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 ClaudeHookCommand = {\n type: \"command\";\n command: string;\n};\n\n/** Claude Code 0.12+ matcher group (see code.claude.com/docs/en/hooks). */\ntype ClaudeHookMatcherGroup = {\n matcher: string;\n hooks: ClaudeHookCommand[];\n};\n\ntype ClaudeSettings = {\n hooks?: Record<string, unknown[]>;\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, ClaudeHookMatcherGroup[]> = {};\n\n for (const [event, raw] of Object.entries(next.hooks ?? {})) {\n hooks[event] = normalizeEventHooks(raw);\n }\n\n for (const event of CLAUDE_HOOK_EVENTS) {\n const withoutInsights = removeAgentInsightsMatchers(\n hooks[event] ?? [],\n );\n hooks[event] = addAgentInsightsHook(withoutInsights, event);\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: Record<string, ClaudeHookMatcherGroup[]> = {};\n let touched = false;\n\n for (const [event, raw] of Object.entries(settings.hooks)) {\n const before = normalizeEventHooks(raw);\n const after = removeAgentInsightsMatchers(before);\n if (after.length !== before.length) touched = true;\n else if (JSON.stringify(after) !== JSON.stringify(before)) {\n touched = true;\n }\n if (after.length > 0) hooks[event] = after;\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((raw) =>\n normalizeEventHooks(raw).some((group) =>\n group.hooks.some((h) => isAgentInsightsCommand(h.command)),\n ),\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 if (cmd.startsWith(`${HOOK_COMMAND_NAME} hook`)) return true;\n return /\\bagent-insights\\b.*\\bcli\\.js\\b.*\\bhook\\b/.test(cmd);\n}\n\nfunction isHookCommand(value: unknown): value is ClaudeHookCommand {\n if (!value || typeof value !== \"object\") return false;\n const entry = value as Record<string, unknown>;\n return entry.type === \"command\" && typeof entry.command === \"string\";\n}\n\n/** Accept matcher groups and migrate legacy flat `{ type, command }` entries. */\nfunction normalizeEventHooks(raw: unknown[]): ClaudeHookMatcherGroup[] {\n const groups: ClaudeHookMatcherGroup[] = [];\n\n for (const entry of raw) {\n if (!entry || typeof entry !== \"object\") continue;\n const record = entry as Record<string, unknown>;\n\n if (Array.isArray(record.hooks)) {\n const commands = record.hooks.filter(isHookCommand);\n if (commands.length === 0) continue;\n groups.push({\n matcher: typeof record.matcher === \"string\" ? record.matcher : \"\",\n hooks: commands,\n });\n continue;\n }\n\n if (isHookCommand(record)) {\n groups.push({\n matcher: \"\",\n hooks: [record],\n });\n }\n }\n\n return groups;\n}\n\nfunction removeAgentInsightsMatchers(\n matchers: ClaudeHookMatcherGroup[],\n): ClaudeHookMatcherGroup[] {\n const result: ClaudeHookMatcherGroup[] = [];\n\n for (const group of matchers) {\n const hooks = group.hooks.filter(\n (h) => !isAgentInsightsCommand(h.command),\n );\n if (hooks.length > 0) {\n result.push({ ...group, hooks });\n }\n }\n\n return result;\n}\n\nfunction addAgentInsightsHook(\n matchers: ClaudeHookMatcherGroup[],\n event: string,\n): ClaudeHookMatcherGroup[] {\n const command = hookCommandString(event);\n const entry: ClaudeHookCommand = { type: \"command\", command };\n\n for (const group of matchers) {\n if (group.matcher === \"\") {\n if (!group.hooks.some((h) => h.command === command)) {\n group.hooks.push(entry);\n }\n return matchers;\n }\n }\n\n return [...matchers, { matcher: \"\", hooks: [entry] }];\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 { realpathSync } from \"node:fs\";\n\n/**\n * Resolve the absolute command that an agent's hook config should\n * invoke. GUI-launched agents (Cursor IDE, etc.) don't inherit the\n * user's shell PATH, so a bare `agent-insights hook <Event>` command\n * won't resolve under fnm / nvm / volta where the bin directory only\n * lives on the interactive shell's PATH.\n *\n * We build the command from process.execPath (the Node binary that's\n * running this CLI) and the real path of the cli.js entry point,\n * which together work regardless of PATH state.\n */\n\nfunction resolve(): { node: string; script: string } {\n const node = process.execPath;\n let script = process.argv[1] ?? \"\";\n try {\n script = realpathSync(script);\n } catch {\n // Fall back to argv[1] as-is.\n }\n return { node, script };\n}\n\n/** Quote a path for safe inclusion in a shell-style command string. */\nfunction quote(s: string): string {\n if (s.length === 0) return \"''\";\n if (!/[\\s'\"\\\\$`<>|&;()*?#~]/.test(s)) return s;\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/**\n * `<node> <cli.js> hook <Event>` — appropriate for hook configs that\n * expect a single shell command string (Claude Code, Codex, Cursor).\n */\nexport function hookCommandString(event: string): string {\n const { node, script } = resolve();\n return `${quote(node)} ${quote(script)} hook ${event}`;\n}\n\n/**\n * Argv form — appropriate for callers using node:child_process.spawn\n * (e.g. the generated Pi extension).\n */\nexport function hookCommandArgv(event: string): {\n node: string;\n script: string;\n args: string[];\n} {\n const { node, script } = resolve();\n return { node, script, args: [\"hook\", event] };\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 { hookCommandString } from \"../util/hook-command.js\";\nimport type { Adapter, HookPayload, MappedEvent } from \"./types.js\";\n\nconst HOOK_COMMAND_NAME = \"agent-insights\";\n\n/**\n * Codex (OpenAI) hooks reference:\n * https://developers.openai.com/codex/hooks\n *\n * Same JSON shape as Claude Code: a `hooks` object keyed by event name,\n * each value is an array of matcher groups `{ matcher, hooks: [{ type:\n * \"command\", command }] }`. We write `~/.codex/hooks.json` (global) or\n * `<repo>/.codex/hooks.json` (local).\n *\n * Note: Codex does not have a separate `SessionEnd` event — `Stop`\n * fires when a turn completes and ships the `transcript_path` we need,\n * so we map Stop → session.end for transcript-upload purposes.\n */\nconst HOOK_MAP: Record<string, AgentEventType> = {\n SessionStart: \"session.start\",\n UserPromptSubmit: \"user.prompt.submit\",\n PreToolUse: \"tool.start\",\n PostToolUse: \"tool.end\",\n PermissionRequest: \"permission.request\",\n Stop: \"session.end\",\n};\n\nexport const CODEX_HOOK_EVENTS: ReadonlyArray<string> = Object.keys(HOOK_MAP);\n\ntype CodexHookCommand = {\n type: \"command\";\n command: string;\n};\n\ntype CodexHookMatcherGroup = {\n matcher: string;\n hooks: CodexHookCommand[];\n};\n\ntype CodexHooksFile = {\n hooks?: Record<string, unknown[]>;\n [key: string]: unknown;\n};\n\nexport const codexAdapter: Adapter = {\n id: \"codex\",\n agent: \"codex\",\n label: \"Codex\",\n settingsFile: \".codex/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 =\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 = resolveCodexPath(repoRoot, scope);\n const settings = await readJson<CodexHooksFile>(path);\n const next: CodexHooksFile = { ...(settings ?? {}) };\n const hooks: Record<string, CodexHookMatcherGroup[]> = {};\n\n for (const [event, raw] of Object.entries(next.hooks ?? {})) {\n hooks[event] = normalizeEventHooks(raw);\n }\n\n for (const event of CODEX_HOOK_EVENTS) {\n const withoutInsights = removeAgentInsightsMatchers(hooks[event] ?? []);\n hooks[event] = addAgentInsightsHook(withoutInsights, event);\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 = resolveCodexPath(repoRoot, scope);\n const settings = await readJson<CodexHooksFile>(path);\n if (!settings?.hooks) return { removed: false, path };\n const hooks: Record<string, CodexHookMatcherGroup[]> = {};\n let touched = false;\n\n for (const [event, raw] of Object.entries(settings.hooks)) {\n const before = normalizeEventHooks(raw);\n const after = removeAgentInsightsMatchers(before);\n if (\n after.length !== before.length ||\n JSON.stringify(after) !== JSON.stringify(before)\n ) {\n touched = true;\n }\n if (after.length > 0) hooks[event] = after;\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<CodexHooksFile>(\n resolveCodexPath(repoRoot, scope),\n );\n if (!settings?.hooks) return false;\n return Object.values(settings.hooks).some((raw) =>\n normalizeEventHooks(raw).some((group) =>\n group.hooks.some((h) => isAgentInsightsCommand(h.command)),\n ),\n );\n },\n};\n\nfunction resolveCodexPath(repoRoot: string, scope: HooksScope): string {\n return scope === \"global\"\n ? join(homedir(), \".codex\", \"hooks.json\")\n : join(repoRoot, \".codex\", \"hooks.json\");\n}\n\nfunction isAgentInsightsCommand(cmd: string | undefined): boolean {\n if (!cmd) return false;\n if (cmd.startsWith(`${HOOK_COMMAND_NAME} hook`)) return true;\n return /\\bagent-insights\\b.*\\bcli\\.js\\b.*\\bhook\\b/.test(cmd);\n}\n\nfunction isHookCommand(value: unknown): value is CodexHookCommand {\n if (!value || typeof value !== \"object\") return false;\n const entry = value as Record<string, unknown>;\n return entry.type === \"command\" && typeof entry.command === \"string\";\n}\n\nfunction normalizeEventHooks(raw: unknown[]): CodexHookMatcherGroup[] {\n const groups: CodexHookMatcherGroup[] = [];\n for (const entry of raw) {\n if (!entry || typeof entry !== \"object\") continue;\n const record = entry as Record<string, unknown>;\n if (Array.isArray(record.hooks)) {\n const commands = record.hooks.filter(isHookCommand);\n if (commands.length === 0) continue;\n groups.push({\n matcher: typeof record.matcher === \"string\" ? record.matcher : \"\",\n hooks: commands,\n });\n continue;\n }\n if (isHookCommand(record)) {\n groups.push({ matcher: \"\", hooks: [record] });\n }\n }\n return groups;\n}\n\nfunction removeAgentInsightsMatchers(\n matchers: CodexHookMatcherGroup[],\n): CodexHookMatcherGroup[] {\n const result: CodexHookMatcherGroup[] = [];\n for (const group of matchers) {\n const hooks = group.hooks.filter(\n (h) => !isAgentInsightsCommand(h.command),\n );\n if (hooks.length > 0) result.push({ ...group, hooks });\n }\n return result;\n}\n\nfunction addAgentInsightsHook(\n matchers: CodexHookMatcherGroup[],\n event: string,\n): CodexHookMatcherGroup[] {\n const command = hookCommandString(event);\n const entry: CodexHookCommand = { type: \"command\", command };\n\n for (const group of matchers) {\n if (group.matcher === \"\") {\n if (!group.hooks.some((h) => h.command === command)) {\n group.hooks.push(entry);\n }\n return matchers;\n }\n }\n return [...matchers, { matcher: \"\", hooks: [entry] }];\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 { hookCommandString } from \"../util/hook-command.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: hookCommandString(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 // Legacy bare-name form\n if (cmd.startsWith(`${HOOK_COMMAND_NAME} hook`)) return true;\n // Absolute-path form: node /…/agent-insights/dist/cli.js hook <Event>\n return /\\bagent-insights\\b.*\\bcli\\.js\\b.*\\bhook\\b/.test(cmd);\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, unlink, 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 { hookCommandArgv } from \"../util/hook-command.js\";\nimport type { Adapter, HookPayload, MappedEvent } from \"./types.js\";\n\nconst EXTENSION_FILENAME = \"agent-insights.ts\";\n\n/**\n * Pi (earendil-works/pi-mono) doesn't have a file-based hooks config\n * like Claude Code or Codex. Instead it has a TypeScript extension\n * system that subscribes to lifecycle events via `pi.on(...)`.\n *\n * We install a tiny extension at `~/.pi/agent/extensions/agent-insights.ts`\n * (or `<repo>/.pi/extensions/agent-insights.ts` for local scope) that\n * shells out to `agent-insights hook <event>` whenever a Pi lifecycle\n * event fires, with the same stdin-JSON contract the other adapters\n * use. Pi loads it automatically on next session start.\n */\nconst HOOK_MAP: Record<string, AgentEventType> = {\n session_start: \"session.start\",\n session_shutdown: \"session.end\",\n tool_call: \"tool.start\",\n tool_result: \"tool.end\",\n};\n\nexport const PI_HOOK_EVENTS: ReadonlyArray<string> = Object.keys(HOOK_MAP);\n\nconst EXTENSION_MARKER = \"// installed-by: agent-insights\";\n\nfunction extensionSource(): string {\n // Bake the absolute paths to Node + cli.js so the extension works\n // even when Pi is launched from a context (GUI / system service)\n // that doesn't have the user's shell PATH.\n const { node, script } = hookCommandArgv(\"Placeholder\");\n return `${EXTENSION_MARKER}\n// Generated by \\`agent-insights init\\`. Edit-with-caution: it's overwritten on every install.\n\nimport type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\nimport { spawn } from \"node:child_process\";\n\nconst EVENTS = ${JSON.stringify(PI_HOOK_EVENTS)} as const;\nconst NODE_BIN = ${JSON.stringify(node)};\nconst CLI_SCRIPT = ${JSON.stringify(script)};\n\nfunction emit(event: string, payload: Record<string, unknown>): void {\n try {\n const child = spawn(NODE_BIN, [CLI_SCRIPT, \"hook\", event], {\n stdio: [\"pipe\", \"ignore\", \"ignore\"],\n detached: true,\n });\n child.on(\"error\", () => {});\n child.stdin.end(JSON.stringify(payload));\n child.unref();\n } catch {\n // best-effort\n }\n}\n\nexport default function (pi: ExtensionAPI) {\n for (const event of EVENTS) {\n pi.on(event as never, async (raw: unknown, ctx: { sessionManager?: { getSessionFile?: () => string | undefined; getSessionId?: () => string | undefined } }) => {\n const transcript_path = ctx.sessionManager?.getSessionFile?.();\n const session_id = ctx.sessionManager?.getSessionId?.();\n const e = (raw ?? {}) as Record<string, unknown>;\n emit(event, {\n ...(session_id ? { session_id } : {}),\n ...(transcript_path ? { transcript_path } : {}),\n ...(\"toolName\" in e && typeof e.toolName === \"string\"\n ? { tool_name: e.toolName }\n : {}),\n ...(\"reason\" in e ? { reason: e.reason } : {}),\n });\n });\n }\n}\n`;\n}\n\nexport const piAdapter: Adapter = {\n id: \"pi\",\n agent: \"pi\",\n label: \"Pi\",\n settingsFile: \".pi/extensions/agent-insights.ts\",\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\"]) ?? 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, scope: HooksScope = \"global\") {\n const path = resolvePath(repoRoot, scope);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, extensionSource(), \"utf8\");\n return { written: true, path };\n },\n\n async uninstall(repoRoot: string, scope: HooksScope = \"global\") {\n const path = resolvePath(repoRoot, scope);\n try {\n const raw = await readFile(path, \"utf8\");\n if (!raw.includes(EXTENSION_MARKER)) {\n // Not ours — leave it alone.\n return { removed: false, path };\n }\n await unlink(path);\n return { removed: true, path };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { removed: false, path };\n }\n throw err;\n }\n },\n\n async isInstalled(repoRoot: string, scope: HooksScope = \"global\") {\n const path = resolvePath(repoRoot, scope);\n try {\n const raw = await readFile(path, \"utf8\");\n return raw.includes(EXTENSION_MARKER);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return false;\n throw err;\n }\n },\n};\n\nfunction resolvePath(repoRoot: string, scope: HooksScope): string {\n return scope === \"global\"\n ? join(homedir(), \".pi\", \"agent\", \"extensions\", EXTENSION_FILENAME)\n : join(repoRoot, \".pi\", \"extensions\", EXTENSION_FILENAME);\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 { codexAdapter } from \"./codex.js\";\nimport { cursorAdapter } from \"./cursor.js\";\nimport { piAdapter } from \"./pi.js\";\nimport type { Adapter, AgentId } from \"./types.js\";\n\nexport const adapters: Record<AgentId, Adapter> = {\n \"claude-code\": claudeCodeAdapter,\n cursor: cursorAdapter,\n codex: codexAdapter,\n pi: piAdapter,\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 { getAnalyzerUrl } from \"./analyzer.js\";\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\" | \"codex\" | \"pi\", 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 // Upload via the analyzer using the SiwV bearer token — no client-side\n // Blob token required. Set `transcriptStore.type` to \"blob\" in\n // config.json to switch to direct uploads with BLOB_READ_WRITE_TOKEN.\n type: \"analyzer\",\n },\n sessionAnalysis: {\n enabled: false,\n analyzerUrl: getAnalyzerUrl(),\n githubIssueRepo: \"vercel-labs/agent-insights\",\n },\n agents: {\n claudeCode: { enabled: false },\n cursor: { enabled: false },\n codex: { enabled: false },\n pi: { 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 codex: { ...base.agents.codex, ...(patch.agents?.codex ?? {}) },\n pi: { ...base.agents.pi, ...(patch.agents?.pi ?? {}) },\n },\n privacy: base.privacy,\n };\n}\n","/**\n * Production analyzer deployment. Override via AGENT_INSIGHTS_ANALYZER_URL\n * for local/dev/staging analyzers.\n */\nexport const DEFAULT_ANALYZER_URL = \"https://agent-insights.labs.vercel.dev\";\n\n/** Resolve the analyzer base URL: env var if set, otherwise the prod default. */\nexport function getAnalyzerUrl(): string {\n return process.env.AGENT_INSIGHTS_ANALYZER_URL || DEFAULT_ANALYZER_URL;\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\n/** Vercel deployment-protection bypass secret (`chmod 600`). */\nexport function bypassSecretFile(): string {\n return join(configRoot(), \"bypass-secret\");\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 { getValidToken } from \"../auth/store.js\";\nimport { getAnalyzerUrl } from \"../config/analyzer.js\";\nimport { withBypassHeader } from \"../config/bypass.js\";\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 * Vercel email captured at `agent-insights login`. Lets the analyzer\n * join sessions to real user accounts; never logged or written into\n * the transcript itself.\n */\n userEmail?: 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\n/**\n * Uploads transcripts via the analyzer's POST /api/sessions/upload endpoint\n * using the Sign-in-with-Vercel bearer token. The analyzer then proxies the\n * write to Vercel Blob with its own server-side credentials, so this client\n * does not need a Blob read/write token.\n */\nclass AnalyzerTranscriptStore implements TranscriptStore {\n constructor(private readonly analyzerUrl: string) {}\n\n async upload(input: TranscriptUploadInput): Promise<TranscriptUploadResult> {\n const token = await getValidToken();\n if (!token) {\n throw new Error(\n \"transcript store: not logged in — run `agent-insights login`\",\n );\n }\n const body = await readFile(input.transcriptPath);\n const url = new URL(\"/api/sessions/upload\", this.analyzerUrl).toString();\n const headers = await withBypassHeader({\n authorization: `Bearer ${token}`,\n \"content-type\": \"application/jsonl\",\n \"x-session-id\": input.sessionId,\n \"x-agent\": input.agent,\n \"x-user-hash\": input.userHash,\n ...(input.userEmail ? { \"x-user-email\": input.userEmail } : {}),\n });\n const res = await fetch(url, { method: \"POST\", headers, body });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\n `transcript upload failed (${res.status}): ${text || res.statusText}`,\n );\n }\n const json = (await res.json()) as {\n ok: boolean;\n blobUrl?: string;\n pathname?: string;\n size?: number;\n error?: string;\n };\n if (!json.ok || !json.blobUrl || !json.pathname) {\n throw new Error(`transcript upload error: ${json.error ?? \"unknown\"}`);\n }\n return {\n pathname: json.pathname,\n url: json.blobUrl,\n size: json.size ?? body.byteLength,\n };\n }\n\n async ping(): Promise<{ ok: true } | { ok: false; reason: string }> {\n const token = await getValidToken();\n if (!token) {\n return {\n ok: false,\n reason: \"not logged in — run `agent-insights login`\",\n };\n }\n // Try the actual upload endpoint with an intentionally bad request — a\n // healthy analyzer responds 400 (missing headers); a protection wall\n // responds 401/403 with HTML; an unreachable host throws.\n try {\n const url = new URL(\"/api/sessions/upload\", this.analyzerUrl).toString();\n const headers = await withBypassHeader({\n authorization: `Bearer ${token}`,\n });\n const res = await fetch(url, { method: \"POST\", headers });\n if (res.status === 400) return { ok: true };\n if (res.status === 401 || res.status === 403) {\n const ct = res.headers.get(\"content-type\") ?? \"\";\n if (ct.includes(\"text/html\")) {\n return {\n ok: false,\n reason:\n \"blocked by Vercel deployment protection — set a bypass secret via `agent-insights init --bypass-secret <secret>`\",\n };\n }\n return { ok: false, reason: `analyzer rejected token (${res.status})` };\n }\n return { ok: false, reason: `unexpected status ${res.status}` };\n } catch (err) {\n return {\n ok: false,\n reason: `analyzer unreachable: ${(err as Error).message}`,\n };\n }\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 return new AnalyzerTranscriptStore(getAnalyzerUrl());\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 \\`agent-insights init --force\\` to switch to analyzer-proxied uploads (no token required), or \\`vercel env pull .env.local\\` to keep direct Blob uploads`,\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","import { chmod, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { mkdir } from \"node:fs/promises\";\nimport { authFile } from \"../config/paths.js\";\nimport { fetchUserInfo, refreshAccessToken } from \"./oauth.js\";\n\nexport type AuthState = {\n accessToken: string;\n refreshToken?: string;\n /** Unix epoch milliseconds when the access token expires. */\n expiresAt: number;\n /** Captured at login for per-user analytics joins on the analyzer side. */\n email?: string;\n};\n\nexport async function loadEmail(): Promise<string | undefined> {\n return (await loadAuth())?.email;\n}\n\n/** How many ms before expiry we proactively refresh. */\nconst REFRESH_BUFFER_MS = 60_000;\n\nexport async function loadAuth(): Promise<AuthState | undefined> {\n try {\n const raw = await readFile(authFile(), \"utf8\");\n const parsed = JSON.parse(raw) as Partial<AuthState>;\n if (typeof parsed.accessToken === \"string\" && parsed.accessToken.length > 0) {\n return parsed as AuthState;\n }\n return undefined;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw err;\n }\n}\n\nexport async function saveAuth(state: AuthState): Promise<void> {\n const path = authFile();\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(state, null, 2)}\\n`, {\n encoding: \"utf8\",\n mode: 0o600,\n });\n try {\n await chmod(path, 0o600);\n } catch {\n // Windows doesn't support chmod — ignore\n }\n}\n\nexport async function clearAuth(): Promise<void> {\n const { unlink } = await import(\"node:fs/promises\");\n try {\n await unlink(authFile());\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n}\n\n/**\n * Returns a valid access token, refreshing silently if needed.\n * Returns `undefined` if not logged in or refresh fails (caller should prompt login).\n */\nexport async function getValidToken(): Promise<string | undefined> {\n const auth = await loadAuth();\n if (!auth) return undefined;\n\n const needsRefresh = Date.now() >= auth.expiresAt - REFRESH_BUFFER_MS;\n if (!needsRefresh) return auth.accessToken;\n\n if (!auth.refreshToken) {\n // Access token expired, no refresh token — clear and force re-login\n await clearAuth();\n return undefined;\n }\n\n try {\n const tokens = await refreshAccessToken(auth.refreshToken);\n const refreshed: AuthState = {\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token ?? auth.refreshToken,\n expiresAt: Date.now() + tokens.expires_in * 1000,\n ...(auth.email ? { email: auth.email } : {}),\n };\n await saveAuth(refreshed);\n return refreshed.accessToken;\n } catch {\n // Refresh failed (revoked, expired beyond 30 days) — clear and force re-login\n await clearAuth();\n return undefined;\n }\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { exec } from \"node:child_process\";\nimport { createServer } from \"node:http\";\nimport { platform } from \"node:os\";\n\n// ---------------------------------------------------------------------------\n// OAuth app config — Sign-in-with-Vercel client ID (public, not a secret).\n// Override at runtime via AGENT_INSIGHTS_CLIENT_ID env var.\n// ---------------------------------------------------------------------------\nexport const CLIENT_ID =\n process.env.AGENT_INSIGHTS_CLIENT_ID ?? \"cl_TxqqNFTHcF9kEtM48LBFsYfwjhJ9T8sz\";\n\nconst AUTH_ENDPOINT = \"https://vercel.com/oauth/authorize\";\nconst TOKEN_ENDPOINT = \"https://api.vercel.com/login/oauth/token\";\nconst USERINFO_ENDPOINT = \"https://api.vercel.com/login/oauth/userinfo\";\nconst REVOKE_ENDPOINT = \"https://api.vercel.com/login/oauth/token/revoke\";\n\n// Fixed port — must match the redirect URI registered in the OAuth app.\nconst CALLBACK_PORT = 9797;\nexport const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}/callback`;\n\nconst SCOPES = \"openid email profile offline_access\";\n\nexport type TokenResponse = {\n access_token: string;\n refresh_token?: string;\n expires_in: number;\n token_type: string;\n id_token?: string;\n};\n\nexport type UserInfo = {\n sub: string;\n email: string;\n email_verified: boolean;\n name?: string;\n preferred_username?: string;\n picture?: string;\n};\n\n// ---------------------------------------------------------------------------\n// PKCE helpers\n// ---------------------------------------------------------------------------\n\nfunction generateCodeVerifier(): string {\n return randomBytes(48).toString(\"base64url\");\n}\n\nfunction generateCodeChallenge(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\nfunction generateState(): string {\n return randomBytes(16).toString(\"hex\");\n}\n\n// ---------------------------------------------------------------------------\n// Authorization Code + PKCE flow\n// ---------------------------------------------------------------------------\n\nexport type AuthCodeResult =\n | { status: \"success\"; code: string; state: string }\n | { status: \"error\"; message: string };\n\n/**\n * Starts a localhost callback server, opens the Vercel authorization URL in\n * the browser, and waits for the redirect with the auth code.\n * Resolves (or rejects after timeout) when Vercel redirects back.\n */\nexport async function startPkceFlow(timeoutMs = 120_000): Promise<{\n code: string;\n codeVerifier: string;\n}> {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n const authUrl = new URL(AUTH_ENDPOINT);\n authUrl.searchParams.set(\"client_id\", CLIENT_ID);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"redirect_uri\", REDIRECT_URI);\n authUrl.searchParams.set(\"scope\", SCOPES);\n authUrl.searchParams.set(\"code_challenge\", codeChallenge);\n authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authUrl.searchParams.set(\"state\", state);\n\n const url = authUrl.toString();\n const codePromise = listenForCallback(state, timeoutMs);\n openBrowser(url);\n // Always print the URL — browser open may fail silently\n process.stderr.write(`\\nIf the browser did not open, visit:\\n ${url}\\n\\n`);\n\n const code = await codePromise;\n return { code, codeVerifier };\n}\n\nfunction listenForCallback(\n expectedState: string,\n timeoutMs: number,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? \"/\", `http://localhost:${CALLBACK_PORT}`);\n if (url.pathname !== \"/callback\") {\n res.writeHead(404, { connection: \"close\" });\n res.end(\"Not found\");\n return;\n }\n\n const code = url.searchParams.get(\"code\");\n const returnedState = url.searchParams.get(\"state\");\n const error = url.searchParams.get(\"error\");\n\n const html = (msg: string) =>\n `<html><body style=\"font-family:sans-serif;text-align:center;padding:80px\">` +\n `<h2>${msg}</h2><p>You can close this tab.</p></body></html>`;\n\n // Tell the browser to drop the socket so the server can shut down cleanly\n // and Node's event loop can exit once the callback returns.\n const headers = { \"content-type\": \"text/html\", connection: \"close\" };\n\n const finish = (\n next: () => void,\n body: string,\n status: number,\n ): void => {\n res.writeHead(status, headers);\n res.end(body, () => {\n clearTimeout(timer);\n server.closeAllConnections?.();\n server.close();\n next();\n });\n };\n\n if (error) {\n finish(\n () => reject(new Error(`OAuth error: ${error}`)),\n html(`Login failed: ${error}`),\n 400,\n );\n return;\n }\n\n if (returnedState !== expectedState) {\n finish(\n () => reject(new Error(\"State mismatch — possible CSRF\")),\n html(\"Invalid state parameter — please try again.\"),\n 400,\n );\n return;\n }\n\n if (!code) {\n finish(\n () => reject(new Error(\"No code in callback\")),\n html(\"No authorization code received.\"),\n 400,\n );\n return;\n }\n\n finish(\n () => resolve(code),\n html(\"✓ Logged in! You can close this tab.\"),\n 200,\n );\n });\n\n const timer = setTimeout(() => {\n server.closeAllConnections?.();\n server.close();\n reject(new Error(\"Login timed out — no response within 2 minutes.\"));\n }, timeoutMs);\n timer.unref();\n\n server.listen(CALLBACK_PORT, \"127.0.0.1\", () => {\n // Server is ready; browser will be opened by caller\n });\n\n server.on(\"error\", (err: NodeJS.ErrnoException) => {\n clearTimeout(timer);\n if (err.code === \"EADDRINUSE\") {\n reject(\n new Error(\n `Port ${CALLBACK_PORT} is already in use. Close any other agent-insights login process and try again.`,\n ),\n );\n } else {\n reject(err);\n }\n });\n });\n}\n\n// ---------------------------------------------------------------------------\n// Token exchange\n// ---------------------------------------------------------------------------\n\nexport async function exchangeCodeForToken(\n code: string,\n codeVerifier: string,\n): Promise<TokenResponse> {\n const res = await fetch(TOKEN_ENDPOINT, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"authorization_code\",\n client_id: CLIENT_ID,\n code,\n code_verifier: codeVerifier,\n redirect_uri: REDIRECT_URI,\n }),\n });\n if (!res.ok) {\n const body = await res.text();\n throw new Error(`Token exchange failed (${res.status}): ${body}`);\n }\n return res.json() as Promise<TokenResponse>;\n}\n\nexport async function refreshAccessToken(\n refreshToken: string,\n): Promise<TokenResponse> {\n const res = await fetch(TOKEN_ENDPOINT, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"refresh_token\",\n client_id: CLIENT_ID,\n refresh_token: refreshToken,\n }),\n });\n if (!res.ok) {\n const body = await res.text();\n throw new Error(`Token refresh failed (${res.status}): ${body}`);\n }\n return res.json() as Promise<TokenResponse>;\n}\n\nexport async function revokeToken(token: string): Promise<void> {\n await fetch(REVOKE_ENDPOINT, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({ token }),\n }).catch(() => {\n // best-effort — don't fail logout if revocation fails\n });\n}\n\nexport async function fetchUserInfo(accessToken: string): Promise<UserInfo> {\n const res = await fetch(USERINFO_ENDPOINT, {\n headers: { authorization: `Bearer ${accessToken}` },\n });\n if (!res.ok) {\n throw new Error(`Failed to fetch user info (${res.status})`);\n }\n return res.json() as Promise<UserInfo>;\n}\n\n/** Open a URL in the system browser. Fails silently — callers always print the URL too. */\nexport function openBrowser(url: string): void {\n const os = platform();\n let cmd: string;\n if (os === \"darwin\") cmd = `open \"${url}\"`;\n else if (os === \"win32\") cmd = `start \"\" \"${url}\"`;\n else cmd = `xdg-open \"${url}\"`;\n\n // Detach + unref so the child doesn't keep the event loop alive after login.\n const child = exec(cmd);\n child.on(\"error\", () => {\n // Silently ignore — caller has already printed the URL\n });\n child.unref();\n}\n","import { chmod, mkdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { bypassSecretFile } from \"./paths.js\";\n\n/**\n * Resolve the Vercel deployment-protection bypass secret.\n * Precedence:\n * 1. `AGENT_INSIGHTS_BYPASS_SECRET` (explicit override)\n * 2. `VERCEL_AUTOMATION_BYPASS_SECRET` (Vercel's standard env)\n * 3. `~/.agent-insights/bypass-secret` (set via `agent-insights init`)\n */\nexport async function loadBypassSecret(): Promise<string | undefined> {\n const fromEnv =\n process.env.AGENT_INSIGHTS_BYPASS_SECRET ||\n process.env.VERCEL_AUTOMATION_BYPASS_SECRET;\n if (fromEnv) return fromEnv;\n\n try {\n const raw = (await readFile(bypassSecretFile(), \"utf8\")).trim();\n return raw || undefined;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n throw err;\n }\n}\n\nexport async function saveBypassSecret(secret: string): Promise<void> {\n const path = bypassSecretFile();\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, secret.trim(), { encoding: \"utf8\", mode: 0o600 });\n try {\n await chmod(path, 0o600);\n } catch {\n // Windows — ignore.\n }\n}\n\nexport async function clearBypassSecret(): Promise<void> {\n try {\n await unlink(bypassSecretFile());\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n}\n\n/**\n * Add the `x-vercel-protection-bypass` header onto an existing headers\n * object if a secret is configured. Mutates and returns the input.\n */\nexport async function withBypassHeader<T extends Record<string, string>>(\n headers: T,\n): Promise<T> {\n const secret = await loadBypassSecret();\n if (secret) {\n (headers as Record<string, string>)[\"x-vercel-protection-bypass\"] = secret;\n }\n return headers;\n}\n"],"mappings":"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","names":["mkdir","readFile","writeFile","homedir","dirname","join","HOOK_COMMAND_NAME","HOOK_MAP","asString","readJson","normalizeEventHooks","removeAgentInsightsMatchers","addAgentInsightsHook","mkdir","dirname","writeFile","isAgentInsightsCommand","join","homedir","isHookCommand","readFile","mkdir","readFile","writeFile","homedir","dirname","join","HOOK_COMMAND_NAME","HOOK_MAP","asString","readJson","isAgentInsightsCommand","mkdir","dirname","writeFile","join","homedir","readFile","mkdir","readFile","writeFile","homedir","dirname","join","HOOK_MAP","asString","mkdir","dirname","writeFile","readFile","join","homedir","mkdir","readFile","writeFile","dirname","homedir","join","join","readFile","writeFile","mkdir","dirname","readFile","readFile","writeFile","dirname","mkdir","createHash","readFile","mkdir","dirname","writeFile","unlink","chmod","mkdir","readFile","unlink","writeFile","dirname","readFile","readFile"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-insights",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
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",