radar-cc 0.1.2 → 0.1.3

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/index.js CHANGED
@@ -36,7 +36,9 @@ function parseLogRecord(record, sessionId) {
36
36
  const timestampMs = Math.floor(Number(BigInt(timeNano) / 1000000n));
37
37
  const attrs = buildAttrMap(record.attributes);
38
38
  const promptId = getString(attrs, "prompt.id");
39
- const base = { type: "unknown", promptId, sessionId, timestampMs };
39
+ const recordSessionId = getString(attrs, "session.id");
40
+ const effectiveSessionId = recordSessionId || sessionId;
41
+ const base = { type: "unknown", promptId, sessionId: effectiveSessionId, timestampMs };
40
42
  switch (eventName) {
41
43
  case "claude_code.user_prompt": {
42
44
  const e = {
@@ -83,6 +85,16 @@ function parseLogRecord(record, sessionId) {
83
85
  if (statusCode) e.statusCode = statusCode;
84
86
  return e;
85
87
  }
88
+ case "claude_code.tool_decision": {
89
+ const e = {
90
+ ...base,
91
+ type: "tool_decision",
92
+ toolName: getString(attrs, "tool_name"),
93
+ decision: getString(attrs, "decision"),
94
+ source: getString(attrs, "source")
95
+ };
96
+ return e;
97
+ }
86
98
  default:
87
99
  return null;
88
100
  }
@@ -134,6 +146,10 @@ var OtlpReceiver = class extends EventEmitter {
134
146
  });
135
147
  }
136
148
  handleRequest(req, res) {
149
+ if (req.method === "POST" && req.url === "/v1/hook/stop") {
150
+ this.handleStop(req, res);
151
+ return;
152
+ }
137
153
  if (req.method !== "POST" || req.url !== "/v1/logs") {
138
154
  res.writeHead(404, { "Content-Type": "application/json" });
139
155
  res.end(JSON.stringify({ error: "Not found" }));
@@ -159,6 +175,28 @@ var OtlpReceiver = class extends EventEmitter {
159
175
  });
160
176
  req.on("error", (err) => {
161
177
  process.stderr.write(`[radar/otlp] request error: ${String(err)}
178
+ `);
179
+ });
180
+ }
181
+ handleStop(req, res) {
182
+ const chunks = [];
183
+ req.on("data", (chunk) => {
184
+ chunks.push(chunk);
185
+ });
186
+ req.on("end", () => {
187
+ res.writeHead(200, { "Content-Type": "application/json" });
188
+ res.end(JSON.stringify({ ok: true }));
189
+ try {
190
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
191
+ const sessionId = typeof body.sessionId === "string" ? body.sessionId.trim() : "";
192
+ if (sessionId) {
193
+ this.emit("stop", sessionId);
194
+ }
195
+ } catch {
196
+ }
197
+ });
198
+ req.on("error", (err) => {
199
+ process.stderr.write(`[radar/otlp] stop request error: ${String(err)}
162
200
  `);
163
201
  });
164
202
  }
@@ -216,21 +254,19 @@ function buildPublicContext(internal) {
216
254
  toolNames
217
255
  };
218
256
  }
