radar-cc 0.1.1 → 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,11 +1,11 @@
1
1
  # radar-cc
2
2
 
3
- Non-blocking 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. No hooks, no blocking, no changes to Claude's behavior.
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
- - Node.js >= 18
8
- - `ANTHROPIC_API_KEY` set in your environment
7
+ - Node.js >= 22
8
+ - An Anthropic API key
9
9
 
10
10
  ## Install
11
11
 
@@ -14,7 +14,13 @@ npm install -g radar-cc
14
14
  radar setup
15
15
  ```
16
16
 
17
- `radar setup` writes the required OTel environment variables to `~/.claude/settings.json`. Restart Claude Code after running it.
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.
18
+
19
+ You can also pass the key directly to skip the prompt:
20
+
21
+ ```sh
22
+ radar setup --api-key <your-key>
23
+ ```
18
24
 
19
25
  ## Usage
20
26
 
@@ -88,12 +94,16 @@ These are written to the `env` block in `~/.claude/settings.json`. If Radar is n
88
94
  ## Options
89
95
 
90
96
  ```
97
+ radar setup [options]
98
+
99
+ -k, --api-key <key> API key to store (skips the interactive prompt)
100
+
91
101
  radar watch [options]
92
102
 
93
103
  -p, --port <number> OTLP listener port (default: 4820)
94
104
  -t, --timeout <ms> Turn boundary silence window (default: 5000)
95
105
  -s, --threshold <score> Ambiguity score threshold 0.0–1.0 (default: 0.6)
