radar-cc 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -1,104 +1,111 @@
1
- # Radar
1
+ # radar-cc
2
2
 
3
- A Claude Code sidecar that flags ambiguous prompts and checks whether Claude's actions matched your intentdisplayed in a second terminal pane.
3
+ Intent alignment checker for Claude Code. Radar watches Claude Code's OpenTelemetry stream and tells you — in a second terminal pane — whether your prompt was clear before Claude starts, and whether Claude stayed on target after it finishes.
4
4
 
5
- ---
5
+ ## Requirements
6
6
 
7
- ## The problem
7
+ - Node.js >= 22
8
+ - An Anthropic API key
8
9
 
9
- Claude Code's responses are only as good as the developer's intent, but intent is not often communicated precisely.
10
+ ## Install
10
11
 
11
- | You say | Claude does | You meant |
12
- |---|---|---|
13
- | "clean up this module" | Restructures imports, renames functions, splits files | Delete the 3 commented-out functions |
14
- | "the API is slow" | Adds Redis caching across 4 endpoints | Profile this one DB query |
15
- | "update the tests" | Rewrites the entire test suite | Add coverage for 2 new edge cases |
16
-
17
-
18
- Radar analyses at two moments: early in Claude's turn (pre-advisory, based on prompt text) and after Claude finishes (post-advisory, based on what tools Claude used and what it changed).
19
- There's a limitiation in the current version: Sonnet is inferring intent alignment from side effects without seeing Claude's actual response. It knows Claude touched 5 files but not why Claude explained it did so. That's a reasonable proxy most of the time — scope creep and wrong-target are visible in tool activity — but it'll miss subtler misalignments where Claude did the right amount of work on the wrong thing within the same. The reason for that is that the OTel events Claude Code emits are operational telemetry — user_prompt, tool_result, api_request, api_error. They tell you what happened mechanically. The actual assistant message (what Claude wrote back to the developer) isn't an OTel event. In a future version we can fix that subtlety through parsing the jsonl files.
20
-
21
-
22
- ---
23
-
24
- ## Installation
12
+ ```sh
13
+ npm install -g radar-cc
14
+ radar setup
15
+ ```
25
16
 
26
- The package isn't published to npm yet. To install locally from the repo:
17
+ `radar setup` writes the required OTel environment variables to `~/.claude/settings.json` and walks you through storing your Anthropic API key — either on local disk or in 1Password. Restart Claude Code after running it.
27
18
 
28
- ```bash
29
- git clone https://github.com/ahaydar/radar.git
30
- cd radar
31
- npm install
32
- npm run build
33
- npm link
34
- ```
19
+ You can also pass the key directly to skip the prompt:
35
20
 
36
- Add the OTel env vars to your shell profile:
37
-
38
- ```bash
39
- cat >> ~/.zshrc << 'EOF'
40
-
41
- # Radar: Claude Code OTel config
42
- export CLAUDE_CODE_ENABLE_TELEMETRY=1
43
- export OTEL_LOGS_EXPORTER=otlp
44
- export OTEL_EXPORTER_OTLP_PROTOCOL=http/json
45
- export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://localhost:4820/v1/logs
46
- export OTEL_LOG_USER_PROMPTS=1
47
- export OTEL_LOG_TOOL_DETAILS=1
48
- export OTEL_LOGS_EXPORT_INTERVAL=2000
49
- EOF
50
- source ~/.zshrc
21
+ ```sh
22
+ radar setup --api-key <your-key>
51
23
  ```
52
24
 
53
- Replace `~/.zshrc` with `~/.bashrc` if using Bash. These are safe to have permanently — if Radar isn't running, Claude Code silently ignores them.
54
-
55
25
  ## Usage
56
26
 
57
- Radar requires an Anthropic API key. Set it in your environment:
27
+ Open a second terminal pane alongside Claude Code and run:
58
28
 
59
- ```bash
60
- export ANTHROPIC_API_KEY=sk-ant-...
29
+ ```sh
30
+ radar watch
61
31
  ```
62
32
 
63
- Or pass it directly:
33
+ Send prompts in your Claude Code pane as normal. Radar listens passively on `localhost:4820`.
64
34
 
65
- ```bash
66
- radar watch --api-key sk-ant-...
35
+ ## Output
36
+
37
+ ```
38
+ ── Radar v0.1.0 ─────────────────────────────────────
39
+ Listening on localhost:4820
40
+ Waiting for Claude Code telemetry...
41
+ ─────────────────────────────────────────────────────
42
+
43
+ ── PRE ── 14:23:07 ── score: 0.34 ── ✓ Clear ────────
44
+
45
+ ── PRE ── 14:25:12 ── score: 0.78 ───────────────────
46
+ ⚠ "clean up this module" is ambiguous.
47
+ Claude will likely restructure imports and rename functions.
48
+ Did you mean: remove the 3 commented-out functions?
49
+ → Try: "delete the dead code in auth.ts — the 3 commented
50
+ functions at the bottom"
51
+ ─────────────────────────────────────────────────────
52
+
53
+ ── POST ── 14:25:38 ──────────────────────────────────
54
+ ✓ Response aligned with intent.
55
+ Tools: Edit (2 files) · 847 tokens · $0.003
56
+ ─────────────────────────────────────────────────────
57
+
58
+ ── POST ── 14:31:02 ──────────────────────────────────
59
+ ✗ Scope exceeded likely intent.
60
+ Claude ran Edit on 5 files, Bash (3 commands), 12k tokens, $0.08.
61
+ Developer likely wanted: coverage for 2 new edge cases only.
62
+ → "undo all changes. add test cases for the null input
63
+ and timeout edge cases in processOrder — nothing else"
64
+ ─────────────────────────────────────────────────────
67
65
  ```
68
66
 
69
- In one terminal pane, start Radar:
67
+ PRE advisories fire within ~2 seconds of your prompt. POST advisories fire after Claude's turn ends, based on what tools it used and what it changed — not Claude's response text, which OTel does not expose.
70
68
 
71
- ```bash
72
- radar watch
73
- ```
69
+ ## How it works
74
70
 
75
- In another pane, start Claude Code normally. Telemetry flows automatically.
71
+ Claude Code emits structured OTel log events (`user_prompt`, `tool_result`, `api_request`) when telemetry is enabled. Radar runs a lightweight OTLP HTTP receiver on `localhost:4820` that collects these events without touching Claude's execution path.
76
72
 
77
- Claude Code must be started (or restarted) after the env vars are set it reads them at launch.
73
+ - **Pre-advisory:** Haiku scores the prompt for ambiguity. If the score is >= 0.6, Sonnet prints a warning with the most likely misinterpretation and a suggested clarification.
74
+ - **Post-advisory:** After no new events for ~5 seconds (turn boundary), Sonnet reviews the accumulated tool activity against the original prompt intent and flags scope drift.
78
75
 
79
- ### Dev mode
76
+ Claude is never blocked or interrupted.
80
77
 
81
- `npm run dev` compiles TypeScript in watch mode and simultaneously runs `radar watch`, restarting it whenever the build changes:
78
+ ## OTel variables (set by `radar setup`)
82
79
 
83
- ```bash
84
- ANTHROPIC_API_KEY=sk-ant-... npm run dev
85
- ```
80
+ | Variable | Value |
81
+ |---|---|
82
+ | `CLAUDE_CODE_ENABLE_TELEMETRY` | `1` |
83
+ | `OTEL_LOGS_EXPORTER` | `otlp` |
84
+ | `OTEL_EXPORTER_OTLP_PROTOCOL` | `http/json` |
85
+ | `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | `http://localhost:4820/v1/logs` |
86
+ | `OTEL_LOG_USER_PROMPTS` | `1` |
87
+ | `OTEL_LOG_TOOL_DETAILS` | `1` |
88
+ | `OTEL_LOGS_EXPORT_INTERVAL` | `2000` |
86
89
 
87
- ---
90
+ `OTEL_LOG_USER_PROMPTS=1` is required for prompt content analysis. Without it, Radar can detect turn boundaries but cannot analyze intent.
88
91
 
89
- ## How it works
92
+ These are written to the `env` block in `~/.claude/settings.json`. If Radar is not running, Claude Code silently ignores them.
90
93
 
91
- **Pre-advisory:** When you submit a prompt, Claude Code emits it via OTel. Radar's Haiku classifier scores its ambiguity. If risk is high, Sonnet prints a warning in the Radar pane — what Claude will likely misinterpret and how to rephrase. Claude is already working at this point; you can hit Esc to interrupt if the warning is serious.
94
+ ## Options
92
95
 
