@delt/claude-jev-advisor 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,10 +27,10 @@ npm i -g @delt/claude-jev-advisor
27
27
  Then register the hooks:
28
28
 
29
29
  ```bash
30
- claude-jev-advisor install --key-file C:/path/to/jev-key.env
30
+ claude-jev-advisor install
31
31
  ```
32
32
 
33
- The key file holds a line `TYPESAFE_API_KEY=...`. Only its path is saved, never the key.
33
+ When the context helper is installed and no key is found, `install` asks for your TypeSafe API key. What you type is hidden. It checks the key with one small Jev call and saves it to `~/.claude/claude-jev-advisor/jev-key.env`. Press Enter to skip; `claude-jev-advisor key` asks again later. Instead of typing it, you can set `TYPESAFE_API_KEY`, or pass `--key-file <path>` to a file holding a line `TYPESAFE_API_KEY=...` (then only that path is saved). The key is never printed or logged. On Windows that file is protected by your user folder's permissions only.
34
34
 
35
35
  Before it changes `~/.claude/settings.json`, `install` copies it to `~/.claude/backups/settings.json.<YYYY-MM-DD-HHmmss>-before-claude-jev-advisor`. If a backup from the same second already exists, it adds `-2`, `-3` and so on instead of overwriting it. It then adds or replaces only this package's entries. Running it again changes nothing.
36
36
 
@@ -44,6 +44,7 @@ The hooks also reach Claude Code sessions that are already open; the bottom-row
44
44
  | `claude-jev-advisor uninstall [rm] [context]` | Removes this package's hooks and display from `settings.json` |
45
45
  | `claude-jev-advisor on [rm] [context]` / `off [rm] [context]` | Switches helpers on or off without touching `settings.json` |
46
46
  | `claude-jev-advisor status` | Shows what is registered and on, the display, whether a key is set, the thresholds and the last judgment |
47
+ | `claude-jev-advisor key` | Asks for the TypeSafe API key, checks it and saves it (needs an interactive terminal) |
47
48
  | `claude-jev-advisor help` | Prints the usage |
48
49
 
49
50
  If you leave out the helper names, the command applies to all helpers. The display is set up when `context` is installed; run `claude-jev-advisor install context --display <mode>` to switch it.
@@ -98,6 +99,20 @@ A target cannot be worked out when it uses:
98
99
  - a wildcard in a folder name
99
100
  - a `cd` to such a path earlier in the command
100
101
 
102
+ It does not see deletions made any other way:
103
+
104
+ - through the PowerShell tool (`Remove-Item` and its aliases). On Windows with Git Bash installed, Claude Code turns this tool on by default for claude.ai and Console accounts and uses PowerShell as its main shell, so many deletions go through it.
105
+ - through a nested shell: `sh -c`, `bash -c`, `powershell -Command`, `pwsh -c`, `cmd /c del`
106
+ - with other commands or programs, such as `find -delete`, `git clean` or a script that deletes files
107
+
108
+ What its decisions do depends on Claude Code's permission mode:
109
+
110
+ | Mode | `ask` | `deny` | No decision |
111
+ |---|---|---|---|
112
+ | `auto` | A prompt. The auto mode classifier can still block the call but cannot approve it on its own (Claude Code 2.1.211 or later). | Blocked | The classifier decides |
113
+ | `bypassPermissions` | A prompt. Claude Code's documentation does not say this; it was seen in a test on 2026-10-03. | Blocked | Runs |
114
+ | Other modes | A prompt (`dontAsk` refuses the call instead) | Blocked | Claude Code's normal permission handling |
115
+
101
116
  `install rm` also replaces an older personal hook at `~/.claude/hooks/rm-guard/rm-guard.mjs` if one is registered. Its files are left in place.
102
117
 
103
118
  If a hook of this package fails in any way, it prints nothing and exits 0, so it never blocks Claude Code.
@@ -113,6 +128,8 @@ npm rm -g @delt/claude-jev-advisor
113
128
 
114
129
  If the package is removed first, the hook entries point to a missing file. Claude Code keeps running, but the helpers are off. To clean up, reinstall the package and run `claude-jev-advisor uninstall`, or delete the entries from `settings.json` by hand.
115
130
 
131
+ A key saved by `install` or `key` stays in `~/.claude/claude-jev-advisor/jev-key.env`; delete that file if you no longer need it.
132
+
116
133
  ## Files
117
134
 
