agent-insights 0.0.13 → 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/cli.js CHANGED
@@ -74,7 +74,7 @@ If the browser did not open, visit:
74
74
  return { code, codeVerifier };
75
75
  }
76
76
  function listenForCallback(expectedState, timeoutMs) {
77
- return new Promise((resolve, reject) => {
77
+ return new Promise((resolve2, reject) => {
78
78
  const server = createServer((req, res) => {
79
79
  const url = new URL(req.url ?? "/", `http://localhost:${CALLBACK_PORT}`);
80
80
  if (url.pathname !== "/callback") {
@@ -121,7 +121,7 @@ function listenForCallback(expectedState, timeoutMs) {
121
121
  return;
122
122
  }
123
123
  finish(
124
- () => resolve(code),
124
+ () => resolve2(code),
125
125
  html("\u2713 Logged in! You can close this tab."),
126
126
  200
127
127
  );
@@ -225,15 +225,15 @@ __export(store_exports, {
225
225
  loadEmail: () => loadEmail,
226
226
  saveAuth: () => saveAuth
227
227
  });
228
- import { chmod, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
229
- import { dirname as dirname4 } from "path";
230
- 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
231
  async function loadEmail() {
232
232
  return (await loadAuth())?.email;
233
233
  }
234
234
  async function loadAuth() {
235
235
  try {
236
- const raw = await readFile4(authFile(), "utf8");
236
+ const raw = await readFile6(authFile(), "utf8");
237
237
  const parsed = JSON.parse(raw);
238
238
  if (typeof parsed.accessToken === "string" && parsed.accessToken.length > 0) {
239
239
  return parsed;
@@ -246,8 +246,8 @@ async function loadAuth() {
246
246
  }
247
247
  async function saveAuth(state) {
248
248
  const path = authFile();
249
- await mkdir4(dirname4(path), { recursive: true });
250
- await writeFile4(path, `${JSON.stringify(state, null, 2)}
249
+ await mkdir6(dirname6(path), { recursive: true });
250
+ await writeFile6(path, `${JSON.stringify(state, null, 2)}
251
251
  `, {
252
252
  encoding: "utf8",
253
253
  mode: 384
@@ -258,9 +258,9 @@ async function saveAuth(state) {
258
258
  }
259
259
  }
260
260
  async function clearAuth() {
261
- const { unlink: unlink2 } = await import("fs/promises");
261
+ const { unlink: unlink3 } = await import("fs/promises");
262
262
  try {
263
- await unlink2(authFile());
263
+ await unlink3(authFile());
264
264
  } catch (err) {
265
265
  if (err.code !== "ENOENT") throw err;
266
266
  }
@@ -351,7 +351,9 @@ function defaultConfig() {
351
351
  },
352
352
  agents: {
353
353
  claudeCode: { enabled: false },
354
- cursor: { enabled: false }
354
+ cursor: { enabled: false },
355
+ codex: { enabled: false },
356
+ pi: { enabled: false }
355
357
  },
356
358
  privacy: {
357
359
  capturePrompts: false,
@@ -398,7 +400,9 @@ function mergeConfig(base, patch) {
398
400
  ...base.agents.claudeCode,
399
401
  ...patch.agents?.claudeCode ?? {}
400
402
  },
401
- 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 ?? {} }
402
406
  },
403
407
  privacy: base.privacy
404
408
  };
@@ -595,16 +599,16 @@ async function runCursorWrapper(opts) {
595
599
  process.exitCode = code;
596
600
  }
597
601
  function spawnCursor(path) {
598
- return new Promise((resolve) => {
602
+ return new Promise((resolve2) => {
599
603
  const child = spawn("cursor", [path], {
600
604
  stdio: "inherit",
601
605
  env: process.env
602
606
  });
603
607
  child.on("error", (err) => {
604
608
  log.fail(`failed to launch cursor: ${err.message}`);
605
- resolve(127);
609
+ resolve2(127);
606
610
  });
607
- child.on("exit", (code) => resolve(code ?? 0));
611
+ child.on("exit", (code) => resolve2(code ?? 0));
608
612
  });
609
613
  }
610
614
  function emit(event) {
@@ -622,6 +626,33 @@ import { rm } from "fs/promises";
622
626
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
623
627
  import { homedir as homedir2 } from "os";
624
628
  import { dirname as dirname2, join as join2 } from "path";
629
+
630
+ // src/util/hook-command.ts
631
+ import { realpathSync } from "fs";
632
+ function resolve() {
633
+ const node = process.execPath;
634
+ let script = process.argv[1] ?? "";
635
+ try {
636
+ script = realpathSync(script);
637
+ } catch {
638
+ }
639
+ return { node, script };
640
+ }
641
+ function quote(s) {
642
+ if (s.length === 0) return "''";
643
+ if (!/[\s'"\\$`<>|&;()*?#~]/.test(s)) return s;
644
+ return `'${s.replace(/'/g, "'\\''")}'`;
645
+ }
646
+ function hookCommandString(event) {
647
+ const { node, script } = resolve();
648
+ return `${quote(node)} ${quote(script)} hook ${event}`;
649
+ }
650
+ function hookCommandArgv(event) {
651
+ const { node, script } = resolve();
652
+ return { node, script, args: ["hook", event] };
653
+ }
654
+
655
+ // src/adapters/claude-code.ts
625
656
  var HOOK_COMMAND_NAME = "agent-insights";
626
657
  var HOOK_MAP = {
627
658
  SessionStart: "session.start",
@@ -716,7 +747,8 @@ function resolveClaudePath(repoRoot, scope) {
716
747
  }
717
748
  function isAgentInsightsCommand(cmd) {
718
749
  if (!cmd) return false;
719
- return cmd.startsWith(`${HOOK_COMMAND_NAME} hook`);
750
+ if (cmd.startsWith(`${HOOK_COMMAND_NAME} hook`)) return true;
751
+ return /\bagent-insights\b.*\bcli\.js\b.*\bhook\b/.test(cmd);
720
752
  }
721
753
  function isHookCommand(value) {
722
754
  if (!value || typeof value !== "object") return false;
@@ -759,7 +791,7 @@ function removeAgentInsightsMatchers(matchers) {
759
791
  return result;
760
792
  }
761
793
  function addAgentInsightsHook(matchers, event) {
762
- const command = `${HOOK_COMMAND_NAME} hook ${event}`;
794
+ const command = hookCommandString(event);
763
795
  const entry = { type: "command", command };
764
796
  for (const group of matchers) {
765
797
  if (group.matcher === "") {
@@ -784,12 +816,163 @@ function asString(v) {
784
816
  return typeof v === "string" && v.length > 0 ? v : void 0;
785
817
  }
786
818
 
787
- // src/adapters/cursor.ts
819
+ // src/adapters/codex.ts
788
820
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
789
821
  import { homedir as homedir3 } from "os";
790
822
  import { dirname as dirname3, join as join3 } from "path";
791
823
  var HOOK_COMMAND_NAME2 = "agent-insights";
792
824
  var HOOK_MAP2 = {
825
+ SessionStart: "session.start",
826
+ UserPromptSubmit: "user.prompt.submit",
827
+ PreToolUse: "tool.start",
828
+ PostToolUse: "tool.end",
829
+ PermissionRequest: "permission.request",
830
+ Stop: "session.end"
831
+ };
832
+ var CODEX_HOOK_EVENTS = Object.keys(HOOK_MAP2);
833
+ var codexAdapter = {
834
+ id: "codex",
835
+ agent: "codex",
836
+ label: "Codex",
837
+ settingsFile: ".codex/hooks.json",
838
+ map(payload) {
839
+ const type = HOOK_MAP2[payload.event];
840
+ if (!type) return void 0;
841
+ const data = payload.data ?? {};
842
+ const sessionId = asString2(data["session_id"]) ?? asString2(data["sessionId"]);
843
+ const toolName = asString2(data["tool_name"]) ?? asString2(data["toolName"]) ?? asString2(data["tool"]?.["name"]);
844
+ const transcriptPath = asString2(data["transcript_path"]) ?? asString2(data["transcriptPath"]);
845
+ return {
846
+ type,
847
+ ...sessionId !== void 0 ? { sessionId } : {},
848
+ ...toolName !== void 0 ? { toolName } : {},
849
+ ...transcriptPath !== void 0 ? { transcriptPath } : {}
850
+ };
851
+ },
852
+ async install(repoRoot, scope = "global") {
853
+ const path = resolveCodexPath(repoRoot, scope);
854
+ const settings = await readJson2(path);
855
+ const next = { ...settings ?? {} };
856
+ const hooks = {};
857
+ for (const [event, raw] of Object.entries(next.hooks ?? {})) {
858
+ hooks[event] = normalizeEventHooks2(raw);
859
+ }
860
+ for (const event of CODEX_HOOK_EVENTS) {
861
+ const withoutInsights = removeAgentInsightsMatchers2(hooks[event] ?? []);
862
+ hooks[event] = addAgentInsightsHook2(withoutInsights, event);
863
+ }
864
+ next.hooks = hooks;
865
+ await mkdir3(dirname3(path), { recursive: true });
866
+ await writeFile3(path, `${JSON.stringify(next, null, 2)}
867
+ `, "utf8");
868
+ return { written: true, path };
869
+ },
870
+ async uninstall(repoRoot, scope = "global") {
871
+ const path = resolveCodexPath(repoRoot, scope);
872
+ const settings = await readJson2(path);
873
+ if (!settings?.hooks) return { removed: false, path };
874
+ const hooks = {};
875
+ let touched = false;
876
+ for (const [event, raw] of Object.entries(settings.hooks)) {
877
+ const before = normalizeEventHooks2(raw);
878
+ const after = removeAgentInsightsMatchers2(before);
879
+ if (after.length !== before.length || JSON.stringify(after) !== JSON.stringify(before)) {
880
+ touched = true;
881
+ }
882
+ if (after.length > 0) hooks[event] = after;
883
+ }
884
+ settings.hooks = hooks;
885
+ await writeFile3(path, `${JSON.stringify(settings, null, 2)}
886
+ `, "utf8");
887
+ return { removed: touched, path };
888
+ },
889
+ async isInstalled(repoRoot, scope = "global") {
890
+ const settings = await readJson2(
891
+ resolveCodexPath(repoRoot, scope)
892
+ );
893
+ if (!settings?.hooks) return false;
894
+ return Object.values(settings.hooks).some(
895
+ (raw) => normalizeEventHooks2(raw).some(
896
+ (group) => group.hooks.some((h) => isAgentInsightsCommand2(h.command))
897
+ )
898
+ );
899
+ }
900
+ };
901
+ function resolveCodexPath(repoRoot, scope) {
902
+ return scope === "global" ? join3(homedir3(), ".codex", "hooks.json") : join3(repoRoot, ".codex", "hooks.json");
903
+ }
904
+ function isAgentInsightsCommand2(cmd) {
905
+ if (!cmd) return false;
906
+ if (cmd.startsWith(`${HOOK_COMMAND_NAME2} hook`)) return true;
907
+ return /\bagent-insights\b.*\bcli\.js\b.*\bhook\b/.test(cmd);
908
+ }
909
+ function isHookCommand2(value) {
910
+ if (!value || typeof value !== "object") return false;
911
+ const entry = value;
912
+ return entry.type === "command" && typeof entry.command === "string";
913
+ }
914
+ function normalizeEventHooks2(raw) {
915
+ const groups = [];
916
+ for (const entry of raw) {
917
+ if (!entry || typeof entry !== "object") continue;
918
+ const record = entry;
919
+ if (Array.isArray(record.hooks)) {
920
+ const commands = record.hooks.filter(isHookCommand2);
921
+ if (commands.length === 0) continue;
922
+ groups.push({
923
+ matcher: typeof record.matcher === "string" ? record.matcher : "",
924
+ hooks: commands
925
+ });
926
+ continue;
927
+ }
928
+ if (isHookCommand2(record)) {
929
+ groups.push({ matcher: "", hooks: [record] });
930
+ }
931
+ }
932
+ return groups;
933
+ }
934
+ function removeAgentInsightsMatchers2(matchers) {
935
+ const result = [];
936
+ for (const group of matchers) {
937
+ const hooks = group.hooks.filter(
938
+ (h) => !isAgentInsightsCommand2(h.command)
939
+ );
940
+ if (hooks.length > 0) result.push({ ...group, hooks });
941
+ }
942
+ return result;
943
+ }
944
+ function addAgentInsightsHook2(matchers, event) {
945
+ const command = hookCommandString(event);
946
+ const entry = { type: "command", command };
947
+ for (const group of matchers) {
948
+ if (group.matcher === "") {
949
+ if (!group.hooks.some((h) => h.command === command)) {
950
+ group.hooks.push(entry);
951
+ }
952
+ return matchers;
953
+ }
954
+ }
955
+ return [...matchers, { matcher: "", hooks: [entry] }];
956
+ }
957
+ async function readJson2(path) {
958
+ try {
959
+ const raw = await readFile3(path, "utf8");
960
+ return JSON.parse(raw);
961
+ } catch (err) {
962
+ if (err.code === "ENOENT") return void 0;
963
+ throw err;
964
+ }
965
+ }
966
+ function asString2(v) {
967
+ return typeof v === "string" && v.length > 0 ? v : void 0;
968
+ }
969
+
970
+ // src/adapters/cursor.ts
971
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
972
+ import { homedir as homedir4 } from "os";
973
+ import { dirname as dirname4, join as join4 } from "path";
974
+ var HOOK_COMMAND_NAME3 = "agent-insights";
975
+ var HOOK_MAP3 = {
793
976
  sessionStart: "session.start",
794
977
  sessionEnd: "session.end",
795
978
  beforeSubmitPrompt: "user.prompt.submit",
@@ -806,19 +989,19 @@ var HOOK_MAP2 = {
806
989
  afterFileEdit: "tool.end",
807
990
  workspaceOpen: "session.start"
808
991
  };
809
- var CURSOR_HOOK_EVENTS = Object.keys(HOOK_MAP2);
992
+ var CURSOR_HOOK_EVENTS = Object.keys(HOOK_MAP3);
810
993
  var cursorAdapter = {
811
994
  id: "cursor",
812
995
  agent: "cursor",
813
996
  label: "Cursor",
814
997
  settingsFile: ".cursor/hooks.json",
815
998
  map(payload) {
816
- const type = HOOK_MAP2[payload.event];
999
+ const type = HOOK_MAP3[payload.event];
817
1000
  if (!type) return void 0;
818
1001
  const data = payload.data ?? {};
819
- const sessionId = asString2(data["session_id"]) ?? asString2(data["sessionId"]) ?? asString2(data["conversation_id"]);
820
- const toolName = asString2(data["tool_name"]) ?? asString2(data["toolName"]);
821
- const transcriptPath = asString2(data["transcript_path"]) ?? asString2(data["transcriptPath"]) ?? asString2(process.env.CURSOR_TRANSCRIPT_PATH);
1002
+ const sessionId = asString3(data["session_id"]) ?? asString3(data["sessionId"]) ?? asString3(data["conversation_id"]);
1003
+ const toolName = asString3(data["tool_name"]) ?? asString3(data["toolName"]);
1004
+ const transcriptPath = asString3(data["transcript_path"]) ?? asString3(data["transcriptPath"]) ?? asString3(process.env.CURSOR_TRANSCRIPT_PATH);
822
1005
  return {
823
1006
  type,
824
1007
  ...sessionId !== void 0 ? { sessionId } : {},
@@ -828,7 +1011,7 @@ var cursorAdapter = {
828
1011
  },
829
1012
  async install(repoRoot, scope = "global") {
830
1013
  const path = resolveCursorPath(repoRoot, scope);
831
- const settings = await readJson2(path);
1014
+ const settings = await readJson3(path);
832
1015
  const next = {
833
1016
  version: 1,
834
1017
  ...settings ?? {}
@@ -836,26 +1019,26 @@ var cursorAdapter = {
836
1019
  const hooks = { ...next.hooks ?? {} };
837
1020
  for (const event of CURSOR_HOOK_EVENTS) {
838
1021
  const existing = (hooks[event] ?? []).filter(
839
- (h) => !isAgentInsightsCommand2(h.command)
1022
+ (h) => !isAgentInsightsCommand3(h.command)
840
1023
  );
841
- existing.push({ command: `${HOOK_COMMAND_NAME2} hook ${event}` });
1024
+ existing.push({ command: hookCommandString(event) });
842
1025
  hooks[event] = existing;
843
1026
  }
844
1027
  next.hooks = hooks;
845
- await mkdir3(dirname3(path), { recursive: true });
846
- await writeFile3(path, `${JSON.stringify(next, null, 2)}
1028
+ await mkdir4(dirname4(path), { recursive: true });
1029
+ await writeFile4(path, `${JSON.stringify(next, null, 2)}
847
1030
  `, "utf8");
848
1031
  return { written: true, path };
849
1032
  },
850
1033
  async uninstall(repoRoot, scope = "global") {
851
1034
  const path = resolveCursorPath(repoRoot, scope);
852
- const settings = await readJson2(path);
1035
+ const settings = await readJson3(path);
853
1036
  if (!settings?.hooks) return { removed: false, path };
854
1037
  const hooks = { ...settings.hooks };
855
1038
  let touched = false;
856
1039
  for (const [event, entries] of Object.entries(hooks)) {
857
1040
  const filtered = entries.filter(
858
- (h) => !isAgentInsightsCommand2(h.command)
1041
+ (h) => !isAgentInsightsCommand3(h.command)
859
1042
  );
860
1043
  if (filtered.length !== entries.length) touched = true;
861
1044
  if (filtered.length === 0) {
@@ -865,44 +1048,164 @@ var cursorAdapter = {
865
1048
  }
866
1049
  }
867
1050
  settings.hooks = hooks;
868
- await writeFile3(path, `${JSON.stringify(settings, null, 2)}
1051
+ await writeFile4(path, `${JSON.stringify(settings, null, 2)}
869
1052
  `, "utf8");
870
1053
  return { removed: touched, path };
871
1054
  },
872
1055
  async isInstalled(repoRoot, scope = "global") {
873
- const settings = await readJson2(
1056
+ const settings = await readJson3(
874
1057
  resolveCursorPath(repoRoot, scope)
875
1058
  );
876
1059
  if (!settings?.hooks) return false;
877
1060
  return Object.values(settings.hooks).some(
878
- (entries) => entries.some((h) => isAgentInsightsCommand2(h.command))
1061
+ (entries) => entries.some((h) => isAgentInsightsCommand3(h.command))
879
1062
  );
880
1063
  }
881
1064
  };
882
1065
  function resolveCursorPath(repoRoot, scope) {
883
- return scope === "global" ? join3(homedir3(), ".cursor", "hooks.json") : join3(repoRoot, ".cursor", "hooks.json");
1066
+ return scope === "global" ? join4(homedir4(), ".cursor", "hooks.json") : join4(repoRoot, ".cursor", "hooks.json");
884
1067
  }
885
- function isAgentInsightsCommand2(cmd) {
1068
+ function isAgentInsightsCommand3(cmd) {
886
1069
  if (!cmd) return false;
887
- return cmd.startsWith(`${HOOK_COMMAND_NAME2} hook`);
1070
+ if (cmd.startsWith(`${HOOK_COMMAND_NAME3} hook`)) return true;
1071
+ return /\bagent-insights\b.*\bcli\.js\b.*\bhook\b/.test(cmd);
888
1072
  }
889
- async function readJson2(path) {
1073
+ async function readJson3(path) {
890
1074
  try {
891
- const raw = await readFile3(path, "utf8");
1075
+ const raw = await readFile4(path, "utf8");
892
1076
  return JSON.parse(raw);
893
1077
  } catch (err) {
894
1078
  if (err.code === "ENOENT") return void 0;
895
1079
  throw err;
896
1080
  }
897
1081
  }
898
- function asString2(v) {
1082
+ function asString3(v) {
1083
+ return typeof v === "string" && v.length > 0 ? v : void 0;
1084
+ }
1085
+
1086
+ // src/adapters/pi.ts
1087
+ import { mkdir as mkdir5, readFile as readFile5, unlink, writeFile as writeFile5 } from "fs/promises";
1088
+ import { homedir as homedir5 } from "os";
1089
+ import { dirname as dirname5, join as join5 } from "path";
1090
+ var EXTENSION_FILENAME = "agent-insights.ts";
1091
+ var HOOK_MAP4 = {
1092
+ session_start: "session.start",
1093
+ session_shutdown: "session.end",
1094
+ tool_call: "tool.start",
1095
+ tool_result: "tool.end"
1096
+ };
1097
+ var PI_HOOK_EVENTS = Object.keys(HOOK_MAP4);
1098
+ var EXTENSION_MARKER = "// installed-by: agent-insights";
1099
+ function extensionSource() {
1100
+ const { node, script } = hookCommandArgv("Placeholder");
1101
+ return `${EXTENSION_MARKER}
1102
+ // Generated by \`agent-insights init\`. Edit-with-caution: it's overwritten on every install.
1103
+
1104
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
1105
+ import { spawn } from "node:child_process";
1106
+
1107
+ const EVENTS = ${JSON.stringify(PI_HOOK_EVENTS)} as const;
1108
+ const NODE_BIN = ${JSON.stringify(node)};
1109
+ const CLI_SCRIPT = ${JSON.stringify(script)};
1110
+
1111
+ function emit(event: string, payload: Record<string, unknown>): void {
1112
+ try {
1113
+ const child = spawn(NODE_BIN, [CLI_SCRIPT, "hook", event], {
1114
+ stdio: ["pipe", "ignore", "ignore"],
1115
+ detached: true,
1116
+ });
1117
+ child.on("error", () => {});
1118
+ child.stdin.end(JSON.stringify(payload));
1119
+ child.unref();
1120
+ } catch {
1121
+ // best-effort
1122
+ }
1123
+ }
1124
+
1125
+ export default function (pi: ExtensionAPI) {
1126
+ for (const event of EVENTS) {
1127
+ pi.on(event as never, async (raw: unknown, ctx: { sessionManager?: { getSessionFile?: () => string | undefined; getSessionId?: () => string | undefined } }) => {
1128
+ const transcript_path = ctx.sessionManager?.getSessionFile?.();
1129
+ const session_id = ctx.sessionManager?.getSessionId?.();
1130
+ const e = (raw ?? {}) as Record<string, unknown>;
1131
+ emit(event, {
1132
+ ...(session_id ? { session_id } : {}),
1133
+ ...(transcript_path ? { transcript_path } : {}),
1134
+ ...("toolName" in e && typeof e.toolName === "string"
1135
+ ? { tool_name: e.toolName }
1136
+ : {}),
1137
+ ...("reason" in e ? { reason: e.reason } : {}),
1138
+ });
1139
+ });
1140
+ }
1141
+ }
1142
+ `;
1143
+ }
1144
+ var piAdapter = {
1145
+ id: "pi",
1146
+ agent: "pi",
1147
+ label: "Pi",
1148
+ settingsFile: ".pi/extensions/agent-insights.ts",
1149
+ map(payload) {
1150
+ const type = HOOK_MAP4[payload.event];
1151
+ if (!type) return void 0;
1152
+ const data = payload.data ?? {};
1153
+ const sessionId = asString4(data["session_id"]) ?? asString4(data["sessionId"]);
1154
+ const toolName = asString4(data["tool_name"]) ?? asString4(data["toolName"]);
1155
+ const transcriptPath = asString4(data["transcript_path"]) ?? asString4(data["transcriptPath"]);
1156
+ return {
1157
+ type,
1158
+ ...sessionId !== void 0 ? { sessionId } : {},
1159
+ ...toolName !== void 0 ? { toolName } : {},
1160
+ ...transcriptPath !== void 0 ? { transcriptPath } : {}
1161
+ };
1162
+ },
1163
+ async install(repoRoot, scope = "global") {
1164
+ const path = resolvePath(repoRoot, scope);
1165
+ await mkdir5(dirname5(path), { recursive: true });
1166
+ await writeFile5(path, extensionSource(), "utf8");
1167
+ return { written: true, path };
1168
+ },
1169
+ async uninstall(repoRoot, scope = "global") {
1170
+ const path = resolvePath(repoRoot, scope);
1171
+ try {
1172
+ const raw = await readFile5(path, "utf8");
1173
+ if (!raw.includes(EXTENSION_MARKER)) {
1174
+ return { removed: false, path };
1175
+ }
1176
+ await unlink(path);
1177
+ return { removed: true, path };
1178
+ } catch (err) {
1179
+ if (err.code === "ENOENT") {
1180
+ return { removed: false, path };
1181
+ }
1182
+ throw err;
1183
+ }
1184
+ },
1185
+ async isInstalled(repoRoot, scope = "global") {
1186
+ const path = resolvePath(repoRoot, scope);
1187
+ try {
1188
+ const raw = await readFile5(path, "utf8");
1189
+ return raw.includes(EXTENSION_MARKER);
1190
+ } catch (err) {
1191
+ if (err.code === "ENOENT") return false;
1192
+ throw err;
1193
+ }
1194
+ }
1195
+ };
1196
+ function resolvePath(repoRoot, scope) {
1197
+ return scope === "global" ? join5(homedir5(), ".pi", "agent", "extensions", EXTENSION_FILENAME) : join5(repoRoot, ".pi", "extensions", EXTENSION_FILENAME);
1198
+ }
1199
+ function asString4(v) {
899
1200
  return typeof v === "string" && v.length > 0 ? v : void 0;
900
1201
  }
901
1202
 
902
1203
  // src/adapters/registry.ts
903
1204
  var adapters = {
904
1205
  "claude-code": claudeCodeAdapter,
905
- cursor: cursorAdapter
1206
+ cursor: cursorAdapter,
1207
+ codex: codexAdapter,
1208
+ pi: piAdapter
906
1209
  };
907
1210
  var adapterList = Object.values(adapters);
908
1211
 
@@ -946,18 +1249,18 @@ init_store();
946
1249
 
947
1250
  // src/transcript/store.ts
948
1251
  init_store();
949
- import { readFile as readFile6 } from "fs/promises";
1252
+ import { readFile as readFile8 } from "fs/promises";
950
1253
  import { put } from "@vercel/blob";
951
1254
 
952
1255
  // src/config/bypass.ts
953
1256
  init_paths();
954
- import { chmod as chmod2, mkdir as mkdir5, readFile as readFile5, unlink, writeFile as writeFile5 } from "fs/promises";
955
- import { dirname as dirname5 } from "path";
1257
+ import { chmod as chmod2, mkdir as mkdir7, readFile as readFile7, unlink as unlink2, writeFile as writeFile7 } from "fs/promises";
1258
+ import { dirname as dirname7 } from "path";
956
1259
  async function loadBypassSecret() {
957
1260
  const fromEnv = process.env.AGENT_INSIGHTS_BYPASS_SECRET || process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
958
1261
  if (fromEnv) return fromEnv;
959
1262
  try {
960
- const raw = (await readFile5(bypassSecretFile(), "utf8")).trim();
1263
+ const raw = (await readFile7(bypassSecretFile(), "utf8")).trim();
961
1264
  return raw || void 0;
962
1265
  } catch (err) {
963
1266
  if (err.code === "ENOENT") return void 0;
@@ -966,8 +1269,8 @@ async function loadBypassSecret() {
966
1269
  }
967
1270
  async function saveBypassSecret(secret) {
968
1271
  const path = bypassSecretFile();
969
- await mkdir5(dirname5(path), { recursive: true });
970
- await writeFile5(path, secret.trim(), { encoding: "utf8", mode: 384 });
1272
+ await mkdir7(dirname7(path), { recursive: true });
1273
+ await writeFile7(path, secret.trim(), { encoding: "utf8", mode: 384 });
971
1274
  try {
972
1275
  await chmod2(path, 384);
973
1276
  } catch {
@@ -975,7 +1278,7 @@ async function saveBypassSecret(secret) {
975
1278
  }
976
1279
  async function clearBypassSecret() {
977
1280
  try {
978
- await unlink(bypassSecretFile());
1281
+ await unlink2(bypassSecretFile());
979
1282
  } catch (err) {
980
1283
  if (err.code !== "ENOENT") throw err;
981
1284
  }
@@ -1005,7 +1308,7 @@ var BlobTranscriptStore = class {
1005
1308
  token;
1006
1309
  prefix;
1007
1310
  async upload(input) {
1008
- const body = await readFile6(input.transcriptPath);
1311
+ const body = await readFile8(input.transcriptPath);
1009
1312
  const requestedPath = buildPathname(
1010
1313
  this.prefix,
1011
1314
  input.userHash,
@@ -1036,7 +1339,7 @@ var AnalyzerTranscriptStore = class {
1036
1339
  "transcript store: not logged in \u2014 run `agent-insights login`"
1037
1340
  );
1038
1341
  }
1039
- const body = await readFile6(input.transcriptPath);
1342
+ const body = await readFile8(input.transcriptPath);
1040
1343
  const url = new URL("/api/sessions/upload", this.analyzerUrl).toString();
1041
1344
  const headers = await withBypassHeader({
1042
1345
  authorization: `Bearer ${token}`,
@@ -1155,7 +1458,7 @@ async function runDoctor(opts) {
1155
1458
  }
1156
1459
  }
1157
1460
  for (const adapter of adapterList) {
1158
- const enabled = adapter.id === "claude-code" ? cfg.agents.claudeCode.enabled : cfg.agents.cursor.enabled;
1461
+ 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;
1159
1462
  if (!enabled) continue;
1160
1463
  const installed = await adapter.isInstalled(repoRoot, cfg.hooksScope);
1161
1464
  checks.push({
@@ -1287,7 +1590,7 @@ function buildEvent(adapter, mapped) {
1287
1590
  var STDIN_TIMEOUT_MS = 200;
1288
1591
  async function readStdinSafely() {
1289
1592
  if (process.stdin.isTTY) return "";
1290
- return new Promise((resolve) => {
1593
+ return new Promise((resolve2) => {
1291
1594
  let buf = "";
1292
1595
  let settled = false;
1293
1596
  const finish = () => {
@@ -1301,7 +1604,7 @@ async function readStdinSafely() {
1301
1604
  process.stdin.unref();
1302
1605
  } catch {
1303
1606
  }
1304
- resolve(buf);
1607
+ resolve2(buf);
1305
1608
  };
1306
1609
  const idle = setTimeout(finish, STDIN_TIMEOUT_MS);
1307
1610
  idle.unref();
@@ -1401,6 +1704,8 @@ async function runInit(opts) {
1401
1704
  cfg.userConsent.eventTelemetry = choices.telemetry;
1402
1705
  cfg.agents.claudeCode.enabled = choices.agents.has("claude-code");
1403
1706
  cfg.agents.cursor.enabled = choices.agents.has("cursor");
1707
+ cfg.agents.codex.enabled = choices.agents.has("codex");
1708
+ cfg.agents.pi.enabled = choices.agents.has("pi");
1404
1709
  await saveConfig(cfg);
1405
1710
  log.ok(`Wrote config to ${configFile()}`);
1406
1711
  if (choices.bypassSecret !== void 0) {
@@ -1416,8 +1721,7 @@ async function runInit(opts) {
1416
1721
  const repoRoot = process.cwd();
1417
1722
  if (installHooks) {
1418
1723
  for (const adapter of adapterList) {
1419
- const enabled = adapter.id === "claude-code" && cfg.agents.claudeCode.enabled || adapter.id === "cursor" && cfg.agents.cursor.enabled;
1420
- if (!enabled) continue;
1724
+ if (!isAdapterEnabled(cfg, adapter.id)) continue;
1421
1725
  const { path } = await adapter.install(repoRoot, cfg.hooksScope);
1422
1726
  log.ok(`Installed ${adapter.label} hooks \u2192 ${rel(path)}`);
1423
1727
  }
@@ -1466,6 +1770,9 @@ async function promptChoices(opts) {
1466
1770
  bypassSecret = opts.bypassSecret;
1467
1771
  } else {
1468
1772
  const existing = await loadBypassSecret();
1773
+ log.hint(
1774
+ `Generate one at ${kleur3.underline("https://vercel.com/vercel-labs/agent-insights/settings/deployment-protection#protection-bypass-for-automation")}`
1775
+ );
1469
1776
  const answer = await safePrompt(
1470
1777
  () => password({
1471
1778
  message: existing ? "Vercel deployment-protection bypass secret (Enter to keep existing, '-' to clear):" : "Vercel deployment-protection bypass secret (Enter to skip):",
@@ -1497,6 +1804,9 @@ async function safePrompt(run) {
1497
1804
  throw err;
1498
1805
  }
1499
1806
  }
1807
+ var KNOWN_AGENT_IDS = new Set(
1808
+ adapterList.map((a) => a.id)
1809
+ );
1500
1810
  function parseAgents(value) {
1501
1811
  const out = /* @__PURE__ */ new Set();
1502
1812
  const trimmed = (value ?? "").trim();
@@ -1504,9 +1814,9 @@ function parseAgents(value) {
1504
1814
  for (const a of adapterList) out.add(a.id);
1505
1815
  return out;
1506
1816
  }
1507
- const list = (trimmed || "claude-code,cursor").split(",").map((s) => s.trim()).filter(Boolean);
1817
+ const list = (trimmed || adapterList.map((a) => a.id).join(",")).split(",").map((s) => s.trim()).filter(Boolean);
1508
1818
  for (const id of list) {
1509
- if (id === "claude-code" || id === "cursor") out.add(id);
1819
+ if (KNOWN_AGENT_IDS.has(id)) out.add(id);
1510
1820
  else log.warn(`Unknown agent id "${id}" \u2014 skipping.`);
1511
1821
  }
1512
1822
  return out;
@@ -1515,6 +1825,18 @@ function rel(p) {
1515
1825
  const cwd = process.cwd();
1516
1826
  return p.startsWith(cwd) ? p.slice(cwd.length + 1) : p;
1517
1827
  }
1828
+ function isAdapterEnabled(cfg, id) {
1829
+ switch (id) {
1830
+ case "claude-code":
1831
+ return cfg.agents.claudeCode.enabled;
1832
+ case "cursor":
1833
+ return cfg.agents.cursor.enabled;
1834
+ case "codex":
1835
+ return cfg.agents.codex.enabled;
1836
+ case "pi":
1837
+ return cfg.agents.pi.enabled;
1838
+ }
1839
+ }
1518
1840
 
1519
1841
  // src/commands/login.ts
1520
1842
  init_oauth();
@@ -1568,7 +1890,7 @@ async function runLogout() {
1568
1890
 
1569
1891
  // src/commands/status.ts
1570
1892
  import { existsSync } from "fs";
1571
- import { join as join4 } from "path";
1893
+ import { join as join6 } from "path";
1572
1894
  import kleur5 from "kleur";
1573
1895
  init_paths();
1574
1896
  async function runStatus(opts) {
@@ -1594,8 +1916,8 @@ async function runStatus(opts) {
1594
1916
  enabled: cfg.enabled,
1595
1917
  consent: cfg.userConsent,
1596
1918
  vercel: {
1597
- linked: existsSync(join4(repoRoot, ".vercel/project.json")),
1598
- envFile: existsSync(join4(repoRoot, ".env.local"))
1919
+ linked: existsSync(join6(repoRoot, ".vercel/project.json")),
1920
+ envFile: existsSync(join6(repoRoot, ".env.local"))
1599
1921
  },
1600
1922
  adapters: adapterStatus
1601
1923
  });
@@ -1619,9 +1941,9 @@ async function runStatus(opts) {
1619
1941
  log.info("");
1620
1942
  log.info(kleur5.bold("Vercel"));
1621
1943
  log.info(
1622
- ` linked ${yn(existsSync(join4(repoRoot, ".vercel/project.json")))}`
1944
+ ` linked ${yn(existsSync(join6(repoRoot, ".vercel/project.json")))}`
1623
1945
  );
1624
- log.info(` .env.local ${yn(existsSync(join4(repoRoot, ".env.local")))}`);
1946
+ log.info(` .env.local ${yn(existsSync(join6(repoRoot, ".env.local")))}`);
1625
1947
  }
1626
1948
  function yn(v) {
1627
1949
  return v ? kleur5.green("yes") : kleur5.dim("no");
@@ -1631,7 +1953,7 @@ function yn(v) {
1631
1953
  var program = new Command();
1632
1954
  program.name("agent-insights").description(
1633
1955
  "Internal CLI for AI coding agent observability (Claude Code, Cursor)."
1634
- ).version("0.0.13");
1956
+ ).version("0.0.18");
1635
1957
  program.command("login").description("Authenticate with Vercel (Sign in with Vercel).").action(async () => {
1636
1958
  await runLogin();
1637
1959
  });