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 +424 -76
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.js +399 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
228
|
-
import { dirname as
|
|
229
|
-
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
|
+
async function loadEmail() {
|
|
232
|
+
return (await loadAuth())?.email;
|
|
233
|
+
}
|
|
230
234
|
async function loadAuth() {
|
|
231
235
|
try {
|
|
232
|
-
const raw = await
|
|
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
|
|
246
|
-
await
|
|
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:
|
|
261
|
+
const { unlink: unlink3 } = await import("fs/promises");
|
|
258
262
|
try {
|
|
259
|
-
await
|
|
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 = {
|
|
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
|
|
664
|
-
|
|
675
|
+
const withoutInsights = removeAgentInsightsMatchers(
|
|
676
|
+
hooks[event] ?? []
|
|
665
677
|
);
|
|
666
|
-
|
|
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 = {
|
|
690
|
+
const hooks = {};
|
|
683
691
|
let touched = false;
|
|
684
|
-
for (const [event,
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
);
|
|
688
|
-
if (
|
|
689
|
-
|
|
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
|
-
(
|
|
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/
|
|
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(
|
|
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 =
|
|
970
|
+
const type = HOOK_MAP3[payload.event];
|
|
760
971
|
if (!type) return void 0;
|
|
761
972
|
const data = payload.data ?? {};
|
|
762
|
-
const sessionId =
|
|
763
|
-
const toolName =
|
|
764
|
-
const transcriptPath =
|
|
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
|
|
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) => !
|
|
993
|
+
(h) => !isAgentInsightsCommand3(h.command)
|
|
783
994
|
);
|
|
784
|
-
existing.push({ command: `${
|
|
995
|
+
existing.push({ command: `${HOOK_COMMAND_NAME3} hook ${event}` });
|
|
785
996
|
hooks[event] = existing;
|
|
786
997
|
}
|
|
787
998
|
next.hooks = hooks;
|
|
788
|
-
await
|
|
789
|
-
await
|
|
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
|
|
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) => !
|
|
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
|
|
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
|
|
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) =>
|
|
1032
|
+
(entries) => entries.some((h) => isAgentInsightsCommand3(h.command))
|
|
822
1033
|
);
|
|
823
1034
|
}
|
|
824
1035
|
};
|
|
825
1036
|
function resolveCursorPath(repoRoot, scope) {
|
|
826
|
-
return scope === "global" ?
|
|
1037
|
+
return scope === "global" ? join4(homedir4(), ".cursor", "hooks.json") : join4(repoRoot, ".cursor", "hooks.json");
|
|
827
1038
|
}
|
|
828
|
-
function
|
|
1039
|
+
function isAgentInsightsCommand3(cmd) {
|
|
829
1040
|
if (!cmd) return false;
|
|
830
|
-
return cmd.startsWith(`${
|
|
1041
|
+
return cmd.startsWith(`${HOOK_COMMAND_NAME3} hook`);
|
|
831
1042
|
}
|
|
832
|
-
async function
|
|
1043
|
+
async function readJson3(path) {
|
|
833
1044
|
try {
|
|
834
|
-
const raw = await
|
|
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
|
|
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
|
|
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
|
|
898
|
-
import { dirname as
|
|
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
|
|
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
|
|
913
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 || "
|
|
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
|
|
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
|
|
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(
|
|
1536
|
-
envFile: existsSync(
|
|
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(
|
|
1908
|
+
` linked ${yn(existsSync(join6(repoRoot, ".vercel/project.json")))}`
|
|
1561
1909
|
);
|
|
1562
|
-
log.info(` .env.local ${yn(existsSync(
|
|
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.
|
|
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
|
});
|