118
135
  | Path | Contents |
@@ -121,6 +138,7 @@ If the package is removed first, the hook entries point to a missing file. Claud
121
138
  | `~/.claude/claude-jev-advisor/state/<session>.json` | The last judgment of each open session, read by the display. Removed when the session ends. |
122
139
  | `~/.claude/claude-jev-advisor/log/YYYY-MM.jsonl` | One line per judgment: the size, what was sent to Jev (parts of your conversation), the answers and the result. Never the key. |
123
140
  | `~/.claude/claude-jev-advisor/statusline-before.json` | Your own status line while `--display statusline` is in use |
141
+ | `~/.claude/claude-jev-advisor/jev-key.env` | Your TypeSafe API key, when you typed it in `install` or `key` |
124
142
  | `~/.claude/backups/settings.json.*-before-claude-jev-advisor` | Copies of `settings.json` from before each change |
125
143
 
126
144
  Default config:
package/dist/cli.js CHANGED
@@ -2,39 +2,77 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import os from "os";
5
- import path8 from "path";
5
+ import path9 from "path";
6
6
  import { fileURLToPath } from "url";
7
7
 
8
- // src/cli/run.ts
9
- import path7 from "path";
10
-
11
- // src/features.ts
12
- var FEATURES = ["rm", "context"];
13
- var HOOK_SPECS = {
14
- rm: [{ event: "PreToolUse", matcher: "Bash", script: "rm-hook.js", args: [] }],
15
- context: [
16
- { event: "Stop", script: "context-hook.js", args: ["stop"] },
17
- { event: "SessionEnd", script: "context-hook.js", args: ["session-end"] }
18
- ]
19
- };
20
- var SUPPORTED_PLATFORMS = {
21
- rm: ["win32"],
22
- context: null
23
- };
24
- var HOOK_TIMEOUT_SECONDS = 15;
25
- var LEGACY_RM_GUARD = /\/\.claude\/hooks\/rm-guard\/rm-guard\.mjs/;
26
- var STATUSLINE_SCRIPT = "statusline.js";
27
- var MOD_PLUGIN_NAME = "jev-advisor";
28
- var MOD_DIR_PATTERN = /claude-jev-advisor\/mod\/?$/;
29
- function ownScriptPattern(script) {
30
- return new RegExp(`claude-jev-advisor/dist/${script.replace(/\./g, "\\.")}`);
31
- }
32
- function isFeature(value) {
33
- return FEATURES.includes(value);
8
+ // src/cli/prompt.ts
9
+ var ENTER = /* @__PURE__ */ new Set([10, 13]);
10
+ var BACKSPACE = /* @__PURE__ */ new Set([8, 127]);
11
+ var CTRL_C = 3;
12
+ var ESC = 27;
13
+ var ERASE = `${String.fromCharCode(8)} ${String.fromCharCode(8)}`;
14
+ var PROCESS_TERMINAL = { stdin: process.stdin, stdout: process.stdout, exit: (code) => process.exit(code) };
15
+ function terminalPrompt(terminal = PROCESS_TERMINAL) {
16
+ const { stdin, stdout, exit } = terminal;
17
+ if (!stdin.isTTY || !stdout.isTTY) return void 0;
18
+ return {
19
+ ask: (question) => new Promise((resolve) => {
20
+ stdout.write(question);
21
+ let value = "";
22
+ let inEscape = false;
23
+ const stop = () => {
24
+ stdin.removeListener("data", onData);
25
+ stdin.removeListener("end", onEnd);
26
+ stdin.setRawMode(false);
27
+ stdin.pause();
28
+ stdout.write("\n");
29
+ };
30
+ const onEnd = () => {
31
+ stop();
32
+ resolve("");
33
+ };
34
+ const onData = (chunk) => {
35
+ for (const ch of chunk) {
36
+ const code = ch.charCodeAt(0);
37
+ if (inEscape) {
38
+ if (ch !== "[" && code >= 64 && code <= 126) inEscape = false;
39
+ continue;
40
+ }
41
+ if (code === ESC) {
42
+ inEscape = true;
43
+ } else if (ENTER.has(code)) {
44
+ stop();
45
+ resolve(value);
46
+ return;
47
+ } else if (code === CTRL_C) {
48
+ stop();
49
+ exit(130);
50
+ return;
51
+ } else if (BACKSPACE.has(code)) {
52
+ if (value) stdout.write(ERASE);
53
+ value = Array.from(value).slice(0, -1).join("");
54
+ } else if (code >= 32) {
55
+ value += ch;
56
+ stdout.write("*");
57
+ }
58
+ }
59
+ };
60
+ stdin.setEncoding("utf8");
61
+ stdin.setRawMode(true);
62
+ stdin.resume();
63
+ stdin.on("data", onData);
64
+ stdin.on("end", onEnd);
65
+ }),
66
+ say: (line) => {
67
+ stdout.write(`${line}
68
+ `);
69
+ }
70
+ };
34
71
  }
35
72
 
36
- // src/install.ts
37
- import path5 from "path";
73
+ // src/cli/run.ts
74
+ import fs7 from "fs";
75
+ import path8 from "path";
38
76
 
39
77
  // src/config.ts
40
78
  import fs from "fs";
@@ -81,6 +119,7 @@ var settingsPath = (home) => path.join(claudeDir(home), "settings.json");
81
119
  var backupsDir = (home) => path.join(claudeDir(home), "backups");
82
120
  var stateDir = (home) => path.join(dataDir(home), "state");
83
121
  var statusLineBeforePath = (home) => path.join(dataDir(home), "statusline-before.json");
122
+ var savedKeyPath = (home) => path.join(dataDir(home), "jev-key.env");
84
123
 
85
124
  // src/config.ts
86
125
  function readConfig(home) {
@@ -102,6 +141,34 @@ function updateConfig(home, change) {
102
141
  return next;
103
142
  }
104
143
 
144
+ // src/features.ts
145
+ var FEATURES = ["rm", "context"];
146
+ var HOOK_SPECS = {
147
+ rm: [{ event: "PreToolUse", matcher: "Bash", script: "rm-hook.js", args: [] }],
148
+ context: [
149
+ { event: "Stop", script: "context-hook.js", args: ["stop"] },
150
+ { event: "SessionEnd", script: "context-hook.js", args: ["session-end"] }
151
+ ]
152
+ };
153
+ var SUPPORTED_PLATFORMS = {
154
+ rm: ["win32"],
155
+ context: null
156
+ };
157
+ var HOOK_TIMEOUT_SECONDS = 15;
158
+ var LEGACY_RM_GUARD = /\/\.claude\/hooks\/rm-guard\/rm-guard\.mjs/;
159
+ var STATUSLINE_SCRIPT = "statusline.js";
160
+ var MOD_PLUGIN_NAME = "jev-advisor";
161
+ var MOD_DIR_PATTERN = /claude-jev-advisor\/mod\/?$/;
162
+ function ownScriptPattern(script) {
163
+ return new RegExp(`claude-jev-advisor/dist/${script.replace(/\./g, "\\.")}`);
164
+ }
165
+ function isFeature(value) {
166
+ return FEATURES.includes(value);
167
+ }
168
+
169
+ // src/install.ts
170
+ import path5 from "path";
171
+
105
172
  // src/display/settings.ts
106
173
  import fs2 from "fs";
107
174
  import path3 from "path";
@@ -383,9 +450,107 @@ function setEnabled(home, features, enabled) {
383
450
  }));