219
- var TurnAggregator = class extends EventEmitter2 {
220
- boundaryTimeoutMs;
221
- cleanupAfterMs;
257
+ var TurnAggregator = class _TurnAggregator extends EventEmitter2 {
258
+ // Delay to allow in-flight OTel events to arrive after Stop hook fires.
259
+ // Must be longer than OTEL_LOGS_EXPORT_INTERVAL (configured at 2000ms).
260
+ static COMPLETION_DELAY_MS = 3500;
222
261
  contexts = /* @__PURE__ */ new Map();
223
- boundaryTimers = /* @__PURE__ */ new Map();
224
- // Tracks pending context-cleanup timers so they can be cancelled if a promptId
225
- // is reused before the cleanup window expires, preventing silent context deletion.
226
- cleanupTimers = /* @__PURE__ */ new Map();
262
+ // Maps sessionId promptId for the most recent active turn
263
+ activeTurns = /* @__PURE__ */ new Map();
264
+ // Sessions where Stop hook fired before any OTel events arrived
265
+ pendingStops = /* @__PURE__ */ new Set();
266
+ // Active delayed-completion timers, keyed by sessionId
267
+ completionTimers = /* @__PURE__ */ new Map();
227
268
  sessions = /* @__PURE__ */ new Map();
228
269
  sessionCounter = 0;
229
- constructor(options) {
230
- super();
231
- this.boundaryTimeoutMs = options?.boundaryTimeoutMs ?? 5e3;
232
- this.cleanupAfterMs = options?.cleanupAfterMs ?? 3e5;
233
- }
234
270
  getSessions() {
235
271
  return [...this.sessions.values()];
236
272
  }
@@ -241,11 +277,6 @@ var TurnAggregator = class extends EventEmitter2 {
241
277
  const { promptId } = event;
242
278
  const isNew = !this.contexts.has(promptId);
243
279
  if (isNew) {
244
- const pendingCleanup = this.cleanupTimers.get(promptId);
245
- if (pendingCleanup !== void 0) {
246
- clearTimeout(pendingCleanup);
247
- this.cleanupTimers.delete(promptId);
248
- }
249
280
  const internal = {
250
281
  promptId,
251
282
  sessionId: event.sessionId,
@@ -254,9 +285,15 @@ var TurnAggregator = class extends EventEmitter2 {
254
285
  startedAt: Date.now(),
255
286
  toolResults: [],
256
287
  apiRequests: [],
257
- errors: []
288
+ errors: [],
289
+ toolDecisions: []
258
290
  };
259
291
  this.contexts.set(promptId, internal);
292
+ this.activeTurns.set(event.sessionId, promptId);
293
+ if (this.pendingStops.has(event.sessionId)) {
294
+ this.pendingStops.delete(event.sessionId);
295
+ this._scheduleDelayedComplete(event.sessionId);
296
+ }
260
297
  if (!this.sessions.has(event.sessionId)) {
261
298
  const session = {
262
299
  sessionId: event.sessionId,
@@ -309,40 +346,67 @@ var TurnAggregator = class extends EventEmitter2 {
309
346
  ctx.errors.push(event.error);
310
347
  break;
311
348
  }
349
+ case "tool_decision": {
350
+ ctx.toolDecisions.push({
351
+ toolName: event.toolName,
352
+ decision: event.decision,
353
+ source: event.source
354
+ });
355
+ break;
356
+ }
312
357
  }
313
358
  if (isNew) {
314
359
  this.emit("turn_start", buildPublicContext(ctx));
315
360
  }
316
- this.resetBoundaryTimer(promptId);
317
361
  }
318
- getContext(promptId) {
319
- const internal = this.contexts.get(promptId);
320
- if (!internal) return void 0;
321
- return buildPublicContext(internal);
322
- }
323
- resetBoundaryTimer(promptId) {
324
- const existing = this.boundaryTimers.get(promptId);
362
+ /**
363
+ * Schedule turn completion after the Stop hook fires, with a delay to allow
364
+ * in-flight OTel events to arrive. Handles two cases:
365
+ * - Stop fires after OTel: waits COMPLETION_DELAY_MS then completes.
366
+ * - Stop fires before OTel: stores a pending stop; addEvent will reschedule
367
+ * once the turn context is created.
368
+ */
369
+ scheduleCompletion(sessionId) {
370
+ const existing = this.completionTimers.get(sessionId);
325
371
  if (existing !== void 0) {
326
372
  clearTimeout(existing);
373
+ this.completionTimers.delete(sessionId);
374
+ }
375
+ if (!this.activeTurns.has(sessionId)) {
376
+ this.pendingStops.add(sessionId);
377
+ return;
327
378
  }
379
+ this._scheduleDelayedComplete(sessionId);
380
+ }
381
+ _scheduleDelayedComplete(sessionId) {
328
382
  const timer = setTimeout(() => {
329
- this.boundaryTimers.delete(promptId);
330
- const internal = this.contexts.get(promptId);
331
- if (internal) {
332
- const session = this.sessions.get(internal.sessionId);
333
- if (session) {
334
- session.completedTurns++;
335
- session.totalCostUsd += internal.apiRequests.reduce((s, r) => s + r.costUsd, 0);
336
- }
337
- this.emit("turn_complete", buildPublicContext(internal));
338
- const cleanupTimer = setTimeout(() => {
339
- this.contexts.delete(promptId);
340
- this.cleanupTimers.delete(promptId);
341
- }, this.cleanupAfterMs);
342
- this.cleanupTimers.set(promptId, cleanupTimer);
343
- }
344
- }, this.boundaryTimeoutMs);
345
- this.boundaryTimers.set(promptId, timer);
383
+ this.completionTimers.delete(sessionId);
384
+ this.completeTurn(sessionId);
385
+ }, _TurnAggregator.COMPLETION_DELAY_MS);
386
+ this.completionTimers.set(sessionId, timer);
387
+ }
388
+ /**
389
+ * Signal that the turn for a given session is complete (called when the Stop
390
+ * hook fires). Emits 'turn_complete' and cleans up the context immediately.
391
+ */
392
+ completeTurn(sessionId) {
393
+ const promptId = this.activeTurns.get(sessionId);
394
+ if (!promptId) return;
395
+ const internal = this.contexts.get(promptId);
396
+ if (!internal) return;
397
+ this.activeTurns.delete(sessionId);
398
+ const session = this.sessions.get(sessionId);
399
+ if (session) {
400
+ session.completedTurns++;
401
+ session.totalCostUsd += internal.apiRequests.reduce((s, r) => s + r.costUsd, 0);
402
+ }
403
+ this.emit("turn_complete", buildPublicContext(internal));
404
+ this.contexts.delete(promptId);
405
+ }
406
+ getContext(promptId) {
407
+ const internal = this.contexts.get(promptId);
408
+ if (!internal) return void 0;
409
+ return buildPublicContext(internal);
346
410
  }
347
411
  };
348
412
 
@@ -450,11 +514,12 @@ ${prompt}`
450
514
  };
451
515
  function parseClassifierResponse(raw) {
452
516
  try {
453
- const jsonMatch = raw.match(/\{[^{}]*\}/);
454
- if (!jsonMatch) {
517
+ const start = raw.indexOf("{");
518
+ const end = raw.lastIndexOf("}");
519
+ if (start === -1 || end === -1 || end < start) {
455
520
  return CLASSIFIER_FALLBACK;
456
521
  }
457
- const parsed = JSON.parse(jsonMatch[0]);
522
+ const parsed = JSON.parse(raw.slice(start, end + 1));
458
523
  if (typeof parsed !== "object" || parsed === null || !("score" in parsed) || !("reason" in parsed)) {
459
524
  return CLASSIFIER_FALLBACK;
460
525
  }
@@ -567,8 +632,8 @@ var GREEN = "\x1B[32m";
567
632
  var YELLOW = "\x1B[33m";
568
633
  var RED = "\x1B[31m";
569
634
  var CYAN = "\x1B[36m";
570
- var LINE_WIDTH = 52;
571
- var WRAP_WIDTH = 50;
635
+ var LINE_WIDTH = 76;
636
+ var WRAP_WIDTH = 74;
572
637
  function formatTime(date) {
573
638
  const hh = String(date.getHours()).padStart(2, "0");
574
639
  const mm = String(date.getMinutes()).padStart(2, "0");
@@ -637,8 +702,11 @@ function printPost(color, content, sessionLabel) {
637
702
  }
638
703
  writeln(DIM + separator() + RESET);
639
704
  }
640
- function printPostAligned(summary, sessionLabel) {
641
- printPost(GREEN, summary, sessionLabel);
705
+ function printPostAligned(_summary, sessionLabel) {
706
+ const time = formatTime(/* @__PURE__ */ new Date());
707
+ const sessionPart = sessionLabel ? ` [${sessionLabel}]` : "";
708
+ const prefix = `\u2500\u2500 POST${sessionPart} \u2500\u2500 ${time} \u2500\u2500 \u2713 Aligned `;
709
+ writeln(DIM + GREEN + separator(prefix) + RESET);
642
710
  }
643
711
  function printPostMisaligned(advisory, sessionLabel) {
644
712
  printPost(RED, advisory, sessionLabel);
@@ -672,9 +740,7 @@ async function startWatch(options = {}) {
672
740
  const port = options.port ?? 4820;
673
741
  const scoreThreshold = options.scoreThreshold ?? 0.6;
674
742
  const receiver = new OtlpReceiver(port);
675
- const aggregator = new TurnAggregator({
676
- boundaryTimeoutMs: options.boundaryTimeoutMs ?? 5e3
677
- });
743
+ const aggregator = new TurnAggregator();
678
744
  const classifier = new Classifier(options.apiKey);
679
745
  const advisor = new Advisor(options.apiKey);
680
746
  let warnedAboutMissingPrompt = false;
@@ -700,6 +766,9 @@ async function startWatch(options = {}) {
700
766
  receiver.on("error", (err) => {
701
767
  printError(`OTLP server error: ${err.message}`);
702
768
  });
769
+ receiver.on("stop", (sessionId) => {
770
+ aggregator.scheduleCompletion(sessionId);
771
+ });
703
772
  aggregator.on("turn_complete", (ctx) => {
704
773
  void runPostAdvisory(ctx, sessionLabels.get(ctx.sessionId));
705
774
  });
@@ -725,8 +794,10 @@ async function startWatch(options = {}) {
725
794
  const result = await advisor.postAdvisory(ctx);
726
795
  if (result.aligned === false) {
727
796
  printPostMisaligned(result.text, sessionLabel);
728
- } else {
797
+ } else if (result.aligned === true) {
729
798
  printPostAligned(result.text, sessionLabel);
799
+ } else {
800
+ printWarning(`Post-advisory: ${result.text}`);
730
801
  }
731
802
  } catch (err) {
732
803
  printError(`Post-advisory failed for prompt ${ctx.promptId}: ${errMsg(err)}`);
@@ -764,14 +835,14 @@ async function startWatch(options = {}) {
764
835
  }
765
836
 
766
837
  // src/cli/setup.ts
767
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
838
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, chmodSync } from "fs";
768
839
  import { join as join2 } from "path";
769
840
  import { homedir as homedir2 } from "os";
770
841
  import { createInterface } from "readline";
771
842
 
772
843
  // src/cli/config.ts
773
844
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
774
- import { execSync } from "child_process";
845
+ import { execSync, execFileSync } from "child_process";
775
846
  import { join } from "path";
776
847
  import { homedir } from "os";
777
848
  var CONFIG_DIR = join(homedir(), ".config", "radar");
@@ -786,9 +857,9 @@ function readConfig() {
786
857
  }
787
858
  function writeConfig(config) {
788
859
  if (!existsSync(CONFIG_DIR)) {
789
- mkdirSync(CONFIG_DIR, { recursive: true });
860
+ mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
790
861
  }
791
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf8");
862
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", { encoding: "utf8", mode: 384 });
792
863
  }
793
864
  function configPath() {
794
865
  return CONFIG_PATH;
@@ -803,15 +874,16 @@ function isOpAvailable() {
803
874
  }
804
875
  function createOpItem(apiKey) {
805
876
  const title = "Radar Anthropic API Key";
806
- const raw = execSync(
807
- `op item create --category=login --title="${title}" "password=${apiKey}" --format json`,
877
+ const raw = execFileSync(
878
+ "op",
879
+ ["item", "create", "--category=login", `--title=${title}`, `password=${apiKey}`, "--format", "json"],
808
880
  { encoding: "utf8" }
809
881
  );
810
882
  const item = JSON.parse(raw);
811
883
  return `op://${item.vault.name}/${item.title}/password`;
812
884
  }
813
885
  function readOpItem(ref) {
814
- return execSync(`op read "${ref}"`, { encoding: "utf8" }).trim();
886
+ return execFileSync("op", ["read", ref], { encoding: "utf8" }).trim();
815
887
  }
816
888
  function resolveStoredApiKey() {
817
889
  const config = readConfig();
@@ -834,6 +906,8 @@ Detail: ${detail}`
834
906
  // src/cli/setup.ts
835
907
  var CLAUDE_DIR = join2(homedir2(), ".claude");
836
908
  var SETTINGS_PATH = join2(CLAUDE_DIR, "settings.json");
909
+ var RADAR_HOOKS_DIR = join2(homedir2(), ".radar", "hooks");
910
+ var STOP_HOOK_PATH = join2(RADAR_HOOKS_DIR, "stop.sh");
837
911
  var OTEL_VARS = {
838
912
  CLAUDE_CODE_ENABLE_TELEMETRY: "1",
839
913
  OTEL_LOGS_EXPORTER: "otlp",
@@ -873,6 +947,51 @@ function writeSettings(settings) {
873
947
  function question(rl, prompt) {
874
948
  return new Promise((resolve) => rl.question(prompt, resolve));
875
949
  }
950
+ var STOP_HOOK_SCRIPT = `#!/bin/bash
951
+ INPUT=$(cat)
952
+ SESSION_ID=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('session_id',''))" 2>/dev/null)
953
+ if [ -n "$SESSION_ID" ]; then
954
+ curl -s -X POST "http://localhost:\${RADAR_PORT:-4820}/v1/hook/stop" \\
955
+ -H "Content-Type: application/json" \\
956
+ -d "{\\"sessionId\\": \\"$SESSION_ID\\"}" &>/dev/null &
957
+ fi
958
+ `;
959
+ function writeStopHook() {
960
+ if (!existsSync2(RADAR_HOOKS_DIR)) {
961
+ mkdirSync2(RADAR_HOOKS_DIR, { recursive: true });
962
+ }
963
+ writeFileSync2(STOP_HOOK_PATH, STOP_HOOK_SCRIPT, "utf8");
964
+ chmodSync(STOP_HOOK_PATH, 493);
965
+ }
966
+ function installStopHooks(settings) {
967
+ const hooks = settings.hooks ?? {};
968
+ const hookEntry = {
969
+ hooks: [
970
+ {
971
+ type: "command",
972
+ command: STOP_HOOK_PATH,
973
+ async: true
974
+ }
975
+ ]
976
+ };
977
+ function appendHook(hookName) {
978
+ const existing = hooks[hookName] ?? [];
979
+ const alreadyInstalled = existing.some((h) => {
980
+ if (typeof h !== "object" || h === null) return false;
981
+ const hooksArr = h.hooks;
982
+ if (!Array.isArray(hooksArr)) return false;
983
+ return hooksArr.some(
984
+ (inner) => typeof inner === "object" && inner !== null && inner.command === STOP_HOOK_PATH
985
+ );
986
+ });
987
+ if (!alreadyInstalled) {
988
+ hooks[hookName] = [...existing, hookEntry];
989
+ }
990
+ }
991
+ appendHook("Stop");
992
+ appendHook("StopFailure");
993
+ settings.hooks = hooks;
994
+ }
876
995
  async function runSetup(preEnteredKey) {
877
996
  writeln2(CYAN2 + BOLD2 + sep("\u2500\u2500 Radar Setup ") + RESET2);
878
997
  let settings;
@@ -891,6 +1010,20 @@ async function runSetup(preEnteredKey) {
891
1010
  writeln2(` ${GREEN2}\u2713${RESET2} ${key}${tag}`);
892
1011
  }
893
1012
  settings.env = { ...existingEnv, ...OTEL_VARS };
1013
+ writeln2();
1014
+ writeln2("Installing Stop hooks...");
1015
+ try {
1016
+ writeStopHook();
1017
+ writeln2(` ${GREEN2}\u2713${RESET2} Hook script written to ${STOP_HOOK_PATH.replace(homedir2(), "~")}`);
1018
+ } catch (err) {
1019
+ writeln2(` ${YELLOW2}\u26A0${RESET2} Failed to write hook script: ${err instanceof Error ? err.message : String(err)}`);
1020
+ }
1021
+ try {
1022
+ installStopHooks(settings);
1023
+ writeln2(` ${GREEN2}\u2713${RESET2} Stop + StopFailure hooks registered in settings.json`);
1024
+ } catch (err) {
1025
+ writeln2(` ${YELLOW2}\u26A0${RESET2} Failed to register hooks: ${err instanceof Error ? err.message : String(err)}`);
1026
+ }
894
1027
  try {
895
1028
  writeSettings(settings);
896
1029
  } catch (err) {
@@ -902,7 +1035,7 @@ async function runSetup(preEnteredKey) {
902
1035
  writeln2(`${GREEN2}\u2713${RESET2} Settings written to ${SETTINGS_PATH.replace(homedir2(), "~")}`);
903
1036
  await promptApiKey(preEnteredKey);
904
1037
  writeln2();
905
- writeln2("Ready. Start Radar in a second terminal pane:");
1038
+ writeln2("Ready. Restart Claude Code for the Stop hooks to take effect, then:");
906
1039
  writeln2(` ${BOLD2}radar watch${RESET2}`);
907
1040
  writeln2(DIM2 + sep() + RESET2);
908
1041
  }
@@ -1027,29 +1160,147 @@ async function useExistingOpRef() {
1027
1160
  writeln2(`${GREEN2}\u2713${RESET2} 1Password reference saved: ${DIM2}${trimmed}${RESET2}`);
1028
1161
  }
1029
1162
 
1163
+ // src/cli/sniff.ts
1164
+ import * as http2 from "http";
1165
+ var RESET3 = "\x1B[0m";
1166
+ var DIM3 = "\x1B[2m";
1167
+ var BOLD3 = "\x1B[1m";
1168
+ var CYAN3 = "\x1B[36m";
1169
+ var YELLOW3 = "\x1B[33m";
1170
+ var GREEN3 = "\x1B[32m";
1171
+ function rawAttrValue(v) {
1172
+ if (v.stringValue !== void 0) return v.stringValue;
1173
+ if (v.intValue !== void 0) return v.intValue;
1174
+ if (v.doubleValue !== void 0) return v.doubleValue;
1175
+ if (v.boolValue !== void 0) return v.boolValue;
1176
+ return void 0;
1177
+ }
1178
+ function attrsToRecord(attrs) {
1179
+ const out = {};
1180
+ for (const a of attrs ?? []) {
1181
+ const v = rawAttrValue(a.value);
1182
+ if (v !== void 0) out[a.key] = v;
1183
+ }
1184
+ return out;
1185
+ }
1186
+ function printCompact(record) {
1187
+ const timeNano = record.timeUnixNano ?? "0";
1188
+ const timestampMs = Math.floor(Number(BigInt(timeNano) / 1000000n));
1189
+ const ts = formatTime(new Date(timestampMs));
1190
+ const eventName = record.body?.stringValue ?? "(no name)";
1191
+ const attrs = attrsToRecord(record.attributes);
1192
+ let nameColour = CYAN3;
1193
+ if (eventName.includes("user_prompt")) nameColour = GREEN3;
1194
+ else if (eventName.includes("api_error")) nameColour = YELLOW3;
1195
+ const attrsStr = Object.keys(attrs).length ? ` ${DIM3}${JSON.stringify(attrs)}${RESET3}` : "";
1196
+ process.stdout.write(
1197
+ `${DIM3}${ts}${RESET3} ${nameColour}${BOLD3}${eventName}${RESET3}${attrsStr}
1198
+ `
1199
+ );
1200
+ }
1201
+ function printJson(record) {
1202
+ const timeNano = record.timeUnixNano ?? "0";
1203
+ const timestampMs = Math.floor(Number(BigInt(timeNano) / 1000000n));
1204
+ const eventName = record.body?.stringValue ?? "";
1205
+ const attrs = attrsToRecord(record.attributes);
1206
+ process.stdout.write(
1207
+ JSON.stringify({ timestampMs, eventName, attrs }) + "\n"
1208
+ );
1209
+ }
1210
+ function forwardBody(body, forwardPort) {
1211
+ const req = http2.request(
1212
+ {
1213
+ hostname: "127.0.0.1",
1214
+ port: forwardPort,
1215
+ path: "/v1/logs",
1216
+ method: "POST",
1217
+ headers: {
1218
+ "Content-Type": "application/json",
1219
+ "Content-Length": Buffer.byteLength(body)
1220
+ }
1221
+ },
1222
+ (res) => {
1223
+ res.resume();
1224
+ }
1225
+ );
1226
+ req.on("error", () => {
1227
+ });
1228
+ req.write(body);
1229
+ req.end();
1230
+ }
1231
+ async function startSniff(options = {}) {
1232
+ const port = options.port ?? 4821;
1233
+ const forwardPort = options.forwardPort ?? 4820;
1234
+ const jsonMode = options.jsonMode ?? false;
1235
+ const server = http2.createServer((req, res) => {
1236
+ if (req.method !== "POST" || req.url !== "/v1/logs") {
1237
+ res.writeHead(404, { "Content-Type": "application/json" });
1238
+ res.end(JSON.stringify({ error: "Not found" }));
1239
+ return;
1240
+ }
1241
+ const chunks = [];
1242
+ req.on("data", (chunk) => {
1243
+ chunks.push(chunk);
1244
+ });
1245
+ req.on("end", () => {
1246
+ res.writeHead(200, { "Content-Type": "application/json" });
1247
+ res.end(JSON.stringify({ partialSuccess: {} }));
1248
+ const body = Buffer.concat(chunks).toString("utf8");
1249
+ forwardBody(body, forwardPort);
1250
+ let payload;
1251
+ try {
1252
+ payload = JSON.parse(body);
1253
+ } catch {
1254
+ process.stderr.write("[radar/sniff] malformed JSON \u2014 skipping print\n");
1255
+ return;
1256
+ }
1257
+ const printer = jsonMode ? printJson : printCompact;
1258
+ for (const resourceLog of payload.resourceLogs ?? []) {
1259
+ for (const scopeLog of resourceLog.scopeLogs ?? []) {
1260
+ for (const record of scopeLog.logRecords ?? []) {
1261
+ printer(record);
1262
+ }
1263
+ }
1264
+ }
1265
+ });
1266
+ req.on("error", (err) => {
1267
+ process.stderr.write(`[radar/sniff] request error: ${String(err)}
1268
+ `);
1269
+ });
1270
+ });
1271
+ await new Promise((resolve, reject) => {
1272
+ server.once("error", reject);
1273
+ server.listen(port, () => resolve());
1274
+ });
1275
+ const mode = jsonMode ? "json" : "compact";
1276
+ process.stdout.write(
1277
+ `${BOLD3}${CYAN3}radar sniff${RESET3} listening on :${port} forwarding to :${forwardPort} mode=${mode}
1278
+
1279
+ `
1280
+ );
1281
+ async function shutdown() {
1282
+ process.stdout.write("\n[radar/sniff] shutting down\n");
1283
+ await new Promise((resolve) => server.close(() => resolve()));
1284
+ process.exit(0);
1285
+ }
1286
+ process.on("SIGINT", () => void shutdown());
1287
+ process.on("SIGTERM", () => void shutdown());
1288
+ }
1289
+
1030
1290
  // src/cli/index.ts
1031
1291
  var program = new Command();
1032
1292
  program.name("radar").description("Non-blocking intent alignment checker for Claude Code, powered by OpenTelemetry").version("0.1.0");
1033
1293
  program.command("watch").description("Start listening for Claude Code telemetry and provide intent advisories").option("-p, --port <number>", "Port to listen on for OTLP log exports", "4820").option(
1034
- "-t, --timeout <ms>",
1035
- "Milliseconds of silence before a turn is considered complete",
1036
- "5000"
1037
- ).option(
1038
1294
  "-s, --threshold <score>",
1039
1295
  "Ambiguity score threshold for triggering a pre-advisory (0.0\u20131.0)",
1040
1296
  "0.6"
1041
1297
  ).option("-k, --api-key <key>", "Anthropic API key (overrides all other sources)").action(async (opts) => {
1042
1298
  const port = parseInt(opts.port, 10);
1043
- const boundaryTimeoutMs = parseInt(opts.timeout, 10);
1044
1299
  const scoreThreshold = parseFloat(opts.threshold);
1045
1300
  if (isNaN(port) || port < 1 || port > 65535) {
1046
1301
  printError("--port must be a number between 1 and 65535");
1047
1302
  process.exit(1);
1048
1303
  }
1049
- if (isNaN(boundaryTimeoutMs) || boundaryTimeoutMs < 500) {
1050
- printError("--timeout must be a number >= 500 (ms)");
1051
- process.exit(1);
1052
- }
1053
1304
  if (isNaN(scoreThreshold) || scoreThreshold < 0 || scoreThreshold > 1) {
1054
1305
  printError("--threshold must be a number between 0.0 and 1.0");
1055
1306
  process.exit(1);
@@ -1065,11 +1316,24 @@ program.command("watch").description("Start listening for Claude Code telemetry
1065
1316
  printError("Anthropic API key not found. Run `radar setup`, set ANTHROPIC_API_KEY, or use --api-key <key>.");
1066
1317
  process.exit(1);
1067
1318
  }
1068
- await startWatch({ port, boundaryTimeoutMs, scoreThreshold, apiKey });
1319
+ await startWatch({ port, scoreThreshold, apiKey });
1069
1320
  });
1070
1321
  program.command("setup").description("Write OTel config to ~/.claude/settings.json and store your Anthropic API key").option("-k, --api-key <key>", "Anthropic API key to store (skips the interactive prompt)").action(async (opts) => {
1071
1322
  await runSetup(opts.apiKey);
1072
1323
  });
1324
+ program.command("sniff").description("Transparent proxy that logs raw OTel events then forwards to Radar").option("-p, --port <number>", "Port to listen on (Claude Code sends here)", "4821").option("-f, --forward <number>", "Port to forward to (where Radar is listening)", "4820").option("--json", "Output full JSON per event instead of compact one-liners").action(async (opts) => {
1325
+ const port = parseInt(opts.port, 10);
1326
+ const forwardPort = parseInt(opts.forward, 10);
1327
+ if (isNaN(port) || port < 1 || port > 65535) {
1328
+ printError("--port must be a number between 1 and 65535");
1329
+ process.exit(1);
1330
+ }
1331
+ if (isNaN(forwardPort) || forwardPort < 1 || forwardPort > 65535) {
1332
+ printError("--forward must be a number between 1 and 65535");
1333
+ process.exit(1);
1334
+ }
1335
+ await startSniff({ port, forwardPort, jsonMode: opts.json ?? false });
1336
+ });
1073
1337
  if (process.argv.length === 2) {
1074
1338
  program.outputHelp();
1075
1339
  process.exit(0);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/index.ts","../../src/receiver/otlp.ts","../../src/aggregator/turn.ts","../../src/analysis/classifier.ts","../../src/analysis/prompts.ts","../../src/util/async.ts","../../src/analysis/advisor.ts","../../src/output/formatter.ts","../../src/cli/watch.ts","../../src/cli/setup.ts","../../src/cli/config.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { startWatch } from './watch.js';\nimport { runSetup } from './setup.js';\nimport { resolveStoredApiKey } from './config.js';\nimport { printError } from '../output/formatter.js';\n\nconst program = new Command();\n\nprogram\n .name('radar')\n .description('Non-blocking intent alignment checker for Claude Code, powered by OpenTelemetry')\n .version('0.1.0');\n\nprogram\n .command('watch')\n .description('Start listening for Claude Code telemetry and provide intent advisories')\n .option('-p, --port <number>', 'Port to listen on for OTLP log exports', '4820')\n .option(\n '-t, --timeout <ms>',\n 'Milliseconds of silence before a turn is considered complete',\n '5000',\n )\n .option(\n '-s, --threshold <score>',\n 'Ambiguity score threshold for triggering a pre-advisory (0.0–1.0)',\n '0.6',\n )\n .option('-k, --api-key <key>', 'Anthropic API key (overrides all other sources)')\n .action(async (opts: { port: string; timeout: string; threshold: string; apiKey?: string }) => {\n const port = parseInt(opts.port, 10);\n const boundaryTimeoutMs = parseInt(opts.timeout, 10);\n const scoreThreshold = parseFloat(opts.threshold);\n\n if (isNaN(port) || port < 1 || port > 65535) {\n printError('--port must be a number between 1 and 65535');\n process.exit(1);\n }\n\n if (isNaN(boundaryTimeoutMs) || boundaryTimeoutMs < 500) {\n printError('--timeout must be a number >= 500 (ms)');\n process.exit(1);\n }\n\n if (isNaN(scoreThreshold) || scoreThreshold < 0 || scoreThreshold > 1) {\n printError('--threshold must be a number between 0.0 and 1.0');\n process.exit(1);\n }\n\n // Resolution order: --api-key flag → ANTHROPIC_API_KEY env → stored config (local or 1Password)\n let apiKey: string | undefined;\n try {\n apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY ?? resolveStoredApiKey();\n } catch (err) {\n printError(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n if (!apiKey) {\n printError('Anthropic API key not found. Run `radar setup`, set ANTHROPIC_API_KEY, or use --api-key <key>.');\n process.exit(1);\n }\n\n await startWatch({ port, boundaryTimeoutMs, scoreThreshold, apiKey });\n });\n\nprogram\n .command('setup')\n .description('Write OTel config to ~/.claude/settings.json and store your Anthropic API key')\n .option('-k, --api-key <key>', 'Anthropic API key to store (skips the interactive prompt)')\n .action(async (opts: { apiKey?: string }) => {\n await runSetup(opts.apiKey);\n });\n\n// Show help if no command is given\nif (process.argv.length === 2) {\n program.outputHelp();\n process.exit(0);\n}\n\nprogram.parse(process.argv);\n","import { EventEmitter } from 'events';\nimport * as http from 'http';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RadarEventType =\n | 'user_prompt'\n | 'tool_result'\n | 'api_request'\n | 'api_error'\n | 'unknown';\n\nexport interface BaseEvent {\n type: RadarEventType;\n promptId: string;\n sessionId: string;\n timestampMs: number;\n}\n\nexport interface UserPromptEvent extends BaseEvent {\n type: 'user_prompt';\n prompt: string;\n promptLength: number;\n}\n\nexport interface ToolResultEvent extends BaseEvent {\n type: 'tool_result';\n toolName: string;\n success: boolean;\n durationMs: number;\n toolParameters?: string;\n resultSizeBytes?: number;\n}\n\nexport interface ApiRequestEvent extends BaseEvent {\n type: 'api_request';\n model: string;\n costUsd: number;\n inputTokens: number;\n outputTokens: number;\n durationMs: number;\n}\n\nexport interface ApiErrorEvent extends BaseEvent {\n type: 'api_error';\n error: string;\n statusCode?: number;\n}\n\nexport type RadarEvent =\n | UserPromptEvent\n | ToolResultEvent\n | ApiRequestEvent\n | ApiErrorEvent;\n\n// ─── OTLP JSON shape (minimal) ────────────────────────────────────────────────\n\ninterface OtlpAttributeValue {\n stringValue?: string;\n intValue?: number;\n doubleValue?: number;\n boolValue?: boolean;\n}\n\ninterface OtlpAttribute {\n key: string;\n value: OtlpAttributeValue;\n}\n\ninterface OtlpLogRecord {\n timeUnixNano?: string;\n severityNumber?: number;\n body?: { stringValue?: string };\n attributes?: OtlpAttribute[];\n}\n\ninterface OtlpScopeLogs {\n scope?: { name?: string };\n logRecords?: OtlpLogRecord[];\n}\n\ninterface OtlpResourceLogs {\n resource?: { attributes?: OtlpAttribute[] };\n scopeLogs?: OtlpScopeLogs[];\n}\n\ninterface OtlpLogsPayload {\n resourceLogs?: OtlpResourceLogs[];\n}\n\n// ─── Attribute helpers ────────────────────────────────────────────────────────\n\ntype AttrValue = string | number | boolean | undefined;\n\n/** Build a lookup Map from an attribute array — O(n) once, then O(1) per key. */\nfunction buildAttrMap(attrs: OtlpAttribute[] | undefined): Map<string, AttrValue> {\n const map = new Map<string, AttrValue>();\n if (!attrs) return map;\n for (const a of attrs) {\n const v = a.value;\n if (v.stringValue !== undefined) map.set(a.key, v.stringValue);\n else if (v.intValue !== undefined) map.set(a.key, v.intValue);\n else if (v.doubleValue !== undefined) map.set(a.key, v.doubleValue);\n else if (v.boolValue !== undefined) map.set(a.key, v.boolValue);\n }\n return map;\n}\n\nfunction getString(map: Map<string, AttrValue>, key: string): string {\n const v = map.get(key);\n return typeof v === 'string' ? v : '';\n}\n\nfunction getNumber(map: Map<string, AttrValue>, key: string): number {\n const v = map.get(key);\n return typeof v === 'number' ? v : 0;\n}\n\nfunction getBool(map: Map<string, AttrValue>, key: string): boolean {\n const v = map.get(key);\n return typeof v === 'boolean' ? v : false;\n}\n\n// ─── Log record → RadarEvent ──────────────────────────────────────────────────\n\nfunction parseLogRecord(record: OtlpLogRecord, sessionId: string): RadarEvent | null {\n const eventName = record.body?.stringValue ?? '';\n\n // timeUnixNano is a string representing nanoseconds (may exceed JS safe int)\n const timeNano = record.timeUnixNano ?? '0';\n const timestampMs = Math.floor(Number(BigInt(timeNano) / 1_000_000n));\n\n // Build the attribute Map once — O(n) — then do O(1) lookups below\n const attrs = buildAttrMap(record.attributes);\n\n const promptId = getString(attrs, 'prompt.id');\n const base: BaseEvent = { type: 'unknown', promptId, sessionId, timestampMs };\n\n switch (eventName) {\n case 'claude_code.user_prompt': {\n const e: UserPromptEvent = {\n ...base,\n type: 'user_prompt',\n prompt: getString(attrs, 'prompt'),\n promptLength: getNumber(attrs, 'prompt_length'),\n };\n return e;\n }\n\n case 'claude_code.tool_result': {\n const e: ToolResultEvent = {\n ...base,\n type: 'tool_result',\n toolName: getString(attrs, 'tool_name'),\n success: getBool(attrs, 'success'),\n durationMs: getNumber(attrs, 'duration_ms'),\n };\n const toolParameters = getString(attrs, 'tool_parameters');\n if (toolParameters) e.toolParameters = toolParameters;\n const resultSizeBytes = getNumber(attrs, 'result_size_bytes');\n if (resultSizeBytes) e.resultSizeBytes = resultSizeBytes;\n return e;\n }\n\n case 'claude_code.api_request': {\n const e: ApiRequestEvent = {\n ...base,\n type: 'api_request',\n model: getString(attrs, 'model'),\n costUsd: getNumber(attrs, 'cost_usd'),\n inputTokens: getNumber(attrs, 'input_tokens'),\n outputTokens: getNumber(attrs, 'output_tokens'),\n durationMs: getNumber(attrs, 'duration_ms'),\n };\n return e;\n }\n\n case 'claude_code.api_error': {\n const e: ApiErrorEvent = {\n ...base,\n type: 'api_error',\n error: getString(attrs, 'error'),\n };\n const statusCode = getNumber(attrs, 'status_code');\n if (statusCode) e.statusCode = statusCode;\n return e;\n }\n\n default:\n return null;\n }\n}\n\n// ─── OtlpReceiver ─────────────────────────────────────────────────────────────\n\nexport class OtlpReceiver extends EventEmitter {\n private readonly port: number;\n private readonly fallbackSessionId: string;\n private server: http.Server | null = null;\n\n constructor(port = 4820) {\n super();\n this.port = port;\n this.fallbackSessionId = `radar-${Math.random().toString(36).slice(2, 10)}`;\n }\n\n private deriveSessionId(resourceAttrs: Map<string, AttrValue>): string {\n const sessionId = resourceAttrs.get('session.id');\n if (typeof sessionId === 'string' && sessionId) return sessionId;\n\n const instanceId = resourceAttrs.get('service.instance.id');\n if (typeof instanceId === 'string' && instanceId) return instanceId;\n\n const pid = resourceAttrs.get('process.pid');\n if (pid !== undefined) return String(pid);\n\n return this.fallbackSessionId;\n }\n\n start(): Promise<void> {\n return new Promise((resolve, reject) => {\n const server = http.createServer((req, res) => {\n this.handleRequest(req, res);\n });\n\n server.on('error', (err) => {\n this.emit('error', err);\n });\n\n server.listen(this.port, () => {\n this.server = server;\n resolve();\n });\n\n // If listen itself throws before the callback\n server.once('error', reject);\n });\n }\n\n stop(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (!this.server) {\n resolve();\n return;\n }\n this.server.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n this.server = null;\n });\n }\n\n private handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void {\n if (req.method !== 'POST' || req.url !== '/v1/logs') {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not found' }));\n return;\n }\n\n const chunks: Buffer[] = [];\n\n req.on('data', (chunk: Buffer) => {\n chunks.push(chunk);\n });\n\n req.on('end', () => {\n // Respond immediately — never block\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ partialSuccess: {} }));\n\n const body = Buffer.concat(chunks).toString('utf8');\n let payload: OtlpLogsPayload;\n\n try {\n payload = JSON.parse(body) as OtlpLogsPayload;\n } catch (err) {\n process.stderr.write(`[radar/otlp] malformed JSON: ${String(err)}\\n`);\n return;\n }\n\n this.processPayload(payload);\n });\n\n req.on('error', (err) => {\n process.stderr.write(`[radar/otlp] request error: ${String(err)}\\n`);\n });\n }\n\n private processPayload(payload: OtlpLogsPayload): void {\n for (const resourceLog of payload.resourceLogs ?? []) {\n const resourceAttrs = buildAttrMap(resourceLog.resource?.attributes);\n const sessionId = this.deriveSessionId(resourceAttrs);\n for (const scopeLog of resourceLog.scopeLogs ?? []) {\n for (const record of scopeLog.logRecords ?? []) {\n const event = parseLogRecord(record, sessionId);\n if (event) {\n this.emit('event', event);\n }\n }\n }\n }\n }\n}\n","import { EventEmitter } from 'events';\nimport type {\n UserPromptEvent,\n ToolResultEvent,\n ApiRequestEvent,\n ApiErrorEvent,\n} from '../receiver/otlp.js';\n\nexport interface ToolResultSummary {\n toolName: string;\n success: boolean;\n durationMs: number;\n bashCommand?: string;\n resultSizeBytes?: number;\n}\n\nexport interface ApiRequestSummary {\n model: string;\n costUsd: number;\n inputTokens: number;\n outputTokens: number;\n durationMs: number;\n}\n\nexport interface SessionSummary {\n sessionId: string;\n label: string; // \"S1\", \"S2\", etc.\n turnCount: number; // turns started\n completedTurns: number; // turns that hit boundary timeout\n startedAt: number; // ms since epoch\n lastSeenAt: number; // ms since epoch\n totalCostUsd: number; // sum across completed turns\n}\n\nexport interface TurnContext {\n promptId: string;\n sessionId: string;\n prompt: string;\n promptLength: number;\n startedAt: number;\n toolResults: ToolResultSummary[];\n apiRequests: ApiRequestSummary[];\n errors: string[];\n // Computed helpers:\n totalCostUsd: number;\n totalInputTokens: number;\n totalOutputTokens: number;\n toolNames: string[];\n}\n\nexport interface TurnAggregatorOptions {\n boundaryTimeoutMs?: number;\n cleanupAfterMs?: number;\n}\n\ntype InternalTurnContext = Omit<\n TurnContext,\n 'totalCostUsd' | 'totalInputTokens' | 'totalOutputTokens' | 'toolNames'\n>;\n\n\nfunction extractBashCommand(toolParameters: unknown): string | undefined {\n if (toolParameters === undefined || toolParameters === null) return undefined;\n\n if (typeof toolParameters === 'string') {\n try {\n const parsed = JSON.parse(toolParameters) as unknown;\n if (typeof parsed === 'object' && parsed !== null && 'command' in parsed) {\n const cmd = (parsed as Record<string, unknown>).command;\n if (typeof cmd === 'string') {\n return cmd.slice(0, 200);\n }\n }\n } catch {\n // not JSON — fall back to raw string truncated\n return toolParameters.slice(0, 200);\n }\n }\n\n if (typeof toolParameters === 'object' && 'command' in (toolParameters as object)) {\n const cmd = (toolParameters as Record<string, unknown>).command;\n if (typeof cmd === 'string') {\n return cmd.slice(0, 200);\n }\n }\n\n return undefined;\n}\n\nfunction buildPublicContext(internal: InternalTurnContext): TurnContext {\n const totalCostUsd = internal.apiRequests.reduce((sum, r) => sum + r.costUsd, 0);\n const totalInputTokens = internal.apiRequests.reduce((sum, r) => sum + r.inputTokens, 0);\n const totalOutputTokens = internal.apiRequests.reduce((sum, r) => sum + r.outputTokens, 0);\n const toolNames = [...new Set(internal.toolResults.map((t) => t.toolName))];\n\n return {\n ...internal,\n totalCostUsd,\n totalInputTokens,\n totalOutputTokens,\n toolNames,\n };\n}\n\nexport class TurnAggregator extends EventEmitter {\n private readonly boundaryTimeoutMs: number;\n private readonly cleanupAfterMs: number;\n\n private readonly contexts = new Map<string, InternalTurnContext>();\n private readonly boundaryTimers = new Map<string, ReturnType<typeof setTimeout>>();\n // Tracks pending context-cleanup timers so they can be cancelled if a promptId\n // is reused before the cleanup window expires, preventing silent context deletion.\n private readonly cleanupTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\n private readonly sessions = new Map<string, SessionSummary>();\n private sessionCounter = 0;\n\n constructor(options?: TurnAggregatorOptions) {\n super();\n this.boundaryTimeoutMs = options?.boundaryTimeoutMs ?? 5000;\n this.cleanupAfterMs = options?.cleanupAfterMs ?? 300_000;\n }\n\n getSessions(): SessionSummary[] {\n return [...this.sessions.values()];\n }\n\n getSession(sessionId: string): SessionSummary | undefined {\n return this.sessions.get(sessionId);\n }\n\n addEvent(event: UserPromptEvent | ToolResultEvent | ApiRequestEvent | ApiErrorEvent): void {\n const { promptId } = event;\n\n const isNew = !this.contexts.has(promptId);\n\n if (isNew) {\n // Cancel any pending cleanup for this promptId (handles promptId reuse\n // within the cleanup window — prevents an orphaned timer from silently\n // deleting the freshly created context mid-flight).\n const pendingCleanup = this.cleanupTimers.get(promptId);\n if (pendingCleanup !== undefined) {\n clearTimeout(pendingCleanup);\n this.cleanupTimers.delete(promptId);\n }\n\n const internal: InternalTurnContext = {\n promptId,\n sessionId: event.sessionId,\n prompt: '',\n promptLength: 0,\n startedAt: Date.now(),\n toolResults: [],\n apiRequests: [],\n errors: [],\n };\n this.contexts.set(promptId, internal);\n\n // Session tracking\n if (!this.sessions.has(event.sessionId)) {\n const session: SessionSummary = {\n sessionId: event.sessionId,\n label: `S${++this.sessionCounter}`,\n turnCount: 1,\n completedTurns: 0,\n startedAt: Date.now(),\n lastSeenAt: Date.now(),\n totalCostUsd: 0,\n };\n this.sessions.set(event.sessionId, session);\n this.emit('session_start', { ...session });\n } else {\n const session = this.sessions.get(event.sessionId)!;\n session.lastSeenAt = Date.now();\n session.turnCount++;\n }\n }\n\n const ctx = this.contexts.get(promptId)!;\n\n switch (event.type) {\n case 'user_prompt': {\n ctx.prompt = event.prompt ?? '';\n ctx.promptLength = event.promptLength ?? ctx.prompt.length;\n break;\n }\n case 'tool_result': {\n const summary: ToolResultSummary = {\n toolName: event.toolName,\n success: event.success,\n durationMs: event.durationMs,\n resultSizeBytes: event.resultSizeBytes,\n };\n if (event.toolName === 'Bash') {\n summary.bashCommand = extractBashCommand(event.toolParameters);\n }\n ctx.toolResults.push(summary);\n break;\n }\n case 'api_request': {\n ctx.apiRequests.push({\n model: event.model,\n costUsd: event.costUsd,\n inputTokens: event.inputTokens,\n outputTokens: event.outputTokens,\n durationMs: event.durationMs,\n });\n break;\n }\n case 'api_error': {\n ctx.errors.push(event.error);\n break;\n }\n }\n\n if (isNew) {\n this.emit('turn_start', buildPublicContext(ctx));\n }\n\n this.resetBoundaryTimer(promptId);\n }\n\n getContext(promptId: string): TurnContext | undefined {\n const internal = this.contexts.get(promptId);\n if (!internal) return undefined;\n return buildPublicContext(internal);\n }\n\n private resetBoundaryTimer(promptId: string): void {\n const existing = this.boundaryTimers.get(promptId);\n if (existing !== undefined) {\n clearTimeout(existing);\n }\n\n const timer = setTimeout(() => {\n this.boundaryTimers.delete(promptId);\n const internal = this.contexts.get(promptId);\n if (internal) {\n // Update session stats\n const session = this.sessions.get(internal.sessionId);\n if (session) {\n session.completedTurns++;\n session.totalCostUsd += internal.apiRequests.reduce((s, r) => s + r.costUsd, 0);\n }\n this.emit('turn_complete', buildPublicContext(internal));\n // Schedule cleanup — store handle so it can be cancelled if the promptId\n // is reused before the window expires.\n const cleanupTimer = setTimeout(() => {\n this.contexts.delete(promptId);\n this.cleanupTimers.delete(promptId);\n }, this.cleanupAfterMs);\n this.cleanupTimers.set(promptId, cleanupTimer);\n }\n }, this.boundaryTimeoutMs);\n\n this.boundaryTimers.set(promptId, timer);\n }\n}\n","import Anthropic from '@anthropic-ai/sdk';\nimport { CLASSIFIER_SYSTEM_PROMPT } from './prompts.js';\nimport { withTimeout } from '../util/async.js';\n\nexport interface ClassifierResult {\n score: number; // 0.0 – 1.0\n reason: string;\n}\n\nconst CLASSIFIER_TIMEOUT_MS = 3000;\nconst CLASSIFIER_FALLBACK: ClassifierResult = {\n score: 0.5,\n reason: 'Classification timed out',\n};\n\nexport class Classifier {\n private readonly client: Anthropic;\n\n constructor(apiKey?: string) {\n this.client = new Anthropic({ apiKey: apiKey ?? process.env.ANTHROPIC_API_KEY });\n }\n\n async classify(prompt: string): Promise<ClassifierResult> {\n const classifyPromise = (async (): Promise<ClassifierResult> => {\n const message = await this.client.messages.create({\n model: 'claude-haiku-4-5',\n max_tokens: 100,\n system: CLASSIFIER_SYSTEM_PROMPT,\n messages: [\n {\n role: 'user',\n content: `User prompt to classify:\\n${prompt}`,\n },\n ],\n });\n\n const content = message.content[0];\n if (content.type !== 'text') {\n return CLASSIFIER_FALLBACK;\n }\n\n return parseClassifierResponse(content.text);\n })();\n\n return withTimeout(classifyPromise, CLASSIFIER_TIMEOUT_MS, CLASSIFIER_FALLBACK);\n }\n}\n\nfunction parseClassifierResponse(raw: string): ClassifierResult {\n try {\n // Extract JSON — handle cases where the model wraps it in markdown code blocks\n const jsonMatch = raw.match(/\\{[^{}]*\\}/);\n if (!jsonMatch) {\n return CLASSIFIER_FALLBACK;\n }\n\n const parsed = JSON.parse(jsonMatch[0]) as unknown;\n\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n !('score' in parsed) ||\n !('reason' in parsed)\n ) {\n return CLASSIFIER_FALLBACK;\n }\n\n const obj = parsed as Record<string, unknown>;\n const score = Number(obj.score);\n const reason = String(obj.reason);\n\n if (isNaN(score) || score < 0 || score > 1) {\n return CLASSIFIER_FALLBACK;\n }\n\n return { score, reason };\n } catch {\n return CLASSIFIER_FALLBACK;\n }\n}\n","// Classifier system prompt — used with Haiku (passed as `system:` field)\nexport const CLASSIFIER_SYSTEM_PROMPT: string = `You are an intent-ambiguity classifier for Claude Code, an AI coding assistant.\n\nYour job is to score how likely a user prompt will cause Claude to confidently execute a reasonable but WRONG interpretation — leading to wasted work, unintended changes, or the user having to undo what Claude did.\n\nBe CONSERVATIVE. Only flag genuine ambiguity. Most prompts are clear enough.\n\nCommon failure modes to watch for:\n- Scope ambiguity: \"clean up this module\", \"refactor the service\" — which files? what counts as clean?\n- Target ambiguity: \"the API is slow\", \"fix the tests\" — which API? which tests?\n- Intent ambiguity: \"update the tests\", \"improve error handling\" — add new tests? fix existing? what kind of improvement?\n- Symptom vs cause: \"auth isn't working\" — fix the symptom or find the root cause?\n\nScore guide:\n- 0.0–0.3: Clear and specific. Claude knows exactly what to do.\n- 0.4–0.59: Some ambiguity, but Claude will likely ask for clarification or make a safe default choice.\n- 0.6–0.79: Real risk. Claude will pick an interpretation and run with it — the user might not like the result.\n- 0.8–1.0: High risk. Multiple very different valid interpretations; high chance of wasted work.\n\nDo NOT flag:\n- Questions or requests for explanation (\"how does X work?\", \"what is Y?\")\n- Conversational messages (\"thanks\", \"ok\", \"sounds good\")\n- Read-only or low-stakes requests (\"show me\", \"list\", \"describe\")\n\nRespond with ONLY a JSON object on a single line:\n{\"score\": <0.0-1.0>, \"reason\": \"<one sentence explaining the ambiguity or why it is clear>\"}`;\n\n// Pre-advisory prompt — used with Sonnet\n// {prompt}, {score}, {reason} will be replaced\nexport const PRE_ADVISORY_SYSTEM_PROMPT: string = `You are a concise advisory assistant helping a developer clarify their intent before sending a prompt to Claude Code.\n\nA classifier has flagged the prompt as potentially ambiguous. Your job is to help the user understand the risk and either rephrase or confirm their intent.\n\nOutput at most 4 lines of plain text. No markdown headers, no bullet symbols, no lists. Just plain sentences.\n\nCover:\n1. What Claude will most likely do (the probable misinterpretation that could go wrong)\n2. The specific scope or target risk (what is under-specified)\n3. One clarifying question OR a concrete rephrasing that removes the ambiguity\n\nBe direct and brief. Do not repeat the prompt back verbatim.`;\n\nexport const PRE_ADVISORY_USER_TEMPLATE: string = `Prompt: {prompt}\n\nAmbiguity score: {score}\nReason: {reason}`;\n\n// Post-advisory prompt — used with Sonnet\n// {prompt}, {tools}, {cost}, {tokens} will be replaced\nexport const POST_ADVISORY_SYSTEM_PROMPT: string = `You are a post-execution reviewer for Claude Code. You compare what the user asked for against what Claude actually did.\n\nGiven the original prompt and a summary of tool activity, determine alignment and give brief feedback.\n\nIf aligned: respond with exactly one line starting with \"✓\" — a brief confirmation — followed by a one-line tools/cost summary.\nIf misaligned: respond with a line starting with \"✗\" describing what went wrong, then a line starting with \"→\" containing an exact re-prompt suggestion in quotes.\n\nFormat: plain text, no markdown. Maximum 5 lines total.`;\n\nexport const POST_ADVISORY_USER_TEMPLATE: string = `Original prompt: {prompt}\n\nTool activity: {toolSummary}\nTotal cost: {totalCost}\nTotal tokens: {totalTokens}`;\n","/**\n * Race a promise against a timeout. Cancels the timer when the promise settles,\n * preventing dangling timer handles in long-running processes.\n */\nexport function withTimeout<T>(promise: Promise<T>, ms: number, fallback: T): Promise<T> {\n let timerId: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<T>((resolve) => {\n timerId = setTimeout(() => resolve(fallback), ms);\n });\n return Promise.race([promise, timeout]).finally(() => clearTimeout(timerId));\n}\n","import Anthropic from '@anthropic-ai/sdk';\nimport type { TurnContext } from '../aggregator/turn.js';\nimport type { ClassifierResult } from './classifier.js';\nimport {\n PRE_ADVISORY_SYSTEM_PROMPT,\n PRE_ADVISORY_USER_TEMPLATE,\n POST_ADVISORY_SYSTEM_PROMPT,\n POST_ADVISORY_USER_TEMPLATE,\n} from './prompts.js';\nimport { withTimeout } from '../util/async.js';\n\nexport interface AdvisoryResult {\n text: string;\n aligned?: boolean; // only set for post-advisory\n}\n\nconst ADVISORY_TIMEOUT_MS = 10000;\n\nconst PRE_ADVISORY_FALLBACK: AdvisoryResult = { text: 'Advisory unavailable (timeout)' };\nconst POST_ADVISORY_FALLBACK_TIMEOUT: AdvisoryResult = { text: 'Advisory unavailable (timeout)', aligned: undefined };\nconst POST_ADVISORY_FALLBACK_ERROR: AdvisoryResult = { text: 'Advisory unavailable (error)', aligned: undefined };\n\nexport class Advisor {\n private readonly client: Anthropic;\n\n constructor(apiKey?: string) {\n this.client = new Anthropic({ apiKey: apiKey ?? process.env.ANTHROPIC_API_KEY });\n }\n\n // Pre-advisory: called when classifier score >= 0.6\n async preAdvisory(prompt: string, classification: ClassifierResult): Promise<AdvisoryResult> {\n const userMessage = PRE_ADVISORY_USER_TEMPLATE\n .replace('{prompt}', prompt)\n .replace('{score}', classification.score.toFixed(2))\n .replace('{reason}', classification.reason);\n\n const advisoryPromise = (async (): Promise<AdvisoryResult> => {\n const message = await this.client.messages.create({\n model: 'claude-sonnet-4-5',\n max_tokens: 200,\n system: PRE_ADVISORY_SYSTEM_PROMPT,\n messages: [{ role: 'user', content: userMessage }],\n });\n\n const content = message.content[0];\n if (content.type !== 'text') {\n return PRE_ADVISORY_FALLBACK;\n }\n\n return { text: content.text.trim() };\n })();\n\n return withTimeout(advisoryPromise, ADVISORY_TIMEOUT_MS, PRE_ADVISORY_FALLBACK);\n }\n\n // Post-advisory: called on turn complete\n async postAdvisory(context: TurnContext): Promise<AdvisoryResult> {\n const toolSummary = buildToolSummary(context);\n const totalCost = `$${context.totalCostUsd.toFixed(3)}`;\n const totalTokens = (context.totalInputTokens + context.totalOutputTokens).toLocaleString();\n\n const userMessage = POST_ADVISORY_USER_TEMPLATE\n .replace('{prompt}', context.prompt)\n .replace('{toolSummary}', toolSummary)\n .replace('{totalCost}', totalCost)\n .replace('{totalTokens}', totalTokens);\n\n const advisoryPromise = (async (): Promise<AdvisoryResult> => {\n const message = await this.client.messages.create({\n model: 'claude-sonnet-4-5',\n max_tokens: 300,\n system: POST_ADVISORY_SYSTEM_PROMPT,\n messages: [{ role: 'user', content: userMessage }],\n });\n\n const content = message.content[0];\n if (content.type !== 'text') {\n return POST_ADVISORY_FALLBACK_ERROR;\n }\n\n const text = content.text.trim();\n const aligned = text.startsWith('✓') ? true : text.startsWith('✗') ? false : undefined;\n\n return { text, aligned };\n })();\n\n return withTimeout(advisoryPromise, ADVISORY_TIMEOUT_MS, POST_ADVISORY_FALLBACK_TIMEOUT);\n }\n}\n\nfunction buildToolSummary(context: TurnContext): string {\n const parts: string[] = [];\n\n // Group tool calls by name, tracking Bash separately\n const toolCounts = new Map<string, number>();\n const bashCommands: string[] = [];\n let bashCallCount = 0;\n\n for (const result of context.toolResults) {\n if (result.toolName === 'Bash') {\n bashCallCount++;\n if (result.bashCommand) {\n bashCommands.push(`'${result.bashCommand}'`);\n }\n } else {\n toolCounts.set(result.toolName, (toolCounts.get(result.toolName) ?? 0) + 1);\n }\n }\n\n // Add non-bash tools\n for (const [toolName, count] of toolCounts.entries()) {\n parts.push(count === 1 ? toolName : `${toolName} (${count} calls)`);\n }\n\n // Add bash summary\n if (bashCallCount > 0) {\n if (bashCommands.length > 0) {\n const bashLabel = bashCommands.length <= 3\n ? `Bash: ${bashCommands.join(', ')}`\n : `Bash: ${bashCommands.slice(0, 3).join(', ')} +${bashCommands.length - 3} more`;\n parts.push(bashLabel);\n } else {\n parts.push(`Bash (${bashCallCount} calls)`);\n }\n }\n\n // Token and cost summary\n const totalTokens = context.totalInputTokens + context.totalOutputTokens;\n if (totalTokens > 0) {\n parts.push(`${totalTokens.toLocaleString()} tokens`);\n }\n if (context.totalCostUsd > 0) {\n parts.push(`$${context.totalCostUsd.toFixed(3)}`);\n }\n\n return parts.join(' · ') || 'No tools used';\n}\n","// ─── ANSI helpers ─────────────────────────────────────────────────────────────\n\nconst RESET = '\\x1b[0m';\nconst DIM = '\\x1b[2m';\nconst BOLD = '\\x1b[1m';\nconst GREEN = '\\x1b[32m';\nconst YELLOW = '\\x1b[33m';\nconst RED = '\\x1b[31m';\nconst CYAN = '\\x1b[36m';\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst LINE_WIDTH = 52;\nconst WRAP_WIDTH = 50;\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/**\n * Format a Date as \"HH:MM:SS\".\n */\nexport function formatTime(date: Date): string {\n const hh = String(date.getHours()).padStart(2, '0');\n const mm = String(date.getMinutes()).padStart(2, '0');\n const ss = String(date.getSeconds()).padStart(2, '0');\n return `${hh}:${mm}:${ss}`;\n}\n\n/**\n * Build a separator line of exactly LINE_WIDTH chars, padded with \"─\".\n * The prefix is included in the total width.\n */\nfunction separator(prefix = ''): string {\n const dashes = '─'.repeat(Math.max(0, LINE_WIDTH - prefix.length));\n return prefix + dashes;\n}\n\n/**\n * Wrap text to at most maxWidth characters per line, preserving existing newlines.\n * Continuation lines are indented with `indent` spaces.\n */\nfunction wrapText(text: string, maxWidth: number, indent: string): string[] {\n const rawLines = text.split('\\n');\n const result: string[] = [];\n\n for (const rawLine of rawLines) {\n const words = rawLine.split(' ');\n let current = '';\n\n for (const word of words) {\n if (current === '') {\n current = word;\n } else if (current.length + 1 + word.length <= maxWidth) {\n current += ' ' + word;\n } else {\n result.push(current);\n current = indent + word;\n }\n }\n\n if (current !== '') {\n result.push(current);\n }\n }\n\n return result;\n}\n\n/**\n * Render advisory text as output lines. The first line is left as-is (the\n * caller has already formatted it). Subsequent lines and long first lines are\n * word-wrapped at WRAP_WIDTH with a 2-space indent on continuations.\n */\nfunction renderAdvisoryLines(advisory: string): string[] {\n return wrapText(advisory.trim(), WRAP_WIDTH, ' ');\n}\n\nfunction writeln(text = ''): void {\n process.stdout.write(text + '\\n');\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Print a \"clear\" one-liner — dim, no box.\n *\n * Example:\n * ── PRE ── 14:23:07 ── score: 0.34 ── ✓ Clear ─────\n * ── PRE [S1] ── 14:23:07 ── score: 0.34 ── ✓ Clear ─────\n */\nexport function printPreClear(score: number, sessionLabel?: string): void {\n const time = formatTime(new Date());\n const sessionPart = sessionLabel ? ` [${sessionLabel}]` : '';\n const prefix = `── PRE${sessionPart} ── ${time} ── score: ${score.toFixed(2)} ── ✓ Clear `;\n const line = separator(prefix);\n writeln(DIM + line + RESET);\n}\n\n/**\n * Print a pre-advisory warning box with a yellow header.\n *\n * Example:\n * ── PRE ── 14:25:12 ── score: 0.78 ─────────────────\n * ── PRE [S1] ── 14:25:12 ── score: 0.78 ─────────────────\n */\nexport function printPreAdvisory(score: number, advisory: string, sessionLabel?: string): void {\n const time = formatTime(new Date());\n const sessionPart = sessionLabel ? ` [${sessionLabel}]` : '';\n const headerPrefix = `── PRE${sessionPart} ── ${time} ── score: ${score.toFixed(2)} `;\n const header = separator(headerPrefix);\n\n writeln(YELLOW + BOLD + header + RESET);\n\n const lines = renderAdvisoryLines(advisory);\n for (const line of lines) {\n writeln(line);\n }\n\n writeln(DIM + separator() + RESET);\n}\n\n/** Shared implementation for both post-advisory box variants. */\nfunction printPost(color: string, content: string, sessionLabel?: string): void {\n const time = formatTime(new Date());\n const sessionPart = sessionLabel ? ` [${sessionLabel}]` : '';\n const header = separator(`── POST${sessionPart} ── ${time} `);\n\n writeln(color + BOLD + header + RESET);\n\n const lines = renderAdvisoryLines(content);\n for (const line of lines) {\n writeln(line);\n }\n\n writeln(DIM + separator() + RESET);\n}\n\n/**\n * Print a post-advisory \"aligned\" box with a green header.\n *\n * Example:\n * ── POST ── 14:25:38 ────────────────────────────────\n * ── POST [S1] ── 14:25:38 ────────────────────────────────\n */\nexport function printPostAligned(summary: string, sessionLabel?: string): void {\n printPost(GREEN, summary, sessionLabel);\n}\n\n/**\n * Print a post-advisory \"misaligned\" box with a red header.\n *\n * Example:\n * ── POST ── 14:31:02 ────────────────────────────────\n * ── POST [S1] ── 14:31:02 ────────────────────────────────\n */\nexport function printPostMisaligned(advisory: string, sessionLabel?: string): void {\n printPost(RED, advisory, sessionLabel);\n}\n\n/**\n * Print a dim cyan session-connected line.\n *\n * Example:\n * ── S1 connected (abcd1234…) ── 14:23:07\n */\nexport function printSessionStart(label: string, sessionId: string): void {\n const time = formatTime(new Date());\n const shortId = sessionId.slice(0, 8);\n writeln(DIM + CYAN + `── ${label} connected (${shortId}…) ── ${time}` + RESET);\n}\n\n/**\n * Print the startup banner.\n *\n * Example:\n * ── Radar v0.1.0 ────────────────────────────────────\n * Listening on localhost:4820\n * Waiting for Claude Code telemetry...\n * Set OTEL_LOG_USER_PROMPTS=1 for prompt content analysis.\n * ────────────────────────────────────────────────────\n */\nexport function printBanner(port: number): void {\n const headerPrefix = '── Radar v0.1.0 ';\n const header = separator(headerPrefix);\n\n writeln(CYAN + BOLD + header + RESET);\n writeln(`Listening on localhost:${port}`);\n writeln('Waiting for Claude Code telemetry...');\n writeln('Set OTEL_LOG_USER_PROMPTS=1 for prompt content analysis.');\n writeln(DIM + separator() + RESET);\n}\n\n/**\n * Print a warning message in yellow.\n */\nexport function printWarning(message: string): void {\n writeln(YELLOW + '⚠ ' + message + RESET);\n}\n\n/**\n * Print an error message in red.\n */\nexport function printError(message: string): void {\n writeln(RED + '✗ ' + message + RESET);\n}\n","import { OtlpReceiver } from '../receiver/otlp.js';\nimport type { RadarEvent } from '../receiver/otlp.js';\nimport { TurnAggregator } from '../aggregator/turn.js';\nimport type { TurnContext, SessionSummary } from '../aggregator/turn.js';\nimport { Classifier } from '../analysis/classifier.js';\nimport { Advisor } from '../analysis/advisor.js';\nimport {\n printBanner,\n printPreClear,\n printPreAdvisory,\n printPostAligned,\n printPostMisaligned,\n printSessionStart,\n printWarning,\n printError,\n} from '../output/formatter.js';\n\nexport interface WatchOptions {\n port?: number;\n boundaryTimeoutMs?: number;\n scoreThreshold?: number;\n apiKey?: string;\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nexport async function startWatch(options: WatchOptions = {}): Promise<void> {\n const port = options.port ?? 4820;\n const scoreThreshold = options.scoreThreshold ?? 0.6;\n\n const receiver = new OtlpReceiver(port);\n const aggregator = new TurnAggregator({\n boundaryTimeoutMs: options.boundaryTimeoutMs ?? 5000,\n });\n const classifier = new Classifier(options.apiKey);\n const advisor = new Advisor(options.apiKey);\n\n // Track whether we've seen any events without prompt content — warn once\n let warnedAboutMissingPrompt = false;\n // Prevent double-classification if the same promptId is seen more than once\n const classifying = new Set<string>();\n // Map sessionId → display label (\"S1\", \"S2\", …)\n const sessionLabels = new Map<string, string>();\n\n // ── Wire: session_start → label tracking + display ────────────────────────\n aggregator.on('session_start', (s: SessionSummary) => {\n sessionLabels.set(s.sessionId, s.label);\n printSessionStart(s.label, s.sessionId);\n });\n\n // ── Wire: OtlpReceiver → TurnAggregator + classification ───────────────────\n //\n // A single listener handles both jobs in order: aggregation first so that\n // TurnContext exists by the time classification starts, then classification\n // for user_prompt events.\n receiver.on('event', (event: RadarEvent) => {\n // 1. Always feed the aggregator\n aggregator.addEvent(event);\n\n // 2. On user_prompt, trigger pre-advisory (fire-and-forget)\n if (event.type !== 'user_prompt') return;\n\n if (classifying.has(event.promptId)) return;\n classifying.add(event.promptId);\n\n if (!event.prompt && !warnedAboutMissingPrompt) {\n warnedAboutMissingPrompt = true;\n printWarning(\n 'Prompt content not available. Set OTEL_LOG_USER_PROMPTS=1 to enable intent analysis.',\n );\n }\n\n void runPreAdvisory(event.prompt, event.promptId, sessionLabels.get(event.sessionId));\n });\n\n receiver.on('error', (err: Error) => {\n printError(`OTLP server error: ${err.message}`);\n });\n\n // ── Wire: TurnAggregator → post-advisory ───────────────────────────────────\n aggregator.on('turn_complete', (ctx: TurnContext) => {\n void runPostAdvisory(ctx, sessionLabels.get(ctx.sessionId));\n });\n\n // ── Pre-advisory pipeline ───────────────────────────────────────────────────\n async function runPreAdvisory(prompt: string, promptId: string, sessionLabel?: string): Promise<void> {\n try {\n if (!prompt) return; // no prompt text — skip silently\n\n const result = await classifier.classify(prompt);\n\n if (result.score < scoreThreshold) {\n printPreClear(result.score, sessionLabel);\n return;\n }\n\n // Score >= threshold: escalate to Sonnet\n const advisory = await advisor.preAdvisory(prompt, result);\n printPreAdvisory(result.score, advisory.text, sessionLabel);\n } catch (err) {\n printError(`Pre-advisory failed for prompt ${promptId}: ${errMsg(err)}`);\n } finally {\n // Always release the deduplication guard once pre-advisory finishes,\n // whether it succeeded, failed, or was skipped due to missing prompt.\n classifying.delete(promptId);\n }\n }\n\n // ── Post-advisory pipeline ──────────────────────────────────────────────────\n async function runPostAdvisory(ctx: TurnContext, sessionLabel?: string): Promise<void> {\n if (!ctx.prompt) return; // no prompt text — skip silently\n\n try {\n const result = await advisor.postAdvisory(ctx);\n\n if (result.aligned === false) {\n printPostMisaligned(result.text, sessionLabel);\n } else {\n printPostAligned(result.text, sessionLabel);\n }\n } catch (err) {\n printError(`Post-advisory failed for prompt ${ctx.promptId}: ${errMsg(err)}`);\n }\n }\n\n // ── OTel env var check ─────────────────────────────────────────────────────\n const requiredOtelVars = [\n 'CLAUDE_CODE_ENABLE_TELEMETRY',\n 'OTEL_LOGS_EXPORTER',\n 'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT',\n ];\n const missingVars = requiredOtelVars.filter((v) => !process.env[v]);\n if (missingVars.length > 0) {\n printWarning('OTel env vars not configured. Run `radar setup` and restart Claude Code.');\n }\n\n // ── Start ───────────────────────────────────────────────────────────────────\n try {\n await receiver.start();\n } catch (err) {\n const msg = errMsg(err);\n if (msg.includes('EADDRINUSE')) {\n printError(`Port ${port} is already in use. Use --port <n> to choose a different port.`);\n } else {\n printError(`Failed to start OTLP receiver: ${msg}`);\n }\n process.exit(1);\n }\n\n printBanner(port);\n\n // ── Graceful shutdown ───────────────────────────────────────────────────────\n async function shutdown(): Promise<void> {\n process.stdout.write('\\n');\n printWarning('Shutting down Radar...');\n await receiver.stop();\n process.exit(0);\n }\n\n process.on('SIGINT', () => void shutdown());\n process.on('SIGTERM', () => void shutdown());\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { createInterface } from 'node:readline';\nimport {\n readConfig,\n writeConfig,\n configPath,\n isOpAvailable,\n createOpItem,\n} from './config.js';\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst CLAUDE_DIR = join(homedir(), '.claude');\nconst SETTINGS_PATH = join(CLAUDE_DIR, 'settings.json');\n\nconst OTEL_VARS: Record<string, string> = {\n CLAUDE_CODE_ENABLE_TELEMETRY: '1',\n OTEL_LOGS_EXPORTER: 'otlp',\n OTEL_EXPORTER_OTLP_PROTOCOL: 'http/json',\n OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: 'http://localhost:4820/v1/logs',\n OTEL_LOG_USER_PROMPTS: '1',\n OTEL_LOG_TOOL_DETAILS: '1',\n OTEL_LOGS_EXPORT_INTERVAL: '2000',\n};\n\n// ─── ANSI helpers (self-contained, no formatter dependency) ──────────────────\n\nconst RESET = '\\x1b[0m';\nconst DIM = '\\x1b[2m';\nconst BOLD = '\\x1b[1m';\nconst GREEN = '\\x1b[32m';\nconst YELLOW = '\\x1b[33m';\nconst CYAN = '\\x1b[36m';\n\nconst LINE_WIDTH = 52;\n\nfunction sep(prefix = ''): string {\n return prefix + '─'.repeat(Math.max(0, LINE_WIDTH - prefix.length));\n}\n\nfunction writeln(text = ''): void {\n process.stdout.write(text + '\\n');\n}\n\n// ─── Settings helpers ─────────────────────────────────────────────────────────\n\nfunction readSettings(): Record<string, unknown> {\n if (!existsSync(SETTINGS_PATH)) return {};\n try {\n return JSON.parse(readFileSync(SETTINGS_PATH, 'utf8')) as Record<string, unknown>;\n } catch {\n throw new Error(`Could not parse ${SETTINGS_PATH}. Fix the JSON syntax and try again.`);\n }\n}\n\nfunction writeSettings(settings: Record<string, unknown>): void {\n if (!existsSync(CLAUDE_DIR)) {\n mkdirSync(CLAUDE_DIR, { recursive: true });\n }\n writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\\n', 'utf8');\n}\n\n// ─── readline helper ──────────────────────────────────────────────────────────\n\nfunction question(rl: ReturnType<typeof createInterface>, prompt: string): Promise<string> {\n return new Promise((resolve) => rl.question(prompt, resolve));\n}\n\n// ─── Main ─────────────────────────────────────────────────────────────────────\n\nexport async function runSetup(preEnteredKey?: string): Promise<void> {\n writeln(CYAN + BOLD + sep('── Radar Setup ') + RESET);\n\n // ── Write OTel config ─────────────────────────────────────────────────────\n let settings: Record<string, unknown>;\n try {\n settings = readSettings();\n } catch (err) {\n writeln(YELLOW + '✗ ' + (err instanceof Error ? err.message : String(err)) + RESET);\n process.exit(1);\n }\n\n const existingEnv = (settings.env as Record<string, string> | undefined) ?? {};\n\n writeln(`Writing OTel config to ${SETTINGS_PATH.replace(homedir(), '~')}...`);\n writeln();\n\n for (const [key, value] of Object.entries(OTEL_VARS)) {\n const alreadySet = key in existingEnv && existingEnv[key] === value;\n const tag = alreadySet ? DIM + ' (already set)' + RESET : '';\n writeln(` ${GREEN}✓${RESET} ${key}${tag}`);\n }\n\n settings.env = { ...existingEnv, ...OTEL_VARS };\n\n try {\n writeSettings(settings);\n } catch (err) {\n writeln();\n writeln(YELLOW + '✗ Failed to write settings: ' + (err instanceof Error ? err.message : String(err)) + RESET);\n process.exit(1);\n }\n\n writeln();\n writeln(`${GREEN}✓${RESET} Settings written to ${SETTINGS_PATH.replace(homedir(), '~')}`);\n\n // ── API key ───────────────────────────────────────────────────────────────\n await promptApiKey(preEnteredKey);\n\n // ── Done ──────────────────────────────────────────────────────────────────\n writeln();\n writeln('Ready. Start Radar in a second terminal pane:');\n writeln(` ${BOLD}radar watch${RESET}`);\n writeln(DIM + sep() + RESET);\n}\n\n// ─── API key prompt ────────────────────────────────────────────────────────────\n\nasync function promptApiKey(preEnteredKey?: string): Promise<void> {\n writeln();\n\n let apiKey: string | undefined = preEnteredKey;\n\n // ── If no key was passed via --api-key, check for an existing one or prompt ─\n if (!apiKey) {\n const config = readConfig();\n const envKey = process.env.ANTHROPIC_API_KEY;\n const hasStored = config.apiKey ?? config.apiKeyRef;\n const activeKey = envKey ?? config.apiKey;\n\n if (hasStored || envKey) {\n // Show what's already configured\n if (envKey) {\n const masked = envKey.slice(0, 10) + '…' + envKey.slice(-4);\n writeln(`${GREEN}✓${RESET} API key found via ANTHROPIC_API_KEY env var: ${DIM}${masked}${RESET}`);\n } else if (config.apiKeyRef) {\n writeln(`${GREEN}✓${RESET} API key linked via 1Password: ${DIM}${config.apiKeyRef}${RESET}`);\n } else if (config.apiKey) {\n const masked = config.apiKey.slice(0, 10) + '…' + config.apiKey.slice(-4);\n writeln(`${GREEN}✓${RESET} API key stored locally: ${DIM}${masked}${RESET}`);\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const answer = await question(rl, ' Replace it? [y/N] ');\n rl.close();\n\n if (answer.trim().toLowerCase() !== 'y') return;\n\n // Fall through to prompt for new key\n void activeKey; // suppress unused warning\n } else {\n writeln(`${YELLOW}⚠${RESET} No API key found. Radar needs one to run analysis.`);\n }\n\n // Prompt for the key\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const entered = await question(rl, ' Enter your Anthropic API key: ');\n rl.close();\n\n apiKey = entered.trim();\n if (!apiKey) {\n writeln(`${YELLOW}⚠${RESET} No key entered — skipping. Re-run setup or use --api-key <key>.`);\n return;\n }\n }\n\n // ── Ask where to store it ─────────────────────────────────────────────────\n await promptStorage(apiKey);\n}\n\n// ─── Storage choice ────────────────────────────────────────────────────────────\n\nasync function promptStorage(apiKey: string): Promise<void> {\n const opAvailable = isOpAvailable();\n\n writeln();\n writeln('Where would you like to store the API key?');\n writeln(` ${BOLD}1${RESET} Local disk ${DIM}(${configPath().replace(homedir(), '~')})${RESET}`);\n\n if (opAvailable) {\n writeln(` ${BOLD}2${RESET} 1Password ${DIM}(recommended)${RESET}`);\n } else {\n writeln(` ${DIM}2 1Password (op CLI not found — see instructions below)${RESET}`);\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const answer = await question(rl, ` Choice [1${opAvailable ? '/2' : ''}]: `);\n rl.close();\n\n const choice = answer.trim();\n\n if (choice === '2') {\n if (!opAvailable) {\n writeln();\n writeln(`${YELLOW}⚠${RESET} 1Password CLI (op) is not installed.`);\n writeln(' To set it up:');\n writeln(` ${DIM}1. Install: https://developer.1password.com/docs/cli/get-started${RESET}`);\n writeln(` ${DIM}2. Sign in: op signin${RESET}`);\n writeln(` ${DIM}3. Re-run: radar setup --api-key <key>${RESET}`);\n return;\n }\n\n await storeIn1Password(apiKey);\n return;\n }\n\n // Default: local disk\n storeLocally(apiKey);\n}\n\nfunction storeLocally(apiKey: string): void {\n const config = readConfig();\n writeConfig({ ...config, apiKey, apiKeyRef: undefined });\n writeln();\n writeln(`${GREEN}✓${RESET} API key saved to ${configPath().replace(homedir(), '~')}`);\n}\n\nasync function storeIn1Password(apiKey: string): Promise<void> {\n writeln();\n writeln('How would you like to store it in 1Password?');\n writeln(` ${BOLD}1${RESET} Create a new item ${DIM}(radar will add it for you)${RESET}`);\n writeln(` ${BOLD}2${RESET} Use an existing item ${DIM}(enter an op:// reference)${RESET}`);\n\n const rl1 = createInterface({ input: process.stdin, output: process.stdout });\n const choice = await question(rl1, ' Choice [1/2]: ');\n rl1.close();\n\n if (choice.trim() === '2') {\n await useExistingOpRef();\n return;\n }\n\n // Create a new item\n writeln();\n writeln('Creating item in 1Password...');\n\n try {\n const ref = createOpItem(apiKey);\n const config = readConfig();\n writeConfig({ ...config, apiKey: undefined, apiKeyRef: ref });\n writeln(`${GREEN}✓${RESET} API key stored in 1Password`);\n writeln(` Reference: ${DIM}${ref}${RESET}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n writeln(`${YELLOW}✗${RESET} Failed to store in 1Password: ${msg}`);\n writeln(' Make sure you are signed in: ' + DIM + 'op signin' + RESET);\n\n // Offer local fallback\n const rl2 = createInterface({ input: process.stdin, output: process.stdout });\n const fallback = await question(rl2, ' Store on local disk instead? [Y/n] ');\n rl2.close();\n\n if (fallback.trim().toLowerCase() !== 'n') {\n storeLocally(apiKey);\n }\n }\n}\n\nasync function useExistingOpRef(): Promise<void> {\n writeln();\n writeln(` Enter the ${BOLD}op://${RESET} reference for your API key.`);\n writeln(` ${DIM}Example: op://Personal/Anthropic/credential${RESET}`);\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ref = await question(rl, ' Reference: ');\n rl.close();\n\n const trimmed = ref.trim();\n if (!trimmed.startsWith('op://')) {\n writeln(`${YELLOW}⚠${RESET} Invalid reference — must start with op://. Re-run setup to try again.`);\n return;\n }\n\n const config = readConfig();\n writeConfig({ ...config, apiKey: undefined, apiKeyRef: trimmed });\n writeln(`${GREEN}✓${RESET} 1Password reference saved: ${DIM}${trimmed}${RESET}`);\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { execSync } from 'node:child_process';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\n// ─── Paths ─────────────────────────────────────────────────────────────────────\n\nconst CONFIG_DIR = join(homedir(), '.config', 'radar');\nconst CONFIG_PATH = join(CONFIG_DIR, 'config.json');\n\n// ─── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface RadarConfig {\n apiKey?: string; // plaintext local storage\n apiKeyRef?: string; // 1Password reference e.g. op://vault/item/field\n}\n\n// ─── Read / write ──────────────────────────────────────────────────────────────\n\nexport function readConfig(): RadarConfig {\n if (!existsSync(CONFIG_PATH)) return {};\n try {\n return JSON.parse(readFileSync(CONFIG_PATH, 'utf8')) as RadarConfig;\n } catch {\n return {};\n }\n}\n\nexport function writeConfig(config: RadarConfig): void {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true });\n }\n writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\\n', 'utf8');\n}\n\nexport function configPath(): string {\n return CONFIG_PATH;\n}\n\n// ─── 1Password helpers ─────────────────────────────────────────────────────────\n\nexport function isOpAvailable(): boolean {\n try {\n execSync('op --version', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Run `op item create` and return the `op://` reference for the stored key. */\nexport function createOpItem(apiKey: string): string {\n const title = 'Radar Anthropic API Key';\n const raw = execSync(\n `op item create --category=login --title=\"${title}\" \"password=${apiKey}\" --format json`,\n { encoding: 'utf8' },\n );\n const item = JSON.parse(raw) as { title: string; vault: { name: string } };\n return `op://${item.vault.name}/${item.title}/password`;\n}\n\n/** Read a secret from 1Password by its `op://` reference. */\nexport function readOpItem(ref: string): string {\n return execSync(`op read \"${ref}\"`, { encoding: 'utf8' }).trim();\n}\n\n// ─── Key resolution ────────────────────────────────────────────────────────────\n\n/**\n * Resolve the API key from the stored config.\n * Tries plaintext `apiKey` first, then fetches via `op read` if `apiKeyRef` is set.\n * Returns undefined if no key is configured.\n * Throws if a 1Password reference is configured but the op read fails.\n */\nexport function resolveStoredApiKey(): string | undefined {\n const config = readConfig();\n if (config.apiKey) return config.apiKey;\n if (config.apiKeyRef) {\n try {\n return readOpItem(config.apiKeyRef);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Failed to read API key from 1Password (${config.apiKeyRef}).\\n` +\n `Make sure you are signed in: op signin\\n` +\n `Detail: ${detail}`,\n );\n }\n }\n return undefined;\n}\n"],"mappings":"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","names":["EventEmitter","Anthropic","Anthropic","readFileSync","writeFileSync","existsSync","mkdirSync","join","homedir","join","homedir","RESET","DIM","BOLD","GREEN","YELLOW","CYAN","LINE_WIDTH","writeln","existsSync","readFileSync","mkdirSync","writeFileSync","rl"]}
