agent-insights 0.0.12 → 0.0.16

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
@@ -136,16 +136,15 @@ var claudeCodeAdapter = {
136
136
  const path = resolveClaudePath(repoRoot, scope);
137
137
  const settings = await readJson(path);
138
138
  const next = { ...settings ?? {} };
139
- const hooks = { ...next.hooks ?? {} };
139
+ const hooks = {};
140
+ for (const [event, raw] of Object.entries(next.hooks ?? {})) {
141
+ hooks[event] = normalizeEventHooks(raw);
142
+ }
140
143
  for (const event of CLAUDE_HOOK_EVENTS) {
141
- const existing = (hooks[event] ?? []).filter(
142
- (h) => !isAgentInsightsCommand(h.command)
144
+ const withoutInsights = removeAgentInsightsMatchers(
145
+ hooks[event] ?? []
143
146
  );
144
- existing.push({
145
- type: "command",
146
- command: `${HOOK_COMMAND_NAME} hook ${event}`
147
- });
148
- hooks[event] = existing;
147
+ hooks[event] = addAgentInsightsHook(withoutInsights, event);
149
148
  }
150
149
  next.hooks = hooks;
151
150
  await mkdir(dirname(path), { recursive: true });
@@ -157,18 +156,16 @@ var claudeCodeAdapter = {
157
156
  const path = resolveClaudePath(repoRoot, scope);
158
157
  const settings = await readJson(path);
159
158
  if (!settings?.hooks) return { removed: false, path };
160
- const hooks = { ...settings.hooks };
159
+ const hooks = {};
161
160
  let touched = false;
162
- for (const [event, entries] of Object.entries(hooks)) {
163
- const filtered = entries.filter(
164
- (h) => !isAgentInsightsCommand(h.command)
165
- );
166
- if (filtered.length !== entries.length) touched = true;
167
- if (filtered.length === 0) {
168
- delete hooks[event];
169
- } else {
170
- hooks[event] = filtered;
161
+ for (const [event, raw] of Object.entries(settings.hooks)) {
162
+ const before = normalizeEventHooks(raw);
163
+ const after = removeAgentInsightsMatchers(before);
164
+ if (after.length !== before.length) touched = true;
165
+ else if (JSON.stringify(after) !== JSON.stringify(before)) {
166
+ touched = true;
171
167
  }
168
+ if (after.length > 0) hooks[event] = after;
172
169
  }
173
170
  settings.hooks = hooks;
174
171
  await writeFile(path, `${JSON.stringify(settings, null, 2)}
@@ -181,7 +178,9 @@ var claudeCodeAdapter = {
181
178
  );
182
179
  if (!settings?.hooks) return false;
183
180
  return Object.values(settings.hooks).some(
184
- (entries) => entries.some((h) => isAgentInsightsCommand(h.command))
181
+ (raw) => normalizeEventHooks(raw).some(
182
+ (group) => group.hooks.some((h) => isAgentInsightsCommand(h.command))
183
+ )
185
184
  );
186
185
  }
187
186
  };
@@ -192,6 +191,59 @@ function isAgentInsightsCommand(cmd) {
192
191
  if (!cmd) return false;
193
192
  return cmd.startsWith(`${HOOK_COMMAND_NAME} hook`);
194
193
  }
194
+ function isHookCommand(value) {
195
+ if (!value || typeof value !== "object") return false;
196
+ const entry = value;
197
+ return entry.type === "command" && typeof entry.command === "string";
198
+ }
199
+ function normalizeEventHooks(raw) {
200
+ const groups = [];
201
+ for (const entry of raw) {
202
+ if (!entry || typeof entry !== "object") continue;
203
+ const record = entry;
204
+ if (Array.isArray(record.hooks)) {
205
+ const commands = record.hooks.filter(isHookCommand);
206
+ if (commands.length === 0) continue;
207
+ groups.push({
208
+ matcher: typeof record.matcher === "string" ? record.matcher : "",
209
+ hooks: commands
210
+ });
211
+ continue;
212
+ }
213
+ if (isHookCommand(record)) {
214
+ groups.push({
215
+ matcher: "",
216
+ hooks: [record]
217
+ });
218
+ }
219
+ }
220
+ return groups;
221
+ }
222
+ function removeAgentInsightsMatchers(matchers) {
223
+ const result = [];
224
+ for (const group of matchers) {
225
+ const hooks = group.hooks.filter(
226
+ (h) => !isAgentInsightsCommand(h.command)
227
+ );
228
+ if (hooks.length > 0) {
229
+ result.push({ ...group, hooks });
230
+ }
231
+ }
232
+ return result;
233
+ }
234
+ function addAgentInsightsHook(matchers, event) {
235
+ const command = `${HOOK_COMMAND_NAME} hook ${event}`;
236
+ const entry = { type: "command", command };
237
+ for (const group of matchers) {
238
+ if (group.matcher === "") {
239
+ if (!group.hooks.some((h) => h.command === command)) {
240
+ group.hooks.push(entry);
241
+ }
242
+ return matchers;
243
+ }
244
+ }
245
+ return [...matchers, { matcher: "", hooks: [entry] }];
246
+ }
195
247
  async function readJson(path) {
196
248
  try {
197
249
  const raw = await readFile(path, "utf8");
@@ -205,12 +257,162 @@ function asString(v) {
205
257
  return typeof v === "string" && v.length > 0 ? v : void 0;
206
258
  }
207
259
 
208
- // src/adapters/cursor.ts
260
+ // src/adapters/codex.ts
209
261
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
210
262
  import { homedir as homedir2 } from "os";
211
263
  import { dirname as dirname2, join as join2 } from "path";
212
264
  var HOOK_COMMAND_NAME2 = "agent-insights";
213
265
  var HOOK_MAP2 = {
266
+ SessionStart: "session.start",
267
+ UserPromptSubmit: "user.prompt.submit",
268
+ PreToolUse: "tool.start",
269
+ PostToolUse: "tool.end",
270
+ PermissionRequest: "permission.request",
271
+ Stop: "session.end"
272
+ };
273
+ var CODEX_HOOK_EVENTS = Object.keys(HOOK_MAP2);
274
+ var codexAdapter = {
275
+ id: "codex",
276
+ agent: "codex",
277
+ label: "Codex",
278
+ settingsFile: ".codex/hooks.json",
279
+ map(payload) {
280
+ const type = HOOK_MAP2[payload.event];
281
+ if (!type) return void 0;
282
+ const data = payload.data ?? {};
283
+ const sessionId = asString2(data["session_id"]) ?? asString2(data["sessionId"]);
284
+ const toolName = asString2(data["tool_name"]) ?? asString2(data["toolName"]) ?? asString2(data["tool"]?.["name"]);
285
+ const transcriptPath = asString2(data["transcript_path"]) ?? asString2(data["transcriptPath"]);
286
+ return {
287
+ type,
288
+ ...sessionId !== void 0 ? { sessionId } : {},
289
+ ...toolName !== void 0 ? { toolName } : {},
290
+ ...transcriptPath !== void 0 ? { transcriptPath } : {}
291
+ };
292
+ },
293
+ async install(repoRoot, scope = "global") {
294
+ const path = resolveCodexPath(repoRoot, scope);
295
+ const settings = await readJson2(path);
296
+ const next = { ...settings ?? {} };
297
+ const hooks = {};
298
+ for (const [event, raw] of Object.entries(next.hooks ?? {})) {
299
+ hooks[event] = normalizeEventHooks2(raw);
300
+ }
301
+ for (const event of CODEX_HOOK_EVENTS) {
302
+ const withoutInsights = removeAgentInsightsMatchers2(hooks[event] ?? []);
303
+ hooks[event] = addAgentInsightsHook2(withoutInsights, event);
304
+ }
305
+ next.hooks = hooks;
306
+ await mkdir2(dirname2(path), { recursive: true });
307
+ await writeFile2(path, `${JSON.stringify(next, null, 2)}
308
+ `, "utf8");
309
+ return { written: true, path };
310
+ },
311
+ async uninstall(repoRoot, scope = "global") {
312
+ const path = resolveCodexPath(repoRoot, scope);
313
+ const settings = await readJson2(path);
314
+ if (!settings?.hooks) return { removed: false, path };
315
+ const hooks = {};
316
+ let touched = false;
317
+ for (const [event, raw] of Object.entries(settings.hooks)) {
318
+ const before = normalizeEventHooks2(raw);
319
+ const after = removeAgentInsightsMatchers2(before);
320
+ if (after.length !== before.length || JSON.stringify(after) !== JSON.stringify(before)) {
321
+ touched = true;
322
+ }
323
+ if (after.length > 0) hooks[event] = after;
324
+ }
325
+ settings.hooks = hooks;
326
+ await writeFile2(path, `${JSON.stringify(settings, null, 2)}
327
+ `, "utf8");
328
+ return { removed: touched, path };
329
+ },
330
+ async isInstalled(repoRoot, scope = "global") {
331
+ const settings = await readJson2(
332
+ resolveCodexPath(repoRoot, scope)
333
+ );
334
+ if (!settings?.hooks) return false;
335
+ return Object.values(settings.hooks).some(
336
+ (raw) => normalizeEventHooks2(raw).some(
337
+ (group) => group.hooks.some((h) => isAgentInsightsCommand2(h.command))
338
+ )
339
+ );
340
+ }
341
+ };
342
+ function resolveCodexPath(repoRoot, scope) {
343
+ return scope === "global" ? join2(homedir2(), ".codex", "hooks.json") : join2(repoRoot, ".codex", "hooks.json");
344
+ }
345
+ function isAgentInsightsCommand2(cmd) {
346
+ if (!cmd) return false;
347
+ return cmd.startsWith(`${HOOK_COMMAND_NAME2} hook`);
348
+ }
349
+ function isHookCommand2(value) {
350
+ if (!value || typeof value !== "object") return false;
351
+ const entry = value;
352
+ return entry.type === "command" && typeof entry.command === "string";
353
+ }
354
+ function normalizeEventHooks2(raw) {
355
+ const groups = [];
356
+ for (const entry of raw) {
357
+ if (!entry || typeof entry !== "object") continue;
358
+ const record = entry;
359
+ if (Array.isArray(record.hooks)) {
360
+ const commands = record.hooks.filter(isHookCommand2);
361
+ if (commands.length === 0) continue;
362
+ groups.push({
363
+ matcher: typeof record.matcher === "string" ? record.matcher : "",
364
+ hooks: commands
365
+ });
366
+ continue;
367
+ }
368
+ if (isHookCommand2(record)) {
369
+ groups.push({ matcher: "", hooks: [record] });
370
+ }
371
+ }
372
+ return groups;
373
+ }
374
+ function removeAgentInsightsMatchers2(matchers) {
375
+ const result = [];
376
+ for (const group of matchers) {
377
+ const hooks = group.hooks.filter(
378
+ (h) => !isAgentInsightsCommand2(h.command)
379
+ );
380
+ if (hooks.length > 0) result.push({ ...group, hooks });
381
+ }
382
+ return result;
383
+ }
384
+ function addAgentInsightsHook2(matchers, event) {
385
+ const command = `${HOOK_COMMAND_NAME2} hook ${event}`;
386
+ const entry = { type: "command", command };
387
+ for (const group of matchers) {
388
+ if (group.matcher === "") {
389
+ if (!group.hooks.some((h) => h.command === command)) {
390
+ group.hooks.push(entry);
391
+ }
392
+ return matchers;
393
+ }
394
+ }
395
+ return [...matchers, { matcher: "", hooks: [entry] }];
396
+ }
397
+ async function readJson2(path) {
398
+ try {
399
+ const raw = await readFile2(path, "utf8");
400
+ return JSON.parse(raw);
401
+ } catch (err) {
402
+ if (err.code === "ENOENT") return void 0;
403
+ throw err;
404
+ }
405
+ }
406
+ function asString2(v) {
407
+ return typeof v === "string" && v.length > 0 ? v : void 0;
408
+ }
409
+
410
+ // src/adapters/cursor.ts
411
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
412
+ import { homedir as homedir3 } from "os";
413
+ import { dirname as dirname3, join as join3 } from "path";
414
+ var HOOK_COMMAND_NAME3 = "agent-insights";
415
+ var HOOK_MAP3 = {
214
416
  sessionStart: "session.start",
215
417
  sessionEnd: "session.end",
216
418
  beforeSubmitPrompt: "user.prompt.submit",
@@ -227,19 +429,19 @@ var HOOK_MAP2 = {
227
429
  afterFileEdit: "tool.end",
228
430
  workspaceOpen: "session.start"
229
431
  };
230
- var CURSOR_HOOK_EVENTS = Object.keys(HOOK_MAP2);
432
+ var CURSOR_HOOK_EVENTS = Object.keys(HOOK_MAP3);
231
433
  var cursorAdapter = {
232
434
  id: "cursor",
233
435
  agent: "cursor",
234
436
  label: "Cursor",
235
437
  settingsFile: ".cursor/hooks.json",
236
438
  map(payload) {
237
- const type = HOOK_MAP2[payload.event];
439
+ const type = HOOK_MAP3[payload.event];
238
440
  if (!type) return void 0;
239
441
  const data = payload.data ?? {};
240
- const sessionId = asString2(data["session_id"]) ?? asString2(data["sessionId"]) ?? asString2(data["conversation_id"]);
241
- const toolName = asString2(data["tool_name"]) ?? asString2(data["toolName"]);
242
- const transcriptPath = asString2(data["transcript_path"]) ?? asString2(data["transcriptPath"]) ?? asString2(process.env.CURSOR_TRANSCRIPT_PATH);
442
+ const sessionId = asString3(data["session_id"]) ?? asString3(data["sessionId"]) ?? asString3(data["conversation_id"]);
443
+ const toolName = asString3(data["tool_name"]) ?? asString3(data["toolName"]);
444
+ const transcriptPath = asString3(data["transcript_path"]) ?? asString3(data["transcriptPath"]) ?? asString3(process.env.CURSOR_TRANSCRIPT_PATH);
243
445
  return {
244
446
  type,
245
447
  ...sessionId !== void 0 ? { sessionId } : {},
@@ -249,7 +451,7 @@ var cursorAdapter = {
249
451
  },
250
452
  async install(repoRoot, scope = "global") {
251
453
  const path = resolveCursorPath(repoRoot, scope);
252
- const settings = await readJson2(path);
454
+ const settings = await readJson3(path);
253
455
  const next = {
254
456
  version: 1,
255
457
  ...settings ?? {}
@@ -257,26 +459,26 @@ var cursorAdapter = {
257
459
  const hooks = { ...next.hooks ?? {} };
258
460
  for (const event of CURSOR_HOOK_EVENTS) {
259
461
  const existing = (hooks[event] ?? []).filter(
260
- (h) => !isAgentInsightsCommand2(h.command)
462
+ (h) => !isAgentInsightsCommand3(h.command)
261
463
  );
262
- existing.push({ command: `${HOOK_COMMAND_NAME2} hook ${event}` });
464
+ existing.push({ command: `${HOOK_COMMAND_NAME3} hook ${event}` });
263
465
  hooks[event] = existing;
264
466
  }
265
467
  next.hooks = hooks;
266
- await mkdir2(dirname2(path), { recursive: true });
267
- await writeFile2(path, `${JSON.stringify(next, null, 2)}
468
+ await mkdir3(dirname3(path), { recursive: true });
469
+ await writeFile3(path, `${JSON.stringify(next, null, 2)}
268
470
  `, "utf8");
269
471
  return { written: true, path };
270
472
  },
271
473
  async uninstall(repoRoot, scope = "global") {
272
474
  const path = resolveCursorPath(repoRoot, scope);
273
- const settings = await readJson2(path);
475
+ const settings = await readJson3(path);
274
476
  if (!settings?.hooks) return { removed: false, path };
275
477
  const hooks = { ...settings.hooks };
276
478
  let touched = false;
277
479
  for (const [event, entries] of Object.entries(hooks)) {
278
480
  const filtered = entries.filter(
279
- (h) => !isAgentInsightsCommand2(h.command)
481
+ (h) => !isAgentInsightsCommand3(h.command)
280
482
  );
281
483
  if (filtered.length !== entries.length) touched = true;
282
484
  if (filtered.length === 0) {
@@ -286,44 +488,160 @@ var cursorAdapter = {
286
488
  }
287
489
  }
288
490
  settings.hooks = hooks;
289
- await writeFile2(path, `${JSON.stringify(settings, null, 2)}
491
+ await writeFile3(path, `${JSON.stringify(settings, null, 2)}
290
492
  `, "utf8");
291
493
  return { removed: touched, path };
292
494
  },
293
495
  async isInstalled(repoRoot, scope = "global") {
294
- const settings = await readJson2(
496
+ const settings = await readJson3(
295
497
  resolveCursorPath(repoRoot, scope)
296
498
  );
297
499
  if (!settings?.hooks) return false;
298
500
  return Object.values(settings.hooks).some(
299
- (entries) => entries.some((h) => isAgentInsightsCommand2(h.command))
501
+ (entries) => entries.some((h) => isAgentInsightsCommand3(h.command))
300
502
  );
301
503
  }
302
504
  };
303
505
  function resolveCursorPath(repoRoot, scope) {
304
- return scope === "global" ? join2(homedir2(), ".cursor", "hooks.json") : join2(repoRoot, ".cursor", "hooks.json");
506
+ return scope === "global" ? join3(homedir3(), ".cursor", "hooks.json") : join3(repoRoot, ".cursor", "hooks.json");
305
507
  }
306
- function isAgentInsightsCommand2(cmd) {
508
+ function isAgentInsightsCommand3(cmd) {
307
509
  if (!cmd) return false;
308
- return cmd.startsWith(`${HOOK_COMMAND_NAME2} hook`);
510
+ return cmd.startsWith(`${HOOK_COMMAND_NAME3} hook`);
309
511
  }
310
- async function readJson2(path) {
512
+ async function readJson3(path) {
311
513
  try {
312
- const raw = await readFile2(path, "utf8");
514
+ const raw = await readFile3(path, "utf8");
313
515
  return JSON.parse(raw);
314
516
  } catch (err) {
315
517
  if (err.code === "ENOENT") return void 0;
316
518
  throw err;
317
519
  }
318
520
  }
319
- function asString2(v) {
521
+ function asString3(v) {
522
+ return typeof v === "string" && v.length > 0 ? v : void 0;
523
+ }
524
+
525
+ // src/adapters/pi.ts
526
+ import { mkdir as mkdir4, readFile as readFile4, unlink, writeFile as writeFile4 } from "fs/promises";
527
+ import { homedir as homedir4 } from "os";
528
+ import { dirname as dirname4, join as join4 } from "path";
529
+ var EXTENSION_FILENAME = "agent-insights.ts";
530
+ var HOOK_MAP4 = {
531
+ session_start: "session.start",
532
+ session_shutdown: "session.end",
533
+ tool_call: "tool.start",
534
+ tool_result: "tool.end"
535
+ };
536
+ var PI_HOOK_EVENTS = Object.keys(HOOK_MAP4);
537
+ var EXTENSION_MARKER = "// installed-by: agent-insights";
538
+ function extensionSource() {
539
+ return `${EXTENSION_MARKER}
540
+ // Generated by \`agent-insights init\`. Edit-with-caution: it's overwritten on every install.
541
+
542
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
543
+ import { spawn } from "node:child_process";
544
+
545
+ const EVENTS = ${JSON.stringify(PI_HOOK_EVENTS)} as const;
546
+
547
+ function emit(event: string, payload: Record<string, unknown>): void {
548
+ try {
549
+ const child = spawn("agent-insights", ["hook", event], {
550
+ stdio: ["pipe", "ignore", "ignore"],
551
+ detached: true,
552
+ });
553
+ child.on("error", () => {});
554
+ child.stdin.end(JSON.stringify(payload));
555
+ child.unref();
556
+ } catch {
557
+ // best-effort
558
+ }
559
+ }
560
+
561
+ export default function (pi: ExtensionAPI) {
562
+ for (const event of EVENTS) {
563
+ pi.on(event as never, async (raw: unknown, ctx: { sessionManager?: { getSessionFile?: () => string | undefined; getSessionId?: () => string | undefined } }) => {
564
+ const transcript_path = ctx.sessionManager?.getSessionFile?.();
565
+ const session_id = ctx.sessionManager?.getSessionId?.();
566
+ const e = (raw ?? {}) as Record<string, unknown>;
567
+ emit(event, {
568
+ ...(session_id ? { session_id } : {}),
569
+ ...(transcript_path ? { transcript_path } : {}),
570
+ ...("toolName" in e && typeof e.toolName === "string"
571
+ ? { tool_name: e.toolName }
572
+ : {}),
573
+ ...("reason" in e ? { reason: e.reason } : {}),
574
+ });
575
+ });
576
+ }
577
+ }
578
+ `;
579
+ }
580
+ var piAdapter = {
581
+ id: "pi",
582
+ agent: "pi",
583
+ label: "Pi",
584
+ settingsFile: ".pi/extensions/agent-insights.ts",
585
+ map(payload) {
586
+ const type = HOOK_MAP4[payload.event];
587
+ if (!type) return void 0;
588
+ const data = payload.data ?? {};
589
+ const sessionId = asString4(data["session_id"]) ?? asString4(data["sessionId"]);
590
+ const toolName = asString4(data["tool_name"]) ?? asString4(data["toolName"]);
591
+ const transcriptPath = asString4(data["transcript_path"]) ?? asString4(data["transcriptPath"]);
592
+ return {
593
+ type,
594
+ ...sessionId !== void 0 ? { sessionId } : {},
595
+ ...toolName !== void 0 ? { toolName } : {},
596
+ ...transcriptPath !== void 0 ? { transcriptPath } : {}
597
+ };
598
+ },
599
+ async install(repoRoot, scope = "global") {
600
+ const path = resolvePath(repoRoot, scope);
601
+ await mkdir4(dirname4(path), { recursive: true });
602
+ await writeFile4(path, extensionSource(), "utf8");
603
+ return { written: true, path };
604
+ },
605
+ async uninstall(repoRoot, scope = "global") {
606
+ const path = resolvePath(repoRoot, scope);
607
+ try {
608
+ const raw = await readFile4(path, "utf8");
609
+ if (!raw.includes(EXTENSION_MARKER)) {
610
+ return { removed: false, path };
611
+ }
612
+ await unlink(path);
613
+ return { removed: true, path };
614
+ } catch (err) {
615
+ if (err.code === "ENOENT") {
616
+ return { removed: false, path };
617
+ }
618
+ throw err;
619
+ }
620
+ },
621
+ async isInstalled(repoRoot, scope = "global") {
622
+ const path = resolvePath(repoRoot, scope);
623
+ try {
624
+ const raw = await readFile4(path, "utf8");
625
+ return raw.includes(EXTENSION_MARKER);
626
+ } catch (err) {
627
+ if (err.code === "ENOENT") return false;
628
+ throw err;
629
+ }
630
+ }
631
+ };
632
+ function resolvePath(repoRoot, scope) {
633
+ return scope === "global" ? join4(homedir4(), ".pi", "agent", "extensions", EXTENSION_FILENAME) : join4(repoRoot, ".pi", "extensions", EXTENSION_FILENAME);
634
+ }
635
+ function asString4(v) {
320
636
  return typeof v === "string" && v.length > 0 ? v : void 0;
321
637
  }
322
638
 
323
639
  // src/adapters/registry.ts
324
640
  var adapters = {
325
641
  "claude-code": claudeCodeAdapter,
326
- cursor: cursorAdapter
642
+ cursor: cursorAdapter,
643
+ codex: codexAdapter,
644
+ pi: piAdapter
327
645
  };
328
646
  var adapterList = Object.values(adapters);
329
647
  function getAdapter(id) {
@@ -331,8 +649,8 @@ function getAdapter(id) {
331
649
  }
332
650
 
333
651
  // src/config/config.ts
334
- import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
335
- import { dirname as dirname3 } from "path";
652
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
653
+ import { dirname as dirname5 } from "path";
336
654
 
337
655
  // src/config/analyzer.ts
338
656
  var DEFAULT_ANALYZER_URL = "https://agent-insights.labs.vercel.dev";
@@ -341,25 +659,25 @@ function getAnalyzerUrl() {
341
659
  }
342
660
 
343
661
  // src/config/paths.ts
344
- import { homedir as homedir3 } from "os";
345
- import { join as join3 } from "path";
662
+ import { homedir as homedir5 } from "os";
663
+ import { join as join5 } from "path";
346
664
  function configRoot() {
347
- return process.env.AGENT_INSIGHTS_HOME ?? join3(homedir3(), ".agent-insights");
665
+ return process.env.AGENT_INSIGHTS_HOME ?? join5(homedir5(), ".agent-insights");
348
666
  }
349
667
  function configFile() {
350
- return join3(configRoot(), "config.json");
668
+ return join5(configRoot(), "config.json");
351
669
  }
352
670
  function authFile() {
353
- return join3(configRoot(), "auth.json");
671
+ return join5(configRoot(), "auth.json");
354
672
  }
355
673
  function bypassSecretFile() {
356
- return join3(configRoot(), "bypass-secret");
674
+ return join5(configRoot(), "bypass-secret");
357
675
  }
358
676
  function sessionCacheDir() {
359
- return join3(configRoot(), "session-cache");
677
+ return join5(configRoot(), "session-cache");
360
678
  }
361
679
  function logsDir() {
362
- return join3(configRoot(), "logs");
680
+ return join5(configRoot(), "logs");
363
681
  }
364
682
 
365
683
  // src/config/config.ts
@@ -396,7 +714,9 @@ function defaultConfig() {
396
714
  },
397
715
  agents: {
398
716
  claudeCode: { enabled: false },
399
- cursor: { enabled: false }
717
+ cursor: { enabled: false },
718
+ codex: { enabled: false },
719
+ pi: { enabled: false }
400
720
  },
401
721
  privacy: {
402
722
  capturePrompts: false,
@@ -408,7 +728,7 @@ function defaultConfig() {
408
728
  }
409
729
  async function loadConfig() {
410
730
  try {
411
- const raw = await readFile3(configFile(), "utf8");
731
+ const raw = await readFile5(configFile(), "utf8");
412
732
  const parsed = JSON.parse(raw);
413
733
  return mergeConfig(defaultConfig(), parsed);
414
734
  } catch (err) {
@@ -418,13 +738,13 @@ async function loadConfig() {
418
738
  }
419
739
  async function saveConfig(cfg) {
420
740
  await ensureDirs();
421
- await writeFile3(configFile(), `${JSON.stringify(cfg, null, 2)}
741
+ await writeFile5(configFile(), `${JSON.stringify(cfg, null, 2)}
422
742
  `, "utf8");
423
743
  }
424
744
  async function ensureDirs() {
425
- await mkdir3(dirname3(configFile()), { recursive: true });
426
- await mkdir3(sessionCacheDir(), { recursive: true });
427
- await mkdir3(logsDir(), { recursive: true });
745
+ await mkdir5(dirname5(configFile()), { recursive: true });
746
+ await mkdir5(sessionCacheDir(), { recursive: true });
747
+ await mkdir5(logsDir(), { recursive: true });
428
748
  }
429
749
  function mergeConfig(base, patch) {
430
750
  return {
@@ -443,20 +763,22 @@ function mergeConfig(base, patch) {
443
763
  ...base.agents.claudeCode,
444
764
  ...patch.agents?.claudeCode ?? {}
445
765
  },
446
- cursor: { ...base.agents.cursor, ...patch.agents?.cursor ?? {} }
766
+ cursor: { ...base.agents.cursor, ...patch.agents?.cursor ?? {} },
767
+ codex: { ...base.agents.codex, ...patch.agents?.codex ?? {} },
768
+ pi: { ...base.agents.pi, ...patch.agents?.pi ?? {} }
447
769
  },
448
770
  privacy: base.privacy
449
771
  };
450
772
  }
451
773
 
452
774
  // src/transcript/store.ts
453
- import { readFile as readFile6 } from "fs/promises";
775
+ import { readFile as readFile8 } from "fs/promises";
454
776
  import { put } from "@vercel/blob";
455
777
 
456
778
  // src/auth/store.ts
457
- import { chmod, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
458
- import { dirname as dirname4 } from "path";
459
- import { mkdir as mkdir4 } from "fs/promises";
779
+ import { chmod, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
780
+ import { dirname as dirname6 } from "path";
781
+ import { mkdir as mkdir6 } from "fs/promises";
460
782
 
461
783
  // src/auth/oauth.ts
462
784
  import { createHash as createHash2, randomBytes } from "crypto";
@@ -488,7 +810,7 @@ async function refreshAccessToken(refreshToken) {
488
810
  var REFRESH_BUFFER_MS = 6e4;
489
811
  async function loadAuth() {
490
812
  try {
491
- const raw = await readFile4(authFile(), "utf8");
813
+ const raw = await readFile6(authFile(), "utf8");
492
814
  const parsed = JSON.parse(raw);
493
815
  if (typeof parsed.accessToken === "string" && parsed.accessToken.length > 0) {
494
816
  return parsed;
@@ -501,8 +823,8 @@ async function loadAuth() {
501
823
  }
502
824
  async function saveAuth(state) {
503
825
  const path = authFile();
504
- await mkdir4(dirname4(path), { recursive: true });
505
- await writeFile4(path, `${JSON.stringify(state, null, 2)}
826
+ await mkdir6(dirname6(path), { recursive: true });
827
+ await writeFile6(path, `${JSON.stringify(state, null, 2)}
506
828
  `, {
507
829
  encoding: "utf8",
508
830
  mode: 384
@@ -513,9 +835,9 @@ async function saveAuth(state) {
513
835
  }
514
836
  }
515
837
  async function clearAuth() {
516
- const { unlink: unlink2 } = await import("fs/promises");
838
+ const { unlink: unlink3 } = await import("fs/promises");
517
839
  try {
518
- await unlink2(authFile());
840
+ await unlink3(authFile());
519
841
  } catch (err) {
520
842
  if (err.code !== "ENOENT") throw err;
521
843
  }
@@ -534,7 +856,8 @@ async function getValidToken() {
534
856
  const refreshed = {
535
857
  accessToken: tokens.access_token,
536
858
  refreshToken: tokens.refresh_token ?? auth.refreshToken,
537
- expiresAt: Date.now() + tokens.expires_in * 1e3
859
+ expiresAt: Date.now() + tokens.expires_in * 1e3,
860
+ ...auth.email ? { email: auth.email } : {}
538
861
  };
539
862
  await saveAuth(refreshed);
540
863
  return refreshed.accessToken;
@@ -545,13 +868,13 @@ async function getValidToken() {
545
868
  }
546
869
 
547
870
  // src/config/bypass.ts
548
- import { chmod as chmod2, mkdir as mkdir5, readFile as readFile5, unlink, writeFile as writeFile5 } from "fs/promises";
549
- import { dirname as dirname5 } from "path";
871
+ import { chmod as chmod2, mkdir as mkdir7, readFile as readFile7, unlink as unlink2, writeFile as writeFile7 } from "fs/promises";
872
+ import { dirname as dirname7 } from "path";
550
873
  async function loadBypassSecret() {
551
874
  const fromEnv = process.env.AGENT_INSIGHTS_BYPASS_SECRET || process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
552
875
  if (fromEnv) return fromEnv;
553
876
  try {
554
- const raw = (await readFile5(bypassSecretFile(), "utf8")).trim();
877
+ const raw = (await readFile7(bypassSecretFile(), "utf8")).trim();
555
878
  return raw || void 0;
556
879
  } catch (err) {
557
880
  if (err.code === "ENOENT") return void 0;
@@ -583,7 +906,7 @@ var BlobTranscriptStore = class {
583
906
  token;
584
907
  prefix;
585
908
  async upload(input) {
586
- const body = await readFile6(input.transcriptPath);
909
+ const body = await readFile8(input.transcriptPath);
587
910
  const requestedPath = buildPathname(
588
911
  this.prefix,
589
912
  input.userHash,
@@ -614,14 +937,15 @@ var AnalyzerTranscriptStore = class {
614
937
  "transcript store: not logged in \u2014 run `agent-insights login`"
615
938
  );
616
939
  }
617
- const body = await readFile6(input.transcriptPath);
940
+ const body = await readFile8(input.transcriptPath);
618
941
  const url = new URL("/api/sessions/upload", this.analyzerUrl).toString();
619
942
  const headers = await withBypassHeader({
620
943
  authorization: `Bearer ${token}`,
621
944
  "content-type": "application/jsonl",
622
945
  "x-session-id": input.sessionId,
623
946
  "x-agent": input.agent,
624
- "x-user-hash": input.userHash
947
+ "x-user-hash": input.userHash,
948
+ ...input.userEmail ? { "x-user-email": input.userEmail } : {}
625
949
  });
626
950
  const res = await fetch(url, { method: "POST", headers, body });
627
951
  if (!res.ok) {