@delt/claude-jev-advisor 0.3.0 → 0.4.1
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 +27 -13
- package/dist/cli.js +298 -124
- package/dist/context-hook.js +30 -17
- package/dist/rm-hook.js +2 -0
- package/dist/statusline.js +28 -15
- package/mod/.claude-plugin/plugin.json +2 -2
- package/mod/hooks/register.js +139 -16
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -71,7 +71,7 @@ function terminalPrompt(terminal = PROCESS_TERMINAL) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// src/cli/run.ts
|
|
74
|
-
import
|
|
74
|
+
import fs10 from "fs";
|
|
75
75
|
import path9 from "path";
|
|
76
76
|
|
|
77
77
|
// src/config.ts
|
|
@@ -84,6 +84,7 @@ var DEFAULT_CONFIG = {
|
|
|
84
84
|
lang: "ko",
|
|
85
85
|
keyFile: null,
|
|
86
86
|
display: "mod",
|
|
87
|
+
shortcut: null,
|
|
87
88
|
context: { enabled: true, minTokens: 25e4, compactMinTokens: 25e4, redRemainingPct: 20, unitDoneYes: 0.6, phaseDoneYes: 0.6 },
|
|
88
89
|
rm: { enabled: true, jev: true, throwawayYes: 0.8, maxDirFiles: 50 }
|
|
89
90
|
};
|
|
@@ -111,6 +112,7 @@ function normalizeConfig(raw) {
|
|
|
111
112
|
lang: typeof r.lang === "string" && LANGS.includes(r.lang) ? r.lang : DEFAULT_CONFIG.lang,
|
|
112
113
|
keyFile: typeof r.keyFile === "string" && r.keyFile !== "" ? r.keyFile : null,
|
|
113
114
|
display: typeof r.display === "string" && DISPLAYS.includes(r.display) ? r.display : DEFAULT_CONFIG.display,
|
|
115
|
+
shortcut: typeof r.shortcut === "string" && r.shortcut !== "" ? r.shortcut : null,
|
|
114
116
|
context: mergeSection(DEFAULT_CONFIG.context, r.version === CONFIG_VERSION ? r.context : withoutLegacyDefaults(r.context)),
|
|
115
117
|
rm: mergeSection(DEFAULT_CONFIG.rm, r.rm)
|
|
116
118
|
};
|
|
@@ -135,6 +137,7 @@ var claudeDir = (home) => path.join(home, ".claude");
|
|
|
135
137
|
var dataDir = (home) => path.join(claudeDir(home), "claude-jev-advisor");
|
|
136
138
|
var configPath = (home) => path.join(dataDir(home), "config.json");
|
|
137
139
|
var settingsPath = (home) => path.join(claudeDir(home), "settings.json");
|
|
140
|
+
var keybindingsPath = (home) => path.join(claudeDir(home), "keybindings.json");
|
|
138
141
|
var backupsDir = (home) => path.join(claudeDir(home), "backups");
|
|
139
142
|
var stateDir = (home) => path.join(dataDir(home), "state");
|
|
140
143
|
var logDir = (home) => path.join(dataDir(home), "log");
|
|
@@ -161,6 +164,11 @@ function updateConfig(home, change) {
|
|
|
161
164
|
return next;
|
|
162
165
|
}
|
|
163
166
|
|
|
167
|
+
// src/display/shortcut.ts
|
|
168
|
+
var SHORTCUT_CHORD = "ctrl+x ctrl+f";
|
|
169
|
+
var SHORTCUT_ACTION = "diff:back";
|
|
170
|
+
var SHORTCUT_CONTEXT = "DiffDialog";
|
|
171
|
+
|
|
164
172
|
// src/features.ts
|
|
165
173
|
var FEATURES = ["rm", "context"];
|
|
166
174
|
var HOOK_SPECS = {
|
|
@@ -189,9 +197,187 @@ function isFeature(value) {
|
|
|
189
197
|
// src/install.ts
|
|
190
198
|
import path5 from "path";
|
|
191
199
|
|
|
192
|
-
// src/display/
|
|
200
|
+
// src/display/keybindings.ts
|
|
201
|
+
import fs3 from "fs";
|
|
202
|
+
|
|
203
|
+
// src/settings-file.ts
|
|
193
204
|
import fs2 from "fs";
|
|
194
205
|
import path3 from "path";
|
|
206
|
+
function readSettingsFile(file) {
|
|
207
|
+
let text;
|
|
208
|
+
try {
|
|
209
|
+
text = fs2.readFileSync(file, "utf8");
|
|
210
|
+
} catch (err) {
|
|
211
|
+
if (err.code === "ENOENT") return {};
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
214
|
+
const parsed = JSON.parse(text.replace(/^\uFEFF/, ""));
|
|
215
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`${file} does not hold a JSON object`);
|
|
216
|
+
return parsed;
|
|
217
|
+
}
|
|
218
|
+
function backupName(now, fileName = "settings.json") {
|
|
219
|
+
const two = (n) => String(n).padStart(2, "0");
|
|
220
|
+
const stamp = `${now.getFullYear()}-${two(now.getMonth() + 1)}-${two(now.getDate())}-${two(now.getHours())}${two(now.getMinutes())}${two(now.getSeconds())}`;
|
|
221
|
+
return `${fileName}.${stamp}-before-claude-jev-advisor`;
|
|
222
|
+
}
|
|
223
|
+
function backupSettingsFile(file, dir, now) {
|
|
224
|
+
if (!fs2.existsSync(file)) return null;
|
|
225
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
226
|
+
const baseName = backupName(now, path3.basename(file));
|
|
227
|
+
let target = path3.join(dir, baseName);
|
|
228
|
+
let suffix = 2;
|
|
229
|
+
while (true) {
|
|
230
|
+
try {
|
|
231
|
+
fs2.copyFileSync(file, target, fs2.constants.COPYFILE_EXCL);
|
|
232
|
+
return target;
|
|
233
|
+
} catch (err) {
|
|
234
|
+
if (err.code === "EEXIST") {
|
|
235
|
+
target = path3.join(dir, `${baseName}-${suffix}`);
|
|
236
|
+
suffix++;
|
|
237
|
+
} else {
|
|
238
|
+
throw err;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function writeSettingsFile(file, settings) {
|
|
244
|
+
fs2.mkdirSync(path3.dirname(file), { recursive: true });
|
|
245
|
+
const temp = `${file}.claude-jev-advisor.tmp`;
|
|
246
|
+
fs2.writeFileSync(temp, `${JSON.stringify(settings, null, 2)}
|
|
247
|
+
`);
|
|
248
|
+
try {
|
|
249
|
+
fs2.renameSync(temp, file);
|
|
250
|
+
} catch (err) {
|
|
251
|
+
try {
|
|
252
|
+
fs2.unlinkSync(temp);
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
throw err;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/display/keybindings.ts
|
|
260
|
+
var KEYBINDINGS_SCHEMA = "https://www.schemastore.org/claude-code-keybindings.json";
|
|
261
|
+
var KEYBINDINGS_DOCS = "https://code.claude.com/docs/en/keybindings";
|
|
262
|
+
function isRecord2(value) {
|
|
263
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
264
|
+
}
|
|
265
|
+
function isBlock(value) {
|
|
266
|
+
return isRecord2(value) && typeof value.context === "string" && isRecord2(value.bindings);
|
|
267
|
+
}
|
|
268
|
+
function normalizeChord(chord) {
|
|
269
|
+
return chord.trim().toLowerCase().split(/\s+/).map((stroke) => stroke.split("+").map((key) => key === "control" ? "ctrl" : key).join("+")).join(" ");
|
|
270
|
+
}
|
|
271
|
+
function chordKeys(block) {
|
|
272
|
+
const target = normalizeChord(SHORTCUT_CHORD);
|
|
273
|
+
return Object.keys(block.bindings).filter((key) => normalizeChord(key) === target);
|
|
274
|
+
}
|
|
275
|
+
function isOurs(block, key) {
|
|
276
|
+
return block.context === SHORTCUT_CONTEXT && block.bindings[key] === SHORTCUT_ACTION;
|
|
277
|
+
}
|
|
278
|
+
function parseKeybindings(raw) {
|
|
279
|
+
return isRecord2(raw) && Array.isArray(raw.bindings) ? raw : null;
|
|
280
|
+
}
|
|
281
|
+
function newKeybindings() {
|
|
282
|
+
return { $schema: KEYBINDINGS_SCHEMA, $docs: KEYBINDINGS_DOCS, bindings: [] };
|
|
283
|
+
}
|
|
284
|
+
function hasShortcut(kb) {
|
|
285
|
+
return kb.bindings.some((b) => isBlock(b) && chordKeys(b).some((key) => isOurs(b, key)));
|
|
286
|
+
}
|
|
287
|
+
function shortcutTakenBy(kb) {
|
|
288
|
+
for (const b of kb.bindings) {
|
|
289
|
+
if (!isBlock(b)) continue;
|
|
290
|
+
for (const key of chordKeys(b)) {
|
|
291
|
+
if (!isOurs(b, key)) return `${b.context}: ${String(b.bindings[key])}`;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
function withShortcut(kb) {
|
|
297
|
+
if (hasShortcut(kb)) return kb;
|
|
298
|
+
const bindings = [...kb.bindings];
|
|
299
|
+
const at = bindings.findIndex((b) => isBlock(b) && b.context === SHORTCUT_CONTEXT);
|
|
300
|
+
if (at === -1) {
|
|
301
|
+
bindings.push({ context: SHORTCUT_CONTEXT, bindings: { [SHORTCUT_CHORD]: SHORTCUT_ACTION } });
|
|
302
|
+
} else {
|
|
303
|
+
const block = bindings[at];
|
|
304
|
+
bindings[at] = { ...block, bindings: { ...block.bindings, [SHORTCUT_CHORD]: SHORTCUT_ACTION } };
|
|
305
|
+
}
|
|
306
|
+
return { ...kb, bindings };
|
|
307
|
+
}
|
|
308
|
+
function withoutShortcut(kb) {
|
|
309
|
+
if (!hasShortcut(kb)) return kb;
|
|
310
|
+
const bindings = [];
|
|
311
|
+
for (const b of kb.bindings) {
|
|
312
|
+
if (!isBlock(b)) {
|
|
313
|
+
bindings.push(b);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const keys = chordKeys(b).filter((key) => isOurs(b, key));
|
|
317
|
+
if (!keys.length) {
|
|
318
|
+
bindings.push(b);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const rest = { ...b.bindings };
|
|
322
|
+
for (const key of keys) delete rest[key];
|
|
323
|
+
if (Object.keys(rest).length) bindings.push({ ...b, bindings: rest });
|
|
324
|
+
}
|
|
325
|
+
return { ...kb, bindings };
|
|
326
|
+
}
|
|
327
|
+
function readKeybindings(file) {
|
|
328
|
+
if (!fs3.existsSync(file)) return "missing";
|
|
329
|
+
return parseKeybindings(readSettingsFile(file));
|
|
330
|
+
}
|
|
331
|
+
function installShortcut(home, now) {
|
|
332
|
+
const none = (takenBy, problem) => ({ shortcut: null, takenBy, problem, backup: null });
|
|
333
|
+
const file = keybindingsPath(home);
|
|
334
|
+
try {
|
|
335
|
+
const read = readKeybindings(file);
|
|
336
|
+
if (read === null) return none(null, 'it holds no "bindings" list');
|
|
337
|
+
const kb = read === "missing" ? newKeybindings() : read;
|
|
338
|
+
const takenBy = shortcutTakenBy(kb);
|
|
339
|
+
if (takenBy) return none(takenBy, null);
|
|
340
|
+
if (hasShortcut(kb)) return { shortcut: SHORTCUT_CHORD, takenBy: null, problem: null, backup: null };
|
|
341
|
+
const backup = backupSettingsFile(file, backupsDir(home), now);
|
|
342
|
+
writeSettingsFile(file, withShortcut(kb));
|
|
343
|
+
return { shortcut: SHORTCUT_CHORD, takenBy: null, problem: null, backup };
|
|
344
|
+
} catch (err) {
|
|
345
|
+
return none(null, err.message);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function removeShortcut(home, now) {
|
|
349
|
+
const file = keybindingsPath(home);
|
|
350
|
+
try {
|
|
351
|
+
const read = readKeybindings(file);
|
|
352
|
+
if (read === "missing" || read === null || !hasShortcut(read)) return { removed: false, backup: null };
|
|
353
|
+
const backup = backupSettingsFile(file, backupsDir(home), now);
|
|
354
|
+
writeSettingsFile(file, withoutShortcut(read));
|
|
355
|
+
return { removed: true, backup };
|
|
356
|
+
} catch {
|
|
357
|
+
return { removed: false, backup: null };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function shortcutTakenInFile(home) {
|
|
361
|
+
try {
|
|
362
|
+
const read = readKeybindings(keybindingsPath(home));
|
|
363
|
+
return read === null || read === "missing" ? null : shortcutTakenBy(read);
|
|
364
|
+
} catch {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function shortcutState(home) {
|
|
369
|
+
try {
|
|
370
|
+
const read = readKeybindings(keybindingsPath(home));
|
|
371
|
+
if (read === null) return "unreadable";
|
|
372
|
+
return read !== "missing" && hasShortcut(read) ? "present" : "absent";
|
|
373
|
+
} catch {
|
|
374
|
+
return "unreadable";
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/display/settings.ts
|
|
379
|
+
import fs4 from "fs";
|
|
380
|
+
import path4 from "path";
|
|
195
381
|
|
|
196
382
|
// src/settings.ts
|
|
197
383
|
var forwardSlashes = (text) => text.replace(/\\/g, "/");
|
|
@@ -212,19 +398,19 @@ function withHook(settings, entry) {
|
|
|
212
398
|
}
|
|
213
399
|
function withoutCommands(settings, pattern) {
|
|
214
400
|
if (!settings.hooks) return settings;
|
|
215
|
-
const
|
|
401
|
+
const isOurs2 = (h) => typeof h.command === "string" && commandMatches(h.command, pattern);
|
|
216
402
|
const hooks = {};
|
|
217
403
|
let removedAny = false;
|
|
218
404
|
for (const [event, groups] of Object.entries(settings.hooks)) {
|
|
219
405
|
const kept = [];
|
|
220
406
|
let emptiedByUs = false;
|
|
221
407
|
for (const group of groups) {
|
|
222
|
-
if (!Array.isArray(group.hooks) || !group.hooks.some(
|
|
408
|
+
if (!Array.isArray(group.hooks) || !group.hooks.some(isOurs2)) {
|
|
223
409
|
kept.push(group);
|
|
224
410
|
continue;
|
|
225
411
|
}
|
|
226
412
|
removedAny = true;
|
|
227
|
-
const remaining = group.hooks.filter((h) => !
|
|
413
|
+
const remaining = group.hooks.filter((h) => !isOurs2(h));
|
|
228
414
|
if (remaining.length) kept.push({ ...group, hooks: remaining });
|
|
229
415
|
else emptiedByUs = true;
|
|
230
416
|
}
|
|
@@ -253,7 +439,7 @@ function findCommands(settings, pattern) {
|
|
|
253
439
|
// src/display/settings.ts
|
|
254
440
|
var PLUGIN_DIRS_ENV = "CLAUDE_CODE_PLUGIN_DIRS";
|
|
255
441
|
var STATUSLINE_REFRESH_SECONDS = 3;
|
|
256
|
-
function
|
|
442
|
+
function isRecord3(value) {
|
|
257
443
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
258
444
|
}
|
|
259
445
|
var forwardSlashes2 = (text) => text.replace(/\\/g, "/");
|
|
@@ -263,7 +449,7 @@ function withoutKey(settings, key) {
|
|
|
263
449
|
}
|
|
264
450
|
function withoutModDisplay(settings, delimiter) {
|
|
265
451
|
let next = settings;
|
|
266
|
-
const env =
|
|
452
|
+
const env = isRecord3(settings.env) ? settings.env : null;
|
|
267
453
|
const dirs = env?.[PLUGIN_DIRS_ENV];
|
|
268
454
|
if (env && typeof dirs === "string") {
|
|
269
455
|
const all = dirs.split(delimiter);
|
|
@@ -274,7 +460,7 @@ function withoutModDisplay(settings, delimiter) {
|
|
|
274
460
|
next = Object.keys(nextEnv).length ? { ...next, env: nextEnv } : withoutKey(next, "env");
|
|
275
461
|
}
|
|
276
462
|
}
|
|
277
|
-
const configs =
|
|
463
|
+
const configs = isRecord3(next.pluginConfigs) ? next.pluginConfigs : null;
|
|
278
464
|
if (configs && MOD_PLUGIN_NAME in configs) {
|
|
279
465
|
const { [MOD_PLUGIN_NAME]: _ours, ...others } = configs;
|
|
280
466
|
next = Object.keys(others).length ? { ...next, pluginConfigs: others } : withoutKey(next, "pluginConfigs");
|
|
@@ -283,9 +469,9 @@ function withoutModDisplay(settings, delimiter) {
|
|
|
283
469
|
}
|
|
284
470
|
function withModDisplay(settings, modDir, dataDirPath, delimiter) {
|
|
285
471
|
const base = withoutModDisplay(settings, delimiter);
|
|
286
|
-
const env =
|
|
472
|
+
const env = isRecord3(base.env) ? base.env : {};
|
|
287
473
|
const dirs = typeof env[PLUGIN_DIRS_ENV] === "string" ? env[PLUGIN_DIRS_ENV].split(delimiter).filter(Boolean) : [];
|
|
288
|
-
const configs =
|
|
474
|
+
const configs = isRecord3(base.pluginConfigs) ? base.pluginConfigs : {};
|
|
289
475
|
return {
|
|
290
476
|
...base,
|
|
291
477
|
env: { ...env, [PLUGIN_DIRS_ENV]: [...dirs, forwardSlashes2(modDir)].join(delimiter) },
|
|
@@ -293,12 +479,12 @@ function withModDisplay(settings, modDir, dataDirPath, delimiter) {
|
|
|
293
479
|
};
|
|
294
480
|
}
|
|
295
481
|
function isOurStatusLine(value) {
|
|
296
|
-
return
|
|
482
|
+
return isRecord3(value) && typeof value.command === "string" && commandMatches(value.command, ownScriptPattern(STATUSLINE_SCRIPT));
|
|
297
483
|
}
|
|
298
484
|
function readStatusLineBefore(home) {
|
|
299
485
|
try {
|
|
300
|
-
const value = JSON.parse(
|
|
301
|
-
return
|
|
486
|
+
const value = JSON.parse(fs4.readFileSync(statusLineBeforePath(home), "utf8"));
|
|
487
|
+
return isRecord3(value) ? value : null;
|
|
302
488
|
} catch {
|
|
303
489
|
return null;
|
|
304
490
|
}
|
|
@@ -306,17 +492,17 @@ function readStatusLineBefore(home) {
|
|
|
306
492
|
function writeStatusLineBefore(home, value) {
|
|
307
493
|
const file = statusLineBeforePath(home);
|
|
308
494
|
if (!value) {
|
|
309
|
-
|
|
495
|
+
fs4.rmSync(file, { force: true });
|
|
310
496
|
return;
|
|
311
497
|
}
|
|
312
|
-
|
|
313
|
-
|
|
498
|
+
fs4.mkdirSync(path4.dirname(file), { recursive: true });
|
|
499
|
+
fs4.writeFileSync(file, `${JSON.stringify(value, null, 2)}
|
|
314
500
|
`);
|
|
315
501
|
}
|
|
316
502
|
function applyStatusLineDisplay(settings, home, command) {
|
|
317
503
|
const current = settings.statusLine;
|
|
318
504
|
const ours = isOurStatusLine(current);
|
|
319
|
-
const userLine = ours ? readStatusLineBefore(home) :
|
|
505
|
+
const userLine = ours ? readStatusLineBefore(home) : isRecord3(current) ? current : null;
|
|
320
506
|
if (command !== null) {
|
|
321
507
|
writeStatusLineBefore(home, userLine);
|
|
322
508
|
return { ...settings, statusLine: { type: "command", command, refreshInterval: STATUSLINE_REFRESH_SECONDS } };
|
|
@@ -328,62 +514,6 @@ function forgetStatusLineBefore(home, settings) {
|
|
|
328
514
|
if (!isOurStatusLine(settings.statusLine)) writeStatusLineBefore(home, null);
|
|
329
515
|
}
|
|
330
516
|
|
|
331
|
-
// src/settings-file.ts
|
|
332
|
-
import fs3 from "fs";
|
|
333
|
-
import path4 from "path";
|
|
334
|
-
function readSettingsFile(file) {
|
|
335
|
-
let text;
|
|
336
|
-
try {
|
|
337
|
-
text = fs3.readFileSync(file, "utf8");
|
|
338
|
-
} catch (err) {
|
|
339
|
-
if (err.code === "ENOENT") return {};
|
|
340
|
-
throw err;
|
|
341
|
-
}
|
|
342
|
-
const parsed = JSON.parse(text.replace(/^\uFEFF/, ""));
|
|
343
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`${file} does not hold a JSON object`);
|
|
344
|
-
return parsed;
|
|
345
|
-
}
|
|
346
|
-
function backupName(now) {
|
|
347
|
-
const two = (n) => String(n).padStart(2, "0");
|
|
348
|
-
const stamp = `${now.getFullYear()}-${two(now.getMonth() + 1)}-${two(now.getDate())}-${two(now.getHours())}${two(now.getMinutes())}${two(now.getSeconds())}`;
|
|
349
|
-
return `settings.json.${stamp}-before-claude-jev-advisor`;
|
|
350
|
-
}
|
|
351
|
-
function backupSettingsFile(file, dir, now) {
|
|
352
|
-
if (!fs3.existsSync(file)) return null;
|
|
353
|
-
fs3.mkdirSync(dir, { recursive: true });
|
|
354
|
-
const baseName = backupName(now);
|
|
355
|
-
let target = path4.join(dir, baseName);
|
|
356
|
-
let suffix = 2;
|
|
357
|
-
while (true) {
|
|
358
|
-
try {
|
|
359
|
-
fs3.copyFileSync(file, target, fs3.constants.COPYFILE_EXCL);
|
|
360
|
-
return target;
|
|
361
|
-
} catch (err) {
|
|
362
|
-
if (err.code === "EEXIST") {
|
|
363
|
-
target = path4.join(dir, `${baseName}-${suffix}`);
|
|
364
|
-
suffix++;
|
|
365
|
-
} else {
|
|
366
|
-
throw err;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
function writeSettingsFile(file, settings) {
|
|
372
|
-
fs3.mkdirSync(path4.dirname(file), { recursive: true });
|
|
373
|
-
const temp = `${file}.claude-jev-advisor.tmp`;
|
|
374
|
-
fs3.writeFileSync(temp, `${JSON.stringify(settings, null, 2)}
|
|
375
|
-
`);
|
|
376
|
-
try {
|
|
377
|
-
fs3.renameSync(temp, file);
|
|
378
|
-
} catch (err) {
|
|
379
|
-
try {
|
|
380
|
-
fs3.unlinkSync(temp);
|
|
381
|
-
} catch {
|
|
382
|
-
}
|
|
383
|
-
throw err;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
517
|
// src/install.ts
|
|
388
518
|
function withoutFeature(settings, feature) {
|
|
389
519
|
return HOOK_SPECS[feature].reduce((s, spec) => withoutCommands(s, ownScriptPattern(spec.script)), settings);
|
|
@@ -436,15 +566,20 @@ function install(opts) {
|
|
|
436
566
|
if (display !== null) next = withDisplay(next, { home: opts.home, distDir: opts.distDir, display, delimiter });
|
|
437
567
|
const backup = same(before, next) ? null : save(opts.home, next, opts.now);
|
|
438
568
|
forgetStatusLineBefore(opts.home, next);
|
|
569
|
+
let shortcut = null;
|
|
570
|
+
let shortcutRemoved = null;
|
|
571
|
+
if (display === "mod") shortcut = installShortcut(opts.home, opts.now);
|
|
572
|
+
else if (display !== null) shortcutRemoved = removeShortcut(opts.home, opts.now);
|
|
439
573
|
updateConfig(opts.home, (c) => ({
|
|
440
574
|
...c,
|
|
441
575
|
lang: opts.lang ?? c.lang,
|
|
442
576
|
keyFile: opts.keyFile ?? c.keyFile,
|
|
443
577
|
display: opts.display ?? c.display,
|
|
578
|
+
shortcut: shortcut ? shortcut.shortcut : display !== null ? null : c.shortcut,
|
|
444
579
|
rm: installed.includes("rm") ? { ...c.rm, enabled: true } : c.rm,
|
|
445
580
|
context: installed.includes("context") ? { ...c.context, enabled: true } : c.context
|
|
446
581
|
}));
|
|
447
|
-
return { settingsFile: file, backup, installed, skipped, replacedLegacyRmGuard, display };
|
|
582
|
+
return { settingsFile: file, backup, installed, skipped, replacedLegacyRmGuard, display, shortcut, shortcutRemoved };
|
|
448
583
|
}
|
|
449
584
|
function uninstall(opts) {
|
|
450
585
|
const delimiter = opts.delimiter ?? path5.delimiter;
|
|
@@ -460,7 +595,12 @@ function uninstall(opts) {
|
|
|
460
595
|
}
|
|
461
596
|
const backup = removed.length ? save(opts.home, next, opts.now) : null;
|
|
462
597
|
forgetStatusLineBefore(opts.home, next);
|
|
463
|
-
|
|
598
|
+
let shortcut = null;
|
|
599
|
+
if (opts.features.includes("context")) {
|
|
600
|
+
shortcut = removeShortcut(opts.home, opts.now);
|
|
601
|
+
if (readConfig(opts.home).shortcut !== null) updateConfig(opts.home, (c) => ({ ...c, shortcut: null }));
|
|
602
|
+
}
|
|
603
|
+
return { settingsFile: file, backup, removed, shortcut };
|
|
464
604
|
}
|
|
465
605
|
function setEnabled(home, features, enabled) {
|
|
466
606
|
updateConfig(home, (c) => ({
|
|
@@ -471,7 +611,7 @@ function setEnabled(home, features, enabled) {
|
|
|
471
611
|
}
|
|
472
612
|
|
|
473
613
|
// src/jev.ts
|
|
474
|
-
import
|
|
614
|
+
import fs5 from "fs";
|
|
475
615
|
var JEV_URL = "https://api.typesafe.ai/v1/systemone";
|
|
476
616
|
var JEV_TIMEOUT_MS = 8e3;
|
|
477
617
|
var KEY_PREFIX = "TYPESAFE_API_KEY=";
|
|
@@ -481,7 +621,7 @@ function readJevKey(env, keyFile) {
|
|
|
481
621
|
if (!keyFile) return null;
|
|
482
622
|
let text;
|
|
483
623
|
try {
|
|
484
|
-
text =
|
|
624
|
+
text = fs5.readFileSync(keyFile, "utf8");
|
|
485
625
|
} catch {
|
|
486
626
|
return null;
|
|
487
627
|
}
|
|
@@ -534,7 +674,7 @@ async function callJev(request, opts) {
|
|
|
534
674
|
}
|
|
535
675
|
|
|
536
676
|
// src/key.ts
|
|
537
|
-
import
|
|
677
|
+
import fs6 from "fs";
|
|
538
678
|
import path6 from "path";
|
|
539
679
|
var CHECK_REQUEST = {
|
|
540
680
|
state: { text: "hello" },
|
|
@@ -548,10 +688,10 @@ async function checkJevKey(key, fetchFn) {
|
|
|
548
688
|
}
|
|
549
689
|
function saveJevKey(home, key) {
|
|
550
690
|
const file = savedKeyPath(home);
|
|
551
|
-
|
|
552
|
-
|
|
691
|
+
fs6.mkdirSync(path6.dirname(file), { recursive: true });
|
|
692
|
+
fs6.writeFileSync(file, `TYPESAFE_API_KEY=${key}
|
|
553
693
|
`, { mode: 384 });
|
|
554
|
-
|
|
694
|
+
fs6.chmodSync(file, 384);
|
|
555
695
|
return file;
|
|
556
696
|
}
|
|
557
697
|
async function promptForKey(prompt, check) {
|
|
@@ -569,15 +709,15 @@ async function promptForKey(prompt, check) {
|
|
|
569
709
|
}
|
|
570
710
|
|
|
571
711
|
// src/report.ts
|
|
572
|
-
import
|
|
712
|
+
import fs8 from "fs";
|
|
573
713
|
import path7 from "path";
|
|
574
714
|
|
|
575
715
|
// src/context/transcript.ts
|
|
576
|
-
import
|
|
716
|
+
import fs7 from "fs";
|
|
577
717
|
var TAIL_START_BYTES = 1024 * 1024;
|
|
578
718
|
var TAIL_MAX_BYTES = 8 * 1024 * 1024;
|
|
579
719
|
var REQUEST_ORIGINS = /* @__PURE__ */ new Set(["human", "channel", "peer"]);
|
|
580
|
-
function
|
|
720
|
+
function isRecord4(value) {
|
|
581
721
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
582
722
|
}
|
|
583
723
|
function parseLines(text) {
|
|
@@ -586,7 +726,7 @@ function parseLines(text) {
|
|
|
586
726
|
if (!line.trim()) continue;
|
|
587
727
|
try {
|
|
588
728
|
const value = JSON.parse(line);
|
|
589
|
-
if (
|
|
729
|
+
if (isRecord4(value)) entries.push(value);
|
|
590
730
|
} catch {
|
|
591
731
|
}
|
|
592
732
|
}
|
|
@@ -595,29 +735,31 @@ function parseLines(text) {
|
|
|
595
735
|
function messageText(content) {
|
|
596
736
|
if (typeof content === "string") return content;
|
|
597
737
|
if (!Array.isArray(content)) return "";
|
|
598
|
-
return content.filter((block) =>
|
|
738
|
+
return content.filter((block) => isRecord4(block) && block.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
|
|
599
739
|
}
|
|
600
740
|
function isRequest(entry) {
|
|
601
|
-
return entry.type === "user" && entry.isSidechain !== true &&
|
|
741
|
+
return entry.type === "user" && entry.isSidechain !== true && isRecord4(entry.origin) && REQUEST_ORIGINS.has(String(entry.origin.kind));
|
|
602
742
|
}
|
|
603
743
|
function requestText(entry) {
|
|
604
|
-
return messageText(
|
|
744
|
+
return messageText(isRecord4(entry.message) ? entry.message.content : void 0).trim();
|
|
605
745
|
}
|
|
606
746
|
|
|
607
747
|
// src/display/line.ts
|
|
608
|
-
var
|
|
748
|
+
var QUESTIONS = {
|
|
609
749
|
ko: {
|
|
610
|
-
clear: "\uC0C8\uB86D\uAC8C \uC2DC\uC791\uD558\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?
|
|
611
|
-
compact: "\uC9C0\uAE08\uAE4C\uC9C0 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?
|
|
612
|
-
compactLater: "\uC791\uC5C5\uC774 \uB05D\uB098\uBA74 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?
|
|
750
|
+
clear: "\uC0C8\uB86D\uAC8C \uC2DC\uC791\uD558\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?",
|
|
751
|
+
compact: "\uC9C0\uAE08\uAE4C\uC9C0 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?",
|
|
752
|
+
compactLater: "\uC791\uC5C5\uC774 \uB05D\uB098\uBA74 \uC815\uB9AC\uD558\uACE0 \uC774\uC5B4\uAC00\uB294 \uAC74 \uC5B4\uB5A0\uC138\uC694?"
|
|
613
753
|
},
|
|
614
754
|
en: {
|
|
615
|
-
clear: "Start fresh?
|
|
616
|
-
compact: "Wrap up what you have and continue?
|
|
617
|
-
compactLater: "When this work is done, wrap up and continue?
|
|
755
|
+
clear: "Start fresh?",
|
|
756
|
+
compact: "Wrap up what you have and continue?",
|
|
757
|
+
compactLater: "When this work is done, wrap up and continue?"
|
|
618
758
|
}
|
|
619
759
|
};
|
|
620
|
-
|
|
760
|
+
var COMMANDS = { clear: "/clear", compact: "/compact", compactLater: "/compact" };
|
|
761
|
+
var phrase = (lang, advice) => `${QUESTIONS[lang][advice]} ${COMMANDS[advice]}`;
|
|
762
|
+
function isRecord5(value) {
|
|
621
763
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
622
764
|
}
|
|
623
765
|
function formatSize(tokens) {
|
|
@@ -631,32 +773,41 @@ function adviceKind(size, judgment, config) {
|
|
|
631
773
|
if (judgment.clear) return "clear";
|
|
632
774
|
return size >= config.context.compactMinTokens ? "compact" : null;
|
|
633
775
|
}
|
|
634
|
-
function
|
|
776
|
+
function increaseSuffix(increase) {
|
|
777
|
+
if (increase === null || increase === void 0) return "";
|
|
778
|
+
const k = Math.round(increase / 1e3);
|
|
779
|
+
return k > 0 ? ` +${k}k` : "";
|
|
780
|
+
}
|
|
781
|
+
function redRemaining(size, threshold, config) {
|
|
782
|
+
if (threshold === null || threshold <= 0 || size < threshold * (1 - config.context.redRemainingPct / 100)) return null;
|
|
783
|
+
return remainingPct(size, threshold);
|
|
784
|
+
}
|
|
785
|
+
function adviceLine({ size, threshold, judgment, config, increase }) {
|
|
635
786
|
if (size === null) return "";
|
|
636
|
-
const k = formatSize(size)
|
|
637
|
-
const phrases = PHRASES[config.lang];
|
|
787
|
+
const k = `${formatSize(size)}${increaseSuffix(increase)}`;
|
|
638
788
|
const counted = size >= config.context.minTokens ? judgment : null;
|
|
639
|
-
|
|
640
|
-
|
|
789
|
+
const red = redRemaining(size, threshold, config);
|
|
790
|
+
if (red !== null) {
|
|
791
|
+
const head = `\u{1F534} ${k} ${red}%`;
|
|
641
792
|
if (!counted) return head;
|
|
642
|
-
if (counted.phase === "working") return `${head} \xB7 ${
|
|
643
|
-
return `${head} \xB7 ${counted.clear ?
|
|
793
|
+
if (counted.phase === "working") return `${head} \xB7 ${phrase(config.lang, "compactLater")}`;
|
|
794
|
+
return `${head} \xB7 ${phrase(config.lang, counted.clear ? "clear" : "compact")}`;
|
|
644
795
|
}
|
|
645
796
|
if (!counted) return k;
|
|
646
797
|
const kind = adviceKind(size, counted, config);
|
|
647
|
-
return kind ? `\u{1F7E1} ${k} ${
|
|
798
|
+
return kind ? `\u{1F7E1} ${k} ${phrase(config.lang, kind)}` : `\u{1F7E2} ${k}`;
|
|
648
799
|
}
|
|
649
800
|
function parseState(raw) {
|
|
650
|
-
if (!
|
|
801
|
+
if (!isRecord5(raw) || typeof raw.sessionId !== "string" || typeof raw.at !== "number") return null;
|
|
651
802
|
const j = raw.judgment;
|
|
652
|
-
const judgment =
|
|
803
|
+
const judgment = isRecord5(j) && (j.phase === "working" || j.phase === "unit_done") && typeof j.clear === "boolean" ? { phase: j.phase, clear: j.clear } : null;
|
|
653
804
|
return { sessionId: raw.sessionId, at: raw.at, size: typeof raw.size === "number" ? raw.size : null, judgment };
|
|
654
805
|
}
|
|
655
806
|
|
|
656
807
|
// src/report.ts
|
|
657
808
|
var FOLLOW_REQUESTS = 3;
|
|
658
809
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
659
|
-
function
|
|
810
|
+
function isRecord6(value) {
|
|
660
811
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
661
812
|
}
|
|
662
813
|
function monthFiles(home, since, now) {
|
|
@@ -671,7 +822,7 @@ function readLogRecords(home, since, now) {
|
|
|
671
822
|
for (const file of monthFiles(home, since, now)) {
|
|
672
823
|
let text;
|
|
673
824
|
try {
|
|
674
|
-
text =
|
|
825
|
+
text = fs8.readFileSync(file, "utf8");
|
|
675
826
|
} catch {
|
|
676
827
|
continue;
|
|
677
828
|
}
|
|
@@ -693,7 +844,7 @@ function outcomeAfter(entries, at, endedByClear) {
|
|
|
693
844
|
const time = typeof entry.timestamp === "string" ? Date.parse(entry.timestamp) : NaN;
|
|
694
845
|
if (!(time > at)) continue;
|
|
695
846
|
if (entry.type === "system" && entry.subtype === "compact_boundary") {
|
|
696
|
-
const trigger =
|
|
847
|
+
const trigger = isRecord6(entry.compactMetadata) ? entry.compactMetadata.trigger : void 0;
|
|
697
848
|
return { kind: trigger === "auto" ? "auto_compact" : "compact" };
|
|
698
849
|
}
|
|
699
850
|
if (isRequest(entry) && requestText(entry)) {
|
|
@@ -712,7 +863,7 @@ function adviceRows(home, now, days) {
|
|
|
712
863
|
if (typeof file !== "string") return null;
|
|
713
864
|
if (!transcripts.has(file)) {
|
|
714
865
|
try {
|
|
715
|
-
transcripts.set(file, parseLines(
|
|
866
|
+
transcripts.set(file, parseLines(fs8.readFileSync(file, "utf8")));
|
|
716
867
|
} catch {
|
|
717
868
|
transcripts.set(file, null);
|
|
718
869
|
}
|
|
@@ -726,8 +877,8 @@ function adviceRows(home, now, days) {
|
|
|
726
877
|
if (!advice) continue;
|
|
727
878
|
const at = new Date(record.at);
|
|
728
879
|
const entries = entriesOf(record.transcriptPath);
|
|
729
|
-
const jev =
|
|
730
|
-
const state =
|
|
880
|
+
const jev = isRecord6(record.jev) ? record.jev : {};
|
|
881
|
+
const state = isRecord6(jev.state) ? jev.state : {};
|
|
731
882
|
const requests = Array.isArray(state.recent_requests) ? state.recent_requests : [];
|
|
732
883
|
const last = requests[requests.length - 1];
|
|
733
884
|
rows.push({
|
|
@@ -735,7 +886,7 @@ function adviceRows(home, now, days) {
|
|
|
735
886
|
sessionId: record.sessionId,
|
|
736
887
|
size: record.size,
|
|
737
888
|
advice,
|
|
738
|
-
answers:
|
|
889
|
+
answers: isRecord6(jev.answers) ? jev.answers : null,
|
|
739
890
|
request: typeof last === "string" ? last : "",
|
|
740
891
|
outcome: entries ? outcomeAfter(entries, at.getTime(), cleared.has(record.sessionId)) : { kind: "unknown" }
|
|
741
892
|
});
|
|
@@ -769,7 +920,7 @@ function rmLines(home, now, days) {
|
|
|
769
920
|
const passed = records.filter((r) => r.decision === "pass");
|
|
770
921
|
const lines = [`rm deletions Jev let through: ${passed.length} (asked anyway after a Jev check: ${records.length - passed.length})`];
|
|
771
922
|
for (const record of passed) {
|
|
772
|
-
const targets = Array.isArray(record.targets) ? record.targets.filter(
|
|
923
|
+
const targets = Array.isArray(record.targets) ? record.targets.filter(isRecord6) : [];
|
|
773
924
|
const listed = targets.map((t) => `${String(t.shown)} (${typeof t.p === "number" ? t.p.toFixed(2) : "?"})`).join(", ");
|
|
774
925
|
lines.push(` ${localTime(new Date(record.at))} ${String(record.sessionId ?? "").slice(0, 8)} ${listed}`);
|
|
775
926
|
}
|
|
@@ -794,34 +945,43 @@ function reportLines(home, now, days) {
|
|
|
794
945
|
}
|
|
795
946
|
|
|
796
947
|
// src/status.ts
|
|
797
|
-
import
|
|
948
|
+
import fs9 from "fs";
|
|
798
949
|
import path8 from "path";
|
|
799
950
|
var SCRIPT_IN_COMMAND = /^node "([^"]+)"/;
|
|
800
|
-
function
|
|
951
|
+
function isRecord7(value) {
|
|
801
952
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
802
953
|
}
|
|
803
954
|
function displaySetUp(settings, config) {
|
|
804
955
|
if (config.display === "message") return true;
|
|
805
956
|
if (config.display === "statusline") return isOurStatusLine(settings.statusLine);
|
|
806
|
-
const env =
|
|
957
|
+
const env = isRecord7(settings.env) ? settings.env : {};
|
|
807
958
|
const dirs = typeof env[PLUGIN_DIRS_ENV] === "string" ? env[PLUGIN_DIRS_ENV] : "";
|
|
808
959
|
const hasDir = dirs.split(path8.delimiter).some((dir) => MOD_DIR_PATTERN.test(dir.replace(/\\/g, "/")));
|
|
809
|
-
return hasDir &&
|
|
960
|
+
return hasDir && isRecord7(settings.pluginConfigs) && MOD_PLUGIN_NAME in settings.pluginConfigs;
|
|
810
961
|
}
|
|
811
962
|
function newestState(home) {
|
|
812
963
|
let newest = null;
|
|
813
964
|
try {
|
|
814
|
-
for (const name of
|
|
965
|
+
for (const name of fs9.readdirSync(stateDir(home))) {
|
|
815
966
|
if (!name.endsWith(".json")) continue;
|
|
816
967
|
const file = path8.join(stateDir(home), name);
|
|
817
|
-
const mtime =
|
|
968
|
+
const mtime = fs9.statSync(file).mtimeMs;
|
|
818
969
|
if (!newest || mtime > newest.mtime) newest = { file, mtime };
|
|
819
970
|
}
|
|
820
|
-
return newest ? parseState(JSON.parse(
|
|
971
|
+
return newest ? parseState(JSON.parse(fs9.readFileSync(newest.file, "utf8"))) : null;
|
|
821
972
|
} catch {
|
|
822
973
|
return null;
|
|
823
974
|
}
|
|
824
975
|
}
|
|
976
|
+
function shortcutLine(home, config) {
|
|
977
|
+
const takenBy = shortcutTakenInFile(home);
|
|
978
|
+
if (!config.shortcut) return takenBy ? ` shortcut: none - ${SHORTCUT_CHORD} is already bound (${takenBy}); press the button with a click, or ctrl+x tab then Enter` : ` shortcut: none - run "claude-jev-advisor install context" to bind ${SHORTCUT_CHORD}`;
|
|
979
|
+
if (takenBy) return ` shortcut: ${config.shortcut} (also bound by ${takenBy}, which wins - run "claude-jev-advisor install context")`;
|
|
980
|
+
const state = shortcutState(home);
|
|
981
|
+
if (state === "present") return ` shortcut: ${config.shortcut}`;
|
|
982
|
+
const why = state === "unreadable" ? "keybindings.json cannot be read" : "missing from keybindings.json";
|
|
983
|
+
return ` shortcut: ${config.shortcut} (${why} - run "claude-jev-advisor install context")`;
|
|
984
|
+
}
|
|
825
985
|
function contextLines(home, settings, config, env) {
|
|
826
986
|
const c = config.context;
|
|
827
987
|
const lines = [
|
|
@@ -830,6 +990,7 @@ function contextLines(home, settings, config, env) {
|
|
|
830
990
|
` thresholds: judge from ${formatSize(c.minTokens)}, /compact from ${formatSize(c.compactMinTokens)}, red at ${c.redRemainingPct}% left`,
|
|
831
991
|
` Jev: work done from ${c.unitDoneYes}, stage done (/clear) from ${c.phaseDoneYes}`
|
|
832
992
|
];
|
|
993
|
+
if (config.display === "mod") lines.splice(1, 0, shortcutLine(home, config));
|
|
833
994
|
const last = newestState(home);
|
|
834
995
|
if (last) {
|
|
835
996
|
const line = adviceLine({ size: last.size, threshold: null, judgment: last.judgment, config }) || "(no size)";
|
|
@@ -869,7 +1030,7 @@ function statusLines(home, env = process.env) {
|
|
|
869
1030
|
lines.push(`${feature}: installed, ${state}`);
|
|
870
1031
|
for (const c of commands) {
|
|
871
1032
|
const script = SCRIPT_IN_COMMAND.exec(c.command)?.[1];
|
|
872
|
-
if (script && !
|
|
1033
|
+
if (script && !fs9.existsSync(script)) {
|
|
873
1034
|
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}"`);
|
|
874
1035
|
}
|
|
875
1036
|
}
|
|
@@ -968,6 +1129,11 @@ async function ensureKey(io, keyFileGiven) {
|
|
|
968
1129
|
}
|
|
969
1130
|
await askAndSaveKey(io, io.prompt);
|
|
970
1131
|
}
|
|
1132
|
+
function shortcutLine2(s) {
|
|
1133
|
+
if (s.shortcut) return `shortcut: ${s.shortcut} presses the advice button above the prompt (~/.claude/keybindings.json)`;
|
|
1134
|
+
const why = s.takenBy ? `${SHORTCUT_CHORD} is already bound (${s.takenBy})` : `keybindings.json: ${s.problem}`;
|
|
1135
|
+
return `shortcut: none - ${why}; press the button with a click, or ctrl+x tab then Enter`;
|
|
1136
|
+
}
|
|
971
1137
|
async function runCli(argv, io) {
|
|
972
1138
|
const parsed = parse(argv);
|
|
973
1139
|
const problem = typeof parsed === "string" ? parsed : checkOptions(parsed.command, parsed.options);
|
|
@@ -995,9 +1161,15 @@ async function runCli(argv, io) {
|
|
|
995
1161
|
if (r.replacedLegacyRmGuard) io.out("replaced the legacy rm-guard hook (its files under ~/.claude/hooks/rm-guard were left in place)");
|
|
996
1162
|
if (r.display) io.out(`display: ${r.display}`);
|
|
997
1163
|
if (r.backup) io.out(`backup: ${r.backup}`);
|
|
1164
|
+
if (r.shortcut) io.out(shortcutLine2(r.shortcut));
|
|
1165
|
+
if (r.shortcut?.backup) io.out(`backup: ${r.shortcut.backup}`);
|
|
1166
|
+
if (r.shortcutRemoved?.removed) {
|
|
1167
|
+
io.out(`removed the ${SHORTCUT_CHORD} shortcut from keybindings.json`);
|
|
1168
|
+
if (r.shortcutRemoved.backup) io.out(`backup: ${r.shortcutRemoved.backup}`);
|
|
1169
|
+
}
|
|
998
1170
|
if (r.installed.length) {
|
|
999
1171
|
io.out(
|
|
1000
|
-
r.display === "mod" ? "Hooks apply right away, also in open Claude Code sessions
|
|
1172
|
+
r.display === "mod" ? "Hooks apply right away, also in open Claude Code sessions. The bottom-row display and the advice button start with the next new session; sessions that already show the bottom-row display pick up an upgrade by themselves." : "Hooks apply right away, also in open Claude Code sessions."
|
|
1001
1173
|
);
|
|
1002
1174
|
}
|
|
1003
1175
|
if (r.installed.includes("context")) await ensureKey(io, options["key-file"] !== void 0);
|
|
@@ -1008,7 +1180,9 @@ async function runCli(argv, io) {
|
|
|
1008
1180
|
if (!r.removed.length) io.out("nothing to remove");
|
|
1009
1181
|
for (const f of r.removed) io.out(`removed ${f}`);
|
|
1010
1182
|
if (r.backup) io.out(`backup: ${r.backup}`);
|
|
1011
|
-
if (r.removed
|
|
1183
|
+
if (r.shortcut?.removed) io.out(`removed the ${SHORTCUT_CHORD} shortcut from keybindings.json`);
|
|
1184
|
+
if (r.shortcut?.backup) io.out(`backup: ${r.shortcut.backup}`);
|
|
1185
|
+
if (r.removed.includes("context") && fs10.existsSync(savedKeyPath(io.home))) io.out(`kept your saved TypeSafe key at ${savedKeyPath(io.home)}`);
|
|
1012
1186
|
return 0;
|
|
1013
1187
|
}
|
|
1014
1188
|
case "key": {
|