1
+ {"version":3,"sources":["../../src/cli/index.ts","../../src/receiver/otlp.ts","../../src/aggregator/turn.ts","../../src/analysis/classifier.ts","../../src/analysis/prompts.ts","../../src/util/async.ts","../../src/analysis/advisor.ts","../../src/output/formatter.ts","../../src/cli/watch.ts","../../src/cli/setup.ts","../../src/cli/config.ts","../../src/cli/sniff.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { startWatch } from './watch.js';\nimport { runSetup } from './setup.js';\nimport { startSniff } from './sniff.js';\nimport { resolveStoredApiKey } from './config.js';\nimport { printError } from '../output/formatter.js';\n\nconst program = new Command();\n\nprogram\n .name('radar')\n .description('Non-blocking intent alignment checker for Claude Code, powered by OpenTelemetry')\n .version('0.1.0');\n\nprogram\n .command('watch')\n .description('Start listening for Claude Code telemetry and provide intent advisories')\n .option('-p, --port <number>', 'Port to listen on for OTLP log exports', '4820')\n .option(\n '-s, --threshold <score>',\n 'Ambiguity score threshold for triggering a pre-advisory (0.0–1.0)',\n '0.6',\n )\n .option('-k, --api-key <key>', 'Anthropic API key (overrides all other sources)')\n .action(async (opts: { port: string; threshold: string; apiKey?: string }) => {\n const port = parseInt(opts.port, 10);\n const scoreThreshold = parseFloat(opts.threshold);\n\n if (isNaN(port) || port < 1 || port > 65535) {\n printError('--port must be a number between 1 and 65535');\n process.exit(1);\n }\n\n if (isNaN(scoreThreshold) || scoreThreshold < 0 || scoreThreshold > 1) {\n printError('--threshold must be a number between 0.0 and 1.0');\n process.exit(1);\n }\n\n // Resolution order: --api-key flag → ANTHROPIC_API_KEY env → stored config (local or 1Password)\n let apiKey: string | undefined;\n try {\n apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY ?? resolveStoredApiKey();\n } catch (err) {\n printError(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n if (!apiKey) {\n printError('Anthropic API key not found. Run `radar setup`, set ANTHROPIC_API_KEY, or use --api-key <key>.');\n process.exit(1);\n }\n\n await startWatch({ port, scoreThreshold, apiKey });\n });\n\nprogram\n .command('setup')\n .description('Write OTel config to ~/.claude/settings.json and store your Anthropic API key')\n .option('-k, --api-key <key>', 'Anthropic API key to store (skips the interactive prompt)')\n .action(async (opts: { apiKey?: string }) => {\n await runSetup(opts.apiKey);\n });\n\nprogram\n .command('sniff')\n .description('Transparent proxy that logs raw OTel events then forwards to Radar')\n .option('-p, --port <number>', 'Port to listen on (Claude Code sends here)', '4821')\n .option('-f, --forward <number>', 'Port to forward to (where Radar is listening)', '4820')\n .option('--json', 'Output full JSON per event instead of compact one-liners')\n .action(async (opts: { port: string; forward: string; json?: boolean }) => {\n const port = parseInt(opts.port, 10);\n const forwardPort = parseInt(opts.forward, 10);\n\n if (isNaN(port) || port < 1 || port > 65535) {\n printError('--port must be a number between 1 and 65535');\n process.exit(1);\n }\n\n if (isNaN(forwardPort) || forwardPort < 1 || forwardPort > 65535) {\n printError('--forward must be a number between 1 and 65535');\n process.exit(1);\n }\n\n await startSniff({ port, forwardPort, jsonMode: opts.json ?? false });\n });\n\n// Show help if no command is given\nif (process.argv.length === 2) {\n program.outputHelp();\n process.exit(0);\n}\n\nprogram.parse(process.argv);\n","import { EventEmitter } from 'events';\nimport * as http from 'http';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RadarEventType =\n | 'user_prompt'\n | 'tool_result'\n | 'api_request'\n | 'api_error'\n | 'tool_decision'\n | 'unknown';\n\nexport interface BaseEvent {\n type: RadarEventType;\n promptId: string;\n sessionId: string;\n timestampMs: number;\n}\n\nexport interface UserPromptEvent extends BaseEvent {\n type: 'user_prompt';\n prompt: string;\n promptLength: number;\n}\n\nexport interface ToolResultEvent extends BaseEvent {\n type: 'tool_result';\n toolName: string;\n success: boolean;\n durationMs: number;\n toolParameters?: string;\n resultSizeBytes?: number;\n}\n\nexport interface ApiRequestEvent extends BaseEvent {\n type: 'api_request';\n model: string;\n costUsd: number;\n inputTokens: number;\n outputTokens: number;\n durationMs: number;\n}\n\nexport interface ApiErrorEvent extends BaseEvent {\n type: 'api_error';\n error: string;\n statusCode?: number;\n}\n\nexport interface ToolDecisionEvent extends BaseEvent {\n type: 'tool_decision';\n toolName: string;\n decision: string;\n source: string;\n}\n\nexport type RadarEvent =\n | UserPromptEvent\n | ToolResultEvent\n | ApiRequestEvent\n | ApiErrorEvent\n | ToolDecisionEvent;\n\n// ─── OTLP JSON shape (minimal) ────────────────────────────────────────────────\n\ninterface OtlpAttributeValue {\n stringValue?: string;\n intValue?: number;\n doubleValue?: number;\n boolValue?: boolean;\n}\n\ninterface OtlpAttribute {\n key: string;\n value: OtlpAttributeValue;\n}\n\ninterface OtlpLogRecord {\n timeUnixNano?: string;\n severityNumber?: number;\n body?: { stringValue?: string };\n attributes?: OtlpAttribute[];\n}\n\ninterface OtlpScopeLogs {\n scope?: { name?: string };\n logRecords?: OtlpLogRecord[];\n}\n\ninterface OtlpResourceLogs {\n resource?: { attributes?: OtlpAttribute[] };\n scopeLogs?: OtlpScopeLogs[];\n}\n\ninterface OtlpLogsPayload {\n resourceLogs?: OtlpResourceLogs[];\n}\n\n// ─── Attribute helpers ────────────────────────────────────────────────────────\n\ntype AttrValue = string | number | boolean | undefined;\n\n/** Build a lookup Map from an attribute array — O(n) once, then O(1) per key. */\nfunction buildAttrMap(attrs: OtlpAttribute[] | undefined): Map<string, AttrValue> {\n const map = new Map<string, AttrValue>();\n if (!attrs) return map;\n for (const a of attrs) {\n const v = a.value;\n if (v.stringValue !== undefined) map.set(a.key, v.stringValue);\n else if (v.intValue !== undefined) map.set(a.key, v.intValue);\n else if (v.doubleValue !== undefined) map.set(a.key, v.doubleValue);\n else if (v.boolValue !== undefined) map.set(a.key, v.boolValue);\n }\n return map;\n}\n\nfunction getString(map: Map<string, AttrValue>, key: string): string {\n const v = map.get(key);\n return typeof v === 'string' ? v : '';\n}\n\nfunction getNumber(map: Map<string, AttrValue>, key: string): number {\n const v = map.get(key);\n return typeof v === 'number' ? v : 0;\n}\n\nfunction getBool(map: Map<string, AttrValue>, key: string): boolean {\n const v = map.get(key);\n return typeof v === 'boolean' ? v : false;\n}\n\n// ─── Log record → RadarEvent ──────────────────────────────────────────────────\n\nfunction parseLogRecord(record: OtlpLogRecord, sessionId: string): RadarEvent | null {\n const eventName = record.body?.stringValue ?? '';\n\n // timeUnixNano is a string representing nanoseconds (may exceed JS safe int)\n const timeNano = record.timeUnixNano ?? '0';\n const timestampMs = Math.floor(Number(BigInt(timeNano) / 1_000_000n));\n\n // Build the attribute Map once — O(n) — then do O(1) lookups below\n const attrs = buildAttrMap(record.attributes);\n\n const promptId = getString(attrs, 'prompt.id');\n // Claude Code sends session.id as a log-record attribute (not a resource attribute).\n // Prefer it over the resource-derived sessionId so that the Stop hook's session ID\n // (which also comes from Claude Code) matches what we store in activeTurns.\n const recordSessionId = getString(attrs, 'session.id');\n const effectiveSessionId = recordSessionId || sessionId;\n const base: BaseEvent = { type: 'unknown', promptId, sessionId: effectiveSessionId, timestampMs };\n\n switch (eventName) {\n case 'claude_code.user_prompt': {\n const e: UserPromptEvent = {\n ...base,\n type: 'user_prompt',\n prompt: getString(attrs, 'prompt'),\n promptLength: getNumber(attrs, 'prompt_length'),\n };\n return e;\n }\n\n case 'claude_code.tool_result': {\n const e: ToolResultEvent = {\n ...base,\n type: 'tool_result',\n toolName: getString(attrs, 'tool_name'),\n success: getBool(attrs, 'success'),\n durationMs: getNumber(attrs, 'duration_ms'),\n };\n const toolParameters = getString(attrs, 'tool_parameters');\n if (toolParameters) e.toolParameters = toolParameters;\n const resultSizeBytes = getNumber(attrs, 'result_size_bytes');\n if (resultSizeBytes) e.resultSizeBytes = resultSizeBytes;\n return e;\n }\n\n case 'claude_code.api_request': {\n const e: ApiRequestEvent = {\n ...base,\n type: 'api_request',\n model: getString(attrs, 'model'),\n costUsd: getNumber(attrs, 'cost_usd'),\n inputTokens: getNumber(attrs, 'input_tokens'),\n outputTokens: getNumber(attrs, 'output_tokens'),\n durationMs: getNumber(attrs, 'duration_ms'),\n };\n return e;\n }\n\n case 'claude_code.api_error': {\n const e: ApiErrorEvent = {\n ...base,\n type: 'api_error',\n error: getString(attrs, 'error'),\n };\n const statusCode = getNumber(attrs, 'status_code');\n if (statusCode) e.statusCode = statusCode;\n return e;\n }\n\n case 'claude_code.tool_decision': {\n const e: ToolDecisionEvent = {\n ...base,\n type: 'tool_decision',\n toolName: getString(attrs, 'tool_name'),\n decision: getString(attrs, 'decision'),\n source: getString(attrs, 'source'),\n };\n return e;\n }\n\n default:\n return null;\n }\n}\n\n// ─── OtlpReceiver ─────────────────────────────────────────────────────────────\n\nexport class OtlpReceiver extends EventEmitter {\n private readonly port: number;\n private readonly fallbackSessionId: string;\n private server: http.Server | null = null;\n\n constructor(port = 4820) {\n super();\n this.port = port;\n this.fallbackSessionId = `radar-${Math.random().toString(36).slice(2, 10)}`;\n }\n\n private deriveSessionId(resourceAttrs: Map<string, AttrValue>): string {\n const sessionId = resourceAttrs.get('session.id');\n if (typeof sessionId === 'string' && sessionId) return sessionId;\n\n const instanceId = resourceAttrs.get('service.instance.id');\n if (typeof instanceId === 'string' && instanceId) return instanceId;\n\n const pid = resourceAttrs.get('process.pid');\n if (pid !== undefined) return String(pid);\n\n return this.fallbackSessionId;\n }\n\n start(): Promise<void> {\n return new Promise((resolve, reject) => {\n const server = http.createServer((req, res) => {\n this.handleRequest(req, res);\n });\n\n server.on('error', (err) => {\n this.emit('error', err);\n });\n\n server.listen(this.port, () => {\n this.server = server;\n resolve();\n });\n\n // If listen itself throws before the callback\n server.once('error', reject);\n });\n }\n\n stop(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (!this.server) {\n resolve();\n return;\n }\n this.server.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n this.server = null;\n });\n }\n\n private handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void {\n if (req.method === 'POST' && req.url === '/v1/hook/stop') {\n this.handleStop(req, res);\n return;\n }\n\n if (req.method !== 'POST' || req.url !== '/v1/logs') {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not found' }));\n return;\n }\n\n const chunks: Buffer[] = [];\n\n req.on('data', (chunk: Buffer) => {\n chunks.push(chunk);\n });\n\n req.on('end', () => {\n // Respond immediately — never block\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ partialSuccess: {} }));\n\n const body = Buffer.concat(chunks).toString('utf8');\n let payload: OtlpLogsPayload;\n\n try {\n payload = JSON.parse(body) as OtlpLogsPayload;\n } catch (err) {\n process.stderr.write(`[radar/otlp] malformed JSON: ${String(err)}\\n`);\n return;\n }\n\n this.processPayload(payload);\n });\n\n req.on('error', (err) => {\n process.stderr.write(`[radar/otlp] request error: ${String(err)}\\n`);\n });\n }\n\n private handleStop(req: http.IncomingMessage, res: http.ServerResponse): void {\n const chunks: Buffer[] = [];\n\n req.on('data', (chunk: Buffer) => {\n chunks.push(chunk);\n });\n\n req.on('end', () => {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ ok: true }));\n\n try {\n const body = JSON.parse(Buffer.concat(chunks).toString('utf8')) as { sessionId?: string };\n const sessionId = typeof body.sessionId === 'string' ? body.sessionId.trim() : '';\n if (sessionId) {\n this.emit('stop', sessionId);\n }\n } catch {\n // ignore malformed stop payloads\n }\n });\n\n req.on('error', (err) => {\n process.stderr.write(`[radar/otlp] stop request error: ${String(err)}\\n`);\n });\n }\n\n private processPayload(payload: OtlpLogsPayload): void {\n for (const resourceLog of payload.resourceLogs ?? []) {\n const resourceAttrs = buildAttrMap(resourceLog.resource?.attributes);\n const sessionId = this.deriveSessionId(resourceAttrs);\n for (const scopeLog of resourceLog.scopeLogs ?? []) {\n for (const record of scopeLog.logRecords ?? []) {\n const event = parseLogRecord(record, sessionId);\n if (event) {\n this.emit('event', event);\n }\n }\n }\n }\n }\n}\n","import { EventEmitter } from 'events';\nimport type {\n UserPromptEvent,\n ToolResultEvent,\n ApiRequestEvent,\n ApiErrorEvent,\n ToolDecisionEvent,\n} from '../receiver/otlp.js';\n\nexport interface ToolResultSummary {\n toolName: string;\n success: boolean;\n durationMs: number;\n bashCommand?: string;\n resultSizeBytes?: number;\n}\n\nexport interface ApiRequestSummary {\n model: string;\n costUsd: number;\n inputTokens: number;\n outputTokens: number;\n durationMs: number;\n}\n\nexport interface ToolDecisionSummary {\n toolName: string;\n decision: string;\n source: string;\n}\n\nexport interface SessionSummary {\n sessionId: string;\n label: string; // \"S1\", \"S2\", etc.\n turnCount: number; // turns started\n completedTurns: number; // turns completed via Stop hook\n startedAt: number; // ms since epoch\n lastSeenAt: number; // ms since epoch\n totalCostUsd: number; // sum across completed turns\n}\n\nexport interface TurnContext {\n promptId: string;\n sessionId: string;\n prompt: string;\n promptLength: number;\n startedAt: number;\n toolResults: ToolResultSummary[];\n apiRequests: ApiRequestSummary[];\n errors: string[];\n toolDecisions: ToolDecisionSummary[];\n // Computed helpers:\n totalCostUsd: number;\n totalInputTokens: number;\n totalOutputTokens: number;\n toolNames: string[];\n}\n\ntype InternalTurnContext = Omit<\n TurnContext,\n 'totalCostUsd' | 'totalInputTokens' | 'totalOutputTokens' | 'toolNames'\n>;\n\n\nfunction extractBashCommand(toolParameters: unknown): string | undefined {\n if (toolParameters === undefined || toolParameters === null) return undefined;\n\n if (typeof toolParameters === 'string') {\n try {\n const parsed = JSON.parse(toolParameters) as unknown;\n if (typeof parsed === 'object' && parsed !== null && 'command' in parsed) {\n const cmd = (parsed as Record<string, unknown>).command;\n if (typeof cmd === 'string') {\n return cmd.slice(0, 200);\n }\n }\n } catch {\n // not JSON — fall back to raw string truncated\n return toolParameters.slice(0, 200);\n }\n }\n\n if (typeof toolParameters === 'object' && 'command' in (toolParameters as object)) {\n const cmd = (toolParameters as Record<string, unknown>).command;\n if (typeof cmd === 'string') {\n return cmd.slice(0, 200);\n }\n }\n\n return undefined;\n}\n\nfunction buildPublicContext(internal: InternalTurnContext): TurnContext {\n const totalCostUsd = internal.apiRequests.reduce((sum, r) => sum + r.costUsd, 0);\n const totalInputTokens = internal.apiRequests.reduce((sum, r) => sum + r.inputTokens, 0);\n const totalOutputTokens = internal.apiRequests.reduce((sum, r) => sum + r.outputTokens, 0);\n const toolNames = [...new Set(internal.toolResults.map((t) => t.toolName))];\n\n return {\n ...internal,\n totalCostUsd,\n totalInputTokens,\n totalOutputTokens,\n toolNames,\n };\n}\n\nexport class TurnAggregator extends EventEmitter {\n // Delay to allow in-flight OTel events to arrive after Stop hook fires.\n // Must be longer than OTEL_LOGS_EXPORT_INTERVAL (configured at 2000ms).\n static readonly COMPLETION_DELAY_MS = 3500;\n\n private readonly contexts = new Map<string, InternalTurnContext>();\n // Maps sessionId → promptId for the most recent active turn\n private readonly activeTurns = new Map<string, string>();\n // Sessions where Stop hook fired before any OTel events arrived\n private readonly pendingStops = new Set<string>();\n // Active delayed-completion timers, keyed by sessionId\n private readonly completionTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\n private readonly sessions = new Map<string, SessionSummary>();\n private sessionCounter = 0;\n\n getSessions(): SessionSummary[] {\n return [...this.sessions.values()];\n }\n\n getSession(sessionId: string): SessionSummary | undefined {\n return this.sessions.get(sessionId);\n }\n\n addEvent(\n event: UserPromptEvent | ToolResultEvent | ApiRequestEvent | ApiErrorEvent | ToolDecisionEvent,\n ): void {\n const { promptId } = event;\n\n const isNew = !this.contexts.has(promptId);\n\n if (isNew) {\n const internal: InternalTurnContext = {\n promptId,\n sessionId: event.sessionId,\n prompt: '',\n promptLength: 0,\n startedAt: Date.now(),\n toolResults: [],\n apiRequests: [],\n errors: [],\n toolDecisions: [],\n };\n this.contexts.set(promptId, internal);\n this.activeTurns.set(event.sessionId, promptId);\n\n // If a stop arrived before this turn's OTel events, schedule completion now\n if (this.pendingStops.has(event.sessionId)) {\n this.pendingStops.delete(event.sessionId);\n this._scheduleDelayedComplete(event.sessionId);\n }\n\n // Session tracking\n if (!this.sessions.has(event.sessionId)) {\n const session: SessionSummary = {\n sessionId: event.sessionId,\n label: `S${++this.sessionCounter}`,\n turnCount: 1,\n completedTurns: 0,\n startedAt: Date.now(),\n lastSeenAt: Date.now(),\n totalCostUsd: 0,\n };\n this.sessions.set(event.sessionId, session);\n this.emit('session_start', { ...session });\n } else {\n const session = this.sessions.get(event.sessionId)!;\n session.lastSeenAt = Date.now();\n session.turnCount++;\n }\n }\n\n const ctx = this.contexts.get(promptId)!;\n\n switch (event.type) {\n case 'user_prompt': {\n ctx.prompt = event.prompt ?? '';\n ctx.promptLength = event.promptLength ?? ctx.prompt.length;\n break;\n }\n case 'tool_result': {\n const summary: ToolResultSummary = {\n toolName: event.toolName,\n success: event.success,\n durationMs: event.durationMs,\n resultSizeBytes: event.resultSizeBytes,\n };\n if (event.toolName === 'Bash') {\n summary.bashCommand = extractBashCommand(event.toolParameters);\n }\n ctx.toolResults.push(summary);\n break;\n }\n case 'api_request': {\n ctx.apiRequests.push({\n model: event.model,\n costUsd: event.costUsd,\n inputTokens: event.inputTokens,\n outputTokens: event.outputTokens,\n durationMs: event.durationMs,\n });\n break;\n }\n case 'api_error': {\n ctx.errors.push(event.error);\n break;\n }\n case 'tool_decision': {\n ctx.toolDecisions.push({\n toolName: event.toolName,\n decision: event.decision,\n source: event.source,\n });\n break;\n }\n }\n\n if (isNew) {\n this.emit('turn_start', buildPublicContext(ctx));\n }\n }\n\n /**\n * Schedule turn completion after the Stop hook fires, with a delay to allow\n * in-flight OTel events to arrive. Handles two cases:\n * - Stop fires after OTel: waits COMPLETION_DELAY_MS then completes.\n * - Stop fires before OTel: stores a pending stop; addEvent will reschedule\n * once the turn context is created.\n */\n scheduleCompletion(sessionId: string): void {\n // Cancel any existing timer for this session\n const existing = this.completionTimers.get(sessionId);\n if (existing !== undefined) {\n clearTimeout(existing);\n this.completionTimers.delete(sessionId);\n }\n\n if (!this.activeTurns.has(sessionId)) {\n // No active turn yet — OTel events haven't arrived.\n // Mark as pending; addEvent will reschedule once the context is created.\n this.pendingStops.add(sessionId);\n return;\n }\n\n this._scheduleDelayedComplete(sessionId);\n }\n\n private _scheduleDelayedComplete(sessionId: string): void {\n const timer = setTimeout(() => {\n this.completionTimers.delete(sessionId);\n this.completeTurn(sessionId);\n }, TurnAggregator.COMPLETION_DELAY_MS);\n this.completionTimers.set(sessionId, timer);\n }\n\n /**\n * Signal that the turn for a given session is complete (called when the Stop\n * hook fires). Emits 'turn_complete' and cleans up the context immediately.\n */\n completeTurn(sessionId: string): void {\n const promptId = this.activeTurns.get(sessionId);\n if (!promptId) return;\n\n const internal = this.contexts.get(promptId);\n if (!internal) return;\n\n this.activeTurns.delete(sessionId);\n\n // Update session stats\n const session = this.sessions.get(sessionId);\n if (session) {\n session.completedTurns++;\n session.totalCostUsd += internal.apiRequests.reduce((s, r) => s + r.costUsd, 0);\n }\n\n this.emit('turn_complete', buildPublicContext(internal));\n\n // Clean up context — turn is done\n this.contexts.delete(promptId);\n }\n\n getContext(promptId: string): TurnContext | undefined {\n const internal = this.contexts.get(promptId);\n if (!internal) return undefined;\n return buildPublicContext(internal);\n }\n}\n","import Anthropic from '@anthropic-ai/sdk';\nimport { CLASSIFIER_SYSTEM_PROMPT } from './prompts.js';\nimport { withTimeout } from '../util/async.js';\n\nexport interface ClassifierResult {\n score: number; // 0.0 – 1.0\n reason: string;\n}\n\nconst CLASSIFIER_TIMEOUT_MS = 3000;\nconst CLASSIFIER_FALLBACK: ClassifierResult = {\n score: 0.5,\n reason: 'Classification timed out',\n};\n\nexport class Classifier {\n private readonly client: Anthropic;\n\n constructor(apiKey?: string) {\n this.client = new Anthropic({ apiKey: apiKey ?? process.env.ANTHROPIC_API_KEY });\n }\n\n async classify(prompt: string): Promise<ClassifierResult> {\n const classifyPromise = (async (): Promise<ClassifierResult> => {\n const message = await this.client.messages.create({\n model: 'claude-haiku-4-5',\n max_tokens: 100,\n system: CLASSIFIER_SYSTEM_PROMPT,\n messages: [\n {\n role: 'user',\n content: `User prompt to classify:\\n${prompt}`,\n },\n ],\n });\n\n const content = message.content[0];\n if (content.type !== 'text') {\n return CLASSIFIER_FALLBACK;\n }\n\n return parseClassifierResponse(content.text);\n })();\n\n return withTimeout(classifyPromise, CLASSIFIER_TIMEOUT_MS, CLASSIFIER_FALLBACK);\n }\n}\n\nfunction parseClassifierResponse(raw: string): ClassifierResult {\n try {\n // Extract JSON — handle markdown code blocks and nested objects by\n // finding the outermost { … } span rather than using a regex that\n // breaks on nested braces.\n const start = raw.indexOf('{');\n const end = raw.lastIndexOf('}');\n if (start === -1 || end === -1 || end < start) {\n return CLASSIFIER_FALLBACK;\n }\n\n const parsed = JSON.parse(raw.slice(start, end + 1)) as unknown;\n\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n !('score' in parsed) ||\n !('reason' in parsed)\n ) {\n return CLASSIFIER_FALLBACK;\n }\n\n const obj = parsed as Record<string, unknown>;\n const score = Number(obj.score);\n const reason = String(obj.reason);\n\n if (isNaN(score) || score < 0 || score > 1) {\n return CLASSIFIER_FALLBACK;\n }\n\n return { score, reason };\n } catch {\n return CLASSIFIER_FALLBACK;\n }\n}\n","// Classifier system prompt — used with Haiku (passed as `system:` field)\nexport const CLASSIFIER_SYSTEM_PROMPT: string = `You are an intent-ambiguity classifier for Claude Code, an AI coding assistant.\n\nYour job is to score how likely a user prompt will cause Claude to confidently execute a reasonable but WRONG interpretation — leading to wasted work, unintended changes, or the user having to undo what Claude did.\n\nBe CONSERVATIVE. Only flag genuine ambiguity. Most prompts are clear enough.\n\nCommon failure modes to watch for:\n- Scope ambiguity: \"clean up this module\", \"refactor the service\" — which files? what counts as clean?\n- Target ambiguity: \"the API is slow\", \"fix the tests\" — which API? which tests?\n- Intent ambiguity: \"update the tests\", \"improve error handling\" — add new tests? fix existing? what kind of improvement?\n- Symptom vs cause: \"auth isn't working\" — fix the symptom or find the root cause?\n\nScore guide:\n- 0.0–0.3: Clear and specific. Claude knows exactly what to do.\n- 0.4–0.59: Some ambiguity, but Claude will likely ask for clarification or make a safe default choice.\n- 0.6–0.79: Real risk. Claude will pick an interpretation and run with it — the user might not like the result.\n- 0.8–1.0: High risk. Multiple very different valid interpretations; high chance of wasted work.\n\nDo NOT flag:\n- Questions or requests for explanation (\"how does X work?\", \"what is Y?\")\n- Conversational messages (\"thanks\", \"ok\", \"sounds good\")\n- Read-only or low-stakes requests (\"show me\", \"list\", \"describe\")\n\nRespond with ONLY a JSON object on a single line:\n{\"score\": <0.0-1.0>, \"reason\": \"<one sentence explaining the ambiguity or why it is clear>\"}`;\n\n// Pre-advisory prompt — used with Sonnet\n// {prompt}, {score}, {reason} will be replaced\nexport const PRE_ADVISORY_SYSTEM_PROMPT: string = `You are a concise advisory assistant helping a developer clarify their intent before sending a prompt to Claude Code.\n\nA classifier has flagged the prompt as potentially ambiguous. Your job is to help the user understand the risk and either rephrase or confirm their intent.\n\nOutput at most 4 lines of plain text. No markdown headers, no bullet symbols, no lists. Just plain sentences.\n\nCover:\n1. What Claude will most likely do (the probable misinterpretation that could go wrong)\n2. The specific scope or target risk (what is under-specified)\n3. One clarifying question OR a concrete rephrasing that removes the ambiguity\n\nBe direct and brief. Do not repeat the prompt back verbatim.`;\n\nexport const PRE_ADVISORY_USER_TEMPLATE: string = `Prompt: {prompt}\n\nAmbiguity score: {score}\nReason: {reason}`;\n\n// Post-advisory prompt — used with Sonnet\n// {prompt}, {tools}, {cost}, {tokens} will be replaced\nexport const POST_ADVISORY_SYSTEM_PROMPT: string = `You are a post-execution reviewer for Claude Code. You compare what the user asked for against what Claude actually did.\n\nGiven the original prompt and a summary of tool activity, determine alignment and give brief feedback.\n\nIf aligned: respond with exactly one line starting with \"✓\" — a brief confirmation — followed by a one-line tools/cost summary.\nIf misaligned: respond with a line starting with \"✗\" describing what went wrong, then a line starting with \"→\" containing an exact re-prompt suggestion in quotes.\n\nFormat: plain text, no markdown. Maximum 5 lines total.`;\n\nexport const POST_ADVISORY_USER_TEMPLATE: string = `Original prompt: {prompt}\n\nTool activity: {toolSummary}\nTotal cost: {totalCost}\nTotal tokens: {totalTokens}`;\n","/**\n * Race a promise against a timeout. Cancels the timer when the promise settles,\n * preventing dangling timer handles in long-running processes.\n */\nexport function withTimeout<T>(promise: Promise<T>, ms: number, fallback: T): Promise<T> {\n let timerId: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<T>((resolve) => {\n timerId = setTimeout(() => resolve(fallback), ms);\n });\n return Promise.race([promise, timeout]).finally(() => clearTimeout(timerId));\n}\n","import Anthropic from '@anthropic-ai/sdk';\nimport type { TurnContext } from '../aggregator/turn.js';\nimport type { ClassifierResult } from './classifier.js';\nimport {\n PRE_ADVISORY_SYSTEM_PROMPT,\n PRE_ADVISORY_USER_TEMPLATE,\n POST_ADVISORY_SYSTEM_PROMPT,\n POST_ADVISORY_USER_TEMPLATE,\n} from './prompts.js';\nimport { withTimeout } from '../util/async.js';\n\nexport interface AdvisoryResult {\n text: string;\n aligned?: boolean; // only set for post-advisory\n}\n\nconst ADVISORY_TIMEOUT_MS = 10000;\n\nconst PRE_ADVISORY_FALLBACK: AdvisoryResult = { text: 'Advisory unavailable (timeout)' };\nconst POST_ADVISORY_FALLBACK_TIMEOUT: AdvisoryResult = { text: 'Advisory unavailable (timeout)', aligned: undefined };\nconst POST_ADVISORY_FALLBACK_ERROR: AdvisoryResult = { text: 'Advisory unavailable (error)', aligned: undefined };\n\nexport class Advisor {\n private readonly client: Anthropic;\n\n constructor(apiKey?: string) {\n this.client = new Anthropic({ apiKey: apiKey ?? process.env.ANTHROPIC_API_KEY });\n }\n\n // Pre-advisory: called when classifier score >= 0.6\n async preAdvisory(prompt: string, classification: ClassifierResult): Promise<AdvisoryResult> {\n const userMessage = PRE_ADVISORY_USER_TEMPLATE\n .replace('{prompt}', prompt)\n .replace('{score}', classification.score.toFixed(2))\n .replace('{reason}', classification.reason);\n\n const advisoryPromise = (async (): Promise<AdvisoryResult> => {\n const message = await this.client.messages.create({\n model: 'claude-sonnet-4-5',\n max_tokens: 200,\n system: PRE_ADVISORY_SYSTEM_PROMPT,\n messages: [{ role: 'user', content: userMessage }],\n });\n\n const content = message.content[0];\n if (content.type !== 'text') {\n return PRE_ADVISORY_FALLBACK;\n }\n\n return { text: content.text.trim() };\n })();\n\n return withTimeout(advisoryPromise, ADVISORY_TIMEOUT_MS, PRE_ADVISORY_FALLBACK);\n }\n\n // Post-advisory: called on turn complete\n async postAdvisory(context: TurnContext): Promise<AdvisoryResult> {\n const toolSummary = buildToolSummary(context);\n const totalCost = `$${context.totalCostUsd.toFixed(3)}`;\n const totalTokens = (context.totalInputTokens + context.totalOutputTokens).toLocaleString();\n\n const userMessage = POST_ADVISORY_USER_TEMPLATE\n .replace('{prompt}', context.prompt)\n .replace('{toolSummary}', toolSummary)\n .replace('{totalCost}', totalCost)\n .replace('{totalTokens}', totalTokens);\n\n const advisoryPromise = (async (): Promise<AdvisoryResult> => {\n const message = await this.client.messages.create({\n model: 'claude-sonnet-4-5',\n max_tokens: 300,\n system: POST_ADVISORY_SYSTEM_PROMPT,\n messages: [{ role: 'user', content: userMessage }],\n });\n\n const content = message.content[0];\n if (content.type !== 'text') {\n return POST_ADVISORY_FALLBACK_ERROR;\n }\n\n const text = content.text.trim();\n const aligned = text.startsWith('✓') ? true : text.startsWith('✗') ? false : undefined;\n\n return { text, aligned };\n })();\n\n return withTimeout(advisoryPromise, ADVISORY_TIMEOUT_MS, POST_ADVISORY_FALLBACK_TIMEOUT);\n }\n}\n\nfunction buildToolSummary(context: TurnContext): string {\n const parts: string[] = [];\n\n // Group tool calls by name, tracking Bash separately\n const toolCounts = new Map<string, number>();\n const bashCommands: string[] = [];\n let bashCallCount = 0;\n\n for (const result of context.toolResults) {\n if (result.toolName === 'Bash') {\n bashCallCount++;\n if (result.bashCommand) {\n bashCommands.push(`'${result.bashCommand}'`);\n }\n } else {\n toolCounts.set(result.toolName, (toolCounts.get(result.toolName) ?? 0) + 1);\n }\n }\n\n // Add non-bash tools\n for (const [toolName, count] of toolCounts.entries()) {\n parts.push(count === 1 ? toolName : `${toolName} (${count} calls)`);\n }\n\n // Add bash summary\n if (bashCallCount > 0) {\n if (bashCommands.length > 0) {\n const bashLabel = bashCommands.length <= 3\n ? `Bash: ${bashCommands.join(', ')}`\n : `Bash: ${bashCommands.slice(0, 3).join(', ')} +${bashCommands.length - 3} more`;\n parts.push(bashLabel);\n } else {\n parts.push(`Bash (${bashCallCount} calls)`);\n }\n }\n\n // Token and cost summary\n const totalTokens = context.totalInputTokens + context.totalOutputTokens;\n if (totalTokens > 0) {\n parts.push(`${totalTokens.toLocaleString()} tokens`);\n }\n if (context.totalCostUsd > 0) {\n parts.push(`$${context.totalCostUsd.toFixed(3)}`);\n }\n\n return parts.join(' · ') || 'No tools used';\n}\n","// ─── ANSI helpers ─────────────────────────────────────────────────────────────\n\nconst RESET = '\\x1b[0m';\nconst DIM = '\\x1b[2m';\nconst BOLD = '\\x1b[1m';\nconst GREEN = '\\x1b[32m';\nconst YELLOW = '\\x1b[33m';\nconst RED = '\\x1b[31m';\nconst CYAN = '\\x1b[36m';\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst LINE_WIDTH = 76;\nconst WRAP_WIDTH = 74;\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/**\n * Format a Date as \"HH:MM:SS\".\n */\nexport function formatTime(date: Date): string {\n const hh = String(date.getHours()).padStart(2, '0');\n const mm = String(date.getMinutes()).padStart(2, '0');\n const ss = String(date.getSeconds()).padStart(2, '0');\n return `${hh}:${mm}:${ss}`;\n}\n\n/**\n * Build a separator line of exactly LINE_WIDTH chars, padded with \"─\".\n * The prefix is included in the total width.\n */\nfunction separator(prefix = ''): string {\n const dashes = '─'.repeat(Math.max(0, LINE_WIDTH - prefix.length));\n return prefix + dashes;\n}\n\n/**\n * Wrap text to at most maxWidth characters per line, preserving existing newlines.\n * Continuation lines are indented with `indent` spaces.\n */\nfunction wrapText(text: string, maxWidth: number, indent: string): string[] {\n const rawLines = text.split('\\n');\n const result: string[] = [];\n\n for (const rawLine of rawLines) {\n const words = rawLine.split(' ');\n let current = '';\n\n for (const word of words) {\n if (current === '') {\n current = word;\n } else if (current.length + 1 + word.length <= maxWidth) {\n current += ' ' + word;\n } else {\n result.push(current);\n current = indent + word;\n }\n }\n\n if (current !== '') {\n result.push(current);\n }\n }\n\n return result;\n}\n\n/**\n * Render advisory text as output lines. The first line is left as-is (the\n * caller has already formatted it). Subsequent lines and long first lines are\n * word-wrapped at WRAP_WIDTH with a 2-space indent on continuations.\n */\nfunction renderAdvisoryLines(advisory: string): string[] {\n return wrapText(advisory.trim(), WRAP_WIDTH, ' ');\n}\n\nfunction writeln(text = ''): void {\n process.stdout.write(text + '\\n');\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Print a \"clear\" one-liner — dim, no box.\n *\n * Example:\n * ── PRE ── 14:23:07 ── score: 0.34 ── ✓ Clear ─────\n * ── PRE [S1] ── 14:23:07 ── score: 0.34 ── ✓ Clear ─────\n */\nexport function printPreClear(score: number, sessionLabel?: string): void {\n const time = formatTime(new Date());\n const sessionPart = sessionLabel ? ` [${sessionLabel}]` : '';\n const prefix = `── PRE${sessionPart} ── ${time} ── score: ${score.toFixed(2)} ── ✓ Clear `;\n const line = separator(prefix);\n writeln(DIM + line + RESET);\n}\n\n/**\n * Print a pre-advisory warning box with a yellow header.\n *\n * Example:\n * ── PRE ── 14:25:12 ── score: 0.78 ─────────────────\n * ── PRE [S1] ── 14:25:12 ── score: 0.78 ─────────────────\n */\nexport function printPreAdvisory(score: number, advisory: string, sessionLabel?: string): void {\n const time = formatTime(new Date());\n const sessionPart = sessionLabel ? ` [${sessionLabel}]` : '';\n const headerPrefix = `── PRE${sessionPart} ── ${time} ── score: ${score.toFixed(2)} `;\n const header = separator(headerPrefix);\n\n writeln(YELLOW + BOLD + header + RESET);\n\n const lines = renderAdvisoryLines(advisory);\n for (const line of lines) {\n writeln(line);\n }\n\n writeln(DIM + separator() + RESET);\n}\n\n/** Shared implementation for both post-advisory box variants. */\nfunction printPost(color: string, content: string, sessionLabel?: string): void {\n const time = formatTime(new Date());\n const sessionPart = sessionLabel ? ` [${sessionLabel}]` : '';\n const header = separator(`── POST${sessionPart} ── ${time} `);\n\n writeln(color + BOLD + header + RESET);\n\n const lines = renderAdvisoryLines(content);\n for (const line of lines) {\n writeln(line);\n }\n\n writeln(DIM + separator() + RESET);\n}\n\n/**\n * Print a post-advisory \"aligned\" one-liner — dim green, no box.\n *\n * Example:\n * ── POST ── 14:25:38 ── ✓ Aligned ──────────────────────────────────────────\n * ── POST [S1] ── 14:25:38 ── ✓ Aligned ──────────────────────────────────────\n */\nexport function printPostAligned(_summary: string, sessionLabel?: string): void {\n const time = formatTime(new Date());\n const sessionPart = sessionLabel ? ` [${sessionLabel}]` : '';\n const prefix = `── POST${sessionPart} ── ${time} ── ✓ Aligned `;\n writeln(DIM + GREEN + separator(prefix) + RESET);\n}\n\n/**\n * Print a post-advisory \"misaligned\" box with a red header.\n *\n * Example:\n * ── POST ── 14:31:02 ────────────────────────────────\n * ── POST [S1] ── 14:31:02 ────────────────────────────────\n */\nexport function printPostMisaligned(advisory: string, sessionLabel?: string): void {\n printPost(RED, advisory, sessionLabel);\n}\n\n/**\n * Print a dim cyan session-connected line.\n *\n * Example:\n * ── S1 connected (abcd1234…) ── 14:23:07\n */\nexport function printSessionStart(label: string, sessionId: string): void {\n const time = formatTime(new Date());\n const shortId = sessionId.slice(0, 8);\n writeln(DIM + CYAN + `── ${label} connected (${shortId}…) ── ${time}` + RESET);\n}\n\n/**\n * Print the startup banner.\n *\n * Example:\n * ── Radar v0.1.0 ────────────────────────────────────\n * Listening on localhost:4820\n * Waiting for Claude Code telemetry...\n * Set OTEL_LOG_USER_PROMPTS=1 for prompt content analysis.\n * ────────────────────────────────────────────────────\n */\nexport function printBanner(port: number): void {\n const headerPrefix = '── Radar v0.1.0 ';\n const header = separator(headerPrefix);\n\n writeln(CYAN + BOLD + header + RESET);\n writeln(`Listening on localhost:${port}`);\n writeln('Waiting for Claude Code telemetry...');\n writeln('Set OTEL_LOG_USER_PROMPTS=1 for prompt content analysis.');\n writeln(DIM + separator() + RESET);\n}\n\n/**\n * Print a warning message in yellow.\n */\nexport function printWarning(message: string): void {\n writeln(YELLOW + '⚠ ' + message + RESET);\n}\n\n/**\n * Print an error message in red.\n */\nexport function printError(message: string): void {\n writeln(RED + '✗ ' + message + RESET);\n}\n","import { OtlpReceiver } from '../receiver/otlp.js';\nimport type { RadarEvent } from '../receiver/otlp.js';\nimport { TurnAggregator } from '../aggregator/turn.js';\nimport type { TurnContext, SessionSummary } from '../aggregator/turn.js';\nimport { Classifier } from '../analysis/classifier.js';\nimport { Advisor } from '../analysis/advisor.js';\nimport {\n printBanner,\n printPreClear,\n printPreAdvisory,\n printPostAligned,\n printPostMisaligned,\n printSessionStart,\n printWarning,\n printError,\n} from '../output/formatter.js';\n\nexport interface WatchOptions {\n port?: number;\n scoreThreshold?: number;\n apiKey?: string;\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nexport async function startWatch(options: WatchOptions = {}): Promise<void> {\n const port = options.port ?? 4820;\n const scoreThreshold = options.scoreThreshold ?? 0.6;\n\n const receiver = new OtlpReceiver(port);\n const aggregator = new TurnAggregator();\n const classifier = new Classifier(options.apiKey);\n const advisor = new Advisor(options.apiKey);\n\n // Track whether we've seen any events without prompt content — warn once\n let warnedAboutMissingPrompt = false;\n // Prevent double-classification if the same promptId is seen more than once\n const classifying = new Set<string>();\n // Map sessionId → display label (\"S1\", \"S2\", …)\n const sessionLabels = new Map<string, string>();\n\n // ── Wire: session_start → label tracking + display ────────────────────────\n aggregator.on('session_start', (s: SessionSummary) => {\n sessionLabels.set(s.sessionId, s.label);\n printSessionStart(s.label, s.sessionId);\n });\n\n // ── Wire: OtlpReceiver → TurnAggregator + classification ───────────────────\n //\n // A single listener handles both jobs in order: aggregation first so that\n // TurnContext exists by the time classification starts, then classification\n // for user_prompt events.\n receiver.on('event', (event: RadarEvent) => {\n // 1. Always feed the aggregator\n aggregator.addEvent(event);\n\n // 2. On user_prompt, trigger pre-advisory (fire-and-forget)\n if (event.type !== 'user_prompt') return;\n\n if (classifying.has(event.promptId)) return;\n classifying.add(event.promptId);\n\n if (!event.prompt && !warnedAboutMissingPrompt) {\n warnedAboutMissingPrompt = true;\n printWarning(\n 'Prompt content not available. Set OTEL_LOG_USER_PROMPTS=1 to enable intent analysis.',\n );\n }\n\n void runPreAdvisory(event.prompt, event.promptId, sessionLabels.get(event.sessionId));\n });\n\n receiver.on('error', (err: Error) => {\n printError(`OTLP server error: ${err.message}`);\n });\n\n // ── Wire: Stop hook → turn completion ──────────────────────────────────────\n // Uses scheduleCompletion (not completeTurn directly) to handle the race\n // between the Stop hook and OTel's export interval (2s): the stop signal\n // often arrives before telemetry events are flushed.\n receiver.on('stop', (sessionId: string) => {\n aggregator.scheduleCompletion(sessionId);\n });\n\n // ── Wire: TurnAggregator → post-advisory ───────────────────────────────────\n aggregator.on('turn_complete', (ctx: TurnContext) => {\n void runPostAdvisory(ctx, sessionLabels.get(ctx.sessionId));\n });\n\n // ── Pre-advisory pipeline ───────────────────────────────────────────────────\n async function runPreAdvisory(prompt: string, promptId: string, sessionLabel?: string): Promise<void> {\n try {\n if (!prompt) return; // no prompt text — skip silently\n\n const result = await classifier.classify(prompt);\n\n if (result.score < scoreThreshold) {\n printPreClear(result.score, sessionLabel);\n return;\n }\n\n // Score >= threshold: escalate to Sonnet\n const advisory = await advisor.preAdvisory(prompt, result);\n printPreAdvisory(result.score, advisory.text, sessionLabel);\n } catch (err) {\n printError(`Pre-advisory failed for prompt ${promptId}: ${errMsg(err)}`);\n } finally {\n // Always release the deduplication guard once pre-advisory finishes,\n // whether it succeeded, failed, or was skipped due to missing prompt.\n classifying.delete(promptId);\n }\n }\n\n // ── Post-advisory pipeline ──────────────────────────────────────────────────\n async function runPostAdvisory(ctx: TurnContext, sessionLabel?: string): Promise<void> {\n if (!ctx.prompt) return; // no prompt text — skip silently\n\n try {\n const result = await advisor.postAdvisory(ctx);\n\n if (result.aligned === false) {\n printPostMisaligned(result.text, sessionLabel);\n } else if (result.aligned === true) {\n printPostAligned(result.text, sessionLabel);\n } else {\n // aligned is undefined: timeout, error, or unexpected model format —\n // surface as a warning rather than silently showing a green box.\n printWarning(`Post-advisory: ${result.text}`);\n }\n } catch (err) {\n printError(`Post-advisory failed for prompt ${ctx.promptId}: ${errMsg(err)}`);\n }\n }\n\n // ── OTel env var check ─────────────────────────────────────────────────────\n const requiredOtelVars = [\n 'CLAUDE_CODE_ENABLE_TELEMETRY',\n 'OTEL_LOGS_EXPORTER',\n 'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT',\n ];\n const missingVars = requiredOtelVars.filter((v) => !process.env[v]);\n if (missingVars.length > 0) {\n printWarning('OTel env vars not configured. Run `radar setup` and restart Claude Code.');\n }\n\n // ── Start ───────────────────────────────────────────────────────────────────\n try {\n await receiver.start();\n } catch (err) {\n const msg = errMsg(err);\n if (msg.includes('EADDRINUSE')) {\n printError(`Port ${port} is already in use. Use --port <n> to choose a different port.`);\n } else {\n printError(`Failed to start OTLP receiver: ${msg}`);\n }\n process.exit(1);\n }\n\n printBanner(port);\n\n // ── Graceful shutdown ───────────────────────────────────────────────────────\n async function shutdown(): Promise<void> {\n process.stdout.write('\\n');\n printWarning('Shutting down Radar...');\n await receiver.stop();\n process.exit(0);\n }\n\n process.on('SIGINT', () => void shutdown());\n process.on('SIGTERM', () => void shutdown());\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { createInterface } from 'node:readline';\nimport {\n readConfig,\n writeConfig,\n configPath,\n isOpAvailable,\n createOpItem,\n} from './config.js';\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst CLAUDE_DIR = join(homedir(), '.claude');\nconst SETTINGS_PATH = join(CLAUDE_DIR, 'settings.json');\nconst RADAR_HOOKS_DIR = join(homedir(), '.radar', 'hooks');\nconst STOP_HOOK_PATH = join(RADAR_HOOKS_DIR, 'stop.sh');\n\nconst OTEL_VARS: Record<string, string> = {\n CLAUDE_CODE_ENABLE_TELEMETRY: '1',\n OTEL_LOGS_EXPORTER: 'otlp',\n OTEL_EXPORTER_OTLP_PROTOCOL: 'http/json',\n OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: 'http://localhost:4820/v1/logs',\n OTEL_LOG_USER_PROMPTS: '1',\n OTEL_LOG_TOOL_DETAILS: '1',\n OTEL_LOGS_EXPORT_INTERVAL: '2000',\n};\n\n// ─── ANSI helpers (self-contained, no formatter dependency) ──────────────────\n\nconst RESET = '\\x1b[0m';\nconst DIM = '\\x1b[2m';\nconst BOLD = '\\x1b[1m';\nconst GREEN = '\\x1b[32m';\nconst YELLOW = '\\x1b[33m';\nconst CYAN = '\\x1b[36m';\n\nconst LINE_WIDTH = 52;\n\nfunction sep(prefix = ''): string {\n return prefix + '─'.repeat(Math.max(0, LINE_WIDTH - prefix.length));\n}\n\nfunction writeln(text = ''): void {\n process.stdout.write(text + '\\n');\n}\n\n// ─── Settings helpers ─────────────────────────────────────────────────────────\n\nfunction readSettings(): Record<string, unknown> {\n if (!existsSync(SETTINGS_PATH)) return {};\n try {\n return JSON.parse(readFileSync(SETTINGS_PATH, 'utf8')) as Record<string, unknown>;\n } catch {\n throw new Error(`Could not parse ${SETTINGS_PATH}. Fix the JSON syntax and try again.`);\n }\n}\n\nfunction writeSettings(settings: Record<string, unknown>): void {\n if (!existsSync(CLAUDE_DIR)) {\n mkdirSync(CLAUDE_DIR, { recursive: true });\n }\n writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\\n', 'utf8');\n}\n\n// ─── readline helper ──────────────────────────────────────────────────────────\n\nfunction question(rl: ReturnType<typeof createInterface>, prompt: string): Promise<string> {\n return new Promise((resolve) => rl.question(prompt, resolve));\n}\n\n// ─── Stop hook installation ───────────────────────────────────────────────────\n\nconst STOP_HOOK_SCRIPT = `#!/bin/bash\nINPUT=$(cat)\nSESSION_ID=$(echo \"$INPUT\" | python3 -c \"import sys,json; print(json.load(sys.stdin).get('session_id',''))\" 2>/dev/null)\nif [ -n \"$SESSION_ID\" ]; then\n curl -s -X POST \"http://localhost:\\${RADAR_PORT:-4820}/v1/hook/stop\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d \"{\\\\\"sessionId\\\\\": \\\\\"$SESSION_ID\\\\\"}\" &>/dev/null &\nfi\n`;\n\nfunction writeStopHook(): void {\n if (!existsSync(RADAR_HOOKS_DIR)) {\n mkdirSync(RADAR_HOOKS_DIR, { recursive: true });\n }\n writeFileSync(STOP_HOOK_PATH, STOP_HOOK_SCRIPT, 'utf8');\n chmodSync(STOP_HOOK_PATH, 0o755);\n}\n\nfunction installStopHooks(settings: Record<string, unknown>): void {\n const hooks = (settings.hooks as Record<string, unknown[]> | undefined) ?? {};\n\n const hookEntry = {\n hooks: [\n {\n type: 'command',\n command: STOP_HOOK_PATH,\n async: true,\n },\n ],\n };\n\n // Helper: append hook entry if not already present (idempotent)\n function appendHook(hookName: string): void {\n const existing = (hooks[hookName] as unknown[] | undefined) ?? [];\n const alreadyInstalled = existing.some((h) => {\n if (typeof h !== 'object' || h === null) return false;\n const hooksArr = (h as Record<string, unknown>).hooks;\n if (!Array.isArray(hooksArr)) return false;\n return hooksArr.some(\n (inner) =>\n typeof inner === 'object' &&\n inner !== null &&\n (inner as Record<string, unknown>).command === STOP_HOOK_PATH,\n );\n });\n if (!alreadyInstalled) {\n hooks[hookName] = [...existing, hookEntry];\n }\n }\n\n appendHook('Stop');\n appendHook('StopFailure');\n\n settings.hooks = hooks;\n}\n\n// ─── Main ─────────────────────────────────────────────────────────────────────\n\nexport async function runSetup(preEnteredKey?: string): Promise<void> {\n writeln(CYAN + BOLD + sep('── Radar Setup ') + RESET);\n\n // ── Write OTel config ─────────────────────────────────────────────────────\n let settings: Record<string, unknown>;\n try {\n settings = readSettings();\n } catch (err) {\n writeln(YELLOW + '✗ ' + (err instanceof Error ? err.message : String(err)) + RESET);\n process.exit(1);\n }\n\n const existingEnv = (settings.env as Record<string, string> | undefined) ?? {};\n\n writeln(`Writing OTel config to ${SETTINGS_PATH.replace(homedir(), '~')}...`);\n writeln();\n\n for (const [key, value] of Object.entries(OTEL_VARS)) {\n const alreadySet = key in existingEnv && existingEnv[key] === value;\n const tag = alreadySet ? DIM + ' (already set)' + RESET : '';\n writeln(` ${GREEN}✓${RESET} ${key}${tag}`);\n }\n\n settings.env = { ...existingEnv, ...OTEL_VARS };\n\n // ── Install Stop hooks ────────────────────────────────────────────────────\n writeln();\n writeln('Installing Stop hooks...');\n\n try {\n writeStopHook();\n writeln(` ${GREEN}✓${RESET} Hook script written to ${STOP_HOOK_PATH.replace(homedir(), '~')}`);\n } catch (err) {\n writeln(` ${YELLOW}⚠${RESET} Failed to write hook script: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n try {\n installStopHooks(settings);\n writeln(` ${GREEN}✓${RESET} Stop + StopFailure hooks registered in settings.json`);\n } catch (err) {\n writeln(` ${YELLOW}⚠${RESET} Failed to register hooks: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n try {\n writeSettings(settings);\n } catch (err) {\n writeln();\n writeln(YELLOW + '✗ Failed to write settings: ' + (err instanceof Error ? err.message : String(err)) + RESET);\n process.exit(1);\n }\n\n writeln();\n writeln(`${GREEN}✓${RESET} Settings written to ${SETTINGS_PATH.replace(homedir(), '~')}`);\n\n // ── API key ───────────────────────────────────────────────────────────────\n await promptApiKey(preEnteredKey);\n\n // ── Done ──────────────────────────────────────────────────────────────────\n writeln();\n writeln('Ready. Restart Claude Code for the Stop hooks to take effect, then:');\n writeln(` ${BOLD}radar watch${RESET}`);\n writeln(DIM + sep() + RESET);\n}\n\n// ─── API key prompt ────────────────────────────────────────────────────────────\n\nasync function promptApiKey(preEnteredKey?: string): Promise<void> {\n writeln();\n\n let apiKey: string | undefined = preEnteredKey;\n\n // ── If no key was passed via --api-key, check for an existing one or prompt ─\n if (!apiKey) {\n const config = readConfig();\n const envKey = process.env.ANTHROPIC_API_KEY;\n const hasStored = config.apiKey ?? config.apiKeyRef;\n const activeKey = envKey ?? config.apiKey;\n\n if (hasStored || envKey) {\n // Show what's already configured\n if (envKey) {\n const masked = envKey.slice(0, 10) + '…' + envKey.slice(-4);\n writeln(`${GREEN}✓${RESET} API key found via ANTHROPIC_API_KEY env var: ${DIM}${masked}${RESET}`);\n } else if (config.apiKeyRef) {\n writeln(`${GREEN}✓${RESET} API key linked via 1Password: ${DIM}${config.apiKeyRef}${RESET}`);\n } else if (config.apiKey) {\n const masked = config.apiKey.slice(0, 10) + '…' + config.apiKey.slice(-4);\n writeln(`${GREEN}✓${RESET} API key stored locally: ${DIM}${masked}${RESET}`);\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const answer = await question(rl, ' Replace it? [y/N] ');\n rl.close();\n\n if (answer.trim().toLowerCase() !== 'y') return;\n\n // Fall through to prompt for new key\n void activeKey; // suppress unused warning\n } else {\n writeln(`${YELLOW}⚠${RESET} No API key found. Radar needs one to run analysis.`);\n }\n\n // Prompt for the key\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const entered = await question(rl, ' Enter your Anthropic API key: ');\n rl.close();\n\n apiKey = entered.trim();\n if (!apiKey) {\n writeln(`${YELLOW}⚠${RESET} No key entered — skipping. Re-run setup or use --api-key <key>.`);\n return;\n }\n }\n\n // ── Ask where to store it ─────────────────────────────────────────────────\n await promptStorage(apiKey);\n}\n\n// ─── Storage choice ────────────────────────────────────────────────────────────\n\nasync function promptStorage(apiKey: string): Promise<void> {\n const opAvailable = isOpAvailable();\n\n writeln();\n writeln('Where would you like to store the API key?');\n writeln(` ${BOLD}1${RESET} Local disk ${DIM}(${configPath().replace(homedir(), '~')})${RESET}`);\n\n if (opAvailable) {\n writeln(` ${BOLD}2${RESET} 1Password ${DIM}(recommended)${RESET}`);\n } else {\n writeln(` ${DIM}2 1Password (op CLI not found — see instructions below)${RESET}`);\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const answer = await question(rl, ` Choice [1${opAvailable ? '/2' : ''}]: `);\n rl.close();\n\n const choice = answer.trim();\n\n if (choice === '2') {\n if (!opAvailable) {\n writeln();\n writeln(`${YELLOW}⚠${RESET} 1Password CLI (op) is not installed.`);\n writeln(' To set it up:');\n writeln(` ${DIM}1. Install: https://developer.1password.com/docs/cli/get-started${RESET}`);\n writeln(` ${DIM}2. Sign in: op signin${RESET}`);\n writeln(` ${DIM}3. Re-run: radar setup --api-key <key>${RESET}`);\n return;\n }\n\n await storeIn1Password(apiKey);\n return;\n }\n\n // Default: local disk\n storeLocally(apiKey);\n}\n\nfunction storeLocally(apiKey: string): void {\n const config = readConfig();\n writeConfig({ ...config, apiKey, apiKeyRef: undefined });\n writeln();\n writeln(`${GREEN}✓${RESET} API key saved to ${configPath().replace(homedir(), '~')}`);\n}\n\nasync function storeIn1Password(apiKey: string): Promise<void> {\n writeln();\n writeln('How would you like to store it in 1Password?');\n writeln(` ${BOLD}1${RESET} Create a new item ${DIM}(radar will add it for you)${RESET}`);\n writeln(` ${BOLD}2${RESET} Use an existing item ${DIM}(enter an op:// reference)${RESET}`);\n\n const rl1 = createInterface({ input: process.stdin, output: process.stdout });\n const choice = await question(rl1, ' Choice [1/2]: ');\n rl1.close();\n\n if (choice.trim() === '2') {\n await useExistingOpRef();\n return;\n }\n\n // Create a new item\n writeln();\n writeln('Creating item in 1Password...');\n\n try {\n const ref = createOpItem(apiKey);\n const config = readConfig();\n writeConfig({ ...config, apiKey: undefined, apiKeyRef: ref });\n writeln(`${GREEN}✓${RESET} API key stored in 1Password`);\n writeln(` Reference: ${DIM}${ref}${RESET}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n writeln(`${YELLOW}✗${RESET} Failed to store in 1Password: ${msg}`);\n writeln(' Make sure you are signed in: ' + DIM + 'op signin' + RESET);\n\n // Offer local fallback\n const rl2 = createInterface({ input: process.stdin, output: process.stdout });\n const fallback = await question(rl2, ' Store on local disk instead? [Y/n] ');\n rl2.close();\n\n if (fallback.trim().toLowerCase() !== 'n') {\n storeLocally(apiKey);\n }\n }\n}\n\nasync function useExistingOpRef(): Promise<void> {\n writeln();\n writeln(` Enter the ${BOLD}op://${RESET} reference for your API key.`);\n writeln(` ${DIM}Example: op://Personal/Anthropic/credential${RESET}`);\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ref = await question(rl, ' Reference: ');\n rl.close();\n\n const trimmed = ref.trim();\n if (!trimmed.startsWith('op://')) {\n writeln(`${YELLOW}⚠${RESET} Invalid reference — must start with op://. Re-run setup to try again.`);\n return;\n }\n\n const config = readConfig();\n writeConfig({ ...config, apiKey: undefined, apiKeyRef: trimmed });\n writeln(`${GREEN}✓${RESET} 1Password reference saved: ${DIM}${trimmed}${RESET}`);\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { execSync, execFileSync } from 'node:child_process';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\n// ─── Paths ─────────────────────────────────────────────────────────────────────\n\nconst CONFIG_DIR = join(homedir(), '.config', 'radar');\nconst CONFIG_PATH = join(CONFIG_DIR, 'config.json');\n\n// ─── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface RadarConfig {\n apiKey?: string; // plaintext local storage\n apiKeyRef?: string; // 1Password reference e.g. op://vault/item/field\n}\n\n// ─── Read / write ──────────────────────────────────────────────────────────────\n\nexport function readConfig(): RadarConfig {\n if (!existsSync(CONFIG_PATH)) return {};\n try {\n return JSON.parse(readFileSync(CONFIG_PATH, 'utf8')) as RadarConfig;\n } catch {\n return {};\n }\n}\n\nexport function writeConfig(config: RadarConfig): void {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });\n }\n // mode 0o600 → readable/writable by owner only; protects the plaintext API key\n writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\\n', { encoding: 'utf8', mode: 0o600 });\n}\n\nexport function configPath(): string {\n return CONFIG_PATH;\n}\n\n// ─── 1Password helpers ─────────────────────────────────────────────────────────\n\nexport function isOpAvailable(): boolean {\n try {\n execSync('op --version', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Run `op item create` and return the `op://` reference for the stored key. */\nexport function createOpItem(apiKey: string): string {\n const title = 'Radar Anthropic API Key';\n // Use execFileSync (no shell) to avoid shell-injection risks when the key\n // contains quotes, dollar signs, or other shell metacharacters.\n const raw = execFileSync(\n 'op',\n ['item', 'create', '--category=login', `--title=${title}`, `password=${apiKey}`, '--format', 'json'],\n { encoding: 'utf8' },\n );\n const item = JSON.parse(raw) as { title: string; vault: { name: string } };\n return `op://${item.vault.name}/${item.title}/password`;\n}\n\n/** Read a secret from 1Password by its `op://` reference. */\nexport function readOpItem(ref: string): string {\n // Use execFileSync (no shell) to avoid injection if the reference contains\n // shell metacharacters (e.g. spaces or backticks from user-supplied input).\n return execFileSync('op', ['read', ref], { encoding: 'utf8' }).trim();\n}\n\n// ─── Key resolution ────────────────────────────────────────────────────────────\n\n/**\n * Resolve the API key from the stored config.\n * Tries plaintext `apiKey` first, then fetches via `op read` if `apiKeyRef` is set.\n * Returns undefined if no key is configured.\n * Throws if a 1Password reference is configured but the op read fails.\n */\nexport function resolveStoredApiKey(): string | undefined {\n const config = readConfig();\n if (config.apiKey) return config.apiKey;\n if (config.apiKeyRef) {\n try {\n return readOpItem(config.apiKeyRef);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Failed to read API key from 1Password (${config.apiKeyRef}).\\n` +\n `Make sure you are signed in: op signin\\n` +\n `Detail: ${detail}`,\n );\n }\n }\n return undefined;\n}\n","import * as http from 'node:http';\nimport { formatTime } from '../output/formatter.js';\n\n// ─── ANSI helpers (local, keep sniff self-contained) ──────────────────────────\n\nconst RESET = '\\x1b[0m';\nconst DIM = '\\x1b[2m';\nconst BOLD = '\\x1b[1m';\nconst CYAN = '\\x1b[36m';\nconst YELLOW = '\\x1b[33m';\nconst GREEN = '\\x1b[32m';\n\n// ─── OTLP types (minimal, mirrors otlp.ts) ────────────────────────────────────\n\ninterface OtlpAttributeValue {\n stringValue?: string;\n intValue?: number;\n doubleValue?: number;\n boolValue?: boolean;\n}\n\ninterface OtlpAttribute {\n key: string;\n value: OtlpAttributeValue;\n}\n\ninterface OtlpLogRecord {\n timeUnixNano?: string;\n body?: { stringValue?: string };\n attributes?: OtlpAttribute[];\n}\n\ninterface OtlpScopeLogs {\n logRecords?: OtlpLogRecord[];\n}\n\ninterface OtlpResourceLogs {\n scopeLogs?: OtlpScopeLogs[];\n}\n\ninterface OtlpLogsPayload {\n resourceLogs?: OtlpResourceLogs[];\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction rawAttrValue(v: OtlpAttributeValue): string | number | boolean | undefined {\n if (v.stringValue !== undefined) return v.stringValue;\n if (v.intValue !== undefined) return v.intValue;\n if (v.doubleValue !== undefined) return v.doubleValue;\n if (v.boolValue !== undefined) return v.boolValue;\n return undefined;\n}\n\nfunction attrsToRecord(attrs: OtlpAttribute[] | undefined): Record<string, string | number | boolean> {\n const out: Record<string, string | number | boolean> = {};\n for (const a of attrs ?? []) {\n const v = rawAttrValue(a.value);\n if (v !== undefined) out[a.key] = v;\n }\n return out;\n}\n\n// ─── Compact printer ──────────────────────────────────────────────────────────\n\nfunction printCompact(record: OtlpLogRecord): void {\n const timeNano = record.timeUnixNano ?? '0';\n const timestampMs = Math.floor(Number(BigInt(timeNano) / 1_000_000n));\n const ts = formatTime(new Date(timestampMs));\n const eventName = record.body?.stringValue ?? '(no name)';\n const attrs = attrsToRecord(record.attributes);\n\n // Highlight known event types\n let nameColour = CYAN;\n if (eventName.includes('user_prompt')) nameColour = GREEN;\n else if (eventName.includes('api_error')) nameColour = YELLOW;\n\n const attrsStr = Object.keys(attrs).length\n ? ` ${DIM}${JSON.stringify(attrs)}${RESET}`\n : '';\n\n process.stdout.write(\n `${DIM}${ts}${RESET} ${nameColour}${BOLD}${eventName}${RESET}${attrsStr}\\n`,\n );\n}\n\nfunction printJson(record: OtlpLogRecord): void {\n const timeNano = record.timeUnixNano ?? '0';\n const timestampMs = Math.floor(Number(BigInt(timeNano) / 1_000_000n));\n const eventName = record.body?.stringValue ?? '';\n const attrs = attrsToRecord(record.attributes);\n\n process.stdout.write(\n JSON.stringify({ timestampMs, eventName, attrs }) + '\\n',\n );\n}\n\n// ─── Forward helper ───────────────────────────────────────────────────────────\n\nfunction forwardBody(body: string, forwardPort: number): void {\n const req = http.request(\n {\n hostname: '127.0.0.1',\n port: forwardPort,\n path: '/v1/logs',\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(body),\n },\n },\n (res) => {\n // Drain response so the socket is released\n res.resume();\n },\n );\n req.on('error', () => {\n // Radar not running — silently swallow; we already responded 200 to Claude Code\n });\n req.write(body);\n req.end();\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\nexport interface SniffOptions {\n port?: number;\n forwardPort?: number;\n jsonMode?: boolean;\n}\n\nexport async function startSniff(options: SniffOptions = {}): Promise<void> {\n const port = options.port ?? 4821;\n const forwardPort = options.forwardPort ?? 4820;\n const jsonMode = options.jsonMode ?? false;\n\n const server = http.createServer((req, res) => {\n if (req.method !== 'POST' || req.url !== '/v1/logs') {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not found' }));\n return;\n }\n\n const chunks: Buffer[] = [];\n\n req.on('data', (chunk: Buffer) => {\n chunks.push(chunk);\n });\n\n req.on('end', () => {\n // Always respond 200 immediately so Claude Code never stalls\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ partialSuccess: {} }));\n\n const body = Buffer.concat(chunks).toString('utf8');\n\n // Forward before parsing so Radar still gets everything even if we throw\n forwardBody(body, forwardPort);\n\n let payload: OtlpLogsPayload;\n try {\n payload = JSON.parse(body) as OtlpLogsPayload;\n } catch {\n process.stderr.write('[radar/sniff] malformed JSON — skipping print\\n');\n return;\n }\n\n const printer = jsonMode ? printJson : printCompact;\n\n for (const resourceLog of payload.resourceLogs ?? []) {\n for (const scopeLog of resourceLog.scopeLogs ?? []) {\n for (const record of scopeLog.logRecords ?? []) {\n printer(record);\n }\n }\n }\n });\n\n req.on('error', (err) => {\n process.stderr.write(`[radar/sniff] request error: ${String(err)}\\n`);\n });\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(port, () => resolve());\n });\n\n const mode = jsonMode ? 'json' : 'compact';\n process.stdout.write(\n `${BOLD}${CYAN}radar sniff${RESET} listening on :${port} forwarding to :${forwardPort} mode=${mode}\\n\\n`,\n );\n\n async function shutdown(): Promise<void> {\n process.stdout.write('\\n[radar/sniff] shutting down\\n');\n await new Promise<void>((resolve) => server.close(() => resolve()));\n process.exit(0);\n }\n\n process.on('SIGINT', () => void shutdown());\n process.on('SIGTERM', () => void shutdown());\n}\n"],"mappings":"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","names":["EventEmitter","Anthropic","Anthropic","readFileSync","writeFileSync","existsSync","mkdirSync","join","homedir","join","homedir","RESET","DIM","BOLD","GREEN","YELLOW","CYAN","LINE_WIDTH","writeln","existsSync","readFileSync","mkdirSync","writeFileSync","rl","http","RESET","DIM","BOLD","CYAN","YELLOW","GREEN"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "radar-cc",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Non-blocking intent alignment checker for Claude Code, powered by OpenTelemetry",
5
5
  "type": "module",
6
6
  "bin": {