384
451
  }
385
452
 
386
- // src/status.ts
453
+ // src/jev.ts
454
+ import fs4 from "fs";
455
+ var JEV_URL = "https://api.typesafe.ai/v1/systemone";
456
+ var JEV_TIMEOUT_MS = 8e3;
457
+ var KEY_PREFIX = "TYPESAFE_API_KEY=";
458
+ function readJevKey(env, keyFile) {
459
+ const fromEnv = env.TYPESAFE_API_KEY?.trim();
460
+ if (fromEnv) return fromEnv;
461
+ if (!keyFile) return null;
462
+ let text;
463
+ try {
464
+ text = fs4.readFileSync(keyFile, "utf8");
465
+ } catch {
466
+ return null;
467
+ }
468
+ const line = text.replace(/^\uFEFF/, "").split(/\r?\n/).find((l) => l.startsWith(KEY_PREFIX));
469
+ const key = line?.slice(KEY_PREFIX.length).trim().replace(/^["']|["']$/g, "");
470
+ return key || null;
471
+ }
472
+ async function callJev(request, opts) {
473
+ const now = opts.now ?? Date.now;
474
+ const fetchFn = opts.fetchFn ?? fetch;
475
+ const limit = opts.timeoutMs ?? JEV_TIMEOUT_MS;
476
+ const redact = (text) => text.split(opts.key).join("[redacted]");
477
+ const started = now();
478
+ const controller = new AbortController();
479
+ let timer;
480
+ const expired = new Promise((_resolve, reject) => {
481
+ timer = setTimeout(() => {
482
+ const err = new Error(`Jev did not answer within ${limit} ms`);
483
+ controller.abort(err);
484
+ reject(err);
485
+ }, limit);
486
+ });
487
+ let requestId = null;
488
+ const exchange = (async () => {
489
+ const res = await fetchFn(JEV_URL, {
490
+ method: "POST",
491
+ headers: { Authorization: `Bearer ${opts.key}`, "Content-Type": "application/json" },
492
+ body: JSON.stringify({ model: "jev-latest", ...request }),
493
+ signal: controller.signal
494
+ });
495
+ requestId = res.headers.get("request-id") ?? res.headers.get("x-request-id");
496
+ return { res, text: await res.text() };
497
+ })();
498
+ exchange.catch(() => {
499
+ });
500
+ try {
501
+ const { res, text } = await Promise.race([exchange, expired]);
502
+ if (!res.ok) return { error: redact(`HTTP ${res.status}: ${text}`).slice(0, 300), requestId, ms: now() - started };
503
+ const body = JSON.parse(text);
504
+ const answers = {};
505
+ for (const [id, answer] of Object.entries(body.answers ?? {})) {
506
+ if (typeof answer?.noul === "number") answers[id] = answer.noul;
507
+ }
508
+ return { answers, model: typeof body.model === "string" ? body.model : null, requestId, ms: now() - started };
509
+ } catch (err) {
510
+ return { error: redact(String(err?.message ?? err)).slice(0, 300), requestId, ms: now() - started };
511
+ } finally {
512
+ clearTimeout(timer);
513
+ }
514
+ }
515
+
516
+ // src/key.ts
387
517
  import fs5 from "fs";
388
518
  import path6 from "path";
519
+ var CHECK_REQUEST = {
520
+ state: { text: "hello" },
521
+ questions: { ok: { type: "noul", instructions: "Is `text` a greeting?", criteria: { true: "It is a greeting.", false: "It is not a greeting." } } }
522
+ };
523
+ var REJECTED = /^HTTP 40[13]\b/;
524
+ async function checkJevKey(key, fetchFn) {
525
+ const result = await callJev(CHECK_REQUEST, { key, fetchFn });
526
+ if (!("error" in result)) return { status: "ok" };
527
+ return REJECTED.test(result.error) ? { status: "rejected", detail: result.error } : { status: "unchecked", detail: result.error };
528
+ }
529
+ function saveJevKey(home, key) {
530
+ const file = savedKeyPath(home);
531
+ fs5.mkdirSync(path6.dirname(file), { recursive: true });
532
+ fs5.writeFileSync(file, `TYPESAFE_API_KEY=${key}
533
+ `, { mode: 384 });
534
+ fs5.chmodSync(file, 384);
535
+ return file;
536
+ }
537
+ async function promptForKey(prompt, check) {
538
+ for (; ; ) {
539
+ const key = (await prompt.ask("TypeSafe API key (Enter to skip): ")).trim();
540
+ if (!key) return null;
541
+ const result = await check(key);
542
+ if (result.status === "ok") return key;
543
+ if (result.status === "unchecked") {
544
+ prompt.say(`Saved without a check: could not check the key (${result.detail}).`);
545
+ return key;
546
+ }
547
+ prompt.say(`That key did not work (${result.detail}). Try again, or press Enter to skip.`);
548
+ }
549
+ }
550
+
551
+ // src/status.ts
552
+ import fs6 from "fs";
553
+ import path7 from "path";
389
554
 
390
555
  // src/display/line.ts
391
556
  var PHRASES = {
@@ -433,24 +598,6 @@ function parseState(raw) {
433
598
  return { sessionId: raw.sessionId, at: raw.at, size: typeof raw.size === "number" ? raw.size : null, judgment };
434
599
  }
435
600
 
436
- // src/jev.ts
437
- import fs4 from "fs";
438
- var KEY_PREFIX = "TYPESAFE_API_KEY=";
439
- function readJevKey(env, keyFile) {
440
- const fromEnv = env.TYPESAFE_API_KEY?.trim();
441
- if (fromEnv) return fromEnv;
442
- if (!keyFile) return null;
443
- let text;
444
- try {
445
- text = fs4.readFileSync(keyFile, "utf8");
446
- } catch {
447
- return null;
448
- }
449
- const line = text.replace(/^\uFEFF/, "").split(/\r?\n/).find((l) => l.startsWith(KEY_PREFIX));
450
- const key = line?.slice(KEY_PREFIX.length).trim().replace(/^["']|["']$/g, "");
451
- return key || null;
452
- }
453
-
454
601
  // src/status.ts
455
602
  var SCRIPT_IN_COMMAND = /^node "([^"]+)"/;
456
603
  function isRecord4(value) {
@@ -461,19 +608,19 @@ function displaySetUp(settings, config) {
461
608
  if (config.display === "statusline") return isOurStatusLine(settings.statusLine);
462
609
  const env = isRecord4(settings.env) ? settings.env : {};
463
610
  const dirs = typeof env[PLUGIN_DIRS_ENV] === "string" ? env[PLUGIN_DIRS_ENV] : "";
464
- const hasDir = dirs.split(path6.delimiter).some((dir) => MOD_DIR_PATTERN.test(dir.replace(/\\/g, "/")));
611
+ const hasDir = dirs.split(path7.delimiter).some((dir) => MOD_DIR_PATTERN.test(dir.replace(/\\/g, "/")));
465
612
  return hasDir && isRecord4(settings.pluginConfigs) && MOD_PLUGIN_NAME in settings.pluginConfigs;
466
613
  }
467
614
  function newestState(home) {
468
615
  let newest = null;
469
616
  try {
470
- for (const name of fs5.readdirSync(stateDir(home))) {
617
+ for (const name of fs6.readdirSync(stateDir(home))) {
471
618
  if (!name.endsWith(".json")) continue;
472
- const file = path6.join(stateDir(home), name);
473
- const mtime = fs5.statSync(file).mtimeMs;
619
+ const file = path7.join(stateDir(home), name);
620
+ const mtime = fs6.statSync(file).mtimeMs;
474
621
  if (!newest || mtime > newest.mtime) newest = { file, mtime };
475
622
  }
476
- return newest ? parseState(JSON.parse(fs5.readFileSync(newest.file, "utf8"))) : null;
623
+ return newest ? parseState(JSON.parse(fs6.readFileSync(newest.file, "utf8"))) : null;
477
624
  } catch {
478
625
  return null;
479
626
  }
@@ -511,7 +658,7 @@ function statusLines(home, env = process.env) {
511
658
  lines.push(`${feature}: installed, ${state}`);
512
659
  for (const c of commands) {
513
660
  const script = SCRIPT_IN_COMMAND.exec(c.command)?.[1];
514
- if (script && !fs5.existsSync(script)) {
661
+ if (script && !fs6.existsSync(script)) {
515
662
  lines.push(` broken: ${c.event} hook points to a missing file (${script}) - run "claude-jev-advisor install ${feature}" again, or "claude-jev-advisor uninstall ${feature}"`);
516
663
  }
517
664
  }
@@ -533,6 +680,7 @@ var USAGE = [
533
680
  " on [rm] [context] switch helpers on (open sessions too)",
534
681
  " off [rm] [context] switch helpers off (open sessions too)",
535
682
  " status show what is registered and switched on",
683
+ " key ask for the TypeSafe API key, check it and save it",
536
684
  "",
537
685
  "Options for install: --lang ko|en --key-file <path> --display mod|statusline|message",
538
686
  'The display is set up when context is installed: "install context --display <mode>" switches it.',
@@ -570,7 +718,33 @@ function checkOptions(command, options) {
570
718
  if (options.display !== void 0 && !DISPLAYS2.includes(options.display)) return "--display takes mod, statusline or message";
571
719
  return null;
572
720
  }
573
- function runCli(argv, io) {
721
+ async function askAndSaveKey(io, prompt) {
722
+ const key = await promptForKey(prompt, io.checkKey ?? ((candidate) => checkJevKey(candidate)));
723
+ if (!key) {
724
+ io.out('No key saved. Run "claude-jev-advisor key" to add one later.');
725
+ return;
726
+ }
727
+ const before = readConfig(io.home).keyFile;
728
+ const file = saveJevKey(io.home, key);
729
+ updateConfig(io.home, (c) => ({ ...c, keyFile: file }));
730
+ io.out(`Saved the key to ${file}`);
731
+ if (before && path8.resolve(before) !== path8.resolve(file)) io.out(`The config now uses this file instead of ${before}.`);
732
+ if ((io.env ?? process.env).TYPESAFE_API_KEY?.trim()) io.out("TYPESAFE_API_KEY is set in your environment; the hooks use it before the saved key.");
733
+ }
734
+ async function ensureKey(io, keyFileGiven) {
735
+ const keyFile = readConfig(io.home).keyFile;
736
+ if (readJevKey(io.env ?? process.env, keyFile)) return;
737
+ if (keyFileGiven) {
738
+ io.out(`No TYPESAFE_API_KEY line found in ${keyFile}; context cannot judge until it has one.`);
739
+ return;
740
+ }
741
+ if (!io.prompt) {
742
+ io.out('No TypeSafe API key yet: run "claude-jev-advisor key" in a terminal to add one (context needs it to judge).');
743
+ return;
744
+ }
745
+ await askAndSaveKey(io, io.prompt);
746
+ }
747
+ async function runCli(argv, io) {
574
748
  const parsed = parse(argv);
575
749
  const problem = typeof parsed === "string" ? parsed : checkOptions(parsed.command, parsed.options);
576
750
  if (typeof parsed === "string" || problem) {
@@ -589,7 +763,7 @@ function runCli(argv, io) {
589
763
  platform: io.platform,
590
764
  now: io.now(),
591
765
  lang: options.lang,
592
- keyFile: options["key-file"] === void 0 || path7.isAbsolute(options["key-file"]) ? options["key-file"] : path7.resolve(options["key-file"]),
766
+ keyFile: options["key-file"] === void 0 || path8.isAbsolute(options["key-file"]) ? options["key-file"] : path8.resolve(options["key-file"]),
593
767
  display: options.display
594
768
  });
595
769
  for (const f of r.installed) io.out(`installed ${f}`);
@@ -597,7 +771,12 @@ function runCli(argv, io) {
597
771
  if (r.replacedLegacyRmGuard) io.out("replaced the legacy rm-guard hook (its files under ~/.claude/hooks/rm-guard were left in place)");
598
772
  if (r.display) io.out(`display: ${r.display}`);
599
773
  if (r.backup) io.out(`backup: ${r.backup}`);
600
- if (r.installed.length) io.out("Hooks apply to Claude Code sessions started from now on.");
774
+ if (r.installed.length) {
775
+ io.out(
776
+ r.display === "mod" ? "Hooks apply right away, also in open Claude Code sessions; the bottom-row display starts with the next new session." : "Hooks apply right away, also in open Claude Code sessions."
777
+ );
778
+ }
779
+ if (r.installed.includes("context")) await ensureKey(io, options["key-file"] !== void 0);
601
780
  return 0;
602
781
  }
603
782
  case "uninstall": {
@@ -605,6 +784,15 @@ function runCli(argv, io) {
605
784
  if (!r.removed.length) io.out("nothing to remove");
606
785
  for (const f of r.removed) io.out(`removed ${f}`);
607
786
  if (r.backup) io.out(`backup: ${r.backup}`);
787
+ if (r.removed.includes("context") && fs7.existsSync(savedKeyPath(io.home))) io.out(`kept your saved TypeSafe key at ${savedKeyPath(io.home)}`);
788
+ return 0;
789
+ }
790
+ case "key": {
791
+ if (!io.prompt) {
792
+ io.err("claude-jev-advisor key needs an interactive terminal (or set TYPESAFE_API_KEY, or use install --key-file).");
793
+ return 1;
794
+ }
795
+ await askAndSaveKey(io, io.prompt);
608
796
  return 0;
609
797
  }
610
798
  case "on":
@@ -632,11 +820,21 @@ function runCli(argv, io) {
632
820
  }
633
821
 
634
822
  // src/cli.ts
635
- process.exitCode = runCli(process.argv.slice(2), {
823
+ runCli(process.argv.slice(2), {
636
824
  home: os.homedir(),
637
- distDir: path8.dirname(fileURLToPath(import.meta.url)),
825
+ distDir: path9.dirname(fileURLToPath(import.meta.url)),
638
826
  platform: process.platform,
639
827
  now: () => /* @__PURE__ */ new Date(),
640
828
  out: (line) => console.log(line),
641
- err: (line) => console.error(line)
642
- });
829
+ err: (line) => console.error(line),
830
+ env: process.env,
831
+ prompt: terminalPrompt()
832
+ }).then(
833
+ (code) => {
834
+ process.exitCode = code;
835
+ },
836
+ (err) => {
837
+ console.error(`claude-jev-advisor: ${err?.message ?? err}`);
838
+ process.exitCode = 1;
839
+ }
840
+ );
@@ -1,14 +1,14 @@
1
- {
2
- "name": "jev-advisor",
3
- "version": "0.1.0",
4
- "description": "Bottom-row compact/clear advice from @delt/claude-jev-advisor (unofficial)",
5
- "author": { "name": "delt96" },
6
- "userConfig": {
7
- "dataDir": {
8
- "type": "string",
9
- "title": "Data folder",
10
- "description": "The claude-jev-advisor data folder holding config.json and state/. Set by claude-jev-advisor install.",
11
- "default": ""
12
- }
13
- }
14
- }
1
+ {
2
+ "name": "jev-advisor",
3
+ "version": "0.1.2",
4
+ "description": "Bottom-row compact/clear advice from @delt/claude-jev-advisor (unofficial)",
5
+ "author": { "name": "delt96" },
6
+ "userConfig": {
7
+ "dataDir": {
8
+ "type": "string",
9
+ "title": "Data folder",
10
+ "description": "The claude-jev-advisor data folder holding config.json and state/. Set by claude-jev-advisor install.",
11
+ "default": ""
12
+ }
13
+ }
14
+ }
package/package.json CHANGED
@@ -1,48 +1,48 @@
1
- {
2
- "name": "@delt/claude-jev-advisor",
3
- "version": "0.1.0",
4
- "description": "Unofficial Claude Code helpers using TypeSafe Jev: compact/clear advice and an rm guard",
5
- "type": "module",
6
- "bin": {
7
- "claude-jev-advisor": "./dist/cli.js"
8
- },
9
- "files": [
10
- "dist",
11
- "mod",
12
- "README.md"
13
- ],
14
- "engines": {
15
- "node": ">=18.0.0"
16
- },
17
- "scripts": {
18
- "build": "tsup",
19
- "typecheck": "tsc --noEmit",
20
- "test": "node --import tsx --import ./test/isolate-home.ts --test --test-timeout=30000 \"test/**/*.test.ts\"",
21
- "prepack": "npm run build"
22
- },
23
- "keywords": [
24
- "claude",
25
- "claude-code",
26
- "hooks",
27
- "compact",
28
- "context",
29
- "typesafe",
30
- "jev",
31
- "rm"
32
- ],
33
- "license": "MIT",
34
- "repository": {
35
- "type": "git",
36
- "url": "https://github.com/delt96/claude-jev-advisor.git"
37
- },
38
- "bugs": {
39
- "url": "https://github.com/delt96/claude-jev-advisor/issues"
40
- },
41
- "homepage": "https://github.com/delt96/claude-jev-advisor#readme",
42
- "devDependencies": {
43
- "@types/node": "^22.0.0",
44
- "tsup": "^8.0.0",
45
- "tsx": "^4.23.15",
46
- "typescript": "^5.5.0"
47
- }
48
- }
1
+ {
2
+ "name": "@delt/claude-jev-advisor",
3
+ "version": "0.1.2",
4
+ "description": "Unofficial Claude Code helpers using TypeSafe Jev: compact/clear advice and an rm guard",
5
+ "type": "module",
6
+ "bin": {
7
+ "claude-jev-advisor": "./dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "mod",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18.0.0"
16
+ },
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "typecheck": "tsc --noEmit",
20
+ "test": "node --import tsx --import ./test/isolate-home.ts --test --test-timeout=30000 \"test/**/*.test.ts\"",
21
+ "prepack": "npm run build"
22
+ },
23
+ "keywords": [
24
+ "claude",
25
+ "claude-code",
26
+ "hooks",
27
+ "compact",
28
+ "context",
29
+ "typesafe",
30
+ "jev",
31
+ "rm"
32
+ ],
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/delt96/claude-jev-advisor.git"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/delt96/claude-jev-advisor/issues"
40
+ },
41
+ "homepage": "https://github.com/delt96/claude-jev-advisor#readme",
42
+ "devDependencies": {
43
+ "@types/node": "^22.0.0",
44
+ "tsup": "^8.0.0",
45
+ "tsx": "^4.23.15",
46
+ "typescript": "^5.5.0"
47
+ }
48
+ }