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