93
- **Post-advisory:** After Claude's turn ends (no new tool calls for ~5s), Sonnet reviews the accumulated activity — tools called, files edited, commands run, tokens spent — and checks whether it matched your likely intent. If misaligned, it prints a suggested re-prompt.
96
+ ```
97
+ radar setup [options]
94
98
 
95
- Radar stays silent when everything looks clear.
99
+ -k, --api-key <key> API key to store (skips the interactive prompt)
96
100
 
97
- ---
101
+ radar watch [options]
98
102
 
99
- ## Tuning
103
+ -p, --port <number> OTLP listener port (default: 4820)
104
+ -t, --timeout <ms> Turn boundary silence window (default: 5000)
105
+ -s, --threshold <score> Ambiguity score threshold 0.0–1.0 (default: 0.6)
106
+ -k, --api-key <key> Anthropic API key (overrides all stored sources)
107
+ ```
100
108
 
101
- Prompt templates are in `src/analysis/prompts.ts`. Key tuning dimensions:
109
+ ## License
102
110
 
103
- - **Sensitivity:** Classifier flags prompts above a 0.6 ambiguity score. Raise to 0.7 if too noisy; lower to 0.5 if missing obvious cases.
104
- - **Post-advisory context:** The advisor sees tool activity (files, commands, cost) but not Claude's response text. Tune the prompt to reference specific tools and costs.
111
+ MIT
package/dist/cli/index.js CHANGED
@@ -732,6 +732,15 @@ async function startWatch(options = {}) {
732
732
  printError(`Post-advisory failed for prompt ${ctx.promptId}: ${errMsg(err)}`);
733
733
  }
734
734
  }
