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/cli.js CHANGED
@@ -222,14 +222,18 @@ __export(store_exports, {
222
222
  clearAuth: () => clearAuth,
223
223
  getValidToken: () => getValidToken,
224
224
  loadAuth: () => loadAuth,
225
+ loadEmail: () => loadEmail,
225
226
  saveAuth: () => saveAuth
226
227
  });
227
- import { chmod, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
228
- import { dirname as dirname4 } from "path";
229
- import { mkdir as mkdir4 } from "fs/promises";
228
+ import { chmod, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
229
+ import { dirname as dirname6 } from "path";
230
+ import { mkdir as mkdir6 } from "fs/promises";
231
+ async function loadEmail() {
232
+ return (await loadAuth())?.email;
233
+ }
230
234
  async function loadAuth() {
231
235
  try {
232
- const raw = await readFile4(authFile(), "utf8");
236
+ const raw = await readFile6(authFile(), "utf8");
233
237
  const parsed = JSON.parse(raw);
234
238
  if (typeof parsed.accessToken === "string" && parsed.accessToken.length > 0) {
235
239
  return parsed;
@@ -242,8 +246,8 @@ async function loadAuth() {
242
246
  }
243
247
  async function saveAuth(state) {
244
248
  const path = authFile();
245
- await mkdir4(dirname4(path), { recursive: true });
246
- await writeFile4(path, `${JSON.stringify(state, null, 2)}
249
+ await mkdir6(dirname6(path), { recursive: true });
250
+ await writeFile6(path, `${JSON.stringify(state, null, 2)}
247
251
  `, {
248
252
  encoding: "utf8",
249
253
  mode: 384
@@ -254,9 +258,9 @@ async function saveAuth(state) {
254
258
  }
255
259
  }
256
260
  async function clearAuth() {
257
- const { unlink: unlink2 } = await import("fs/promises");
261
+ const { unlink: unlink3 } = await import("fs/promises");
258
262
  try {
259
- await unlink2(authFile());
263
+ await unlink3(authFile());
260
264
  } catch (err) {
261
265
  if (err.code !== "ENOENT") throw err;
262
266
  }
@@ -275,7 +279,8 @@ async function getValidToken() {
275
279
  const refreshed = {
276
280
  accessToken: tokens.access_token,
277
281
  refreshToken: tokens.refresh_token ?? auth.refreshToken,
278
- expiresAt: Date.now() + tokens.expires_in * 1e3
282
+ expiresAt: Date.now() + tokens.expires_in * 1e3,
283
+ ...auth.email ? { email: auth.email } : {}
279
284
  };
280
285
  await saveAuth(refreshed);
281
286
  return refreshed.accessToken;
@@ -346,7 +351,9 @@ function defaultConfig() {
346
351
  },
347
352
  agents: {
348
353
  claudeCode: { enabled: false },
349
- cursor: { enabled: false }
354
+ cursor: { enabled: false },
355
+ codex: { enabled: false },
356
+ pi: { enabled: false }
350
357
  },
351
358
  privacy: {
352
359
  capturePrompts: false,
@@ -393,7 +400,9 @@ function mergeConfig(base, patch) {
393
400
  ...base.agents.claudeCode,
394
401
  ...patch.agents?.claudeCode ?? {}
395
402
  },
396
- cursor: { ...base.agents.cursor, ...patch.agents?.cursor ?? {} }
403
+ cursor: { ...base.agents.cursor, ...patch.agents?.cursor ?? {} },
404
+ codex: { ...base.agents.codex, ...patch.agents?.codex ?? {} },
405
+ pi: { ...base.agents.pi, ...patch.agents?.pi ?? {} }
397
406
  },
398
407
  privacy: base.privacy
399
408
  };
@@ -658,16 +667,15 @@ var claudeCodeAdapter = {
658
667
  const path = resolveClaudePath(repoRoot, scope);
659
668
  const settings = await readJson(path);
660
669
  const next = { ...settings ?? {} };
661
- const hooks = { ...next.hooks ?? {} };
670
+ const hooks = {};
671
+ for (const [event, raw] of Object.entries(next.hooks ?? {})) {
672
+ hooks[event] = normalizeEventHooks(raw);
673
+ }
662
674
  for (const event of CLAUDE_HOOK_EVENTS) {
663
- const existing = (hooks[event] ?? []).filter(
664
- (h) => !isAgentInsightsCommand(h.command)
675
+ const withoutInsights = removeAgentInsightsMatchers(
676
+ hooks[event] ?? []
665
677
  );
666
- existing.push({
667
- type: "command",
668
- command: `${HOOK_COMMAND_NAME} hook ${event}`
669
- });
670
- hooks[event] = existing;
678
+ hooks[event] = addAgentInsightsHook(withoutInsights, event);
671
679
  }
672
680
  next.hooks = hooks;
673
681
  await mkdir2(dirname2(path), { recursive: true });
@@ -679,18 +687,16 @@ var claudeCodeAdapter = {
679
687
  const path = resolveClaudePath(repoRoot, scope);
680
688
  const settings = await readJson(path);
681
689
  if (!settings?.hooks) return { removed: false, path };
682
- const hooks = { ...settings.hooks };
690
+ const hooks = {};
683
691
  let touched = false;
684
- for (const [event, entries] of Object.entries(hooks)) {
685
- const filtered = entries.filter(
686
- (h) => !isAgentInsightsCommand(h.command)
687
- );
688
- if (filtered.length !== entries.length) touched = true;
689
- if (filtered.length === 0) {
690
- delete hooks[event];
691
- } else {
692
- hooks[event] = filtered;
692
+ for (const [event, raw] of Object.entries(settings.hooks)) {
693
+ const before = normalizeEventHooks(raw);
694
+ const after = removeAgentInsightsMatchers(before);
695
+ if (after.length !== before.length) touched = true;
696
+ else if (JSON.stringify(after) !== JSON.stringify(before)) {
697
+ touched = true;
693
698
  }
699
+ if (after.length > 0) hooks[event] = after;
694
700
  }
695
701
  settings.hooks = hooks;
696
702
  await writeFile2(path, `${JSON.stringify(settings, null, 2)}
@@ -703,7 +709,9 @@ var claudeCodeAdapter = {
703
709
  );
704
710
  if (!settings?.hooks) return false;
705
711
  return Object.values(settings.hooks).some(
706
- (entries) => entries.some((h) => isAgentInsightsCommand(h.command))
712
+ (raw) => normalizeEventHooks(raw).some(
713
+ (group) => group.hooks.some((h) => isAgentInsightsCommand(h.command))
714
+ )
707
715
  );
708
716
  }
709
717
  };
@@ -714,6 +722,59 @@ function isAgentInsightsCommand(cmd) {
714
722
  if (!cmd) return false;
715
723
  return cmd.startsWith(`${HOOK_COMMAND_NAME} hook`);
716
724
  }
725
+ function isHookCommand(value) {
726
+ if (!value || typeof value !== "object") return false;
727
+ const entry = value;
728
+ return entry.type === "command" && typeof entry.command === "string";
729
+ }
730
+ function normalizeEventHooks(raw) {
731
+ const groups = [];
732
+ for (const entry of raw) {
733
+ if (!entry || typeof entry !== "object") continue;
734
+ const record = entry;
735
+ if (Array.isArray(record.hooks)) {
736
+ const commands = record.hooks.filter(isHookCommand);
737
+ if (commands.length === 0) continue;
738
+ groups.push({
739
+ matcher: typeof record.matcher === "string" ? record.matcher : "",
740
+ hooks: commands
741
+ });
742
+ continue;
743
+ }
744
+ if (isHookCommand(record)) {
745
+ groups.push({
746
+ matcher: "",
747
+ hooks: [record]
748
+ });
749
+ }
750
+ }
751
+ return groups;
752
+ }
753
+ function removeAgentInsightsMatchers(matchers) {
754
+ const result = [];
755
+ for (const group of matchers) {
756
+ const hooks = group.hooks.filter(
757
+ (h) => !isAgentInsightsCommand(h.command)
758
+ );
759
+ if (hooks.length > 0) {
760
+ result.push({ ...group, hooks });
761
+ }
762
+ }
763
+ return result;
764
+ }
765
+ function addAgentInsightsHook(matchers, event) {
766
+ const command = `${HOOK_COMMAND_NAME} hook ${event}`;
767
+ const entry = { type: "command", command };
768
+ for (const group of matchers) {
769
+ if (group.matcher === "") {
770
+ if (!group.hooks.some((h) => h.command === command)) {
771
+ group.hooks.push(entry);
772
+ }
773
+ return matchers;
774
+ }
775
+ }
776
+ return [...matchers, { matcher: "", hooks: [entry] }];
777
+ }
717
778
  async function readJson(path) {
718
779
  try {
719
780
  const raw = await readFile2(path, "utf8");
@@ -727,12 +788,162 @@ function asString(v) {
727
788
  return typeof v === "string" && v.length > 0 ? v : void 0;
728
789
  }
729
790
 
730
- // src/adapters/cursor.ts
791
+ // src/adapters/codex.ts
731
792
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
732
793
  import { homedir as homedir3 } from "os";
733
794
  import { dirname as dirname3, join as join3 } from "path";
734
795
  var HOOK_COMMAND_NAME2 = "agent-insights";
735
796
  var HOOK_MAP2 = {
797
+ SessionStart: "session.start",
798
+ UserPromptSubmit: "user.prompt.submit",
799
+ PreToolUse: "tool.start",
800
+ PostToolUse: "tool.end",
801
+ PermissionRequest: "permission.request",
802
+ Stop: "session.end"
803
+ };
804
+ var CODEX_HOOK_EVENTS = Object.keys(HOOK_MAP2);
805
+ var codexAdapter = {
806
+ id: "codex",
807
+ agent: "codex",
808
+ label: "Codex",
809
+ settingsFile: ".codex/hooks.json",
810
+ map(payload) {
811
+ const type = HOOK_MAP2[payload.event];
812
+ if (!type) return void 0;
813
+ const data = payload.data ?? {};
814
+ const sessionId = asString2(data["session_id"]) ?? asString2(data["sessionId"]);
815
+ const toolName = asString2(data["tool_name"]) ?? asString2(data["toolName"]) ?? asString2(data["tool"]?.["name"]);
816
+ const transcriptPath = asString2(data["transcript_path"]) ?? asString2(data["transcriptPath"]);
817
+ return {
818
+ type,
819
+ ...sessionId !== void 0 ? { sessionId } : {},
820
+ ...toolName !== void 0 ? { toolName } : {},
821
+ ...transcriptPath !== void 0 ? { transcriptPath } : {}
822
+ };
823
+ },
824
+ async install(repoRoot, scope = "global") {
825
+ const path = resolveCodexPath(repoRoot, scope);
826
+ const settings = await readJson2(path);
827
+ const next = { ...settings ?? {} };
828
+ const hooks = {};
829
+ for (const [event, raw] of Object.entries(next.hooks ?? {})) {
830
+ hooks[event] = normalizeEventHooks2(raw);
831
+ }
832
+ for (const event of CODEX_HOOK_EVENTS) {
833
+ const withoutInsights = removeAgentInsightsMatchers2(hooks[event] ?? []);
834
+ hooks[event] = addAgentInsightsHook2(withoutInsights, event);
835
+ }
836
+ next.hooks = hooks;
837
+ await mkdir3(dirname3(path), { recursive: true });
838
+ await writeFile3(path, `${JSON.stringify(next, null, 2)}
839
+ `, "utf8");
840
+ return { written: true, path };
841
+ },
842
+ async uninstall(repoRoot, scope = "global") {
843
+ const path = resolveCodexPath(repoRoot, scope);
844
+ const settings = await readJson2(path);
845
+ if (!settings?.hooks) return { removed: false, path };
846
+ const hooks = {};
847
+ let touched = false;
848
+ for (const [event, raw] of Object.entries(settings.hooks)) {
849
+ const before = normalizeEventHooks2(raw);
850
+ const after = removeAgentInsightsMatchers2(before);
851
+ if (after.length !== before.length || JSON.stringify(after) !== JSON.stringify(before)) {
852
+ touched = true;
853
+ }
854
+ if (after.length > 0) hooks[event] = after;
855
+ }
856
+ settings.hooks = hooks;
857
+ await writeFile3(path, `${JSON.stringify(settings, null, 2)}
858
+ `, "utf8");
859
+ return { removed: touched, path };
860
+ },
861
+ async isInstalled(repoRoot, scope = "global") {
862
+ const settings = await readJson2(
863
+ resolveCodexPath(repoRoot, scope)
864
+ );
865
+ if (!settings?.hooks) return false;
866
+ return Object.values(settings.hooks).some(
867
+ (raw) => normalizeEventHooks2(raw).some(
868
+ (group) => group.hooks.some((h) => isAgentInsightsCommand2(h.command))
869
+ )
870
+ );
871
+ }
872
+ };
873
+ function resolveCodexPath(repoRoot, scope) {
874
+ return scope === "global" ? join3(homedir3(), ".codex", "hooks.json") : join3(repoRoot, ".codex", "hooks.json");
875
+ }
876
+ function isAgentInsightsCommand2(cmd) {
877
+ if (!cmd) return false;
878
+ return cmd.startsWith(`${HOOK_COMMAND_NAME2} hook`);
879
+ }
880
+ function isHookCommand2(value) {
881
+ if (!value || typeof value !== "object") return false;
882
+ const entry = value;
883
+ return entry.type === "command" && typeof entry.command === "string";
884
+ }
885
+ function normalizeEventHooks2(raw) {
886
+ const groups = [];
887
+ for (const entry of raw) {
888
+ if (!entry || typeof entry !== "object") continue;
889
+ const record = entry;
890
+ if (Array.isArray(record.hooks)) {
891
+ const commands = record.hooks.filter(isHookCommand2);
892
+ if (commands.length === 0) continue;
893
+ groups.push({
894
+ matcher: typeof record.matcher === "string" ? record.matcher : "",
895
+ hooks: commands
896
+ });
897
+ continue;
898
+ }
899
+ if (isHookCommand2(record)) {
900
+ groups.push({ matcher: "", hooks: [record] });
901
+ }
902
+ }
903
+ return groups;
904
+ }
905
+ function removeAgentInsightsMatchers2(matchers) {
906
+ const result = [];
907
+ for (const group of matchers) {
908
+ const hooks = group.hooks.filter(
909
+ (h) => !isAgentInsightsCommand2(h.command)
910
+ );
911
+ if (hooks.length > 0) result.push({ ...group, hooks });
912
+ }
913
+ return result;
914
+ }
915
+ function addAgentInsightsHook2(matchers, event) {
916
+ const command = `${HOOK_COMMAND_NAME2} hook ${event}`;
917
+ const entry = { type: "command", command };
918
+ for (const group of matchers) {
919
+ if (group.matcher === "") {
920
+ if (!group.hooks.some((h) => h.command === command)) {
921
+ group.hooks.push(entry);
922
+ }
923
+ return matchers;
924
+ }
925
+ }
926
+ return [...matchers, { matcher: "", hooks: [entry] }];
927
+ }
928
+ async function readJson2(path) {
929
+ try {
930
+ const raw = await readFile3(path, "utf8");
931
+ return JSON.parse(raw);
932
+ } catch (err) {
933
+ if (err.code === "ENOENT") return void 0;
934
+ throw err;
935
+ }
936
+ }
937
+ function asString2(v) {
938
+ return typeof v === "string" && v.length > 0 ? v : void 0;
939
+ }
940
+
941
+ // src/adapters/cursor.ts
942
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
943
+ import { homedir as homedir4 } from "os";
944
+ import { dirname as dirname4, join as join4 } from "path";
945
+ var HOOK_COMMAND_NAME3 = "agent-insights";
946
+ var HOOK_MAP3 = {
736
947
  sessionStart: "session.start",
737
948
  sessionEnd: "session.end",
738
949
  beforeSubmitPrompt: "user.prompt.submit",
@@ -749,19 +960,19 @@ var HOOK_MAP2 = {
749
960
  afterFileEdit: "tool.end",
750
961
  workspaceOpen: "session.start"
751
962
  };
752
- var CURSOR_HOOK_EVENTS = Object.keys(HOOK_MAP2);
963
+ var CURSOR_HOOK_EVENTS = Object.keys(HOOK_MAP3);
753
964
  var cursorAdapter = {
754
965
  id: "cursor",
755
966
  agent: "cursor",
756
967
  label: "Cursor",
757
968
  settingsFile: ".cursor/hooks.json",
758
969
  map(payload) {
759
- const type = HOOK_MAP2[payload.event];
970
+ const type = HOOK_MAP3[payload.event];
760
971
  if (!type) return void 0;
761
972
  const data = payload.data ?? {};
762
- const sessionId = asString2(data["session_id"]) ?? asString2(data["sessionId"]) ?? asString2(data["conversation_id"]);
763
- const toolName = asString2(data["tool_name"]) ?? asString2(data["toolName"]);
764
- const transcriptPath = asString2(data["transcript_path"]) ?? asString2(data["transcriptPath"]) ?? asString2(process.env.CURSOR_TRANSCRIPT_PATH);
973
+ const sessionId = asString3(data["session_id"]) ?? asString3(data["sessionId"]) ?? asString3(data["conversation_id"]);
974
+ const toolName = asString3(data["tool_name"]) ?? asString3(data["toolName"]);
975
+ const transcriptPath = asString3(data["transcript_path"]) ?? asString3(data["transcriptPath"]) ?? asString3(process.env.CURSOR_TRANSCRIPT_PATH);
765
976
  return {
766
977
  type,
767
978
  ...sessionId !== void 0 ? { sessionId } : {},
@@ -771,7 +982,7 @@ var cursorAdapter = {
771
982
  },
772
983
  async install(repoRoot, scope = "global") {
773
984
  const path = resolveCursorPath(repoRoot, scope);
774
- const settings = await readJson2(path);
985
+ const settings = await readJson3(path);
775
986
  const next = {
776
987
  version: 1,
777
988
  ...settings ?? {}
@@ -779,26 +990,26 @@ var cursorAdapter = {
779
990
  const hooks = { ...next.hooks ?? {} };
780
991
  for (const event of CURSOR_HOOK_EVENTS) {
781
992
  const existing = (hooks[event] ?? []).filter(
782
- (h) => !isAgentInsightsCommand2(h.command)
993
+ (h) => !isAgentInsightsCommand3(h.command)
783
994
  );
784
- existing.push({ command: `${HOOK_COMMAND_NAME2} hook ${event}` });
995
+ existing.push({ command: `${HOOK_COMMAND_NAME3} hook ${event}` });
785
996
  hooks[event] = existing;
786
997
  }
787
998
  next.hooks = hooks;
788
- await mkdir3(dirname3(path), { recursive: true });
789
- await writeFile3(path, `${JSON.stringify(next, null, 2)}
999
+ await mkdir4(dirname4(path), { recursive: true });
1000
+ await writeFile4(path, `${JSON.stringify(next, null, 2)}
790
1001
  `, "utf8");
791
1002
  return { written: true, path };
792
1003
  },
793
1004
  async uninstall(repoRoot, scope = "global") {
794
1005
  const path = resolveCursorPath(repoRoot, scope);
795
- const settings = await readJson2(path);
1006
+ const settings = await readJson3(path);
796
1007
  if (!settings?.hooks) return { removed: false, path };
797
1008
  const hooks = { ...settings.hooks };
798
1009
  let touched = false;
799
1010
  for (const [event, entries] of Object.entries(hooks)) {
800
1011
  const filtered = entries.filter(
801
- (h) => !isAgentInsightsCommand2(h.command)
1012
+ (h) => !isAgentInsightsCommand3(h.command)
802
1013
  );
803
1014
  if (filtered.length !== entries.length) touched = true;
804
1015
  if (filtered.length === 0) {
@@ -808,44 +1019,160 @@ var cursorAdapter = {
808
1019
  }
809
1020
  }
810
1021
  settings.hooks = hooks;
811
- await writeFile3(path, `${JSON.stringify(settings, null, 2)}
1022
+ await writeFile4(path, `${JSON.stringify(settings, null, 2)}
812
1023
  `, "utf8");
813
1024
  return { removed: touched, path };
814
1025
  },
815
1026
  async isInstalled(repoRoot, scope = "global") {
816
- const settings = await readJson2(
1027
+ const settings = await readJson3(
817
1028
  resolveCursorPath(repoRoot, scope)
818
1029
  );
819
1030
  if (!settings?.hooks) return false;
820
1031
  return Object.values(settings.hooks).some(
821
- (entries) => entries.some((h) => isAgentInsightsCommand2(h.command))
1032
+ (entries) => entries.some((h) => isAgentInsightsCommand3(h.command))
822
1033
  );
823
1034
  }
824
1035
  };
825
1036
  function resolveCursorPath(repoRoot, scope) {
826
- return scope === "global" ? join3(homedir3(), ".cursor", "hooks.json") : join3(repoRoot, ".cursor", "hooks.json");
1037
+ return scope === "global" ? join4(homedir4(), ".cursor", "hooks.json") : join4(repoRoot, ".cursor", "hooks.json");
827
1038
  }
828
- function isAgentInsightsCommand2(cmd) {
1039
+ function isAgentInsightsCommand3(cmd) {
829
1040
  if (!cmd) return false;
830
- return cmd.startsWith(`${HOOK_COMMAND_NAME2} hook`);
1041
+ return cmd.startsWith(`${HOOK_COMMAND_NAME3} hook`);
831
1042
  }
832
- async function readJson2(path) {
1043
+ async function readJson3(path) {
833
1044
  try {
834
- const raw = await readFile3(path, "utf8");
1045
+ const raw = await readFile4(path, "utf8");
835
1046
  return JSON.parse(raw);
836
1047
  } catch (err) {
837
1048
  if (err.code === "ENOENT") return void 0;
838
1049
  throw err;
839
1050
  }
840
1051
  }
841
- function asString2(v) {
1052
+ function asString3(v) {
1053
+ return typeof v === "string" && v.length > 0 ? v : void 0;
1054
+ }
1055
+
1056
+ // src/adapters/pi.ts
1057
+ import { mkdir as mkdir5, readFile as readFile5, unlink, writeFile as writeFile5 } from "fs/promises";
1058
+ import { homedir as homedir5 } from "os";
1059
+ import { dirname as dirname5, join as join5 } from "path";
1060
+ var EXTENSION_FILENAME = "agent-insights.ts";
1061
+ var HOOK_MAP4 = {
1062
+ session_start: "session.start",
1063
+ session_shutdown: "session.end",
1064
+ tool_call: "tool.start",
1065
+ tool_result: "tool.end"
1066
+ };
1067
+ var PI_HOOK_EVENTS = Object.keys(HOOK_MAP4);
1068
+ var EXTENSION_MARKER = "// installed-by: agent-insights";
1069
+ function extensionSource() {
1070
+ return `${EXTENSION_MARKER}
1071
+ // Generated by \`agent-insights init\`. Edit-with-caution: it's overwritten on every install.
1072
+
1073
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
1074
+ import { spawn } from "node:child_process";
1075
+
1076
+ const EVENTS = ${JSON.stringify(PI_HOOK_EVENTS)} as const;
1077
+
1078
+ function emit(event: string, payload: Record<string, unknown>): void {
1079
+ try {
1080
+ const child = spawn("agent-insights", ["hook", event], {
1081
+ stdio: ["pipe", "ignore", "ignore"],
1082
+ detached: true,
1083
+ });
1084
+ child.on("error", () => {});
1085
+ child.stdin.end(JSON.stringify(payload));
1086
+ child.unref();
1087
+ } catch {
1088
+ // best-effort
1089
+ }
1090
+ }
1091
+
1092
+ export default function (pi: ExtensionAPI) {
1093
+ for (const event of EVENTS) {
1094
+ pi.on(event as never, async (raw: unknown, ctx: { sessionManager?: { getSessionFile?: () => string | undefined; getSessionId?: () => string | undefined } }) => {
1095
+ const transcript_path = ctx.sessionManager?.getSessionFile?.();
1096
+ const session_id = ctx.sessionManager?.getSessionId?.();
1097
+ const e = (raw ?? {}) as Record<string, unknown>;
1098
+ emit(event, {
1099
+ ...(session_id ? { session_id } : {}),
1100
+ ...(transcript_path ? { transcript_path } : {}),
1101
+ ...("toolName" in e && typeof e.toolName === "string"
1102
+ ? { tool_name: e.toolName }
1103
+ : {}),
1104
+ ...("reason" in e ? { reason: e.reason } : {}),
1105
+ });
1106
+ });
1107
+ }
1108
+ }
1109
+ `;
1110
+ }
1111
+ var piAdapter = {
1112
+ id: "pi",
1113
+ agent: "pi",
1114
+ label: "Pi",
1115
+ settingsFile: ".pi/extensions/agent-insights.ts",
1116
+ map(payload) {
1117
+ const type = HOOK_MAP4[payload.event];
1118
+ if (!type) return void 0;
1119
+ const data = payload.data ?? {};
1120
+ const sessionId = asString4(data["session_id"]) ?? asString4(data["sessionId"]);
1121
+ const toolName = asString4(data["tool_name"]) ?? asString4(data["toolName"]);
1122
+ const transcriptPath = asString4(data["transcript_path"]) ?? asString4(data["transcriptPath"]);
1123
+ return {
1124
+ type,
1125
+ ...sessionId !== void 0 ? { sessionId } : {},
1126
+ ...toolName !== void 0 ? { toolName } : {},
1127
+ ...transcriptPath !== void 0 ? { transcriptPath } : {}
1128
+ };
1129
+ },
1130
+ async install(repoRoot, scope = "global") {
1131
+ const path = resolvePath(repoRoot, scope);
1132
+ await mkdir5(dirname5(path), { recursive: true });
1133
+ await writeFile5(path, extensionSource(), "utf8");
1134
+ return { written: true, path };
1135
+ },
1136
+ async uninstall(repoRoot, scope = "global") {
1137
+ const path = resolvePath(repoRoot, scope);
1138
+ try {
1139
+ const raw = await readFile5(path, "utf8");
1140
+ if (!raw.includes(EXTENSION_MARKER)) {
1141
+ return { removed: false, path };
1142
+ }
1143
+ await unlink(path);
1144
+ return { removed: true, path };
1145
+ } catch (err) {
1146
+ if (err.code === "ENOENT") {
1147
+ return { removed: false, path };
1148
+ }
1149
+ throw err;
1150
+ }
1151
+ },
1152
+ async isInstalled(repoRoot, scope = "global") {
1153
+ const path = resolvePath(repoRoot, scope);
1154
+ try {
1155
+ const raw = await readFile5(path, "utf8");
1156
+ return raw.includes(EXTENSION_MARKER);
1157
+ } catch (err) {
1158
+ if (err.code === "ENOENT") return false;
1159
+ throw err;
1160
+ }
1161
+ }
1162
+ };
1163
+ function resolvePath(repoRoot, scope) {
1164
+ return scope === "global" ? join5(homedir5(), ".pi", "agent", "extensions", EXTENSION_FILENAME) : join5(repoRoot, ".pi", "extensions", EXTENSION_FILENAME);
1165
+ }
1166
+ function asString4(v) {
842
1167
  return typeof v === "string" && v.length > 0 ? v : void 0;
843
1168
  }
844
1169
 
845
1170
  // src/adapters/registry.ts
846
1171
  var adapters = {
847
1172
  "claude-code": claudeCodeAdapter,
848
- cursor: cursorAdapter
1173
+ cursor: cursorAdapter,
1174
+ codex: codexAdapter,
1175
+ pi: piAdapter
849
1176
  };
850
1177
  var adapterList = Object.values(adapters);
851
1178
 
@@ -889,18 +1216,18 @@ init_store();
889
1216
 
890
1217
  // src/transcript/store.ts
891
1218
  init_store();
892
- import { readFile as readFile6 } from "fs/promises";
1219
+ import { readFile as readFile8 } from "fs/promises";
893
1220
  import { put } from "@vercel/blob";
894
1221
 
895
1222
  // src/config/bypass.ts
896
1223
  init_paths();
897
- import { chmod as chmod2, mkdir as mkdir5, readFile as readFile5, unlink, writeFile as writeFile5 } from "fs/promises";
898
- import { dirname as dirname5 } from "path";
1224
+ import { chmod as chmod2, mkdir as mkdir7, readFile as readFile7, unlink as unlink2, writeFile as writeFile7 } from "fs/promises";
1225
+ import { dirname as dirname7 } from "path";
899
1226
  async function loadBypassSecret() {
900
1227
  const fromEnv = process.env.AGENT_INSIGHTS_BYPASS_SECRET || process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
901
1228
  if (fromEnv) return fromEnv;
902
1229
  try {
903
- const raw = (await readFile5(bypassSecretFile(), "utf8")).trim();
1230
+ const raw = (await readFile7(bypassSecretFile(), "utf8")).trim();
904
1231
  return raw || void 0;
905
1232
  } catch (err) {
906
1233
  if (err.code === "ENOENT") return void 0;
@@ -909,8 +1236,8 @@ async function loadBypassSecret() {
909
1236
  }
910
1237
  async function saveBypassSecret(secret) {
911
1238
  const path = bypassSecretFile();
912
- await mkdir5(dirname5(path), { recursive: true });
913
- await writeFile5(path, secret.trim(), { encoding: "utf8", mode: 384 });
1239
+ await mkdir7(dirname7(path), { recursive: true });
1240
+ await writeFile7(path, secret.trim(), { encoding: "utf8", mode: 384 });
914
1241
  try {
915
1242
  await chmod2(path, 384);
916
1243
  } catch {
@@ -918,7 +1245,7 @@ async function saveBypassSecret(secret) {
918
1245
  }
919
1246
  async function clearBypassSecret() {
920
1247
  try {
921
- await unlink(bypassSecretFile());
1248
+ await unlink2(bypassSecretFile());
922
1249
  } catch (err) {
923
1250
  if (err.code !== "ENOENT") throw err;
924
1251
  }
@@ -948,7 +1275,7 @@ var BlobTranscriptStore = class {
948
1275
  token;
949
1276
  prefix;
950
1277
  async upload(input) {
951
- const body = await readFile6(input.transcriptPath);
1278
+ const body = await readFile8(input.transcriptPath);
952
1279
  const requestedPath = buildPathname(
953
1280
  this.prefix,
954
1281
  input.userHash,
@@ -979,14 +1306,15 @@ var AnalyzerTranscriptStore = class {
979
1306
  "transcript store: not logged in \u2014 run `agent-insights login`"
980
1307
  );
981
1308
  }
982
- const body = await readFile6(input.transcriptPath);
1309
+ const body = await readFile8(input.transcriptPath);
983
1310
  const url = new URL("/api/sessions/upload", this.analyzerUrl).toString();
984
1311
  const headers = await withBypassHeader({
985
1312
  authorization: `Bearer ${token}`,
986
1313
  "content-type": "application/jsonl",
987
1314
  "x-session-id": input.sessionId,
988
1315
  "x-agent": input.agent,
989
- "x-user-hash": input.userHash
1316
+ "x-user-hash": input.userHash,
1317
+ ...input.userEmail ? { "x-user-email": input.userEmail } : {}
990
1318
  });
991
1319
  const res = await fetch(url, { method: "POST", headers, body });
992
1320
  if (!res.ok) {
@@ -1097,7 +1425,7 @@ async function runDoctor(opts) {
1097
1425
  }
1098
1426
  }
1099
1427
  for (const adapter of adapterList) {
1100
- const enabled = adapter.id === "claude-code" ? cfg.agents.claudeCode.enabled : cfg.agents.cursor.enabled;
1428
+ const enabled = adapter.id === "claude-code" ? cfg.agents.claudeCode.enabled : adapter.id === "cursor" ? cfg.agents.cursor.enabled : adapter.id === "codex" ? cfg.agents.codex.enabled : cfg.agents.pi.enabled;
1101
1429
  if (!enabled) continue;
1102
1430
  const installed = await adapter.isInstalled(repoRoot, cfg.hooksScope);
1103
1431
  checks.push({
@@ -1120,6 +1448,7 @@ async function runDoctor(opts) {
1120
1448
  }
1121
1449
 
1122
1450
  // src/commands/hook.ts
1451
+ init_store();
1123
1452
  async function runHook(eventName, opts) {
1124
1453
  const cfg = await loadConfig();
1125
1454
  if (!cfg || !cfg.enabled) {
@@ -1148,11 +1477,13 @@ async function runHook(eventName, opts) {
1148
1477
  if (mapped.type === "session.end" && cfg.userConsent.transcriptSync && mapped.transcriptPath) {
1149
1478
  try {
1150
1479
  const store = createTranscriptStore(cfg.transcriptStore);
1480
+ const userEmail = await loadEmail();
1151
1481
  const result = await store.upload({
1152
1482
  transcriptPath: mapped.transcriptPath,
1153
1483
  userHash: event.userHash ?? userHash(),
1154
1484
  sessionId: mapped.sessionId ?? "unknown",
1155
- agent: adapter.id
1485
+ agent: adapter.id,
1486
+ ...userEmail ? { userEmail } : {}
1156
1487
  });
1157
1488
  debug("transcript uploaded", {
1158
1489
  size: result.size,
@@ -1340,6 +1671,8 @@ async function runInit(opts) {
1340
1671
  cfg.userConsent.eventTelemetry = choices.telemetry;
1341
1672
  cfg.agents.claudeCode.enabled = choices.agents.has("claude-code");
1342
1673
  cfg.agents.cursor.enabled = choices.agents.has("cursor");
1674
+ cfg.agents.codex.enabled = choices.agents.has("codex");
1675
+ cfg.agents.pi.enabled = choices.agents.has("pi");
1343
1676
  await saveConfig(cfg);
1344
1677
  log.ok(`Wrote config to ${configFile()}`);
1345
1678
  if (choices.bypassSecret !== void 0) {
@@ -1355,8 +1688,7 @@ async function runInit(opts) {
1355
1688
  const repoRoot = process.cwd();
1356
1689
  if (installHooks) {
1357
1690
  for (const adapter of adapterList) {
1358
- const enabled = adapter.id === "claude-code" && cfg.agents.claudeCode.enabled || adapter.id === "cursor" && cfg.agents.cursor.enabled;
1359
- if (!enabled) continue;
1691
+ if (!isAdapterEnabled(cfg, adapter.id)) continue;
1360
1692
  const { path } = await adapter.install(repoRoot, cfg.hooksScope);
1361
1693
  log.ok(`Installed ${adapter.label} hooks \u2192 ${rel(path)}`);
1362
1694
  }
@@ -1436,6 +1768,9 @@ async function safePrompt(run) {
1436
1768
  throw err;
1437
1769
  }
1438
1770
  }
1771
+ var KNOWN_AGENT_IDS = new Set(
1772
+ adapterList.map((a) => a.id)
1773
+ );
1439
1774
  function parseAgents(value) {
1440
1775
  const out = /* @__PURE__ */ new Set();
1441
1776
  const trimmed = (value ?? "").trim();
@@ -1443,9 +1778,9 @@ function parseAgents(value) {
1443
1778
  for (const a of adapterList) out.add(a.id);
1444
1779
  return out;
1445
1780
  }
1446
- const list = (trimmed || "claude-code,cursor").split(",").map((s) => s.trim()).filter(Boolean);
1781
+ const list = (trimmed || adapterList.map((a) => a.id).join(",")).split(",").map((s) => s.trim()).filter(Boolean);
1447
1782
  for (const id of list) {
1448
- if (id === "claude-code" || id === "cursor") out.add(id);
1783
+ if (KNOWN_AGENT_IDS.has(id)) out.add(id);
1449
1784
  else log.warn(`Unknown agent id "${id}" \u2014 skipping.`);
1450
1785
  }
1451
1786
  return out;
@@ -1454,6 +1789,18 @@ function rel(p) {
1454
1789
  const cwd = process.cwd();
1455
1790
  return p.startsWith(cwd) ? p.slice(cwd.length + 1) : p;
1456
1791
  }
1792
+ function isAdapterEnabled(cfg, id) {
1793
+ switch (id) {
1794
+ case "claude-code":
1795
+ return cfg.agents.claudeCode.enabled;
1796
+ case "cursor":
1797
+ return cfg.agents.cursor.enabled;
1798
+ case "codex":
1799
+ return cfg.agents.codex.enabled;
1800
+ case "pi":
1801
+ return cfg.agents.pi.enabled;
1802
+ }
1803
+ }
1457
1804
 
1458
1805
  // src/commands/login.ts
1459
1806
  init_oauth();
@@ -1493,7 +1840,8 @@ async function runLogin() {
1493
1840
  await saveAuth({
1494
1841
  accessToken: tokens.access_token,
1495
1842
  ...tokens.refresh_token !== void 0 ? { refreshToken: tokens.refresh_token } : {},
1496
- expiresAt: Date.now() + tokens.expires_in * 1e3
1843
+ expiresAt: Date.now() + tokens.expires_in * 1e3,
1844
+ email: userInfo2.email
1497
1845
  });
1498
1846
  const name = userInfo2.preferred_username ?? userInfo2.email;
1499
1847
  log.ok(`Logged in as ${kleur4.bold(name)} (${userInfo2.email})`);
@@ -1506,7 +1854,7 @@ async function runLogout() {
1506
1854
 
1507
1855
  // src/commands/status.ts
1508
1856
  import { existsSync } from "fs";
1509
- import { join as join4 } from "path";
1857
+ import { join as join6 } from "path";
1510
1858
  import kleur5 from "kleur";
1511
1859
  init_paths();
1512
1860
  async function runStatus(opts) {
@@ -1532,8 +1880,8 @@ async function runStatus(opts) {
1532
1880
  enabled: cfg.enabled,
1533
1881
  consent: cfg.userConsent,
1534
1882
  vercel: {
1535
- linked: existsSync(join4(repoRoot, ".vercel/project.json")),
1536
- envFile: existsSync(join4(repoRoot, ".env.local"))
1883
+ linked: existsSync(join6(repoRoot, ".vercel/project.json")),
1884
+ envFile: existsSync(join6(repoRoot, ".env.local"))
1537
1885
  },
1538
1886
  adapters: adapterStatus
1539
1887
  });
@@ -1557,9 +1905,9 @@ async function runStatus(opts) {
1557
1905
  log.info("");
1558
1906
  log.info(kleur5.bold("Vercel"));
1559
1907
  log.info(
1560
- ` linked ${yn(existsSync(join4(repoRoot, ".vercel/project.json")))}`
1908
+ ` linked ${yn(existsSync(join6(repoRoot, ".vercel/project.json")))}`
1561
1909
  );
1562
- log.info(` .env.local ${yn(existsSync(join4(repoRoot, ".env.local")))}`);
1910
+ log.info(` .env.local ${yn(existsSync(join6(repoRoot, ".env.local")))}`);
1563
1911
  }
1564
1912
  function yn(v) {
1565
1913
  return v ? kleur5.green("yes") : kleur5.dim("no");
@@ -1569,7 +1917,7 @@ function yn(v) {
1569
1917
  var program = new Command();
1570
1918
  program.name("agent-insights").description(
1571
1919
  "Internal CLI for AI coding agent observability (Claude Code, Cursor)."
1572
- ).version("0.0.12");
1920
+ ).version("0.0.16");
1573
1921
  program.command("login").description("Authenticate with Vercel (Sign in with Vercel).").action(async () => {
1574
1922
  await runLogin();
1575
1923
  });