96
- -k, --api-key <key> Anthropic API key (default: ANTHROPIC_API_KEY)
106
+ -k, --api-key <key> Anthropic API key (overrides all stored sources)
97
107
  ```
98
108
 
99
109
  ## License
package/dist/cli/index.js CHANGED
@@ -764,11 +764,76 @@ async function startWatch(options = {}) {
764
764
  }
765
765
 
766
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
767
773
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
774
+ import { execSync } from "child_process";
768
775
  import { join } from "path";
769
776
  import { homedir } from "os";
770
- var CLAUDE_DIR = join(homedir(), ".claude");
771
- 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");
772
837
  var OTEL_VARS = {
773
838
  CLAUDE_CODE_ENABLE_TELEMETRY: "1",
774
839
  OTEL_LOGS_EXPORTER: "otlp",
@@ -792,20 +857,23 @@ function writeln2(text = "") {
792
857
  process.stdout.write(text + "\n");
793
858
  }
794
859
  function readSettings() {
795
- if (!existsSync(SETTINGS_PATH)) return {};
860
+ if (!existsSync2(SETTINGS_PATH)) return {};
796
861
  try {
797
- return JSON.parse(readFileSync(SETTINGS_PATH, "utf8"));
862
+ return JSON.parse(readFileSync2(SETTINGS_PATH, "utf8"));
798
863
  } catch {
799
864
  throw new Error(`Could not parse ${SETTINGS_PATH}. Fix the JSON syntax and try again.`);
800
865
  }
801
866
  }
802
867
  function writeSettings(settings) {
803
- if (!existsSync(CLAUDE_DIR)) {
804
- mkdirSync(CLAUDE_DIR, { recursive: true });
868
+ if (!existsSync2(CLAUDE_DIR)) {
869
+ mkdirSync2(CLAUDE_DIR, { recursive: true });
805
870
  }
806
- 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));
807
875
  }
808
- function runSetup() {
876
+ async function runSetup(preEnteredKey) {
809
877
  writeln2(CYAN2 + BOLD2 + sep("\u2500\u2500 Radar Setup ") + RESET2);
810
878
  let settings;
811
879
  try {
@@ -815,7 +883,7 @@ function runSetup() {
815
883
  process.exit(1);
816
884
  }
817
885
  const existingEnv = settings.env ?? {};
818
- writeln2(`Writing OTel config to ${SETTINGS_PATH.replace(homedir(), "~")}...`);
886
+ writeln2(`Writing OTel config to ${SETTINGS_PATH.replace(homedir2(), "~")}...`);
819
887
  writeln2();
820
888
  for (const [key, value] of Object.entries(OTEL_VARS)) {
821
889
  const alreadySet = key in existingEnv && existingEnv[key] === value;
@@ -831,18 +899,133 @@ function runSetup() {
831
899
  process.exit(1);
832
900
  }
833
901
  writeln2();
834
- writeln2(`${GREEN2}\u2713${RESET2} Settings written to ${SETTINGS_PATH.replace(homedir(), "~")}`);
835
- if (!process.env.ANTHROPIC_API_KEY) {
836
- writeln2();
837
- writeln2(`${YELLOW2}\u26A0${RESET2} ANTHROPIC_API_KEY is not set in your current shell.`);
838
- writeln2(` Radar needs it to run analysis. Set it before starting:`);
839
- writeln2(` ${DIM2}export ANTHROPIC_API_KEY=sk-ant-...${RESET2}`);
840
- }
902
+ writeln2(`${GREEN2}\u2713${RESET2} Settings written to ${SETTINGS_PATH.replace(homedir2(), "~")}`);
903
+ await promptApiKey(preEnteredKey);
841
904
  writeln2();
842
905
  writeln2("Ready. Start Radar in a second terminal pane:");
843
906
  writeln2(` ${BOLD2}radar watch${RESET2}`);
844
907
  writeln2(DIM2 + sep() + RESET2);
845
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
+ }
846
1029
 
847
1030
  // src/cli/index.ts
848
1031
  var program = new Command();
@@ -855,7 +1038,7 @@ program.command("watch").description("Start listening for Claude Code telemetry
855
1038
  "-s, --threshold <score>",
856
1039
  "Ambiguity score threshold for triggering a pre-advisory (0.0\u20131.0)",
857
1040
  "0.6"
858
- ).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) => {
859
1042
  const port = parseInt(opts.port, 10);
860
1043
  const boundaryTimeoutMs = parseInt(opts.timeout, 10);
861
1044
  const scoreThreshold = parseFloat(opts.threshold);
@@ -871,15 +1054,21 @@ program.command("watch").description("Start listening for Claude Code telemetry
871
1054
  printError("--threshold must be a number between 0.0 and 1.0");
872
1055
  process.exit(1);
873
1056
  }
874
- 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
+ }
875
1064
  if (!apiKey) {
876
- 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>.");
877
1066
  process.exit(1);
878
1067
  }
879
1068
  await startWatch({ port, boundaryTimeoutMs, scoreThreshold, apiKey });
880
1069
  });
881
- program.command("setup").description("Write OTel config to ~/.claude/settings.json so Claude Code streams telemetry to Radar").action(() => {
882
- 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);
883
1072
  });
884
1073
  if (process.argv.length === 2) {
885
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 // ── 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';\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":"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","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":";;;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,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,cAAc,iBAAiB,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;AAClE,MAAI,YAAY,SAAS,GAAG;AAC1B,iBAAa,0EAA0E;AAAA,EACzF;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;;;ACnKA,SAAS,gBAAAC,eAAc,iBAAAC,gBAAe,cAAAC,aAAY,aAAAC,kBAAiB;AACnE,SAAS,QAAAC,aAAY;AACrB,SAAS,WAAAC,gBAAe;AACxB,SAAS,uBAAuB;;;ACHhC,SAAS,cAAc,eAAe,YAAY,iBAAiB;AACnE,SAAS,gBAAgB;AACzB,SAAS,YAAY;AACrB,SAAS,eAAe;AAIxB,IAAM,aAAa,KAAK,QAAQ,GAAG,WAAW,OAAO;AACrD,IAAM,cAAc,KAAK,YAAY,aAAa;AAW3C,SAAS,aAA0B;AACxC,MAAI,CAAC,WAAW,WAAW,EAAG,QAAO,CAAC;AACtC,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,aAAa,MAAM,CAAC;AAAA,EACrD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEO,SAAS,YAAY,QAA2B;AACrD,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,cAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3C;AACA,gBAAc,aAAa,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,MAAM,MAAM;AAC3E;AAEO,SAAS,aAAqB;AACnC,SAAO;AACT;AAIO,SAAS,gBAAyB;AACvC,MAAI;AACF,aAAS,gBAAgB,EAAE,OAAO,SAAS,CAAC;AAC5C,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,aAAa,QAAwB;AACnD,QAAM,QAAQ;AACd,QAAM,MAAM;AAAA,IACV,4CAA4C,KAAK,eAAe,MAAM;AAAA,IACtE,EAAE,UAAU,OAAO;AAAA,EACrB;AACA,QAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,SAAO,QAAQ,KAAK,MAAM,IAAI,IAAI,KAAK,KAAK;AAC9C;AAGO,SAAS,WAAW,KAAqB;AAC9C,SAAO,SAAS,YAAY,GAAG,KAAK,EAAE,UAAU,OAAO,CAAC,EAAE,KAAK;AACjE;AAUO,SAAS,sBAA0C;AACxD,QAAM,SAAS,WAAW;AAC1B,MAAI,OAAO,OAAQ,QAAO,OAAO;AACjC,MAAI,OAAO,WAAW;AACpB,QAAI;AACF,aAAO,WAAW,OAAO,SAAS;AAAA,IACpC,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,YAAM,IAAI;AAAA,QACR,0CAA0C,OAAO,SAAS;AAAA;AAAA,UAE/C,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;AD5EA,IAAM,aAAaC,MAAKC,SAAQ,GAAG,SAAS;AAC5C,IAAM,gBAAgBD,MAAK,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,IAAME,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,CAACC,YAAW,aAAa,EAAG,QAAO,CAAC;AACxC,MAAI;AACF,WAAO,KAAK,MAAMC,cAAa,eAAe,MAAM,CAAC;AAAA,EACvD,QAAQ;AACN,UAAM,IAAI,MAAM,mBAAmB,aAAa,sCAAsC;AAAA,EACxF;AACF;AAEA,SAAS,cAAc,UAAyC;AAC9D,MAAI,CAACD,YAAW,UAAU,GAAG;AAC3B,IAAAE,WAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3C;AACA,EAAAC,eAAc,eAAe,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,MAAM,MAAM;AAC/E;AAIA,SAAS,SAAS,IAAwC,QAAiC;AACzF,SAAO,IAAI,QAAQ,CAAC,YAAY,GAAG,SAAS,QAAQ,OAAO,CAAC;AAC9D;AAIA,eAAsB,SAAS,eAAuC;AACpE,EAAAJ,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;AAE7E,EAAAO,SAAQ,0BAA0B,cAAc,QAAQR,SAAQ,GAAG,GAAG,CAAC,KAAK;AAC5E,EAAAQ,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,QAAQD,SAAQ,GAAG,GAAG,CAAC,EAAE;AAGxF,QAAM,aAAa,aAAa;AAGhC,EAAAQ,SAAQ;AACR,EAAAA,SAAQ,+CAA+C;AACvD,EAAAA,SAAQ,KAAKL,KAAI,cAAcF,MAAK,EAAE;AACtC,EAAAO,SAAQN,OAAM,IAAI,IAAID,MAAK;AAC7B;AAIA,eAAe,aAAa,eAAuC;AACjE,EAAAO,SAAQ;AAER,MAAI,SAA6B;AAGjC,MAAI,CAAC,QAAQ;AACX,UAAM,SAAS,WAAW;AAC1B,UAAM,SAAS,QAAQ,IAAI;AAC3B,UAAM,YAAY,OAAO,UAAU,OAAO;AAC1C,UAAM,YAAY,UAAU,OAAO;AAEnC,QAAI,aAAa,QAAQ;AAEvB,UAAI,QAAQ;AACV,cAAM,SAAS,OAAO,MAAM,GAAG,EAAE,IAAI,WAAM,OAAO,MAAM,EAAE;AAC1D,QAAAA,SAAQ,GAAGJ,MAAK,SAAIH,MAAK,iDAAiDC,IAAG,GAAG,MAAM,GAAGD,MAAK,EAAE;AAAA,MAClG,WAAW,OAAO,WAAW;AAC3B,QAAAO,SAAQ,GAAGJ,MAAK,SAAIH,MAAK,kCAAkCC,IAAG,GAAG,OAAO,SAAS,GAAGD,MAAK,EAAE;AAAA,MAC7F,WAAW,OAAO,QAAQ;AACxB,cAAM,SAAS,OAAO,OAAO,MAAM,GAAG,EAAE,IAAI,WAAM,OAAO,OAAO,MAAM,EAAE;AACxE,QAAAO,SAAQ,GAAGJ,MAAK,SAAIH,MAAK,4BAA4BC,IAAG,GAAG,MAAM,GAAGD,MAAK,EAAE;AAAA,MAC7E;AAEA,YAAMY,MAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,YAAM,SAAS,MAAM,SAASA,KAAI,sBAAsB;AACxD,MAAAA,IAAG,MAAM;AAET,UAAI,OAAO,KAAK,EAAE,YAAY,MAAM,IAAK;AAGzC,WAAK;AAAA,IACP,OAAO;AACL,MAAAL,SAAQ,GAAGH,OAAM,SAAIJ,MAAK,qDAAqD;AAAA,IACjF;AAGA,UAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,UAAM,UAAU,MAAM,SAAS,IAAI,kCAAkC;AACrE,OAAG,MAAM;AAET,aAAS,QAAQ,KAAK;AACtB,QAAI,CAAC,QAAQ;AACX,MAAAO,SAAQ,GAAGH,OAAM,SAAIJ,MAAK,uEAAkE;AAC5F;AAAA,IACF;AAAA,EACF;AAGA,QAAM,cAAc,MAAM;AAC5B;AAIA,eAAe,cAAc,QAA+B;AAC1D,QAAM,cAAc,cAAc;AAElC,EAAAO,SAAQ;AACR,EAAAA,SAAQ,4CAA4C;AACpD,EAAAA,SAAQ,KAAKL,KAAI,IAAIF,MAAK,iBAAiBC,IAAG,IAAI,WAAW,EAAE,QAAQF,SAAQ,GAAG,GAAG,CAAC,IAAIC,MAAK,EAAE;AAEjG,MAAI,aAAa;AACf,IAAAO,SAAQ,KAAKL,KAAI,IAAIF,MAAK,iBAAiBC,IAAG,gBAAgBD,MAAK,EAAE;AAAA,EACvE,OAAO;AACL,IAAAO,SAAQ,KAAKN,IAAG,kEAA6DD,MAAK,EAAE;AAAA,EACtF;AAEA,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,QAAM,SAAS,MAAM,SAAS,IAAI,cAAc,cAAc,OAAO,EAAE,KAAK;AAC5E,KAAG,MAAM;AAET,QAAM,SAAS,OAAO,KAAK;AAE3B,MAAI,WAAW,KAAK;AAClB,QAAI,CAAC,aAAa;AAChB,MAAAO,SAAQ;AACR,MAAAA,SAAQ,GAAGH,OAAM,SAAIJ,MAAK,wCAAwC;AAClE,MAAAO,SAAQ,kBAAkB;AAC1B,MAAAA,SAAQ,MAAMN,IAAG,mEAAmED,MAAK,EAAE;AAC3F,MAAAO,SAAQ,MAAMN,IAAG,wBAAwBD,MAAK,EAAE;AAChD,MAAAO,SAAQ,MAAMN,IAAG,0CAA0CD,MAAK,EAAE;AAClE;AAAA,IACF;AAEA,UAAM,iBAAiB,MAAM;AAC7B;AAAA,EACF;AAGA,eAAa,MAAM;AACrB;AAEA,SAAS,aAAa,QAAsB;AAC1C,QAAM,SAAS,WAAW;AAC1B,cAAY,EAAE,GAAG,QAAQ,QAAQ,WAAW,OAAU,CAAC;AACvD,EAAAO,SAAQ;AACR,EAAAA,SAAQ,GAAGJ,MAAK,SAAIH,MAAK,qBAAqB,WAAW,EAAE,QAAQD,SAAQ,GAAG,GAAG,CAAC,EAAE;AACtF;AAEA,eAAe,iBAAiB,QAA+B;AAC7D,EAAAQ,SAAQ;AACR,EAAAA,SAAQ,8CAA8C;AACtD,EAAAA,SAAQ,KAAKL,KAAI,IAAIF,MAAK,uBAAuBC,IAAG,8BAA8BD,MAAK,EAAE;AACzF,EAAAO,SAAQ,KAAKL,KAAI,IAAIF,MAAK,0BAA0BC,IAAG,6BAA6BD,MAAK,EAAE;AAE3F,QAAM,MAAM,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC5E,QAAM,SAAS,MAAM,SAAS,KAAK,kBAAkB;AACrD,MAAI,MAAM;AAEV,MAAI,OAAO,KAAK,MAAM,KAAK;AACzB,UAAM,iBAAiB;AACvB;AAAA,EACF;AAGA,EAAAO,SAAQ;AACR,EAAAA,SAAQ,+BAA+B;AAEvC,MAAI;AACF,UAAM,MAAM,aAAa,MAAM;AAC/B,UAAM,SAAS,WAAW;AAC1B,gBAAY,EAAE,GAAG,QAAQ,QAAQ,QAAW,WAAW,IAAI,CAAC;AAC5D,IAAAA,SAAQ,GAAGJ,MAAK,SAAIH,MAAK,8BAA8B;AACvD,IAAAO,SAAQ,gBAAgBN,IAAG,GAAG,GAAG,GAAGD,MAAK,EAAE;AAAA,EAC7C,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,IAAAO,SAAQ,GAAGH,OAAM,SAAIJ,MAAK,kCAAkC,GAAG,EAAE;AACjE,IAAAO,SAAQ,oCAAoCN,OAAM,cAAcD,MAAK;AAGrE,UAAM,MAAM,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC5E,UAAM,WAAW,MAAM,SAAS,KAAK,uCAAuC;AAC5E,QAAI,MAAM;AAEV,QAAI,SAAS,KAAK,EAAE,YAAY,MAAM,KAAK;AACzC,mBAAa,MAAM;AAAA,IACrB;AAAA,EACF;AACF;AAEA,eAAe,mBAAkC;AAC/C,EAAAO,SAAQ;AACR,EAAAA,SAAQ,eAAeL,KAAI,QAAQF,MAAK,8BAA8B;AACtE,EAAAO,SAAQ,KAAKN,IAAG,8CAA8CD,MAAK,EAAE;AAErE,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,QAAM,MAAM,MAAM,SAAS,IAAI,eAAe;AAC9C,KAAG,MAAM;AAET,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,CAAC,QAAQ,WAAW,OAAO,GAAG;AAChC,IAAAO,SAAQ,GAAGH,OAAM,SAAIJ,MAAK,6EAAwE;AAClG;AAAA,EACF;AAEA,QAAM,SAAS,WAAW;AAC1B,cAAY,EAAE,GAAG,QAAQ,QAAQ,QAAW,WAAW,QAAQ,CAAC;AAChE,EAAAO,SAAQ,GAAGJ,MAAK,SAAIH,MAAK,+BAA+BC,IAAG,GAAG,OAAO,GAAGD,MAAK,EAAE;AACjF;;;AThRA,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,iDAAiD,EAC/E,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;AAGA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,UAAU,QAAQ,IAAI,qBAAqB,oBAAoB;AAAA,EAC/E,SAAS,KAAK;AACZ,eAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC3D,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,MAAI,CAAC,QAAQ;AACX,eAAW,gGAAgG;AAC3G,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,WAAW,EAAE,MAAM,mBAAmB,gBAAgB,OAAO,CAAC;AACtE,CAAC;AAEH,QACG,QAAQ,OAAO,EACf,YAAY,+EAA+E,EAC3F,OAAO,uBAAuB,2DAA2D,EACzF,OAAO,OAAO,SAA8B;AAC3C,QAAM,SAAS,KAAK,MAAM;AAC5B,CAAC;AAGH,IAAI,QAAQ,KAAK,WAAW,GAAG;AAC7B,UAAQ,WAAW;AACnB,UAAQ,KAAK,CAAC;AAChB;AAEA,QAAQ,MAAM,QAAQ,IAAI;","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,6 +1,6 @@
1
1
  {
2
2
  "name": "radar-cc",
3
- "version": "0.1.1",
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": {
@@ -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",