claude-rpc 0.15.1 → 0.15.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/package.json +1 -1
- package/src/cli.js +12 -7
- package/src/install.js +70 -23
- package/src/version.js +1 -1
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1434,7 +1434,7 @@ function profileEnable(on) {
|
|
|
1434
1434
|
userCfg.profile = next;
|
|
1435
1435
|
writeFileSync(CONFIG_PATH, JSON.stringify(userCfg, null, 2));
|
|
1436
1436
|
if (on) {
|
|
1437
|
-
console.log(` ${c.green}✓${c.reset}
|
|
1437
|
+
console.log(` ${c.green}✓${c.reset} publishing enabled ${c.dim}— board syncs on the next flush, or now: ${c.reset}${c.cyan}claude-rpc profile publish${c.reset}`);
|
|
1438
1438
|
profileNextStep();
|
|
1439
1439
|
} else {
|
|
1440
1440
|
console.log(` ${c.green}✓${c.reset} leaderboard publishing disabled`);
|
|
@@ -1746,9 +1746,10 @@ const packagedDefault = IS_PACKAGED && !cmd;
|
|
|
1746
1746
|
// to do everything. Non-Windows: addStartupEntry is a no-op + warning.
|
|
1747
1747
|
case 'setup':
|
|
1748
1748
|
case 'install': {
|
|
1749
|
-
// runInstall prints the phased checklist
|
|
1750
|
-
//
|
|
1751
|
-
|
|
1749
|
+
// runInstall prints the phased checklist (or a one-line "already set
|
|
1750
|
+
// up" on clean re-runs); the daemon row lands after it, then setupOutro
|
|
1751
|
+
// closes the screen — only when something actually changed.
|
|
1752
|
+
const { target, changed } = await runInstall({ exePath: EXE_PATH || process.execPath });
|
|
1752
1753
|
// Slimmer first run: bring the daemon up now so the card appears
|
|
1753
1754
|
// immediately, instead of making the user run a separate `start`.
|
|
1754
1755
|
// Best-effort — a start hiccup must never make `setup` look failed.
|
|
@@ -1763,6 +1764,8 @@ const packagedDefault = IS_PACKAGED && !cmd;
|
|
|
1763
1764
|
});
|
|
1764
1765
|
child.unref();
|
|
1765
1766
|
console.log(` ${c.green}✓${c.reset} ${'daemon launched'.padEnd(16)}${c.dim}log ${shortPath(LOG_PATH)}${c.reset}`);
|
|
1767
|
+
} else {
|
|
1768
|
+
console.log(` ${c.cyan}·${c.reset} ${'daemon running'.padEnd(16)}${c.dim}pid ${daemonPid()}${c.reset}`);
|
|
1766
1769
|
}
|
|
1767
1770
|
} else {
|
|
1768
1771
|
startDaemon();
|
|
@@ -1771,11 +1774,13 @@ const packagedDefault = IS_PACKAGED && !cmd;
|
|
|
1771
1774
|
console.log(` ${c.yellow}!${c.reset} ${'daemon start'.padEnd(16)}${c.dim}couldn't auto-start: ${e.message}${c.reset}`);
|
|
1772
1775
|
console.log(` ${c.gray}↳ run \`claude-rpc start\` when you're ready${c.reset}`);
|
|
1773
1776
|
}
|
|
1774
|
-
setupOutro(target);
|
|
1777
|
+
setupOutro(target, changed);
|
|
1775
1778
|
break;
|
|
1776
1779
|
}
|
|
1777
1780
|
case 'uninstall': await runUninstall(); break;
|
|
1778
|
-
case 'upgrade-config':
|
|
1781
|
+
case 'upgrade-config':
|
|
1782
|
+
if (!migrateConfig()) console.log(` ${c.green}✓${c.reset} config already current — nothing to migrate`);
|
|
1783
|
+
break;
|
|
1779
1784
|
case 'start': startDaemon(); break;
|
|
1780
1785
|
case 'stop': stopDaemon(); break;
|
|
1781
1786
|
case 'restart': restartDaemon(); break;
|
|
@@ -1869,7 +1874,7 @@ const packagedDefault = IS_PACKAGED && !cmd;
|
|
|
1869
1874
|
default: {
|
|
1870
1875
|
if (packagedDefault) {
|
|
1871
1876
|
if (!isInstalled()) {
|
|
1872
|
-
const target = await runInstall({ exePath: EXE_PATH || process.execPath });
|
|
1877
|
+
const { target } = await runInstall({ exePath: EXE_PATH || process.execPath });
|
|
1873
1878
|
startDaemon();
|
|
1874
1879
|
setupOutro(target);
|
|
1875
1880
|
} else {
|
package/src/install.js
CHANGED
|
@@ -26,13 +26,31 @@ const STARTUP_VALUE = 'ClaudeRPC';
|
|
|
26
26
|
// the label column fixed-width so the detail column lines up across phases.
|
|
27
27
|
// The same rows print standalone (doctor --fix, packaged refresh) and still
|
|
28
28
|
// read fine outside the phased layout.
|
|
29
|
+
//
|
|
30
|
+
// Loud when something changes, near-silent when nothing does: a re-run where
|
|
31
|
+
// everything is already in place collapses to ONE summary line instead of
|
|
32
|
+
// re-printing the checklist. State-changing steps print rows (flushing their
|
|
33
|
+
// pending phase header) and mark the run dirty; confirmations record a
|
|
34
|
+
// `noop()` fact for the summary. Failures always print.
|
|
29
35
|
const LABEL_W = 16;
|
|
36
|
+
let pendingPhase = null;
|
|
37
|
+
let runDirty = false;
|
|
38
|
+
let noopFacts = [];
|
|
39
|
+
|
|
40
|
+
function resetRun() { pendingPhase = null; runDirty = false; noopFacts = []; }
|
|
41
|
+
function phase(title) { pendingPhase = title; }
|
|
30
42
|
function step(sym, label, detail = '', log = console.log) {
|
|
43
|
+
if (pendingPhase) {
|
|
44
|
+
console.log(`\n ${c.bold}${pendingPhase}${c.reset}`);
|
|
45
|
+
pendingPhase = null;
|
|
46
|
+
}
|
|
31
47
|
log(` ${sym} ${label.padEnd(LABEL_W)}${detail ? `${c.dim}${detail}${c.reset}` : ''}`);
|
|
32
48
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
49
|
+
function dirtyStep(sym, label, detail = '', log = console.log) {
|
|
50
|
+
runDirty = true;
|
|
51
|
+
step(sym, label, detail, log);
|
|
35
52
|
}
|
|
53
|
+
function noop(fact) { noopFacts.push(fact); }
|
|
36
54
|
|
|
37
55
|
const EVENTS = [
|
|
38
56
|
'SessionStart', 'UserPromptSubmit', 'PreToolUse', 'PostToolUse',
|
|
@@ -56,6 +74,7 @@ function isOurHookCommand(cmd) {
|
|
|
56
74
|
|
|
57
75
|
export function installHooks(exePath) {
|
|
58
76
|
const settings = readJson(CLAUDE_SETTINGS, {});
|
|
77
|
+
const before = JSON.stringify(settings.hooks || {});
|
|
59
78
|
settings.hooks = settings.hooks || {};
|
|
60
79
|
// Three modes, three shapes:
|
|
61
80
|
// packaged → `"<exe>" hook <event>` (canonical exe, no node)
|
|
@@ -82,8 +101,13 @@ export function installHooks(exePath) {
|
|
|
82
101
|
bucket.push({ matcher: '', hooks: [{ type: 'command', command: wanted }] });
|
|
83
102
|
}
|
|
84
103
|
}
|
|
104
|
+
if (JSON.stringify(settings.hooks) === before) {
|
|
105
|
+
noop(`hooks wired (${EVENTS.length} events)`);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
85
108
|
writeJson(CLAUDE_SETTINGS, settings);
|
|
86
|
-
|
|
109
|
+
dirtyStep(SYM_OK, 'hooks wired', `${EVENTS.length} events → ${CLAUDE_SETTINGS}`);
|
|
110
|
+
return true;
|
|
87
111
|
}
|
|
88
112
|
|
|
89
113
|
export function uninstallHooks() {
|
|
@@ -119,7 +143,8 @@ export async function addStartupEntry(exePath) {
|
|
|
119
143
|
'/d', `"${exePath}" daemon`,
|
|
120
144
|
'/f',
|
|
121
145
|
]);
|
|
122
|
-
step(SYM_OK, 'startup entry', `HKCU\\…\\Run\\${STARTUP_VALUE} — daemon starts at login`);
|
|
146
|
+
if (runDirty) step(SYM_OK, 'startup entry', `HKCU\\…\\Run\\${STARTUP_VALUE} — daemon starts at login`);
|
|
147
|
+
else noop('startup entry present');
|
|
123
148
|
}
|
|
124
149
|
|
|
125
150
|
export async function removeStartupEntry() {
|
|
@@ -172,7 +197,7 @@ export function ensureCanonicalExe(currentExe) {
|
|
|
172
197
|
const src = statSync(currentExe);
|
|
173
198
|
const dst = statSync(CANONICAL_EXE);
|
|
174
199
|
if (src.size === dst.size && Math.abs(src.mtimeMs - dst.mtimeMs) < 2000) {
|
|
175
|
-
|
|
200
|
+
noop('exe current');
|
|
176
201
|
return CANONICAL_EXE;
|
|
177
202
|
}
|
|
178
203
|
} catch { /* stat failed — fall through to copy attempt */ }
|
|
@@ -189,7 +214,7 @@ export function ensureCanonicalExe(currentExe) {
|
|
|
189
214
|
}
|
|
190
215
|
copyFileSync(currentExe, CANONICAL_EXE);
|
|
191
216
|
if (process.platform !== 'win32') chmodSync(CANONICAL_EXE, 0o755);
|
|
192
|
-
|
|
217
|
+
dirtyStep(SYM_OK, 'exe installed', CANONICAL_EXE);
|
|
193
218
|
step(SYM_INFO, 'original copy', `${currentExe} — safe to delete`);
|
|
194
219
|
sweepStaleCanonicalBackups();
|
|
195
220
|
return CANONICAL_EXE;
|
|
@@ -211,7 +236,7 @@ export function seedConfig() {
|
|
|
211
236
|
if (!existsSync(CONFIG_PATH) && existsSync(legacyPath)) {
|
|
212
237
|
mkdirSync(USER_CONFIG_DIR, { recursive: true });
|
|
213
238
|
copyFileSync(legacyPath, CONFIG_PATH);
|
|
214
|
-
|
|
239
|
+
dirtyStep(SYM_OK, 'config migrated', CONFIG_PATH);
|
|
215
240
|
step(SYM_INFO, 'legacy copy', `${legacyPath} — safe to delete on the next npm update`);
|
|
216
241
|
return false;
|
|
217
242
|
}
|
|
@@ -221,7 +246,7 @@ export function seedConfig() {
|
|
|
221
246
|
}
|
|
222
247
|
|
|
223
248
|
if (existsSync(CONFIG_PATH)) {
|
|
224
|
-
|
|
249
|
+
noop('config current');
|
|
225
250
|
return false;
|
|
226
251
|
}
|
|
227
252
|
mkdirSync(USER_CONFIG_DIR, { recursive: true });
|
|
@@ -233,7 +258,7 @@ export function seedConfig() {
|
|
|
233
258
|
seeded.community.instanceId = randomUUID();
|
|
234
259
|
}
|
|
235
260
|
writeFileSync(CONFIG_PATH, JSON.stringify(seeded, null, 2));
|
|
236
|
-
|
|
261
|
+
dirtyStep(SYM_OK, 'config seeded', CONFIG_PATH);
|
|
237
262
|
if (seeded.community?.enabled && seeded.community.instanceId) {
|
|
238
263
|
step(SYM_INFO, 'community', `anonymous totals on by default · opt out: ${c.reset}${c.cyan}claude-rpc community off`);
|
|
239
264
|
}
|
|
@@ -370,12 +395,9 @@ export function migrateConfig({ silent = false } = {}) {
|
|
|
370
395
|
if (changed) added.push('presence.buttons[] → CTA');
|
|
371
396
|
}
|
|
372
397
|
|
|
373
|
-
if (added.length === 0)
|
|
374
|
-
if (!silent) step(SYM_OK, 'config current', 'no new defaults to merge');
|
|
375
|
-
return false;
|
|
376
|
-
}
|
|
398
|
+
if (added.length === 0) return false;
|
|
377
399
|
writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2));
|
|
378
|
-
if (!silent)
|
|
400
|
+
if (!silent) dirtyStep(SYM_OK, 'config migrated', `added: ${added.join(', ')}`);
|
|
379
401
|
return true;
|
|
380
402
|
}
|
|
381
403
|
|
|
@@ -426,12 +448,29 @@ function verifyHookPipe(exePath) {
|
|
|
426
448
|
// Best-effort + loud: a failed -g (perms, offline) returns false so the caller
|
|
427
449
|
// can stop with the manual command rather than wire a dead hook.
|
|
428
450
|
function promoteNpxToGlobal() {
|
|
429
|
-
|
|
451
|
+
// Already promoted on a previous run? The PATH-resolved bin answers fast.
|
|
452
|
+
try {
|
|
453
|
+
const v = spawnSync('claude-rpc', ['--version'], {
|
|
454
|
+
encoding: 'utf8', timeout: 4000, windowsHide: true,
|
|
455
|
+
shell: process.platform === 'win32',
|
|
456
|
+
});
|
|
457
|
+
if ((v.stdout || '').trim() === `claude-rpc ${VERSION}`) {
|
|
458
|
+
noop('global install current');
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
} catch { /* not installed yet — promote below */ }
|
|
430
462
|
const r = spawnSync('npm', ['install', '-g', `claude-rpc@${VERSION}`], {
|
|
431
|
-
|
|
463
|
+
encoding: 'utf8',
|
|
432
464
|
shell: process.platform === 'win32', // npm is npm.cmd on Windows
|
|
433
465
|
});
|
|
434
|
-
|
|
466
|
+
if (r.error || r.status !== 0) {
|
|
467
|
+
// The piped npm chatter only matters when it failed.
|
|
468
|
+
if (r.stdout) process.stderr.write(r.stdout);
|
|
469
|
+
if (r.stderr) process.stderr.write(r.stderr);
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
dirtyStep(SYM_OK, 'installed globally', `claude-rpc@${VERSION} — hooks survive npx's throwaway cache`);
|
|
473
|
+
return true;
|
|
435
474
|
}
|
|
436
475
|
|
|
437
476
|
// Best-effort registry check. npx serves stale cached copies without
|
|
@@ -458,6 +497,7 @@ function warnIfStale() {
|
|
|
458
497
|
}
|
|
459
498
|
|
|
460
499
|
export async function install({ exePath, withStartup = true } = {}) {
|
|
500
|
+
resetRun();
|
|
461
501
|
console.log('');
|
|
462
502
|
console.log(` ${c.bold}${c.magenta}◆ claude-rpc setup${c.reset} ${c.dim}v${VERSION}${c.reset}`);
|
|
463
503
|
warnIfStale();
|
|
@@ -490,11 +530,13 @@ export async function install({ exePath, withStartup = true } = {}) {
|
|
|
490
530
|
// without verification is a lie — we caught broken-hook-path bugs
|
|
491
531
|
// twice during v0.3.x because no one ran a real event after install.
|
|
492
532
|
const probe = verifyHookPipe(target);
|
|
493
|
-
if (probe.ok) {
|
|
494
|
-
step(SYM_OK, 'hook verified', probe.detail);
|
|
495
|
-
} else {
|
|
533
|
+
if (!probe.ok) {
|
|
496
534
|
step(SYM_FAIL, 'hook verify', probe.detail, console.warn);
|
|
497
535
|
hintLine('run `claude-rpc doctor` for a full diagnostic', process.stderr);
|
|
536
|
+
} else if (runDirty) {
|
|
537
|
+
step(SYM_OK, 'hook verified', probe.detail);
|
|
538
|
+
} else {
|
|
539
|
+
noop('hook pipe verified');
|
|
498
540
|
}
|
|
499
541
|
|
|
500
542
|
// The CLI's setup case launches the daemon right after this returns, so its
|
|
@@ -504,17 +546,22 @@ export async function install({ exePath, withStartup = true } = {}) {
|
|
|
504
546
|
if (process.platform === 'win32') {
|
|
505
547
|
try { await addStartupEntry(target); }
|
|
506
548
|
catch (e) { step(SYM_WARN, 'startup entry', `failed: ${e.message}`, console.warn); }
|
|
507
|
-
} else {
|
|
549
|
+
} else if (runDirty) {
|
|
508
550
|
step(SYM_INFO, 'startup entry', 'skipped — login autostart is Windows-only');
|
|
509
551
|
}
|
|
510
552
|
}
|
|
511
|
-
|
|
553
|
+
// Nothing changed: the checklist above stayed silent, so say so in one line.
|
|
554
|
+
if (!runDirty && probe.ok) {
|
|
555
|
+
console.log(` ${SYM_OK} ${c.bold}already set up${c.reset} ${c.dim}${noopFacts.join(' · ')}${c.reset}`);
|
|
556
|
+
}
|
|
557
|
+
return { target, changed: runDirty };
|
|
512
558
|
}
|
|
513
559
|
|
|
514
560
|
// The single closing block of `claude-rpc setup` — what to do now, where the
|
|
515
561
|
// levers are. Printed by the CLI after the daemon launch so it always lands
|
|
516
562
|
// last; doctor --fix re-runs install() without it.
|
|
517
|
-
export function setupOutro(target) {
|
|
563
|
+
export function setupOutro(target, changed = true) {
|
|
564
|
+
if (!changed) return;
|
|
518
565
|
const point = (label, value, note = '') =>
|
|
519
566
|
console.log(` ${c.dim}→${c.reset} ${c.dim}${label.padEnd(14)}${c.reset} ${c.cyan}${value}${c.reset}${note ? ` ${c.dim}${note}${c.reset}` : ''}`);
|
|
520
567
|
console.log('');
|