agent-insights 0.0.7 → 0.0.10
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 +207 -41
- package/dist/cli.js.map +1 -1
- package/dist/index.js +163 -9
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -75,7 +75,7 @@ function listenForCallback(expectedState, timeoutMs) {
|
|
|
75
75
|
const server = createServer((req, res) => {
|
|
76
76
|
const url = new URL(req.url ?? "/", `http://localhost:${CALLBACK_PORT}`);
|
|
77
77
|
if (url.pathname !== "/callback") {
|
|
78
|
-
res.writeHead(404);
|
|
78
|
+
res.writeHead(404, { connection: "close" });
|
|
79
79
|
res.end("Not found");
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
@@ -83,33 +83,48 @@ function listenForCallback(expectedState, timeoutMs) {
|
|
|
83
83
|
const returnedState = url.searchParams.get("state");
|
|
84
84
|
const error = url.searchParams.get("error");
|
|
85
85
|
const html = (msg) => `<html><body style="font-family:sans-serif;text-align:center;padding:80px"><h2>${msg}</h2><p>You can close this tab.</p></body></html>`;
|
|
86
|
+
const headers = { "content-type": "text/html", connection: "close" };
|
|
87
|
+
const finish = (next, body, status) => {
|
|
88
|
+
res.writeHead(status, headers);
|
|
89
|
+
res.end(body, () => {
|
|
90
|
+
clearTimeout(timer);
|
|
91
|
+
server.closeAllConnections?.();
|
|
92
|
+
server.close();
|
|
93
|
+
next();
|
|
94
|
+
});
|
|
95
|
+
};
|
|
86
96
|
if (error) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
finish(
|
|
98
|
+
() => reject(new Error(`OAuth error: ${error}`)),
|
|
99
|
+
html(`Login failed: ${error}`),
|
|
100
|
+
400
|
|
101
|
+
);
|
|
91
102
|
return;
|
|
92
103
|
}
|
|
93
104
|
if (returnedState !== expectedState) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
finish(
|
|
106
|
+
() => reject(new Error("State mismatch \u2014 possible CSRF")),
|
|
107
|
+
html("Invalid state parameter \u2014 please try again."),
|
|
108
|
+
400
|
|
109
|
+
);
|
|
98
110
|
return;
|
|
99
111
|
}
|
|
100
112
|
if (!code) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
113
|
+
finish(
|
|
114
|
+
() => reject(new Error("No code in callback")),
|
|
115
|
+
html("No authorization code received."),
|
|
116
|
+
400
|
|
117
|
+
);
|
|
105
118
|
return;
|
|
106
119
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
finish(
|
|
121
|
+
() => resolve(code),
|
|
122
|
+
html("\u2713 Logged in! You can close this tab."),
|
|
123
|
+
200
|
|
124
|
+
);
|
|
111
125
|
});
|
|
112
126
|
const timer = setTimeout(() => {
|
|
127
|
+
server.closeAllConnections?.();
|
|
113
128
|
server.close();
|
|
114
129
|
reject(new Error("Login timed out \u2014 no response within 2 minutes."));
|
|
115
130
|
}, timeoutMs);
|
|
@@ -179,8 +194,10 @@ function openBrowser(url) {
|
|
|
179
194
|
if (os === "darwin") cmd = `open "${url}"`;
|
|
180
195
|
else if (os === "win32") cmd = `start "" "${url}"`;
|
|
181
196
|
else cmd = `xdg-open "${url}"`;
|
|
182
|
-
exec(cmd
|
|
197
|
+
const child = exec(cmd);
|
|
198
|
+
child.on("error", () => {
|
|
183
199
|
});
|
|
200
|
+
child.unref();
|
|
184
201
|
}
|
|
185
202
|
var CLIENT_ID, AUTH_ENDPOINT, TOKEN_ENDPOINT, USERINFO_ENDPOINT, CALLBACK_PORT, REDIRECT_URI, SCOPES;
|
|
186
203
|
var init_oauth = __esm({
|
|
@@ -306,9 +323,10 @@ function defaultConfig() {
|
|
|
306
323
|
dataset: "agent-insights"
|
|
307
324
|
},
|
|
308
325
|
transcriptStore: {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
326
|
+
// Upload via the analyzer using the SiwV bearer token — no client-side
|
|
327
|
+
// Blob token required. Set `transcriptStore.type` to "blob" in
|
|
328
|
+
// config.json to switch to direct uploads with BLOB_READ_WRITE_TOKEN.
|
|
329
|
+
type: "analyzer"
|
|
312
330
|
},
|
|
313
331
|
sessionAnalysis: {
|
|
314
332
|
enabled: false,
|
|
@@ -859,6 +877,7 @@ init_paths();
|
|
|
859
877
|
init_store();
|
|
860
878
|
|
|
861
879
|
// src/transcript/store.ts
|
|
880
|
+
init_store();
|
|
862
881
|
import { readFile as readFile5 } from "fs/promises";
|
|
863
882
|
import { put } from "@vercel/blob";
|
|
864
883
|
var NoopTranscriptStore = class {
|
|
@@ -896,17 +915,74 @@ var BlobTranscriptStore = class {
|
|
|
896
915
|
return { ok: true };
|
|
897
916
|
}
|
|
898
917
|
};
|
|
918
|
+
var AnalyzerTranscriptStore = class {
|
|
919
|
+
constructor(analyzerUrl) {
|
|
920
|
+
this.analyzerUrl = analyzerUrl;
|
|
921
|
+
}
|
|
922
|
+
analyzerUrl;
|
|
923
|
+
async upload(input) {
|
|
924
|
+
const token = await getValidToken();
|
|
925
|
+
if (!token) {
|
|
926
|
+
throw new Error(
|
|
927
|
+
"transcript store: not logged in \u2014 run `agent-insights login`"
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
const body = await readFile5(input.transcriptPath);
|
|
931
|
+
const url = new URL("/api/sessions/upload", this.analyzerUrl).toString();
|
|
932
|
+
const res = await fetch(url, {
|
|
933
|
+
method: "POST",
|
|
934
|
+
headers: {
|
|
935
|
+
authorization: `Bearer ${token}`,
|
|
936
|
+
"content-type": "application/jsonl",
|
|
937
|
+
"x-session-id": input.sessionId,
|
|
938
|
+
"x-agent": input.agent,
|
|
939
|
+
"x-user-hash": input.userHash
|
|
940
|
+
},
|
|
941
|
+
body
|
|
942
|
+
});
|
|
943
|
+
if (!res.ok) {
|
|
944
|
+
const text = await res.text().catch(() => "");
|
|
945
|
+
throw new Error(
|
|
946
|
+
`transcript upload failed (${res.status}): ${text || res.statusText}`
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
const json = await res.json();
|
|
950
|
+
if (!json.ok || !json.blobUrl || !json.pathname) {
|
|
951
|
+
throw new Error(`transcript upload error: ${json.error ?? "unknown"}`);
|
|
952
|
+
}
|
|
953
|
+
return {
|
|
954
|
+
pathname: json.pathname,
|
|
955
|
+
url: json.blobUrl,
|
|
956
|
+
size: json.size ?? body.byteLength
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
async ping() {
|
|
960
|
+
if (!this.analyzerUrl) {
|
|
961
|
+
return {
|
|
962
|
+
ok: false,
|
|
963
|
+
reason: "analyzer URL not set (AGENT_INSIGHTS_ANALYZER_URL)"
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
const token = await getValidToken();
|
|
967
|
+
if (!token) {
|
|
968
|
+
return {
|
|
969
|
+
ok: false,
|
|
970
|
+
reason: "not logged in \u2014 run `agent-insights login`"
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
return { ok: true };
|
|
974
|
+
}
|
|
975
|
+
};
|
|
899
976
|
function createTranscriptStore(cfg) {
|
|
900
977
|
if (cfg.type === "none") return new NoopTranscriptStore();
|
|
901
978
|
if (cfg.type === "analyzer") {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
);
|
|
979
|
+
const analyzerUrl = process.env.AGENT_INSIGHTS_ANALYZER_URL ?? "";
|
|
980
|
+
return new AnalyzerTranscriptStore(analyzerUrl);
|
|
905
981
|
}
|
|
906
982
|
const token = process.env[cfg.tokenEnv];
|
|
907
983
|
if (!token) {
|
|
908
984
|
throw new Error(
|
|
909
|
-
`transcript store: env ${cfg.tokenEnv} is not set
|
|
985
|
+
`transcript store: env ${cfg.tokenEnv} is not set \u2014 run \`agent-insights init --force\` to switch to analyzer-proxied uploads (no token required), or \`vercel env pull .env.local\` to keep direct Blob uploads`
|
|
910
986
|
);
|
|
911
987
|
}
|
|
912
988
|
return new BlobTranscriptStore(token, cfg.prefix);
|
|
@@ -1131,6 +1207,7 @@ function parseJsonObject(raw) {
|
|
|
1131
1207
|
}
|
|
1132
1208
|
|
|
1133
1209
|
// src/commands/init.ts
|
|
1210
|
+
import { checkbox, confirm } from "@inquirer/prompts";
|
|
1134
1211
|
import kleur3 from "kleur";
|
|
1135
1212
|
init_paths();
|
|
1136
1213
|
init_store();
|
|
@@ -1154,18 +1231,48 @@ async function runInit(opts) {
|
|
|
1154
1231
|
log.hint(CONSENT_TEXT);
|
|
1155
1232
|
log.info("");
|
|
1156
1233
|
const existing = await loadConfig();
|
|
1234
|
+
const interactive = !opts.yes && Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1157
1235
|
if (existing && !opts.force) {
|
|
1158
|
-
|
|
1159
|
-
|
|
1236
|
+
if (!interactive) {
|
|
1237
|
+
log.warn(
|
|
1238
|
+
`Config already exists at ${configFile()} \u2014 re-run with --force to overwrite, or use interactive mode.`
|
|
1239
|
+
);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
log.warn(`Config already exists at ${configFile()}.`);
|
|
1243
|
+
const overwrite = await safePrompt(
|
|
1244
|
+
() => confirm({ message: "Overwrite existing config?", default: false })
|
|
1160
1245
|
);
|
|
1246
|
+
if (overwrite === void 0) return;
|
|
1247
|
+
if (!overwrite) {
|
|
1248
|
+
log.info("Aborted \u2014 existing config left in place.");
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
log.info("");
|
|
1252
|
+
}
|
|
1253
|
+
let choices;
|
|
1254
|
+
if (interactive) {
|
|
1255
|
+
const result = await promptChoices(opts);
|
|
1256
|
+
if (!result) return;
|
|
1257
|
+
choices = result;
|
|
1258
|
+
} else {
|
|
1259
|
+
choices = {
|
|
1260
|
+
agents: parseAgents(opts.agents),
|
|
1261
|
+
transcripts: opts.transcripts ?? true,
|
|
1262
|
+
analysis: opts.analysis ?? false,
|
|
1263
|
+
telemetry: opts.telemetry ?? false
|
|
1264
|
+
};
|
|
1161
1265
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
cfg
|
|
1167
|
-
cfg.
|
|
1168
|
-
cfg.
|
|
1266
|
+
if (choices.agents.size === 0) {
|
|
1267
|
+
log.fail("No agents selected \u2014 nothing to install.");
|
|
1268
|
+
process.exit(1);
|
|
1269
|
+
}
|
|
1270
|
+
const cfg = defaultConfig();
|
|
1271
|
+
cfg.userConsent.transcriptSync = choices.transcripts;
|
|
1272
|
+
cfg.userConsent.sessionAnalysis = choices.analysis;
|
|
1273
|
+
cfg.userConsent.eventTelemetry = choices.telemetry;
|
|
1274
|
+
cfg.agents.claudeCode.enabled = choices.agents.has("claude-code");
|
|
1275
|
+
cfg.agents.cursor.enabled = choices.agents.has("cursor");
|
|
1169
1276
|
await saveConfig(cfg);
|
|
1170
1277
|
log.ok(`Wrote config to ${configFile()}`);
|
|
1171
1278
|
const installHooks = opts.hooks ?? true;
|
|
@@ -1182,9 +1289,67 @@ async function runInit(opts) {
|
|
|
1182
1289
|
log.info(`Config root: ${configRoot()}`);
|
|
1183
1290
|
log.hint("Run `agent-insights doctor` to verify the setup.");
|
|
1184
1291
|
}
|
|
1292
|
+
async function promptChoices(opts) {
|
|
1293
|
+
const requested = opts.agents ? parseAgents(opts.agents) : null;
|
|
1294
|
+
const selected = await safePrompt(
|
|
1295
|
+
() => checkbox({
|
|
1296
|
+
message: "Which agents would you like to install hooks for?",
|
|
1297
|
+
choices: adapterList.map((adapter) => ({
|
|
1298
|
+
name: adapter.label,
|
|
1299
|
+
value: adapter.id,
|
|
1300
|
+
checked: requested ? requested.has(adapter.id) : true
|
|
1301
|
+
})),
|
|
1302
|
+
required: true
|
|
1303
|
+
})
|
|
1304
|
+
);
|
|
1305
|
+
if (selected === void 0) return void 0;
|
|
1306
|
+
const transcripts = await safePrompt(
|
|
1307
|
+
() => confirm({
|
|
1308
|
+
message: "Sync session transcripts to Vercel Blob?",
|
|
1309
|
+
default: opts.transcripts ?? true
|
|
1310
|
+
})
|
|
1311
|
+
);
|
|
1312
|
+
if (transcripts === void 0) return void 0;
|
|
1313
|
+
const analysis = await safePrompt(
|
|
1314
|
+
() => confirm({
|
|
1315
|
+
message: "Enable SessionEnd analysis (internal LLM review)?",
|
|
1316
|
+
default: opts.analysis ?? false
|
|
1317
|
+
})
|
|
1318
|
+
);
|
|
1319
|
+
if (analysis === void 0) return void 0;
|
|
1320
|
+
const telemetry = await safePrompt(
|
|
1321
|
+
() => confirm({
|
|
1322
|
+
message: "Send OTLP event telemetry?",
|
|
1323
|
+
default: opts.telemetry ?? false
|
|
1324
|
+
})
|
|
1325
|
+
);
|
|
1326
|
+
if (telemetry === void 0) return void 0;
|
|
1327
|
+
return {
|
|
1328
|
+
agents: new Set(selected),
|
|
1329
|
+
transcripts,
|
|
1330
|
+
analysis,
|
|
1331
|
+
telemetry
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
async function safePrompt(run) {
|
|
1335
|
+
try {
|
|
1336
|
+
return await run();
|
|
1337
|
+
} catch (err) {
|
|
1338
|
+
if (err instanceof Error && (err.name === "ExitPromptError" || err.name === "AbortPromptError")) {
|
|
1339
|
+
log.info("Aborted.");
|
|
1340
|
+
return void 0;
|
|
1341
|
+
}
|
|
1342
|
+
throw err;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1185
1345
|
function parseAgents(value) {
|
|
1186
1346
|
const out = /* @__PURE__ */ new Set();
|
|
1187
|
-
const
|
|
1347
|
+
const trimmed = (value ?? "").trim();
|
|
1348
|
+
if (trimmed === "*") {
|
|
1349
|
+
for (const a of adapterList) out.add(a.id);
|
|
1350
|
+
return out;
|
|
1351
|
+
}
|
|
1352
|
+
const list = (trimmed || "claude-code,cursor").split(",").map((s) => s.trim()).filter(Boolean);
|
|
1188
1353
|
for (const id of list) {
|
|
1189
1354
|
if (id === "claude-code" || id === "cursor") out.add(id);
|
|
1190
1355
|
else log.warn(`Unknown agent id "${id}" \u2014 skipping.`);
|
|
@@ -1310,18 +1475,19 @@ function yn(v) {
|
|
|
1310
1475
|
var program = new Command();
|
|
1311
1476
|
program.name("agent-insights").description(
|
|
1312
1477
|
"Internal CLI for AI coding agent observability (Claude Code, Cursor)."
|
|
1313
|
-
).version("0.0.
|
|
1478
|
+
).version("0.0.10");
|
|
1314
1479
|
program.command("login").description("Authenticate with Vercel (Sign in with Vercel).").action(async () => {
|
|
1315
1480
|
await runLogin();
|
|
1316
1481
|
});
|
|
1317
1482
|
program.command("logout").description("Clear stored credentials.").action(async () => {
|
|
1318
1483
|
await runLogout();
|
|
1319
1484
|
});
|
|
1320
|
-
program.command("init").description(
|
|
1321
|
-
"--
|
|
1322
|
-
|
|
1323
|
-
"
|
|
1324
|
-
|
|
1485
|
+
program.command("init").description(
|
|
1486
|
+
"Install agent hooks globally. Interactive by default; use --yes for non-interactive."
|
|
1487
|
+
).option(
|
|
1488
|
+
"-a, --agents <list>",
|
|
1489
|
+
"Comma-separated agent ids (claude-code,cursor); use '*' for all"
|
|
1490
|
+
).option("--transcripts", "Opt into transcript sync to Vercel Blob").option("--no-transcripts", "Skip transcript sync").option("--analysis", "Opt into SessionEnd analysis (Phase 2)").option("--telemetry", "Opt into OTLP event telemetry (Phase 3)").option("--no-hooks", "Skip installing per-agent hooks").option("--force", "Overwrite existing config").option("-y, --yes", "Non-interactive \u2014 accept defaults / passed flags").action(async (opts) => {
|
|
1325
1491
|
await runInit(opts);
|
|
1326
1492
|
});
|
|
1327
1493
|
program.command("status").description("Show current config, consent, and adapter state.").option("--json", "Emit machine-readable JSON").action(async (opts) => {
|