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 +384 -62
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +359 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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((
|
|
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
|
-
() =>
|
|
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
|
|
229
|
-
import { dirname as
|
|
230
|
-
import { mkdir as
|
|
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
|
|
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
|
|
250
|
-
await
|
|
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:
|
|
261
|
+
const { unlink: unlink3 } = await import("fs/promises");
|
|
262
262
|
try {
|
|
263
|
-
await
|
|
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((
|
|
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
|
-
|
|
609
|
+
resolve2(127);
|
|
606
610
|
});
|
|
607
|
-
child.on("exit", (code) =>
|
|
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
|
-
|
|
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 =
|
|
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/
|
|
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(
|
|
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 =
|
|
999
|
+
const type = HOOK_MAP3[payload.event];
|
|
817
1000
|
if (!type) return void 0;
|
|
818
1001
|
const data = payload.data ?? {};
|
|
819
|
-
const sessionId =
|
|
820
|
-
const toolName =
|
|
821
|
-
const transcriptPath =
|
|
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
|
|
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) => !
|
|
1022
|
+
(h) => !isAgentInsightsCommand3(h.command)
|
|
840
1023
|
);
|
|
841
|
-
existing.push({ command:
|
|
1024
|
+
existing.push({ command: hookCommandString(event) });
|
|
842
1025
|
hooks[event] = existing;
|
|
843
1026
|
}
|
|
844
1027
|
next.hooks = hooks;
|
|
845
|
-
await
|
|
846
|
-
await
|
|
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
|
|
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) => !
|
|
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
|
|
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
|
|
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) =>
|
|
1061
|
+
(entries) => entries.some((h) => isAgentInsightsCommand3(h.command))
|
|
879
1062
|
);
|
|
880
1063
|
}
|
|
881
1064
|
};
|
|
882
1065
|
function resolveCursorPath(repoRoot, scope) {
|
|
883
|
-
return scope === "global" ?
|
|
1066
|
+
return scope === "global" ? join4(homedir4(), ".cursor", "hooks.json") : join4(repoRoot, ".cursor", "hooks.json");
|
|
884
1067
|
}
|
|
885
|
-
function
|
|
1068
|
+
function isAgentInsightsCommand3(cmd) {
|
|
886
1069
|
if (!cmd) return false;
|
|
887
|
-
|
|
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
|
|
1073
|
+
async function readJson3(path) {
|
|
890
1074
|
try {
|
|
891
|
-
const raw = await
|
|
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
|
|
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
|
|
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
|
|
955
|
-
import { dirname as
|
|
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
|
|
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
|
|
970
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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 || "
|
|
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
|
|
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
|
|
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(
|
|
1598
|
-
envFile: existsSync(
|
|
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(
|
|
1944
|
+
` linked ${yn(existsSync(join6(repoRoot, ".vercel/project.json")))}`
|
|
1623
1945
|
);
|
|
1624
|
-
log.info(` .env.local ${yn(existsSync(
|
|
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.
|
|
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
|
});
|