735
+ const requiredOtelVars = [
736
+ "CLAUDE_CODE_ENABLE_TELEMETRY",
737
+ "OTEL_LOGS_EXPORTER",
738
+ "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"
739
+ ];
740
+ const missingVars = requiredOtelVars.filter((v) => !process.env[v]);
741
+ if (missingVars.length > 0) {
742
+ printWarning("OTel env vars not configured. Run `radar setup` and restart Claude Code.");
743
+ }
735
744
  try {
736
745
  await receiver.start();
737
746
  } catch (err) {
@@ -755,11 +764,76 @@ async function startWatch(options = {}) {
755
764
  }
756
765
 
757
766
  // src/cli/setup.ts
767
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
768
+ import { join as join2 } from "path";
769
+ import { homedir as homedir2 } from "os";
770
+ import { createInterface } from "readline";
771
+
772
+ // src/cli/config.ts
758
773
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
774
+ import { execSync } from "child_process";
759
775
  import { join } from "path";
760
776
  import { homedir } from "os";
761
- var CLAUDE_DIR = join(homedir(), ".claude");
762
- var SETTINGS_PATH = join(CLAUDE_DIR, "settings.json");
777
+ var CONFIG_DIR = join(homedir(), ".config", "radar");
778
+ var CONFIG_PATH = join(CONFIG_DIR, "config.json");
779
+ function readConfig() {
780
+ if (!existsSync(CONFIG_PATH)) return {};
781
+ try {
782
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
783
+ } catch {
784
+ return {};
785
+ }
786
+ }
787
+ function writeConfig(config) {
788
+ if (!existsSync(CONFIG_DIR)) {
789
+ mkdirSync(CONFIG_DIR, { recursive: true });
790
+ }
791
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf8");
792
+ }
793
+ function configPath() {
794
+ return CONFIG_PATH;
795
+ }
796
+ function isOpAvailable() {
797
+ try {
798
+ execSync("op --version", { stdio: "ignore" });
799
+ return true;
800
+ } catch {
801
+ return false;
802
+ }
803
+ }
804
+ function createOpItem(apiKey) {
805
+ const title = "Radar Anthropic API Key";
806
+ const raw = execSync(
807
+ `op item create --category=login --title="${title}" "password=${apiKey}" --format json`,
808
+ { encoding: "utf8" }
809
+ );
810
+ const item = JSON.parse(raw);
811
+ return `op://${item.vault.name}/${item.title}/password`;
812
+ }
813
+ function readOpItem(ref) {
814
+ return execSync(`op read "${ref}"`, { encoding: "utf8" }).trim();
815
+ }
816
+ function resolveStoredApiKey() {
817
+ const config = readConfig();
818
+ if (config.apiKey) return config.apiKey;
819
+ if (config.apiKeyRef) {
820
+ try {
821
+ return readOpItem(config.apiKeyRef);
822
+ } catch (err) {
823
+ const detail = err instanceof Error ? err.message : String(err);
824
+ throw new Error(
825
+ `Failed to read API key from 1Password (${config.apiKeyRef}).
826
+ Make sure you are signed in: op signin
827
+ Detail: ${detail}`
828
+ );
829
+ }
830
+ }
831
+ return void 0;
832
+ }
833
+
834
+ // src/cli/setup.ts
835
+ var CLAUDE_DIR = join2(homedir2(), ".claude");
836
+ var SETTINGS_PATH = join2(CLAUDE_DIR, "settings.json");
763
837
  var OTEL_VARS = {
764
838
  CLAUDE_CODE_ENABLE_TELEMETRY: "1",
765
839
  OTEL_LOGS_EXPORTER: "otlp",
@@ -783,20 +857,23 @@ function writeln2(text = "") {
783
857
  process.stdout.write(text + "\n");
784
858
  }
785
859
  function readSettings() {
786
- if (!existsSync(SETTINGS_PATH)) return {};
860
+ if (!existsSync2(SETTINGS_PATH)) return {};
787
861
  try {
788
- return JSON.parse(readFileSync(SETTINGS_PATH, "utf8"));
862
+ return JSON.parse(readFileSync2(SETTINGS_PATH, "utf8"));
789
863
  } catch {
790
864
  throw new Error(`Could not parse ${SETTINGS_PATH}. Fix the JSON syntax and try again.`);
791
865
  }
792
866
  }
793
867
  function writeSettings(settings) {
794
- if (!existsSync(CLAUDE_DIR)) {
795
- mkdirSync(CLAUDE_DIR, { recursive: true });
868
+ if (!existsSync2(CLAUDE_DIR)) {
869
+ mkdirSync2(CLAUDE_DIR, { recursive: true });
796
870
  }
797
- writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf8");
871
+ writeFileSync2(SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf8");
872
+ }
873
+ function question(rl, prompt) {
874
+ return new Promise((resolve) => rl.question(prompt, resolve));
798
875
  }
799
- function runSetup() {
876
+ async function runSetup(preEnteredKey) {
800
877
  writeln2(CYAN2 + BOLD2 + sep("\u2500\u2500 Radar Setup ") + RESET2);
801
878
  let settings;
802
879
  try {
@@ -806,7 +883,7 @@ function runSetup() {
806
883
  process.exit(1);
807
884
  }
808
885
  const existingEnv = settings.env ?? {};
809
- writeln2(`Writing OTel config to ${SETTINGS_PATH.replace(homedir(), "~")}...`);
886
+ writeln2(`Writing OTel config to ${SETTINGS_PATH.replace(homedir2(), "~")}...`);
810
887
  writeln2();
811
888
  for (const [key, value] of Object.entries(OTEL_VARS)) {
812
889
  const alreadySet = key in existingEnv && existingEnv[key] === value;
@@ -822,18 +899,133 @@ function runSetup() {
822
899
  process.exit(1);
823
900
  }
824
901
  writeln2();
825
- writeln2(`${GREEN2}\u2713${RESET2} Settings written to ${SETTINGS_PATH.replace(homedir(), "~")}`);
826
- if (!process.env.ANTHROPIC_API_KEY) {
827
- writeln2();
828
- writeln2(`${YELLOW2}\u26A0${RESET2} ANTHROPIC_API_KEY is not set in your current shell.`);
829
- writeln2(` Radar needs it to run analysis. Set it before starting:`);
830
- writeln2(` ${DIM2}export ANTHROPIC_API_KEY=sk-ant-...${RESET2}`);
831
- }
902
+ writeln2(`${GREEN2}\u2713${RESET2} Settings written to ${SETTINGS_PATH.replace(homedir2(), "~")}`);
903
+ await promptApiKey(preEnteredKey);
832
904
  writeln2();
833
905
  writeln2("Ready. Start Radar in a second terminal pane:");
834
906
  writeln2(` ${BOLD2}radar watch${RESET2}`);
835
907
  writeln2(DIM2 + sep() + RESET2);
836
908
  }
909
+ async function promptApiKey(preEnteredKey) {
910
+ writeln2();
911
+ let apiKey = preEnteredKey;
912
+ if (!apiKey) {
913
+ const config = readConfig();
914
+ const envKey = process.env.ANTHROPIC_API_KEY;
915
+ const hasStored = config.apiKey ?? config.apiKeyRef;
916
+ const activeKey = envKey ?? config.apiKey;
917
+ if (hasStored || envKey) {
918
+ if (envKey) {
919
+ const masked = envKey.slice(0, 10) + "\u2026" + envKey.slice(-4);
920
+ writeln2(`${GREEN2}\u2713${RESET2} API key found via ANTHROPIC_API_KEY env var: ${DIM2}${masked}${RESET2}`);
921
+ } else if (config.apiKeyRef) {
922
+ writeln2(`${GREEN2}\u2713${RESET2} API key linked via 1Password: ${DIM2}${config.apiKeyRef}${RESET2}`);
923
+ } else if (config.apiKey) {
924
+ const masked = config.apiKey.slice(0, 10) + "\u2026" + config.apiKey.slice(-4);
925
+ writeln2(`${GREEN2}\u2713${RESET2} API key stored locally: ${DIM2}${masked}${RESET2}`);
926
+ }
927
+ const rl2 = createInterface({ input: process.stdin, output: process.stdout });
928
+ const answer = await question(rl2, " Replace it? [y/N] ");
929
+ rl2.close();
930
+ if (answer.trim().toLowerCase() !== "y") return;
931
+ void activeKey;
932
+ } else {
933
+ writeln2(`${YELLOW2}\u26A0${RESET2} No API key found. Radar needs one to run analysis.`);
934
+ }
935
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
936
+ const entered = await question(rl, " Enter your Anthropic API key: ");
937
+ rl.close();
938
+ apiKey = entered.trim();
939
+ if (!apiKey) {
940
+ writeln2(`${YELLOW2}\u26A0${RESET2} No key entered \u2014 skipping. Re-run setup or use --api-key <key>.`);
941
+ return;
942
+ }
943
+ }
944
+ await promptStorage(apiKey);
945
+ }
946
+ async function promptStorage(apiKey) {
947
+ const opAvailable = isOpAvailable();
948
+ writeln2();
949
+ writeln2("Where would you like to store the API key?");
950
+ writeln2(` ${BOLD2}1${RESET2} Local disk ${DIM2}(${configPath().replace(homedir2(), "~")})${RESET2}`);
951
+ if (opAvailable) {
952
+ writeln2(` ${BOLD2}2${RESET2} 1Password ${DIM2}(recommended)${RESET2}`);
953
+ } else {
954
+ writeln2(` ${DIM2}2 1Password (op CLI not found \u2014 see instructions below)${RESET2}`);
955
+ }
956
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
957
+ const answer = await question(rl, ` Choice [1${opAvailable ? "/2" : ""}]: `);
958
+ rl.close();
959
+ const choice = answer.trim();
960
+ if (choice === "2") {
961
+ if (!opAvailable) {
962
+ writeln2();
963
+ writeln2(`${YELLOW2}\u26A0${RESET2} 1Password CLI (op) is not installed.`);
964
+ writeln2(" To set it up:");
965
+ writeln2(` ${DIM2}1. Install: https://developer.1password.com/docs/cli/get-started${RESET2}`);
966
+ writeln2(` ${DIM2}2. Sign in: op signin${RESET2}`);
967
+ writeln2(` ${DIM2}3. Re-run: radar setup --api-key <key>${RESET2}`);
968
+ return;
969
+ }
970
+ await storeIn1Password(apiKey);
971
+ return;
972
+ }
973
+ storeLocally(apiKey);
974
+ }
975
+ function storeLocally(apiKey) {
976
+ const config = readConfig();
977
+ writeConfig({ ...config, apiKey, apiKeyRef: void 0 });
978
+ writeln2();
979
+ writeln2(`${GREEN2}\u2713${RESET2} API key saved to ${configPath().replace(homedir2(), "~")}`);
980
+ }
981
+ async function storeIn1Password(apiKey) {
982
+ writeln2();
983
+ writeln2("How would you like to store it in 1Password?");
984
+ writeln2(` ${BOLD2}1${RESET2} Create a new item ${DIM2}(radar will add it for you)${RESET2}`);
985
+ writeln2(` ${BOLD2}2${RESET2} Use an existing item ${DIM2}(enter an op:// reference)${RESET2}`);
986
+ const rl1 = createInterface({ input: process.stdin, output: process.stdout });
987
+ const choice = await question(rl1, " Choice [1/2]: ");
988
+ rl1.close();
989
+ if (choice.trim() === "2") {
990
+ await useExistingOpRef();
991
+ return;
992
+ }
993
+ writeln2();
994
+ writeln2("Creating item in 1Password...");
995
+ try {
996
+ const ref = createOpItem(apiKey);
997
+ const config = readConfig();
998
+ writeConfig({ ...config, apiKey: void 0, apiKeyRef: ref });
999
+ writeln2(`${GREEN2}\u2713${RESET2} API key stored in 1Password`);
1000
+ writeln2(` Reference: ${DIM2}${ref}${RESET2}`);
1001
+ } catch (err) {
1002
+ const msg = err instanceof Error ? err.message : String(err);
1003
+ writeln2(`${YELLOW2}\u2717${RESET2} Failed to store in 1Password: ${msg}`);
1004
+ writeln2(" Make sure you are signed in: " + DIM2 + "op signin" + RESET2);
1005
+ const rl2 = createInterface({ input: process.stdin, output: process.stdout });
1006
+ const fallback = await question(rl2, " Store on local disk instead? [Y/n] ");
1007
+ rl2.close();
1008
+ if (fallback.trim().toLowerCase() !== "n") {
1009
+ storeLocally(apiKey);
1010
+ }
1011
+ }
1012
+ }
1013
+ async function useExistingOpRef() {
1014
+ writeln2();
1015
+ writeln2(` Enter the ${BOLD2}op://${RESET2} reference for your API key.`);
1016
+ writeln2(` ${DIM2}Example: op://Personal/Anthropic/credential${RESET2}`);
1017
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1018
+ const ref = await question(rl, " Reference: ");
1019
+ rl.close();
1020
+ const trimmed = ref.trim();
1021
+ if (!trimmed.startsWith("op://")) {
1022
+ writeln2(`${YELLOW2}\u26A0${RESET2} Invalid reference \u2014 must start with op://. Re-run setup to try again.`);
1023
+ return;
1024
+ }
1025
+ const config = readConfig();
1026
+ writeConfig({ ...config, apiKey: void 0, apiKeyRef: trimmed });
1027
+ writeln2(`${GREEN2}\u2713${RESET2} 1Password reference saved: ${DIM2}${trimmed}${RESET2}`);
1028
+ }
837
1029
 
838
1030
  // src/cli/index.ts
839
1031
  var program = new Command();
@@ -846,7 +1038,7 @@ program.command("watch").description("Start listening for Claude Code telemetry
846
1038
  "-s, --threshold <score>",
847
1039
  "Ambiguity score threshold for triggering a pre-advisory (0.0\u20131.0)",
848
1040
  "0.6"
849
- ).option("-k, --api-key <key>", "Anthropic API key (defaults to ANTHROPIC_API_KEY env var)").action(async (opts) => {
1041
+ ).option("-k, --api-key <key>", "Anthropic API key (overrides all other sources)").action(async (opts) => {
850
1042
  const port = parseInt(opts.port, 10);
851
1043
  const boundaryTimeoutMs = parseInt(opts.timeout, 10);
852
1044
  const scoreThreshold = parseFloat(opts.threshold);
@@ -862,15 +1054,21 @@ program.command("watch").description("Start listening for Claude Code telemetry
862
1054
  printError("--threshold must be a number between 0.0 and 1.0");
863
1055
  process.exit(1);
864
1056
  }
865
- const apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY;
1057
+ let apiKey;
1058
+ try {
1059
+ apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY ?? resolveStoredApiKey();
1060
+ } catch (err) {
1061
+ printError(err instanceof Error ? err.message : String(err));
1062
+ process.exit(1);
1063
+ }
866
1064
  if (!apiKey) {
867
- printError("Anthropic API key not found. Set ANTHROPIC_API_KEY or use --api-key <key>.");
1065
+ printError("Anthropic API key not found. Run `radar setup`, set ANTHROPIC_API_KEY, or use --api-key <key>.");
868
1066
  process.exit(1);
869
1067
  }
870
1068
  await startWatch({ port, boundaryTimeoutMs, scoreThreshold, apiKey });
871
1069
  });
872
- program.command("setup").description("Write OTel config to ~/.claude/settings.json so Claude Code streams telemetry to Radar").action(() => {
873
- runSetup();
1070
+ 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
+ await runSetup(opts.apiKey);
874
1072
  });
875
1073
  if (process.argv.length === 2) {
876
1074
  program.outputHelp();
@@ -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"],"sourcesContent":["import { Command } from 'commander';\nimport { startWatch } from './watch.js';\nimport { runSetup } from './setup.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 (defaults to ANTHROPIC_API_KEY env var)')\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 const apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY;\n if (!apiKey) {\n printError('Anthropic API key not found. 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 so Claude Code streams telemetry to Radar')\n .action(() => {\n runSetup();\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 // ── 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';\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// ─── Main ─────────────────────────────────────────────────────────────────────\n\nexport function runSetup(): void {\n writeln(CYAN + BOLD + sep('── Radar Setup ') + RESET);\n\n // ── Read existing settings ────────────────────────────────────────────────\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 // ── Merge OTel vars ───────────────────────────────────────────────────────\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 check ─────────────────────────────────────────────────────────\n if (!process.env.ANTHROPIC_API_KEY) {\n writeln();\n writeln(`${YELLOW}⚠${RESET} ANTHROPIC_API_KEY is not set in your current shell.`);\n writeln(` Radar needs it to run analysis. Set it before starting:`);\n writeln(` ${DIM}export ANTHROPIC_API_KEY=sk-ant-...${RESET}`);\n }\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"],"mappings":";;;AAAA,SAAS,eAAe;;;ACAxB,SAAS,oBAAoB;AAC7B,YAAY,UAAU;AA8FtB,SAAS,aAAa,OAA4D;AAChF,QAAM,MAAM,oBAAI,IAAuB;AACvC,MAAI,CAAC,MAAO,QAAO;AACnB,aAAW,KAAK,OAAO;AACrB,UAAM,IAAI,EAAE;AACZ,QAAI,EAAE,gBAAgB,OAAW,KAAI,IAAI,EAAE,KAAK,EAAE,WAAW;AAAA,aACpD,EAAE,aAAa,OAAW,KAAI,IAAI,EAAE,KAAK,EAAE,QAAQ;AAAA,aACnD,EAAE,gBAAgB,OAAW,KAAI,IAAI,EAAE,KAAK,EAAE,WAAW;AAAA,aACzD,EAAE,cAAc,OAAW,KAAI,IAAI,EAAE,KAAK,EAAE,SAAS;AAAA,EAChE;AACA,SAAO;AACT;AAEA,SAAS,UAAU,KAA6B,KAAqB;AACnE,QAAM,IAAI,IAAI,IAAI,GAAG;AACrB,SAAO,OAAO,MAAM,WAAW,IAAI;AACrC;AAEA,SAAS,UAAU,KAA6B,KAAqB;AACnE,QAAM,IAAI,IAAI,IAAI,GAAG;AACrB,SAAO,OAAO,MAAM,WAAW,IAAI;AACrC;AAEA,SAAS,QAAQ,KAA6B,KAAsB;AAClE,QAAM,IAAI,IAAI,IAAI,GAAG;AACrB,SAAO,OAAO,MAAM,YAAY,IAAI;AACtC;AAIA,SAAS,eAAe,QAAuB,WAAsC;AACnF,QAAM,YAAY,OAAO,MAAM,eAAe;AAG9C,QAAM,WAAW,OAAO,gBAAgB;AACxC,QAAM,cAAc,KAAK,MAAM,OAAO,OAAO,QAAQ,IAAI,QAAU,CAAC;AAGpE,QAAM,QAAQ,aAAa,OAAO,UAAU;AAE5C,QAAM,WAAW,UAAU,OAAO,WAAW;AAC7C,QAAM,OAAkB,EAAE,MAAM,WAAW,UAAU,WAAW,YAAY;AAE5E,UAAQ,WAAW;AAAA,IACjB,KAAK,2BAA2B;AAC9B,YAAM,IAAqB;AAAA,QACzB,GAAG;AAAA,QACH,MAAM;AAAA,QACN,QAAQ,UAAU,OAAO,QAAQ;AAAA,QACjC,cAAc,UAAU,OAAO,eAAe;AAAA,MAChD;AACA,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,2BAA2B;AAC9B,YAAM,IAAqB;AAAA,QACzB,GAAG;AAAA,QACH,MAAM;AAAA,QACN,UAAU,UAAU,OAAO,WAAW;AAAA,QACtC,SAAS,QAAQ,OAAO,SAAS;AAAA,QACjC,YAAY,UAAU,OAAO,aAAa;AAAA,MAC5C;AACA,YAAM,iBAAiB,UAAU,OAAO,iBAAiB;AACzD,UAAI,eAAgB,GAAE,iBAAiB;AACvC,YAAM,kBAAkB,UAAU,OAAO,mBAAmB;AAC5D,UAAI,gBAAiB,GAAE,kBAAkB;AACzC,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,2BAA2B;AAC9B,YAAM,IAAqB;AAAA,QACzB,GAAG;AAAA,QACH,MAAM;AAAA,QACN,OAAO,UAAU,OAAO,OAAO;AAAA,QAC/B,SAAS,UAAU,OAAO,UAAU;AAAA,QACpC,aAAa,UAAU,OAAO,cAAc;AAAA,QAC5C,cAAc,UAAU,OAAO,eAAe;AAAA,QAC9C,YAAY,UAAU,OAAO,aAAa;AAAA,MAC5C;AACA,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,yBAAyB;AAC5B,YAAM,IAAmB;AAAA,QACvB,GAAG;AAAA,QACH,MAAM;AAAA,QACN,OAAO,UAAU,OAAO,OAAO;AAAA,MACjC;AACA,YAAM,aAAa,UAAU,OAAO,aAAa;AACjD,UAAI,WAAY,GAAE,aAAa;AAC/B,aAAO;AAAA,IACT;AAAA,IAEA;AACE,aAAO;AAAA,EACX;AACF;AAIO,IAAM,eAAN,cAA2B,aAAa;AAAA,EAC5B;AAAA,EACA;AAAA,EACT,SAA6B;AAAA,EAErC,YAAY,OAAO,MAAM;AACvB,UAAM;AACN,SAAK,OAAO;AACZ,SAAK,oBAAoB,SAAS,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,EAC3E;AAAA,EAEQ,gBAAgB,eAA+C;AACrE,UAAM,YAAY,cAAc,IAAI,YAAY;AAChD,QAAI,OAAO,cAAc,YAAY,UAAW,QAAO;AAEvD,UAAM,aAAa,cAAc,IAAI,qBAAqB;AAC1D,QAAI,OAAO,eAAe,YAAY,WAAY,QAAO;AAEzD,UAAM,MAAM,cAAc,IAAI,aAAa;AAC3C,QAAI,QAAQ,OAAW,QAAO,OAAO,GAAG;AAExC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,QAAuB;AACrB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,SAAc,kBAAa,CAAC,KAAK,QAAQ;AAC7C,aAAK,cAAc,KAAK,GAAG;AAAA,MAC7B,CAAC;AAED,aAAO,GAAG,SAAS,CAAC,QAAQ;AAC1B,aAAK,KAAK,SAAS,GAAG;AAAA,MACxB,CAAC;AAED,aAAO,OAAO,KAAK,MAAM,MAAM;AAC7B,aAAK,SAAS;AACd,gBAAQ;AAAA,MACV,CAAC;AAGD,aAAO,KAAK,SAAS,MAAM;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA,EAEA,OAAsB;AACpB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,CAAC,KAAK,QAAQ;AAChB,gBAAQ;AACR;AAAA,MACF;AACA,WAAK,OAAO,MAAM,CAAC,QAAQ;AACzB,YAAI,IAAK,QAAO,GAAG;AAAA,YACd,SAAQ;AAAA,MACf,CAAC;AACD,WAAK,SAAS;AAAA,IAChB,CAAC;AAAA,EACH;AAAA,EAEQ,cAAc,KAA2B,KAAgC;AAC/E,QAAI,IAAI,WAAW,UAAU,IAAI,QAAQ,YAAY;AACnD,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,YAAY,CAAC,CAAC;AAC9C;AAAA,IACF;AAEA,UAAM,SAAmB,CAAC;AAE1B,QAAI,GAAG,QAAQ,CAAC,UAAkB;AAChC,aAAO,KAAK,KAAK;AAAA,IACnB,CAAC;AAED,QAAI,GAAG,OAAO,MAAM;AAElB,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,gBAAgB,CAAC,EAAE,CAAC,CAAC;AAE9C,YAAM,OAAO,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM;AAClD,UAAI;AAEJ,UAAI;AACF,kBAAU,KAAK,MAAM,IAAI;AAAA,MAC3B,SAAS,KAAK;AACZ,gBAAQ,OAAO,MAAM,gCAAgC,OAAO,GAAG,CAAC;AAAA,CAAI;AACpE;AAAA,MACF;AAEA,WAAK,eAAe,OAAO;AAAA,IAC7B,CAAC;AAED,QAAI,GAAG,SAAS,CAAC,QAAQ;AACvB,cAAQ,OAAO,MAAM,+BAA+B,OAAO,GAAG,CAAC;AAAA,CAAI;AAAA,IACrE,CAAC;AAAA,EACH;AAAA,EAEQ,eAAe,SAAgC;AACrD,eAAW,eAAe,QAAQ,gBAAgB,CAAC,GAAG;AACpD,YAAM,gBAAgB,aAAa,YAAY,UAAU,UAAU;AACnE,YAAM,YAAY,KAAK,gBAAgB,aAAa;AACpD,iBAAW,YAAY,YAAY,aAAa,CAAC,GAAG;AAClD,mBAAW,UAAU,SAAS,cAAc,CAAC,GAAG;AAC9C,gBAAM,QAAQ,eAAe,QAAQ,SAAS;AAC9C,cAAI,OAAO;AACT,iBAAK,KAAK,SAAS,KAAK;AAAA,UAC1B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AC/SA,SAAS,gBAAAA,qBAAoB;AA6D7B,SAAS,mBAAmB,gBAA6C;AACvE,MAAI,mBAAmB,UAAa,mBAAmB,KAAM,QAAO;AAEpE,MAAI,OAAO,mBAAmB,UAAU;AACtC,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,cAAc;AACxC,UAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,aAAa,QAAQ;AACxE,cAAM,MAAO,OAAmC;AAChD,YAAI,OAAO,QAAQ,UAAU;AAC3B,iBAAO,IAAI,MAAM,GAAG,GAAG;AAAA,QACzB;AAAA,MACF;AAAA,IACF,QAAQ;AAEN,aAAO,eAAe,MAAM,GAAG,GAAG;AAAA,IACpC;AAAA,EACF;AAEA,MAAI,OAAO,mBAAmB,YAAY,aAAc,gBAA2B;AACjF,UAAM,MAAO,eAA2C;AACxD,QAAI,OAAO,QAAQ,UAAU;AAC3B,aAAO,IAAI,MAAM,GAAG,GAAG;AAAA,IACzB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,mBAAmB,UAA4C;AACtE,QAAM,eAAe,SAAS,YAAY,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,SAAS,CAAC;AAC/E,QAAM,mBAAmB,SAAS,YAAY,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,aAAa,CAAC;AACvF,QAAM,oBAAoB,SAAS,YAAY,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,cAAc,CAAC;AACzF,QAAM,YAAY,CAAC,GAAG,IAAI,IAAI,SAAS,YAAY,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAE1E,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,iBAAN,cAA6BA,cAAa;AAAA,EAC9B;AAAA,EACA;AAAA,EAEA,WAAW,oBAAI,IAAiC;AAAA,EAChD,iBAAiB,oBAAI,IAA2C;AAAA;AAAA;AAAA,EAGhE,gBAAgB,oBAAI,IAA2C;AAAA,EAE/D,WAAW,oBAAI,IAA4B;AAAA,EACpD,iBAAiB;AAAA,EAEzB,YAAY,SAAiC;AAC3C,UAAM;AACN,SAAK,oBAAoB,SAAS,qBAAqB;AACvD,SAAK,iBAAiB,SAAS,kBAAkB;AAAA,EACnD;AAAA,EAEA,cAAgC;AAC9B,WAAO,CAAC,GAAG,KAAK,SAAS,OAAO,CAAC;AAAA,EACnC;AAAA,EAEA,WAAW,WAA+C;AACxD,WAAO,KAAK,SAAS,IAAI,SAAS;AAAA,EACpC;AAAA,EAEA,SAAS,OAAkF;AACzF,UAAM,EAAE,SAAS,IAAI;AAErB,UAAM,QAAQ,CAAC,KAAK,SAAS,IAAI,QAAQ;AAEzC,QAAI,OAAO;AAIT,YAAM,iBAAiB,KAAK,cAAc,IAAI,QAAQ;AACtD,UAAI,mBAAmB,QAAW;AAChC,qBAAa,cAAc;AAC3B,aAAK,cAAc,OAAO,QAAQ;AAAA,MACpC;AAEA,YAAM,WAAgC;AAAA,QACpC;AAAA,QACA,WAAW,MAAM;AAAA,QACjB,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,WAAW,KAAK,IAAI;AAAA,QACpB,aAAa,CAAC;AAAA,QACd,aAAa,CAAC;AAAA,QACd,QAAQ,CAAC;AAAA,MACX;AACA,WAAK,SAAS,IAAI,UAAU,QAAQ;AAGpC,UAAI,CAAC,KAAK,SAAS,IAAI,MAAM,SAAS,GAAG;AACvC,cAAM,UAA0B;AAAA,UAC9B,WAAW,MAAM;AAAA,UACjB,OAAO,IAAI,EAAE,KAAK,cAAc;AAAA,UAChC,WAAW;AAAA,UACX,gBAAgB;AAAA,UAChB,WAAW,KAAK,IAAI;AAAA,UACpB,YAAY,KAAK,IAAI;AAAA,UACrB,cAAc;AAAA,QAChB;AACA,aAAK,SAAS,IAAI,MAAM,WAAW,OAAO;AAC1C,aAAK,KAAK,iBAAiB,EAAE,GAAG,QAAQ,CAAC;AAAA,MAC3C,OAAO;AACL,cAAM,UAAU,KAAK,SAAS,IAAI,MAAM,SAAS;AACjD,gBAAQ,aAAa,KAAK,IAAI;AAC9B,gBAAQ;AAAA,MACV;AAAA,IACF;AAEA,UAAM,MAAM,KAAK,SAAS,IAAI,QAAQ;AAEtC,YAAQ,MAAM,MAAM;AAAA,MAClB,KAAK,eAAe;AAClB,YAAI,SAAS,MAAM,UAAU;AAC7B,YAAI,eAAe,MAAM,gBAAgB,IAAI,OAAO;AACpD;AAAA,MACF;AAAA,MACA,KAAK,eAAe;AAClB,cAAM,UAA6B;AAAA,UACjC,UAAU,MAAM;AAAA,UAChB,SAAS,MAAM;AAAA,UACf,YAAY,MAAM;AAAA,UAClB,iBAAiB,MAAM;AAAA,QACzB;AACA,YAAI,MAAM,aAAa,QAAQ;AAC7B,kBAAQ,cAAc,mBAAmB,MAAM,cAAc;AAAA,QAC/D;AACA,YAAI,YAAY,KAAK,OAAO;AAC5B;AAAA,MACF;AAAA,MACA,KAAK,eAAe;AAClB,YAAI,YAAY,KAAK;AAAA,UACnB,OAAO,MAAM;AAAA,UACb,SAAS,MAAM;AAAA,UACf,aAAa,MAAM;AAAA,UACnB,cAAc,MAAM;AAAA,UACpB,YAAY,MAAM;AAAA,QACpB,CAAC;AACD;AAAA,MACF;AAAA,MACA,KAAK,aAAa;AAChB,YAAI,OAAO,KAAK,MAAM,KAAK;AAC3B;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO;AACT,WAAK,KAAK,cAAc,mBAAmB,GAAG,CAAC;AAAA,IACjD;AAEA,SAAK,mBAAmB,QAAQ;AAAA,EAClC;AAAA,EAEA,WAAW,UAA2C;AACpD,UAAM,WAAW,KAAK,SAAS,IAAI,QAAQ;AAC3C,QAAI,CAAC,SAAU,QAAO;AACtB,WAAO,mBAAmB,QAAQ;AAAA,EACpC;AAAA,EAEQ,mBAAmB,UAAwB;AACjD,UAAM,WAAW,KAAK,eAAe,IAAI,QAAQ;AACjD,QAAI,aAAa,QAAW;AAC1B,mBAAa,QAAQ;AAAA,IACvB;AAEA,UAAM,QAAQ,WAAW,MAAM;AAC7B,WAAK,eAAe,OAAO,QAAQ;AACnC,YAAM,WAAW,KAAK,SAAS,IAAI,QAAQ;AAC3C,UAAI,UAAU;AAEZ,cAAM,UAAU,KAAK,SAAS,IAAI,SAAS,SAAS;AACpD,YAAI,SAAS;AACX,kBAAQ;AACR,kBAAQ,gBAAgB,SAAS,YAAY,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,SAAS,CAAC;AAAA,QAChF;AACA,aAAK,KAAK,iBAAiB,mBAAmB,QAAQ,CAAC;AAGvD,cAAM,eAAe,WAAW,MAAM;AACpC,eAAK,SAAS,OAAO,QAAQ;AAC7B,eAAK,cAAc,OAAO,QAAQ;AAAA,QACpC,GAAG,KAAK,cAAc;AACtB,aAAK,cAAc,IAAI,UAAU,YAAY;AAAA,MAC/C;AAAA,IACF,GAAG,KAAK,iBAAiB;AAEzB,SAAK,eAAe,IAAI,UAAU,KAAK;AAAA,EACzC;AACF;;;ACjQA,OAAO,eAAe;;;ACCf,IAAM,2BAAmC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BzC,IAAM,6BAAqC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAa3C,IAAM,6BAAqC;AAAA;AAAA;AAAA;AAO3C,IAAM,8BAAsC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAS5C,IAAM,8BAAsC;AAAA;AAAA;AAAA;AAAA;;;ACtD5C,SAAS,YAAe,SAAqB,IAAY,UAAyB;AACvF,MAAI;AACJ,QAAM,UAAU,IAAI,QAAW,CAAC,YAAY;AAC1C,cAAU,WAAW,MAAM,QAAQ,QAAQ,GAAG,EAAE;AAAA,EAClD,CAAC;AACD,SAAO,QAAQ,KAAK,CAAC,SAAS,OAAO,CAAC,EAAE,QAAQ,MAAM,aAAa,OAAO,CAAC;AAC7E;;;AFDA,IAAM,wBAAwB;AAC9B,IAAM,sBAAwC;AAAA,EAC5C,OAAO;AAAA,EACP,QAAQ;AACV;AAEO,IAAM,aAAN,MAAiB;AAAA,EACL;AAAA,EAEjB,YAAY,QAAiB;AAC3B,SAAK,SAAS,IAAI,UAAU,EAAE,QAAQ,UAAU,QAAQ,IAAI,kBAAkB,CAAC;AAAA,EACjF;AAAA,EAEA,MAAM,SAAS,QAA2C;AACxD,UAAM,mBAAmB,YAAuC;AAC9D,YAAM,UAAU,MAAM,KAAK,OAAO,SAAS,OAAO;AAAA,QAChD,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,QAAQ;AAAA,QACR,UAAU;AAAA,UACR;AAAA,YACE,MAAM;AAAA,YACN,SAAS;AAAA,EAA6B,MAAM;AAAA,UAC9C;AAAA,QACF;AAAA,MACF,CAAC;AAED,YAAM,UAAU,QAAQ,QAAQ,CAAC;AACjC,UAAI,QAAQ,SAAS,QAAQ;AAC3B,eAAO;AAAA,MACT;AAEA,aAAO,wBAAwB,QAAQ,IAAI;AAAA,IAC7C,GAAG;AAEH,WAAO,YAAY,iBAAiB,uBAAuB,mBAAmB;AAAA,EAChF;AACF;AAEA,SAAS,wBAAwB,KAA+B;AAC9D,MAAI;AAEF,UAAM,YAAY,IAAI,MAAM,YAAY;AACxC,QAAI,CAAC,WAAW;AACd,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,KAAK,MAAM,UAAU,CAAC,CAAC;AAEtC,QACE,OAAO,WAAW,YAClB,WAAW,QACX,EAAE,WAAW,WACb,EAAE,YAAY,SACd;AACA,aAAO;AAAA,IACT;AAEA,UAAM,MAAM;AACZ,UAAM,QAAQ,OAAO,IAAI,KAAK;AAC9B,UAAM,SAAS,OAAO,IAAI,MAAM;AAEhC,QAAI,MAAM,KAAK,KAAK,QAAQ,KAAK,QAAQ,GAAG;AAC1C,aAAO;AAAA,IACT;AAEA,WAAO,EAAE,OAAO,OAAO;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AG/EA,OAAOC,gBAAe;AAgBtB,IAAM,sBAAsB;AAE5B,IAAM,wBAAwC,EAAE,MAAM,iCAAiC;AACvF,IAAM,iCAAiD,EAAE,MAAM,kCAAkC,SAAS,OAAU;AACpH,IAAM,+BAA+C,EAAE,MAAM,gCAAgC,SAAS,OAAU;AAEzG,IAAM,UAAN,MAAc;AAAA,EACF;AAAA,EAEjB,YAAY,QAAiB;AAC3B,SAAK,SAAS,IAAIC,WAAU,EAAE,QAAQ,UAAU,QAAQ,IAAI,kBAAkB,CAAC;AAAA,EACjF;AAAA;AAAA,EAGA,MAAM,YAAY,QAAgB,gBAA2D;AAC3F,UAAM,cAAc,2BACjB,QAAQ,YAAY,MAAM,EAC1B,QAAQ,WAAW,eAAe,MAAM,QAAQ,CAAC,CAAC,EAClD,QAAQ,YAAY,eAAe,MAAM;AAE5C,UAAM,mBAAmB,YAAqC;AAC5D,YAAM,UAAU,MAAM,KAAK,OAAO,SAAS,OAAO;AAAA,QAChD,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,QAAQ;AAAA,QACR,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,YAAY,CAAC;AAAA,MACnD,CAAC;AAED,YAAM,UAAU,QAAQ,QAAQ,CAAC;AACjC,UAAI,QAAQ,SAAS,QAAQ;AAC3B,eAAO;AAAA,MACT;AAEA,aAAO,EAAE,MAAM,QAAQ,KAAK,KAAK,EAAE;AAAA,IACrC,GAAG;AAEH,WAAO,YAAY,iBAAiB,qBAAqB,qBAAqB;AAAA,EAChF;AAAA;AAAA,EAGA,MAAM,aAAa,SAA+C;AAChE,UAAM,cAAc,iBAAiB,OAAO;AAC5C,UAAM,YAAY,IAAI,QAAQ,aAAa,QAAQ,CAAC,CAAC;AACrD,UAAM,eAAe,QAAQ,mBAAmB,QAAQ,mBAAmB,eAAe;AAE1F,UAAM,cAAc,4BACjB,QAAQ,YAAY,QAAQ,MAAM,EAClC,QAAQ,iBAAiB,WAAW,EACpC,QAAQ,eAAe,SAAS,EAChC,QAAQ,iBAAiB,WAAW;AAEvC,UAAM,mBAAmB,YAAqC;AAC5D,YAAM,UAAU,MAAM,KAAK,OAAO,SAAS,OAAO;AAAA,QAChD,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,QAAQ;AAAA,QACR,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,YAAY,CAAC;AAAA,MACnD,CAAC;AAED,YAAM,UAAU,QAAQ,QAAQ,CAAC;AACjC,UAAI,QAAQ,SAAS,QAAQ;AAC3B,eAAO;AAAA,MACT;AAEA,YAAM,OAAO,QAAQ,KAAK,KAAK;AAC/B,YAAM,UAAU,KAAK,WAAW,QAAG,IAAI,OAAO,KAAK,WAAW,QAAG,IAAI,QAAQ;AAE7E,aAAO,EAAE,MAAM,QAAQ;AAAA,IACzB,GAAG;AAEH,WAAO,YAAY,iBAAiB,qBAAqB,8BAA8B;AAAA,EACzF;AACF;AAEA,SAAS,iBAAiB,SAA8B;AACtD,QAAM,QAAkB,CAAC;AAGzB,QAAM,aAAa,oBAAI,IAAoB;AAC3C,QAAM,eAAyB,CAAC;AAChC,MAAI,gBAAgB;AAEpB,aAAW,UAAU,QAAQ,aAAa;AACxC,QAAI,OAAO,aAAa,QAAQ;AAC9B;AACA,UAAI,OAAO,aAAa;AACtB,qBAAa,KAAK,IAAI,OAAO,WAAW,GAAG;AAAA,MAC7C;AAAA,IACF,OAAO;AACL,iBAAW,IAAI,OAAO,WAAW,WAAW,IAAI,OAAO,QAAQ,KAAK,KAAK,CAAC;AAAA,IAC5E;AAAA,EACF;AAGA,aAAW,CAAC,UAAU,KAAK,KAAK,WAAW,QAAQ,GAAG;AACpD,UAAM,KAAK,UAAU,IAAI,WAAW,GAAG,QAAQ,KAAK,KAAK,SAAS;AAAA,EACpE;AAGA,MAAI,gBAAgB,GAAG;AACrB,QAAI,aAAa,SAAS,GAAG;AAC3B,YAAM,YAAY,aAAa,UAAU,IACrC,SAAS,aAAa,KAAK,IAAI,CAAC,KAChC,SAAS,aAAa,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,KAAK,aAAa,SAAS,CAAC;AAC5E,YAAM,KAAK,SAAS;AAAA,IACtB,OAAO;AACL,YAAM,KAAK,SAAS,aAAa,SAAS;AAAA,IAC5C;AAAA,EACF;AAGA,QAAM,cAAc,QAAQ,mBAAmB,QAAQ;AACvD,MAAI,cAAc,GAAG;AACnB,UAAM,KAAK,GAAG,YAAY,eAAe,CAAC,SAAS;AAAA,EACrD;AACA,MAAI,QAAQ,eAAe,GAAG;AAC5B,UAAM,KAAK,IAAI,QAAQ,aAAa,QAAQ,CAAC,CAAC,EAAE;AAAA,EAClD;AAEA,SAAO,MAAM,KAAK,QAAK,KAAK;AAC9B;;;ACtIA,IAAM,QAAQ;AACd,IAAM,MAAM;AACZ,IAAM,OAAO;AACb,IAAM,QAAQ;AACd,IAAM,SAAS;AACf,IAAM,MAAM;AACZ,IAAM,OAAO;AAIb,IAAM,aAAa;AACnB,IAAM,aAAa;AAOZ,SAAS,WAAW,MAAoB;AAC7C,QAAM,KAAK,OAAO,KAAK,SAAS,CAAC,EAAE,SAAS,GAAG,GAAG;AAClD,QAAM,KAAK,OAAO,KAAK,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACpD,QAAM,KAAK,OAAO,KAAK,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACpD,SAAO,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE;AAC1B;AAMA,SAAS,UAAU,SAAS,IAAY;AACtC,QAAM,SAAS,SAAI,OAAO,KAAK,IAAI,GAAG,aAAa,OAAO,MAAM,CAAC;AACjE,SAAO,SAAS;AAClB;AAMA,SAAS,SAAS,MAAc,UAAkB,QAA0B;AAC1E,QAAM,WAAW,KAAK,MAAM,IAAI;AAChC,QAAM,SAAmB,CAAC;AAE1B,aAAW,WAAW,UAAU;AAC9B,UAAM,QAAQ,QAAQ,MAAM,GAAG;AAC/B,QAAI,UAAU;AAEd,eAAW,QAAQ,OAAO;AACxB,UAAI,YAAY,IAAI;AAClB,kBAAU;AAAA,MACZ,WAAW,QAAQ,SAAS,IAAI,KAAK,UAAU,UAAU;AACvD,mBAAW,MAAM;AAAA,MACnB,OAAO;AACL,eAAO,KAAK,OAAO;AACnB,kBAAU,SAAS;AAAA,MACrB;AAAA,IACF;AAEA,QAAI,YAAY,IAAI;AAClB,aAAO,KAAK,OAAO;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AACT;AAOA,SAAS,oBAAoB,UAA4B;AACvD,SAAO,SAAS,SAAS,KAAK,GAAG,YAAY,IAAI;AACnD;AAEA,SAAS,QAAQ,OAAO,IAAU;AAChC,UAAQ,OAAO,MAAM,OAAO,IAAI;AAClC;AAWO,SAAS,cAAc,OAAe,cAA6B;AACxE,QAAM,OAAO,WAAW,oBAAI,KAAK,CAAC;AAClC,QAAM,cAAc,eAAe,KAAK,YAAY,MAAM;AAC1D,QAAM,SAAS,mBAAS,WAAW,iBAAO,IAAI,wBAAc,MAAM,QAAQ,CAAC,CAAC;AAC5E,QAAM,OAAO,UAAU,MAAM;AAC7B,UAAQ,MAAM,OAAO,KAAK;AAC5B;AASO,SAAS,iBAAiB,OAAe,UAAkB,cAA6B;AAC7F,QAAM,OAAO,WAAW,oBAAI,KAAK,CAAC;AAClC,QAAM,cAAc,eAAe,KAAK,YAAY,MAAM;AAC1D,QAAM,eAAe,mBAAS,WAAW,iBAAO,IAAI,wBAAc,MAAM,QAAQ,CAAC,CAAC;AAClF,QAAM,SAAS,UAAU,YAAY;AAErC,UAAQ,SAAS,OAAO,SAAS,KAAK;AAEtC,QAAM,QAAQ,oBAAoB,QAAQ;AAC1C,aAAW,QAAQ,OAAO;AACxB,YAAQ,IAAI;AAAA,EACd;AAEA,UAAQ,MAAM,UAAU,IAAI,KAAK;AACnC;AAGA,SAAS,UAAU,OAAe,SAAiB,cAA6B;AAC9E,QAAM,OAAO,WAAW,oBAAI,KAAK,CAAC;AAClC,QAAM,cAAc,eAAe,KAAK,YAAY,MAAM;AAC1D,QAAM,SAAS,UAAU,oBAAU,WAAW,iBAAO,IAAI,GAAG;AAE5D,UAAQ,QAAQ,OAAO,SAAS,KAAK;AAErC,QAAM,QAAQ,oBAAoB,OAAO;AACzC,aAAW,QAAQ,OAAO;AACxB,YAAQ,IAAI;AAAA,EACd;AAEA,UAAQ,MAAM,UAAU,IAAI,KAAK;AACnC;AASO,SAAS,iBAAiB,SAAiB,cAA6B;AAC7E,YAAU,OAAO,SAAS,YAAY;AACxC;AASO,SAAS,oBAAoB,UAAkB,cAA6B;AACjF,YAAU,KAAK,UAAU,YAAY;AACvC;AAQO,SAAS,kBAAkB,OAAe,WAAyB;AACxE,QAAM,OAAO,WAAW,oBAAI,KAAK,CAAC;AAClC,QAAM,UAAU,UAAU,MAAM,GAAG,CAAC;AACpC,UAAQ,MAAM,OAAO,gBAAM,KAAK,eAAe,OAAO,wBAAS,IAAI,KAAK,KAAK;AAC/E;AAYO,SAAS,YAAY,MAAoB;AAC9C,QAAM,eAAe;AACrB,QAAM,SAAS,UAAU,YAAY;AAErC,UAAQ,OAAO,OAAO,SAAS,KAAK;AACpC,UAAQ,0BAA0B,IAAI,EAAE;AACxC,UAAQ,sCAAsC;AAC9C,UAAQ,0DAA0D;AAClE,UAAQ,MAAM,UAAU,IAAI,KAAK;AACnC;AAKO,SAAS,aAAa,SAAuB;AAClD,UAAQ,SAAS,YAAO,UAAU,KAAK;AACzC;AAKO,SAAS,WAAW,SAAuB;AAChD,UAAQ,MAAM,YAAO,UAAU,KAAK;AACtC;;;ACnLA,SAAS,OAAO,KAAsB;AACpC,SAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;AAEA,eAAsB,WAAW,UAAwB,CAAC,GAAkB;AAC1E,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,iBAAiB,QAAQ,kBAAkB;AAEjD,QAAM,WAAW,IAAI,aAAa,IAAI;AACtC,QAAM,aAAa,IAAI,eAAe;AAAA,IACpC,mBAAmB,QAAQ,qBAAqB;AAAA,EAClD,CAAC;AACD,QAAM,aAAa,IAAI,WAAW,QAAQ,MAAM;AAChD,QAAM,UAAU,IAAI,QAAQ,QAAQ,MAAM;AAG1C,MAAI,2BAA2B;AAE/B,QAAM,cAAc,oBAAI,IAAY;AAEpC,QAAM,gBAAgB,oBAAI,IAAoB;AAG9C,aAAW,GAAG,iBAAiB,CAAC,MAAsB;AACpD,kBAAc,IAAI,EAAE,WAAW,EAAE,KAAK;AACtC,sBAAkB,EAAE,OAAO,EAAE,SAAS;AAAA,EACxC,CAAC;AAOD,WAAS,GAAG,SAAS,CAAC,UAAsB;AAE1C,eAAW,SAAS,KAAK;AAGzB,QAAI,MAAM,SAAS,cAAe;AAElC,QAAI,YAAY,IAAI,MAAM,QAAQ,EAAG;AACrC,gBAAY,IAAI,MAAM,QAAQ;AAE9B,QAAI,CAAC,MAAM,UAAU,CAAC,0BAA0B;AAC9C,iCAA2B;AAC3B;AAAA,QACE;AAAA,MACF;AAAA,IACF;AAEA,SAAK,eAAe,MAAM,QAAQ,MAAM,UAAU,cAAc,IAAI,MAAM,SAAS,CAAC;AAAA,EACtF,CAAC;AAED,WAAS,GAAG,SAAS,CAAC,QAAe;AACnC,eAAW,sBAAsB,IAAI,OAAO,EAAE;AAAA,EAChD,CAAC;AAGD,aAAW,GAAG,iBAAiB,CAAC,QAAqB;AACnD,SAAK,gBAAgB,KAAK,cAAc,IAAI,IAAI,SAAS,CAAC;AAAA,EAC5D,CAAC;AAGD,iBAAe,eAAe,QAAgB,UAAkB,cAAsC;AACpG,QAAI;AACF,UAAI,CAAC,OAAQ;AAEb,YAAM,SAAS,MAAM,WAAW,SAAS,MAAM;AAE/C,UAAI,OAAO,QAAQ,gBAAgB;AACjC,sBAAc,OAAO,OAAO,YAAY;AACxC;AAAA,MACF;AAGA,YAAM,WAAW,MAAM,QAAQ,YAAY,QAAQ,MAAM;AACzD,uBAAiB,OAAO,OAAO,SAAS,MAAM,YAAY;AAAA,IAC5D,SAAS,KAAK;AACZ,iBAAW,kCAAkC,QAAQ,KAAK,OAAO,GAAG,CAAC,EAAE;AAAA,IACzE,UAAE;AAGA,kBAAY,OAAO,QAAQ;AAAA,IAC7B;AAAA,EACF;AAGA,iBAAe,gBAAgB,KAAkB,cAAsC;AACrF,QAAI,CAAC,IAAI,OAAQ;AAEjB,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,aAAa,GAAG;AAE7C,UAAI,OAAO,YAAY,OAAO;AAC5B,4BAAoB,OAAO,MAAM,YAAY;AAAA,MAC/C,OAAO;AACL,yBAAiB,OAAO,MAAM,YAAY;AAAA,MAC5C;AAAA,IACF,SAAS,KAAK;AACZ,iBAAW,mCAAmC,IAAI,QAAQ,KAAK,OAAO,GAAG,CAAC,EAAE;AAAA,IAC9E;AAAA,EACF;AAGA,MAAI;AACF,UAAM,SAAS,MAAM;AAAA,EACvB,SAAS,KAAK;AACZ,UAAM,MAAM,OAAO,GAAG;AACtB,QAAI,IAAI,SAAS,YAAY,GAAG;AAC9B,iBAAW,QAAQ,IAAI,gEAAgE;AAAA,IACzF,OAAO;AACL,iBAAW,kCAAkC,GAAG,EAAE;AAAA,IACpD;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,cAAY,IAAI;AAGhB,iBAAe,WAA0B;AACvC,YAAQ,OAAO,MAAM,IAAI;AACzB,iBAAa,wBAAwB;AACrC,UAAM,SAAS,KAAK;AACpB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,GAAG,UAAU,MAAM,KAAK,SAAS,CAAC;AAC1C,UAAQ,GAAG,WAAW,MAAM,KAAK,SAAS,CAAC;AAC7C;;;ACxJA,SAAS,cAAc,eAAe,YAAY,iBAAiB;AACnE,SAAS,YAAY;AACrB,SAAS,eAAe;AAIxB,IAAM,aAAa,KAAK,QAAQ,GAAG,SAAS;AAC5C,IAAM,gBAAgB,KAAK,YAAY,eAAe;AAEtD,IAAM,YAAoC;AAAA,EACxC,8BAA8B;AAAA,EAC9B,oBAAoB;AAAA,EACpB,6BAA6B;AAAA,EAC7B,kCAAkC;AAAA,EAClC,uBAAuB;AAAA,EACvB,uBAAuB;AAAA,EACvB,2BAA2B;AAC7B;AAIA,IAAMC,SAAQ;AACd,IAAMC,OAAM;AACZ,IAAMC,QAAO;AACb,IAAMC,SAAQ;AACd,IAAMC,UAAS;AACf,IAAMC,QAAO;AAEb,IAAMC,cAAa;AAEnB,SAAS,IAAI,SAAS,IAAY;AAChC,SAAO,SAAS,SAAI,OAAO,KAAK,IAAI,GAAGA,cAAa,OAAO,MAAM,CAAC;AACpE;AAEA,SAASC,SAAQ,OAAO,IAAU;AAChC,UAAQ,OAAO,MAAM,OAAO,IAAI;AAClC;AAIA,SAAS,eAAwC;AAC/C,MAAI,CAAC,WAAW,aAAa,EAAG,QAAO,CAAC;AACxC,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,eAAe,MAAM,CAAC;AAAA,EACvD,QAAQ;AACN,UAAM,IAAI,MAAM,mBAAmB,aAAa,sCAAsC;AAAA,EACxF;AACF;AAEA,SAAS,cAAc,UAAyC;AAC9D,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,cAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3C;AACA,gBAAc,eAAe,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,MAAM,MAAM;AAC/E;AAIO,SAAS,WAAiB;AAC/B,EAAAA,SAAQF,QAAOH,QAAO,IAAI,2BAAiB,IAAIF,MAAK;AAGpD,MAAI;AACJ,MAAI;AACF,eAAW,aAAa;AAAA,EAC1B,SAAS,KAAK;AACZ,IAAAO,SAAQH,UAAS,aAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,KAAKJ,MAAK;AAClF,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,cAAe,SAAS,OAA8C,CAAC;AAG7E,EAAAO,SAAQ,0BAA0B,cAAc,QAAQ,QAAQ,GAAG,GAAG,CAAC,KAAK;AAC5E,EAAAA,SAAQ;AAER,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,UAAM,aAAa,OAAO,eAAe,YAAY,GAAG,MAAM;AAC9D,UAAM,MAAM,aAAaN,OAAM,oBAAoBD,SAAQ;AAC3D,IAAAO,SAAQ,KAAKJ,MAAK,SAAIH,MAAK,IAAI,GAAG,GAAG,GAAG,EAAE;AAAA,EAC5C;AAEA,WAAS,MAAM,EAAE,GAAG,aAAa,GAAG,UAAU;AAE9C,MAAI;AACF,kBAAc,QAAQ;AAAA,EACxB,SAAS,KAAK;AACZ,IAAAO,SAAQ;AACR,IAAAA,SAAQH,UAAS,uCAAkC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,KAAKJ,MAAK;AAC5G,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,EAAAO,SAAQ;AACR,EAAAA,SAAQ,GAAGJ,MAAK,SAAIH,MAAK,wBAAwB,cAAc,QAAQ,QAAQ,GAAG,GAAG,CAAC,EAAE;AAGxF,MAAI,CAAC,QAAQ,IAAI,mBAAmB;AAClC,IAAAO,SAAQ;AACR,IAAAA,SAAQ,GAAGH,OAAM,SAAIJ,MAAK,sDAAsD;AAChF,IAAAO,SAAQ,2DAA2D;AACnE,IAAAA,SAAQ,KAAKN,IAAG,sCAAsCD,MAAK,EAAE;AAAA,EAC/D;AAGA,EAAAO,SAAQ;AACR,EAAAA,SAAQ,+CAA+C;AACvD,EAAAA,SAAQ,KAAKL,KAAI,cAAcF,MAAK,EAAE;AACtC,EAAAO,SAAQN,OAAM,IAAI,IAAID,MAAK;AAC7B;;;ATvGA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,OAAO,EACZ,YAAY,iFAAiF,EAC7F,QAAQ,OAAO;AAElB,QACG,QAAQ,OAAO,EACf,YAAY,yEAAyE,EACrF,OAAO,uBAAuB,0CAA0C,MAAM,EAC9E;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC,OAAO,uBAAuB,2DAA2D,EACzF,OAAO,OAAO,SAAgF;AAC7F,QAAM,OAAO,SAAS,KAAK,MAAM,EAAE;AACnC,QAAM,oBAAoB,SAAS,KAAK,SAAS,EAAE;AACnD,QAAM,iBAAiB,WAAW,KAAK,SAAS;AAEhD,MAAI,MAAM,IAAI,KAAK,OAAO,KAAK,OAAO,OAAO;AAC3C,eAAW,6CAA6C;AACxD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,MAAM,iBAAiB,KAAK,oBAAoB,KAAK;AACvD,eAAW,wCAAwC;AACnD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,MAAM,cAAc,KAAK,iBAAiB,KAAK,iBAAiB,GAAG;AACrE,eAAW,kDAAkD;AAC7D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,SAAS,KAAK,UAAU,QAAQ,IAAI;AAC1C,MAAI,CAAC,QAAQ;AACX,eAAW,4EAA4E;AACvF,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,WAAW,EAAE,MAAM,mBAAmB,gBAAgB,OAAO,CAAC;AACtE,CAAC;AAEH,QACG,QAAQ,OAAO,EACf,YAAY,wFAAwF,EACpG,OAAO,MAAM;AACZ,WAAS;AACX,CAAC;AAGH,IAAI,QAAQ,KAAK,WAAW,GAAG;AAC7B,UAAQ,WAAW;AACnB,UAAQ,KAAK,CAAC;AAChB;AAEA,QAAQ,MAAM,QAAQ,IAAI;","names":["EventEmitter","Anthropic","Anthropic","RESET","DIM","BOLD","GREEN","YELLOW","CYAN","LINE_WIDTH","writeln"]}
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"]}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "radar-cc",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Non-blocking intent alignment checker for Claude Code, powered by OpenTelemetry",
5
5
  "type": "module",
6
6
  "bin": {
7
- "radar": "./dist/cli/index.js"
7
+ "radar": "dist/cli/index.js"
8
8
  },
9
9
  "main": "./dist/cli/index.js",
10
10
  "files": [
@@ -22,13 +22,13 @@
22
22
  "commander": "^12.0.0"
23
23
  },
24
24
  "devDependencies": {
25
+ "@types/node": "^20.0.0",
25
26
  "tsup": "^8.0.0",
26
- "typescript": "^5.4.0",
27
27
  "tsx": "^4.0.0",
28
- "@types/node": "^20.0.0"
28
+ "typescript": "^5.4.0"
29
29
  },
30
30
  "engines": {
31
- "node": ">=18.0.0"
31
+ "node": ">=22.0.0"
32
32
  },
33
33
  "keywords": [
34
34
  "claude-code",