claude-recall 0.24.0 → 0.24.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.
@@ -261,6 +261,99 @@ class ClaudeRecallCLI {
261
261
  });
262
262
  this.logger.info('CLI', 'Failures displayed', { count: displayFailures.length });
263
263
  }
264
+ /**
265
+ * Find project-local node_modules/claude-recall/ installs that shadow the
266
+ * global one when invoked via `npx claude-recall`. npx walks up from cwd
267
+ * looking for node_modules/.bin/claude-recall and uses the first match,
268
+ * not the globally-installed binary. A stray ~/node_modules/claude-recall/
269
+ * (a common WSL/Windows accident) traps every npx invocation under $HOME.
270
+ *
271
+ * Returns dir + version for each stale install found. Skips the global
272
+ * install (heuristic: paths containing /lib/node_modules/ or /.nvm/).
273
+ * Walk stops at $HOME so we never report or touch system locations.
274
+ */
275
+ findStaleLocalInstalls() {
276
+ const found = [];
277
+ const seen = new Set();
278
+ const home = os.homedir();
279
+ const checkDir = (dir) => {
280
+ if (seen.has(dir))
281
+ return;
282
+ seen.add(dir);
283
+ const pkgPath = path.join(dir, 'node_modules', 'claude-recall', 'package.json');
284
+ if (!fs.existsSync(pkgPath))
285
+ return;
286
+ // Skip global install paths so we never propose nuking them.
287
+ if (pkgPath.includes('/lib/node_modules/'))
288
+ return;
289
+ if (pkgPath.includes('/.nvm/'))
290
+ return;
291
+ try {
292
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
293
+ if (pkg && typeof pkg.version === 'string') {
294
+ found.push({ dir, version: pkg.version });
295
+ }
296
+ }
297
+ catch {
298
+ // Unreadable manifest — skip silently.
299
+ }
300
+ };
301
+ // Walk cwd up to $HOME (don't traverse above the user's home).
302
+ let dir = process.cwd();
303
+ while (true) {
304
+ checkDir(dir);
305
+ if (dir === home)
306
+ break;
307
+ const parent = path.dirname(dir);
308
+ if (parent === dir)
309
+ break; // hit filesystem root
310
+ dir = parent;
311
+ }
312
+ // If cwd was outside the home tree, the walk skipped $HOME. Check it.
313
+ checkDir(home);
314
+ return found;
315
+ }
316
+ /**
317
+ * Print a warning about stale local installs. If `autoClean` is true,
318
+ * actually remove them. Otherwise print copy-paste rm commands.
319
+ */
320
+ warnOrCleanStaleLocals(globalVersion, autoClean) {
321
+ const stale = this.findStaleLocalInstalls();
322
+ if (stale.length === 0)
323
+ return;
324
+ console.log('\n⚠ Stale local claude-recall installs detected:');
325
+ for (const s of stale) {
326
+ const drift = s.version !== globalVersion
327
+ ? ` (drift: global is ${globalVersion})`
328
+ : '';
329
+ console.log(` ${path.join(s.dir, 'node_modules', 'claude-recall')} v${s.version}${drift}`);
330
+ }
331
+ console.log('');
332
+ console.log(' These shadow the global install. `npx claude-recall` walks up from cwd');
333
+ console.log(' looking for node_modules/.bin/claude-recall and uses the first match —');
334
+ console.log(` not the global v${globalVersion}.`);
335
+ if (autoClean) {
336
+ console.log('\n🧹 Removing...');
337
+ for (const s of stale) {
338
+ const target = path.join(s.dir, 'node_modules', 'claude-recall');
339
+ try {
340
+ fs.rmSync(target, { recursive: true, force: true });
341
+ console.log(` ✓ removed ${target}`);
342
+ }
343
+ catch (e) {
344
+ console.log(` ✗ failed to remove ${target}: ${e.message}`);
345
+ }
346
+ }
347
+ console.log('\n Verify with: npx claude-recall --version');
348
+ }
349
+ else {
350
+ console.log('\n To remove them all:');
351
+ for (const s of stale) {
352
+ console.log(` rm -rf ${path.join(s.dir, 'node_modules', 'claude-recall')}`);
353
+ }
354
+ console.log('\n Or re-run upgrade with auto-clean: claude-recall upgrade --clean-locals');
355
+ }
356
+ }
264
357
  /**
265
358
  * One-shot upgrade: check registry, install latest globally, clean up any
266
359
  * running MCP servers (so fresh 0.x spawns on next Claude Code tool call).
@@ -269,8 +362,9 @@ class ClaudeRecallCLI {
269
362
  * - EACCES on `/usr/lib/node_modules` → print sudo + permanent-prefix fix
270
363
  * - No npm in PATH → actionable error
271
364
  * - Registry unreachable → clear error, don't leave install half-done
365
+ * - Stale project-local installs shadowing the global → warn (or clean with --clean-locals)
272
366
  */
273
- async upgrade() {
367
+ async upgrade(opts = {}) {
274
368
  const { execSync, spawnSync } = require('child_process');
275
369
  // Current version from package.json shipped with the installed binary
276
370
  let current;
@@ -297,40 +391,51 @@ class ClaudeRecallCLI {
297
391
  }
298
392
  console.log(`Installed: ${current}`);
299
393
  console.log(`Latest: ${latest}`);
300
- if (current === latest) {
301
- console.log('\n✓ Already up to date.');
302
- return;
394
+ const needsInstall = current !== latest;
395
+ if (needsInstall) {
396
+ console.log(`\n📦 Upgrading ${current} → ${latest}...\n`);
397
+ // Run npm install -g, streaming output so the user sees progress / errors live
398
+ const install = spawnSync('npm', ['install', '-g', 'claude-recall@latest'], {
399
+ stdio: 'inherit',
400
+ });
401
+ if (install.status !== 0) {
402
+ // npm prints its own error — add the practical remediation on top
403
+ console.error('\n❌ Install failed.');
404
+ console.error('\nMost common cause: your global npm prefix is owned by root (EACCES).');
405
+ console.error('\nQuick fix:');
406
+ console.error(' sudo npm install -g claude-recall');
407
+ console.error('\nPermanent fix (no more sudo for any global install on this machine):');
408
+ console.error(' mkdir -p ~/.npm-global');
409
+ console.error(" npm config set prefix ~/.npm-global");
410
+ console.error(" echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc");
411
+ console.error(' source ~/.bashrc');
412
+ console.error('\nThen re-run: claude-recall upgrade');
413
+ process.exit(install.status ?? 1);
414
+ }
415
+ // Kill any running MCP servers so Claude Code respawns them with the new binary
416
+ console.log('\n🧹 Cleaning up running MCP servers (Claude Code respawns them on next tool call)...');
417
+ try {
418
+ spawnSync('claude-recall', ['mcp', 'cleanup', '--all'], { stdio: 'inherit' });
419
+ }
420
+ catch {
421
+ // Non-fatal — the user can restart Claude Code manually if this fails
422
+ }
303
423
  }
304
- console.log(`\n📦 Upgrading ${current} ${latest}...\n`);
305
- // Run npm install -g, streaming output so the user sees progress / errors live
306
- const install = spawnSync('npm', ['install', '-g', 'claude-recall@latest'], {
307
- stdio: 'inherit',
308
- });
309
- if (install.status !== 0) {
310
- // npm prints its own error — add the practical remediation on top
311
- console.error('\n❌ Install failed.');
312
- console.error('\nMost common cause: your global npm prefix is owned by root (EACCES).');
313
- console.error('\nQuick fix:');
314
- console.error(' sudo npm install -g claude-recall');
315
- console.error('\nPermanent fix (no more sudo for any global install on this machine):');
316
- console.error(' mkdir -p ~/.npm-global');
317
- console.error(" npm config set prefix ~/.npm-global");
318
- console.error(" echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc");
319
- console.error(' source ~/.bashrc');
320
- console.error('\nThen re-run: claude-recall upgrade');
321
- process.exit(install.status ?? 1);
322
- }
323
- // Kill any running MCP servers so Claude Code respawns them with the new binary
324
- console.log('\n🧹 Cleaning up running MCP servers (Claude Code respawns them on next tool call)...');
325
- try {
326
- spawnSync('claude-recall', ['mcp', 'cleanup', '--all'], { stdio: 'inherit' });
424
+ // Detect stale project-local installs that would shadow the global binary
425
+ // when invoked via `npx claude-recall`. Runs on every upgrade invocation,
426
+ // including when already up-to-date — so `claude-recall upgrade` doubles
427
+ // as a diagnostic for the npx-walks-up-and-finds-stale-local trap, and
428
+ // `claude-recall upgrade --clean-locals` works as a one-shot cleanup
429
+ // command even when no version change is pending.
430
+ this.warnOrCleanStaleLocals(latest, opts.cleanLocals === true);
431
+ if (needsInstall) {
432
+ console.log(`\n✓ Upgraded to ${latest}. No need to re-run \`claude mcp add\` — existing`);
433
+ console.log(' registrations point at the `claude-recall` command and pick up the new');
434
+ console.log(' binary automatically. Just run any tool in Claude Code.');
327
435
  }
328
- catch {
329
- // Non-fatal the user can restart Claude Code manually if this fails
436
+ else {
437
+ console.log('\n✓ Already up to date.');
330
438
  }
331
- console.log(`\n✓ Upgraded to ${latest}. No need to re-run \`claude mcp add\` — existing`);
332
- console.log(' registrations point at the `claude-recall` command and pick up the new');
333
- console.log(' binary automatically. Just run any tool in Claude Code.');
334
439
  }
335
440
  /**
336
441
  * Demote rules loaded often but never cited — excludes them from future load_rules payloads.
@@ -1707,9 +1812,10 @@ async function main() {
1707
1812
  program
1708
1813
  .command('upgrade')
1709
1814
  .description('Upgrade claude-recall to the latest version and clear stale MCP servers')
1710
- .action(async () => {
1815
+ .option('--clean-locals', 'Also remove stale project-local installs that shadow the global binary')
1816
+ .action(async (options) => {
1711
1817
  const cli = new ClaudeRecallCLI(program.opts());
1712
- await cli.upgrade();
1818
+ await cli.upgrade({ cleanLocals: options.cleanLocals === true });
1713
1819
  process.exit(0);
1714
1820
  });
1715
1821
  // Export command
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-recall",
3
- "version": "0.24.0",
3
+ "version": "0.24.2",
4
4
  "description": "Persistent memory for Claude Code and Pi with native Skills integration, automatic capture, failure learning, and project scoping",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -49,6 +49,8 @@
49
49
  "search": "npm run claude-recall search",
50
50
  "preuninstall": "node scripts/uninstall.js",
51
51
  "prepare": "npm run build",
52
+ "prepublishOnly": "node scripts/check-publish.js",
53
+ "check:publish": "node scripts/check-publish.js",
52
54
  "mcp:start": "node dist/cli/claude-recall-cli.js mcp start",
53
55
  "mcp:dev": "ts-node src/cli/claude-recall-cli.ts mcp start",
54
56
  "mcp:debug": "NODE_ENV=development DEBUG=claude-recall:* ts-node src/cli/claude-recall-cli.ts mcp start",