claude-recall 0.7.4 ā 0.7.5
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 +173 -1
- package/dist/cli/claude-recall-cli.js +3 -0
- package/dist/cli/commands/mcp-commands.js +36 -4
- package/dist/cli/commands/project-commands.js +309 -0
- package/dist/mcp/server.js +56 -0
- package/dist/services/project-registry.js +241 -0
- package/package.json +3 -3
- package/scripts/postinstall.js +47 -0
- package/scripts/uninstall.js +89 -0
package/README.md
CHANGED
|
@@ -94,7 +94,22 @@ npm uninstall -g claude-recall
|
|
|
94
94
|
|
|
95
95
|
Claude Recall works on **Windows, Linux, and macOS**. Native binaries (SQLite) compile automatically for your platform during `npm install`.
|
|
96
96
|
|
|
97
|
-
**WSL Users
|
|
97
|
+
**WSL Users - Special Case:**
|
|
98
|
+
|
|
99
|
+
If you use **both Windows and WSL** for the same project (e.g., Electron app runs on Windows, but Claude Code runs in WSL):
|
|
100
|
+
|
|
101
|
+
**Problem**: Installing locally creates Windows binaries, but WSL needs Linux binaries ā "invalid ELF header" errors.
|
|
102
|
+
|
|
103
|
+
**Solution**: Install globally in WSL only:
|
|
104
|
+
```bash
|
|
105
|
+
# From WSL:
|
|
106
|
+
npm install -g claude-recall
|
|
107
|
+
|
|
108
|
+
# Verify:
|
|
109
|
+
claude-recall --version
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Important**: Global installation does NOT affect project-specific memory scoping! See [How Project Scoping Works](#how-project-scoping-works-installation-location) below.
|
|
98
113
|
|
|
99
114
|
**Everyone else:** Both local and global work, but local is still recommended for the benefits above.
|
|
100
115
|
|
|
@@ -526,6 +541,98 @@ All commands support:
|
|
|
526
541
|
- `--config <path>` - Use custom config file
|
|
527
542
|
- `-h, --help` - Show help for any command
|
|
528
543
|
|
|
544
|
+
## Project Management (v0.7.5+)
|
|
545
|
+
|
|
546
|
+
Claude Recall maintains a **project registry** to track all projects using it, enabling better organization and visibility across multiple projects.
|
|
547
|
+
|
|
548
|
+
### Auto-Registration
|
|
549
|
+
|
|
550
|
+
Projects are automatically registered when:
|
|
551
|
+
- The MCP server starts (every time Claude Code connects)
|
|
552
|
+
- You run `npm install claude-recall` locally
|
|
553
|
+
- You manually run `npx claude-recall project register`
|
|
554
|
+
|
|
555
|
+
The registry stores:
|
|
556
|
+
- Project path
|
|
557
|
+
- Claude Recall version
|
|
558
|
+
- Registration date
|
|
559
|
+
- Last activity timestamp
|
|
560
|
+
|
|
561
|
+
### Project Commands
|
|
562
|
+
|
|
563
|
+
**List all registered projects:**
|
|
564
|
+
```bash
|
|
565
|
+
npx claude-recall project list # Human-friendly output
|
|
566
|
+
npx claude-recall project list --json # JSON format
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Shows:
|
|
570
|
+
- Project name and path
|
|
571
|
+
- Claude Recall version
|
|
572
|
+
- Last activity
|
|
573
|
+
- MCP server status (active/inactive)
|
|
574
|
+
|
|
575
|
+
**Show project details:**
|
|
576
|
+
```bash
|
|
577
|
+
npx claude-recall project show # Current project
|
|
578
|
+
npx claude-recall project show my-project # Specific project
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
Displays:
|
|
582
|
+
- Registry information (path, version, timestamps)
|
|
583
|
+
- MCP server status (running/stopped, PID)
|
|
584
|
+
|
|
585
|
+
**Register a project:**
|
|
586
|
+
```bash
|
|
587
|
+
npx claude-recall project register # Current directory
|
|
588
|
+
npx claude-recall project register --path /path/to/project
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
**Unregister a project:**
|
|
592
|
+
```bash
|
|
593
|
+
npx claude-recall project unregister # Current project
|
|
594
|
+
npx claude-recall project unregister my-project
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**Note**: Unregistering does NOT remove memories or MCP configuration - only the registry entry.
|
|
598
|
+
|
|
599
|
+
**Clean up stale entries:**
|
|
600
|
+
```bash
|
|
601
|
+
npx claude-recall project clean # Remove projects not seen in 30 days
|
|
602
|
+
npx claude-recall project clean --days 60 # Custom threshold
|
|
603
|
+
npx claude-recall project clean --dry-run # Preview without changes
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Enhanced MCP Commands
|
|
607
|
+
|
|
608
|
+
MCP commands now show registry information:
|
|
609
|
+
|
|
610
|
+
```bash
|
|
611
|
+
npx claude-recall mcp status # Shows registry info + MCP status
|
|
612
|
+
npx claude-recall mcp ps # Lists all servers with version info
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### Registry Storage
|
|
616
|
+
|
|
617
|
+
Registry stored at: `~/.claude-recall/projects.json`
|
|
618
|
+
|
|
619
|
+
Format:
|
|
620
|
+
```json
|
|
621
|
+
{
|
|
622
|
+
"version": 1,
|
|
623
|
+
"projects": {
|
|
624
|
+
"my-project": {
|
|
625
|
+
"path": "/full/path/to/my-project",
|
|
626
|
+
"registeredAt": "2025-11-09T...",
|
|
627
|
+
"version": "0.7.5",
|
|
628
|
+
"lastSeen": "2025-11-09T..."
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
**Note**: The registry is separate from your memory database. Cleaning the registry does NOT affect your stored memories.
|
|
635
|
+
|
|
529
636
|
## Project Scoping (v0.7.2+)
|
|
530
637
|
|
|
531
638
|
Claude Recall now supports **project-specific memory isolation** while keeping universal preferences available everywhere.
|
|
@@ -610,6 +717,71 @@ npx claude-recall stats --global
|
|
|
610
717
|
- Works like v0.7.1 and earlier
|
|
611
718
|
- Available everywhere unless you explicitly scope them
|
|
612
719
|
|
|
720
|
+
### How Project Scoping Works (Installation Location)
|
|
721
|
+
|
|
722
|
+
**Important**: Where you install claude-recall (global vs local) does NOT affect project-specific memory scoping!
|
|
723
|
+
|
|
724
|
+
**What Determines Project Scope:**
|
|
725
|
+
|
|
726
|
+
1. ā
**Claude Code's working directory** - Where you run Claude Code
|
|
727
|
+
2. ā
**Database scoping logic** - Filters memories by project_id
|
|
728
|
+
3. ā **Installation location** - Doesn't matter for scoping
|
|
729
|
+
|
|
730
|
+
**How It Works:**
|
|
731
|
+
|
|
732
|
+
```
|
|
733
|
+
# Claude Code runs in this directory:
|
|
734
|
+
/home/user/projects/my-app
|
|
735
|
+
|
|
736
|
+
# Project ID detected from directory name:
|
|
737
|
+
project_id = "my-app"
|
|
738
|
+
|
|
739
|
+
# Memories stored/searched with that project_id:
|
|
740
|
+
- "For this project, use PostgreSQL" ā stored with project_id="my-app"
|
|
741
|
+
- "Remember everywhere: I prefer TypeScript" ā stored with scope="universal"
|
|
742
|
+
|
|
743
|
+
# Same behavior whether claude-recall is:
|
|
744
|
+
# - Installed globally: /usr/local/lib/node_modules/claude-recall
|
|
745
|
+
# - Installed locally: ./node_modules/claude-recall
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
**Database Location (Always Global):**
|
|
749
|
+
|
|
750
|
+
Whether you install globally or locally, the database is **always** at:
|
|
751
|
+
```
|
|
752
|
+
~/.claude-recall/claude-recall.db
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
This single database contains:
|
|
756
|
+
- Project-specific memories for each project (isolated by project_id)
|
|
757
|
+
- Universal memories (available in all projects)
|
|
758
|
+
- Unscoped memories (backward compatible)
|
|
759
|
+
|
|
760
|
+
**Example: Two Projects, One Installation**
|
|
761
|
+
|
|
762
|
+
```bash
|
|
763
|
+
# Project A:
|
|
764
|
+
cd ~/projects/project-a
|
|
765
|
+
# Claude Code detects: project_id = "project-a"
|
|
766
|
+
# Memories: isolated to project-a + universal + unscoped
|
|
767
|
+
|
|
768
|
+
# Project B:
|
|
769
|
+
cd ~/projects/project-b
|
|
770
|
+
# Claude Code detects: project_id = "project-b"
|
|
771
|
+
# Memories: isolated to project-b + universal + unscoped
|
|
772
|
+
|
|
773
|
+
# Same global installation, proper isolation!
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
**WSL Users:**
|
|
777
|
+
|
|
778
|
+
This is why **global installation in WSL** works perfectly for project scoping:
|
|
779
|
+
- Code location: `/home/user/.nvm/.../bin/claude-recall` (global)
|
|
780
|
+
- Project detection: Based on Claude Code's `cwd`, NOT installation path
|
|
781
|
+
- Memory isolation: Each project gets its own memories + universal preferences
|
|
782
|
+
|
|
783
|
+
Global installation actually **helps** WSL users by avoiding binary conflicts while maintaining perfect project isolation!
|
|
784
|
+
|
|
613
785
|
## Memory Management
|
|
614
786
|
|
|
615
787
|
Claude Recall automatically manages memory to prevent unlimited database growth, with user notifications:
|
|
@@ -48,6 +48,7 @@ const live_test_1 = require("./commands/live-test");
|
|
|
48
48
|
const queue_integration_1 = require("../services/queue-integration");
|
|
49
49
|
const memory_evolution_1 = require("../services/memory-evolution");
|
|
50
50
|
const mcp_commands_1 = require("./commands/mcp-commands");
|
|
51
|
+
const project_commands_1 = require("./commands/project-commands");
|
|
51
52
|
const program = new commander_1.Command();
|
|
52
53
|
class ClaudeRecallCLI {
|
|
53
54
|
constructor(options) {
|
|
@@ -612,6 +613,8 @@ async function main() {
|
|
|
612
613
|
});
|
|
613
614
|
// Register MCP process management commands
|
|
614
615
|
mcp_commands_1.MCPCommands.register(mcpCmd);
|
|
616
|
+
// Project management commands
|
|
617
|
+
project_commands_1.ProjectCommands.register(program);
|
|
615
618
|
// Migration commands
|
|
616
619
|
migrate_1.MigrateCommand.register(program);
|
|
617
620
|
// Register live test command
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.MCPCommands = void 0;
|
|
7
7
|
const process_manager_1 = require("../../services/process-manager");
|
|
8
8
|
const config_1 = require("../../services/config");
|
|
9
|
+
const project_registry_1 = require("../../services/project-registry");
|
|
9
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
10
11
|
/**
|
|
11
12
|
* MCP Process Management Commands
|
|
@@ -21,6 +22,7 @@ class MCPCommands {
|
|
|
21
22
|
constructor() {
|
|
22
23
|
this.processManager = process_manager_1.ProcessManager.getInstance();
|
|
23
24
|
this.config = config_1.ConfigService.getInstance();
|
|
25
|
+
this.projectRegistry = project_registry_1.ProjectRegistry.getInstance();
|
|
24
26
|
}
|
|
25
27
|
static register(mcpCmd) {
|
|
26
28
|
const commands = new MCPCommands();
|
|
@@ -72,6 +74,7 @@ class MCPCommands {
|
|
|
72
74
|
async showStatus() {
|
|
73
75
|
const projectId = this.config.getProjectId();
|
|
74
76
|
const status = this.processManager.getServerStatus(projectId);
|
|
77
|
+
const registryEntry = this.projectRegistry.get(projectId);
|
|
75
78
|
console.log(chalk_1.default.cyan('\nš MCP Server Status\n'));
|
|
76
79
|
console.log(`Project: ${chalk_1.default.yellow(projectId)}`);
|
|
77
80
|
console.log(`Status: ${status.isRunning ? chalk_1.default.green('ā Running') : chalk_1.default.gray('ā Stopped')}`);
|
|
@@ -82,6 +85,20 @@ class MCPCommands {
|
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
87
|
console.log(`PID File: ${chalk_1.default.gray(status.pidFile)}`);
|
|
88
|
+
// Show registry information if available
|
|
89
|
+
if (registryEntry) {
|
|
90
|
+
console.log();
|
|
91
|
+
console.log(chalk_1.default.bold('Registry Info:'));
|
|
92
|
+
console.log(` Version: ${chalk_1.default.gray(registryEntry.version)}`);
|
|
93
|
+
console.log(` Path: ${chalk_1.default.gray(registryEntry.path)}`);
|
|
94
|
+
console.log(` Registered: ${chalk_1.default.gray(new Date(registryEntry.registeredAt).toLocaleString())}`);
|
|
95
|
+
console.log(` Last Seen: ${chalk_1.default.gray(new Date(registryEntry.lastSeen).toLocaleString())}`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.log();
|
|
99
|
+
console.log(chalk_1.default.yellow('ā Project not registered in registry'));
|
|
100
|
+
console.log(chalk_1.default.gray(' Run `npx claude-recall project register` to register'));
|
|
101
|
+
}
|
|
85
102
|
console.log();
|
|
86
103
|
}
|
|
87
104
|
/**
|
|
@@ -89,9 +106,10 @@ class MCPCommands {
|
|
|
89
106
|
*/
|
|
90
107
|
async listServers() {
|
|
91
108
|
const servers = this.processManager.getAllRunningServers();
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
109
|
+
const registeredProjects = this.projectRegistry.list();
|
|
110
|
+
console.log(chalk_1.default.cyan('\nš MCP Servers & Registered Projects\n'));
|
|
111
|
+
if (servers.length === 0 && registeredProjects.length === 0) {
|
|
112
|
+
console.log(chalk_1.default.gray('No MCP servers or registered projects found.'));
|
|
95
113
|
console.log();
|
|
96
114
|
return;
|
|
97
115
|
}
|
|
@@ -100,7 +118,9 @@ class MCPCommands {
|
|
|
100
118
|
if (runningServers.length > 0) {
|
|
101
119
|
console.log(chalk_1.default.green(`ā Running (${runningServers.length}):`));
|
|
102
120
|
for (const server of runningServers) {
|
|
103
|
-
|
|
121
|
+
const registryEntry = this.projectRegistry.get(server.projectId);
|
|
122
|
+
const versionInfo = registryEntry ? ` [v${registryEntry.version}]` : '';
|
|
123
|
+
console.log(` ${chalk_1.default.yellow(server.projectId.padEnd(40))} PID: ${chalk_1.default.cyan(server.pid)}${chalk_1.default.gray(versionInfo)}`);
|
|
104
124
|
}
|
|
105
125
|
console.log();
|
|
106
126
|
}
|
|
@@ -113,6 +133,18 @@ class MCPCommands {
|
|
|
113
133
|
console.log(chalk_1.default.yellow(`š” Run 'npx claude-recall mcp cleanup' to remove stale PID files`));
|
|
114
134
|
console.log();
|
|
115
135
|
}
|
|
136
|
+
// Show registered projects that don't have running servers
|
|
137
|
+
const runningProjectIds = new Set(runningServers.map(s => s.projectId));
|
|
138
|
+
const inactiveProjects = registeredProjects.filter(({ projectId }) => !runningProjectIds.has(projectId));
|
|
139
|
+
if (inactiveProjects.length > 0) {
|
|
140
|
+
console.log(chalk_1.default.gray(`š Registered but Inactive (${inactiveProjects.length}):`));
|
|
141
|
+
for (const { projectId, entry } of inactiveProjects) {
|
|
142
|
+
console.log(` ${chalk_1.default.gray(projectId.padEnd(40))} v${entry.version}`);
|
|
143
|
+
}
|
|
144
|
+
console.log();
|
|
145
|
+
console.log(chalk_1.default.gray(`š” Run 'npx claude-recall project list' for detailed registry info`));
|
|
146
|
+
console.log();
|
|
147
|
+
}
|
|
116
148
|
}
|
|
117
149
|
/**
|
|
118
150
|
* Stop MCP server
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.ProjectCommands = void 0;
|
|
40
|
+
const project_registry_1 = require("../../services/project-registry");
|
|
41
|
+
const config_1 = require("../../services/config");
|
|
42
|
+
const process_manager_1 = require("../../services/process-manager");
|
|
43
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
/**
|
|
47
|
+
* Project Management Commands
|
|
48
|
+
*
|
|
49
|
+
* Provides CLI commands for managing project registration:
|
|
50
|
+
* - register: Register current or specific project
|
|
51
|
+
* - unregister: Remove project from registry
|
|
52
|
+
* - list: Show all registered projects
|
|
53
|
+
* - show: Show details for specific project
|
|
54
|
+
* - clean: Remove stale registry entries
|
|
55
|
+
*/
|
|
56
|
+
class ProjectCommands {
|
|
57
|
+
constructor() {
|
|
58
|
+
this.projectRegistry = project_registry_1.ProjectRegistry.getInstance();
|
|
59
|
+
this.config = config_1.ConfigService.getInstance();
|
|
60
|
+
this.processManager = process_manager_1.ProcessManager.getInstance();
|
|
61
|
+
}
|
|
62
|
+
static register(program) {
|
|
63
|
+
const commands = new ProjectCommands();
|
|
64
|
+
const projectCmd = program
|
|
65
|
+
.command('project')
|
|
66
|
+
.description('Project registration and management');
|
|
67
|
+
// project register
|
|
68
|
+
projectCmd
|
|
69
|
+
.command('register')
|
|
70
|
+
.description('Register current or specified project')
|
|
71
|
+
.option('--path <path>', 'Project path (default: current directory)')
|
|
72
|
+
.action(async (options) => {
|
|
73
|
+
await commands.registerProject(options);
|
|
74
|
+
});
|
|
75
|
+
// project unregister
|
|
76
|
+
projectCmd
|
|
77
|
+
.command('unregister [project-id]')
|
|
78
|
+
.description('Unregister a project')
|
|
79
|
+
.action(async (projectId) => {
|
|
80
|
+
await commands.unregisterProject(projectId);
|
|
81
|
+
});
|
|
82
|
+
// project list
|
|
83
|
+
projectCmd
|
|
84
|
+
.command('list')
|
|
85
|
+
.description('List all registered projects')
|
|
86
|
+
.option('--json', 'Output as JSON')
|
|
87
|
+
.action(async (options) => {
|
|
88
|
+
await commands.listProjects(options);
|
|
89
|
+
});
|
|
90
|
+
// project show
|
|
91
|
+
projectCmd
|
|
92
|
+
.command('show [project-id]')
|
|
93
|
+
.description('Show details for a project (default: current)')
|
|
94
|
+
.action(async (projectId) => {
|
|
95
|
+
await commands.showProject(projectId);
|
|
96
|
+
});
|
|
97
|
+
// project clean
|
|
98
|
+
projectCmd
|
|
99
|
+
.command('clean')
|
|
100
|
+
.description('Remove stale registry entries')
|
|
101
|
+
.option('--dry-run', 'Show what would be removed without removing')
|
|
102
|
+
.option('--days <number>', 'Days without activity to consider stale', '30')
|
|
103
|
+
.action(async (options) => {
|
|
104
|
+
await commands.cleanRegistry(options);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Register a project
|
|
109
|
+
*/
|
|
110
|
+
async registerProject(options) {
|
|
111
|
+
console.log(chalk_1.default.cyan('\nš Registering Project\n'));
|
|
112
|
+
try {
|
|
113
|
+
// Determine project path
|
|
114
|
+
const projectPath = options.path
|
|
115
|
+
? path.resolve(options.path)
|
|
116
|
+
: process.cwd();
|
|
117
|
+
// Validate path exists
|
|
118
|
+
if (!fs.existsSync(projectPath)) {
|
|
119
|
+
console.log(chalk_1.default.red(`ā Path does not exist: ${projectPath}`));
|
|
120
|
+
console.log();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// Get project ID
|
|
124
|
+
const projectId = path.basename(projectPath);
|
|
125
|
+
// Get version
|
|
126
|
+
const version = this.getVersion();
|
|
127
|
+
// Register
|
|
128
|
+
this.projectRegistry.register(projectId, projectPath, version);
|
|
129
|
+
console.log(`Project: ${chalk_1.default.yellow(projectId)}`);
|
|
130
|
+
console.log(`Path: ${chalk_1.default.gray(projectPath)}`);
|
|
131
|
+
console.log(`Version: ${chalk_1.default.gray(version)}`);
|
|
132
|
+
console.log();
|
|
133
|
+
console.log(chalk_1.default.green('ā Project registered successfully'));
|
|
134
|
+
console.log();
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
console.log(chalk_1.default.red(`ā Failed to register project: ${error}`));
|
|
138
|
+
console.log();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Unregister a project
|
|
143
|
+
*/
|
|
144
|
+
async unregisterProject(projectId) {
|
|
145
|
+
console.log(chalk_1.default.cyan('\nšļø Unregistering Project\n'));
|
|
146
|
+
try {
|
|
147
|
+
// If no projectId provided, use current directory
|
|
148
|
+
const targetProjectId = projectId || this.config.getProjectId();
|
|
149
|
+
// Get project info before removing
|
|
150
|
+
const entry = this.projectRegistry.get(targetProjectId);
|
|
151
|
+
if (!entry) {
|
|
152
|
+
console.log(chalk_1.default.yellow(`ā Project not found in registry: ${targetProjectId}`));
|
|
153
|
+
console.log();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
console.log(`Project: ${chalk_1.default.yellow(targetProjectId)}`);
|
|
157
|
+
console.log(`Path: ${chalk_1.default.gray(entry.path)}`);
|
|
158
|
+
console.log();
|
|
159
|
+
// Unregister
|
|
160
|
+
const success = this.projectRegistry.unregister(targetProjectId);
|
|
161
|
+
if (success) {
|
|
162
|
+
console.log(chalk_1.default.green('ā Project unregistered successfully'));
|
|
163
|
+
console.log();
|
|
164
|
+
console.log(chalk_1.default.gray('Note: This does not remove the MCP server configuration from ~/.claude.json'));
|
|
165
|
+
console.log(chalk_1.default.gray(' Use `claude mcp` commands to manage MCP servers.'));
|
|
166
|
+
console.log();
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
console.log(chalk_1.default.red('ā Failed to unregister project'));
|
|
170
|
+
console.log();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
console.log(chalk_1.default.red(`ā Error: ${error}`));
|
|
175
|
+
console.log();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* List all registered projects
|
|
180
|
+
*/
|
|
181
|
+
async listProjects(options) {
|
|
182
|
+
const projects = this.projectRegistry.list();
|
|
183
|
+
if (options.json) {
|
|
184
|
+
console.log(JSON.stringify(projects, null, 2));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
console.log(chalk_1.default.cyan('\nš Registered Projects\n'));
|
|
188
|
+
if (projects.length === 0) {
|
|
189
|
+
console.log(chalk_1.default.gray('No projects registered.'));
|
|
190
|
+
console.log();
|
|
191
|
+
console.log(chalk_1.default.yellow('š” Run `npx claude-recall project register` to register the current project'));
|
|
192
|
+
console.log();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// Get running servers for status
|
|
196
|
+
const runningServers = this.processManager.getAllRunningServers();
|
|
197
|
+
const runningProjectIds = new Set(runningServers.filter(s => s.isRunning).map(s => s.projectId));
|
|
198
|
+
// Sort by lastSeen (most recent first)
|
|
199
|
+
projects.sort((a, b) => {
|
|
200
|
+
return new Date(b.entry.lastSeen).getTime() - new Date(a.entry.lastSeen).getTime();
|
|
201
|
+
});
|
|
202
|
+
for (const { projectId, entry } of projects) {
|
|
203
|
+
const isRunning = runningProjectIds.has(projectId);
|
|
204
|
+
const lastSeenTime = this.formatRelativeTime(entry.lastSeen);
|
|
205
|
+
console.log(chalk_1.default.bold(projectId));
|
|
206
|
+
console.log(` Path: ${chalk_1.default.gray(entry.path)}`);
|
|
207
|
+
console.log(` Version: ${chalk_1.default.gray(entry.version)}`);
|
|
208
|
+
console.log(` Last seen: ${chalk_1.default.gray(lastSeenTime)}`);
|
|
209
|
+
console.log(` Status: ${isRunning ? chalk_1.default.green('ā Active (MCP running)') : chalk_1.default.gray('ā Inactive')}`);
|
|
210
|
+
console.log();
|
|
211
|
+
}
|
|
212
|
+
console.log(chalk_1.default.gray(`Total: ${projects.length} project(s)`));
|
|
213
|
+
console.log();
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Show details for a specific project
|
|
217
|
+
*/
|
|
218
|
+
async showProject(projectId) {
|
|
219
|
+
const targetProjectId = projectId || this.config.getProjectId();
|
|
220
|
+
console.log(chalk_1.default.cyan('\nš Project Details\n'));
|
|
221
|
+
const entry = this.projectRegistry.get(targetProjectId);
|
|
222
|
+
if (!entry) {
|
|
223
|
+
console.log(chalk_1.default.yellow(`ā Project not found: ${targetProjectId}`));
|
|
224
|
+
console.log();
|
|
225
|
+
console.log(chalk_1.default.gray('Run `npx claude-recall project list` to see registered projects'));
|
|
226
|
+
console.log();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// Check if MCP server is running
|
|
230
|
+
const status = this.processManager.getServerStatus(targetProjectId);
|
|
231
|
+
console.log(`${chalk_1.default.bold('Project:')} ${chalk_1.default.yellow(targetProjectId)}`);
|
|
232
|
+
console.log();
|
|
233
|
+
console.log(chalk_1.default.bold('Registry Information:'));
|
|
234
|
+
console.log(` Path: ${chalk_1.default.gray(entry.path)}`);
|
|
235
|
+
console.log(` Version: ${chalk_1.default.gray(entry.version)}`);
|
|
236
|
+
console.log(` Registered: ${chalk_1.default.gray(new Date(entry.registeredAt).toLocaleString())}`);
|
|
237
|
+
console.log(` Last Seen: ${chalk_1.default.gray(new Date(entry.lastSeen).toLocaleString())} (${this.formatRelativeTime(entry.lastSeen)})`);
|
|
238
|
+
console.log();
|
|
239
|
+
console.log(chalk_1.default.bold('MCP Server Status:'));
|
|
240
|
+
console.log(` Running: ${status.isRunning ? chalk_1.default.green('ā Yes') : chalk_1.default.gray('ā No')}`);
|
|
241
|
+
if (status.pid) {
|
|
242
|
+
console.log(` PID: ${chalk_1.default.gray(status.pid)}`);
|
|
243
|
+
}
|
|
244
|
+
console.log(` PID File: ${chalk_1.default.gray(status.pidFile)}`);
|
|
245
|
+
console.log();
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Clean stale registry entries
|
|
249
|
+
*/
|
|
250
|
+
async cleanRegistry(options) {
|
|
251
|
+
const daysOld = parseInt(options.days || '30', 10);
|
|
252
|
+
console.log(chalk_1.default.cyan('\nš§¹ Cleaning Registry\n'));
|
|
253
|
+
if (options.dryRun) {
|
|
254
|
+
console.log(chalk_1.default.yellow('DRY RUN MODE - No changes will be made\n'));
|
|
255
|
+
}
|
|
256
|
+
console.log(`Removing projects not seen in ${daysOld} days...\n`);
|
|
257
|
+
const removed = this.projectRegistry.clean(daysOld, options.dryRun || false);
|
|
258
|
+
if (removed === 0) {
|
|
259
|
+
console.log(chalk_1.default.green('ā No stale entries found'));
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
if (options.dryRun) {
|
|
263
|
+
console.log(chalk_1.default.gray(`\nWould remove ${removed} stale project(s)`));
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
console.log(chalk_1.default.green(`\nā Removed ${removed} stale project(s)`));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
console.log();
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Format relative time (e.g., "2 minutes ago", "5 days ago")
|
|
273
|
+
*/
|
|
274
|
+
formatRelativeTime(timestamp) {
|
|
275
|
+
const now = Date.now();
|
|
276
|
+
const then = new Date(timestamp).getTime();
|
|
277
|
+
const diffMs = now - then;
|
|
278
|
+
const seconds = Math.floor(diffMs / 1000);
|
|
279
|
+
const minutes = Math.floor(seconds / 60);
|
|
280
|
+
const hours = Math.floor(minutes / 60);
|
|
281
|
+
const days = Math.floor(hours / 24);
|
|
282
|
+
if (days > 0) {
|
|
283
|
+
return `${days} day${days === 1 ? '' : 's'} ago`;
|
|
284
|
+
}
|
|
285
|
+
else if (hours > 0) {
|
|
286
|
+
return `${hours} hour${hours === 1 ? '' : 's'} ago`;
|
|
287
|
+
}
|
|
288
|
+
else if (minutes > 0) {
|
|
289
|
+
return `${minutes} minute${minutes === 1 ? '' : 's'} ago`;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
return 'just now';
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Get current version
|
|
297
|
+
*/
|
|
298
|
+
getVersion() {
|
|
299
|
+
try {
|
|
300
|
+
const packageJsonPath = path.join(__dirname, '../../../package.json');
|
|
301
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
302
|
+
return packageJson.version;
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
return 'unknown';
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
exports.ProjectCommands = ProjectCommands;
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.MCPServer = void 0;
|
|
4
37
|
const stdio_1 = require("./transports/stdio");
|
|
@@ -18,6 +51,9 @@ const context_enhancer_1 = require("../services/context-enhancer");
|
|
|
18
51
|
const conversation_context_manager_1 = require("../services/conversation-context-manager");
|
|
19
52
|
const process_manager_1 = require("../services/process-manager");
|
|
20
53
|
const config_1 = require("../services/config");
|
|
54
|
+
const project_registry_1 = require("../services/project-registry");
|
|
55
|
+
const path = __importStar(require("path"));
|
|
56
|
+
const fs = __importStar(require("fs"));
|
|
21
57
|
class MCPServer {
|
|
22
58
|
constructor() {
|
|
23
59
|
this.tools = new Map();
|
|
@@ -384,6 +420,13 @@ class MCPServer {
|
|
|
384
420
|
this.logger.info('MCPServer', 'Starting Claude Recall MCP server...');
|
|
385
421
|
// Get project ID for PID tracking
|
|
386
422
|
const projectId = this.config.getProjectId();
|
|
423
|
+
// Auto-register project in registry
|
|
424
|
+
const rootDir = this.config.getConfig().project.rootDir;
|
|
425
|
+
const version = this.getVersion();
|
|
426
|
+
const projectRegistry = project_registry_1.ProjectRegistry.getInstance();
|
|
427
|
+
projectRegistry.register(projectId, rootDir, version);
|
|
428
|
+
projectRegistry.updateLastSeen(projectId);
|
|
429
|
+
this.logger.debug('MCPServer', `Project registered: ${projectId} at ${rootDir} (v${version})`);
|
|
387
430
|
// Check for existing MCP server process
|
|
388
431
|
const existingPid = this.processManager.readPidFile(projectId);
|
|
389
432
|
if (existingPid) {
|
|
@@ -460,5 +503,18 @@ class MCPServer {
|
|
|
460
503
|
process.exit(0);
|
|
461
504
|
});
|
|
462
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Get current version from package.json
|
|
508
|
+
*/
|
|
509
|
+
getVersion() {
|
|
510
|
+
try {
|
|
511
|
+
const packageJsonPath = path.join(__dirname, '../../package.json');
|
|
512
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
513
|
+
return packageJson.version;
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
return 'unknown';
|
|
517
|
+
}
|
|
518
|
+
}
|
|
463
519
|
}
|
|
464
520
|
exports.MCPServer = MCPServer;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ProjectRegistry = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const logging_1 = require("./logging");
|
|
40
|
+
const config_1 = require("./config");
|
|
41
|
+
/**
|
|
42
|
+
* ProjectRegistry
|
|
43
|
+
*
|
|
44
|
+
* Manages a registry of projects that have claude-recall installed.
|
|
45
|
+
* Stored in ~/.claude-recall/projects.json
|
|
46
|
+
*
|
|
47
|
+
* Features:
|
|
48
|
+
* - Auto-registration on MCP server start
|
|
49
|
+
* - Manual registration via CLI commands
|
|
50
|
+
* - Discovery of registered projects
|
|
51
|
+
* - Cleanup of stale entries
|
|
52
|
+
*/
|
|
53
|
+
class ProjectRegistry {
|
|
54
|
+
constructor() {
|
|
55
|
+
this.logger = logging_1.LoggingService.getInstance();
|
|
56
|
+
const config = config_1.ConfigService.getInstance();
|
|
57
|
+
const baseDir = path.dirname(config.getDatabasePath());
|
|
58
|
+
this.registryPath = path.join(baseDir, 'projects.json');
|
|
59
|
+
}
|
|
60
|
+
static getInstance() {
|
|
61
|
+
if (!ProjectRegistry.instance) {
|
|
62
|
+
ProjectRegistry.instance = new ProjectRegistry();
|
|
63
|
+
}
|
|
64
|
+
return ProjectRegistry.instance;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Read the project registry from disk
|
|
68
|
+
*/
|
|
69
|
+
readRegistry() {
|
|
70
|
+
if (!fs.existsSync(this.registryPath)) {
|
|
71
|
+
return { version: 1, projects: {} };
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const content = fs.readFileSync(this.registryPath, 'utf-8');
|
|
75
|
+
const data = JSON.parse(content);
|
|
76
|
+
// Validate structure
|
|
77
|
+
if (typeof data.version !== 'number' || typeof data.projects !== 'object') {
|
|
78
|
+
this.logger.warn('ProjectRegistry', 'Invalid registry format, resetting');
|
|
79
|
+
return { version: 1, projects: {} };
|
|
80
|
+
}
|
|
81
|
+
return data;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
this.logger.error('ProjectRegistry', `Failed to read registry: ${error}`);
|
|
85
|
+
return { version: 1, projects: {} };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Write the project registry to disk atomically
|
|
90
|
+
*/
|
|
91
|
+
writeRegistry(data) {
|
|
92
|
+
try {
|
|
93
|
+
// Ensure directory exists
|
|
94
|
+
const dir = path.dirname(this.registryPath);
|
|
95
|
+
if (!fs.existsSync(dir)) {
|
|
96
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
// Atomic write: temp file + rename
|
|
99
|
+
const tempPath = `${this.registryPath}.tmp`;
|
|
100
|
+
fs.writeFileSync(tempPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
101
|
+
fs.renameSync(tempPath, this.registryPath);
|
|
102
|
+
this.logger.debug('ProjectRegistry', `Wrote registry with ${Object.keys(data.projects).length} projects`);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
this.logger.error('ProjectRegistry', `Failed to write registry: ${error}`);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Register a project in the registry
|
|
111
|
+
* @param projectId Project identifier (usually directory basename)
|
|
112
|
+
* @param projectPath Absolute path to project directory
|
|
113
|
+
* @param version Claude Recall version
|
|
114
|
+
*/
|
|
115
|
+
register(projectId, projectPath, version) {
|
|
116
|
+
try {
|
|
117
|
+
const registry = this.readRegistry();
|
|
118
|
+
const now = new Date().toISOString();
|
|
119
|
+
// Check if already registered
|
|
120
|
+
const existing = registry.projects[projectId];
|
|
121
|
+
if (existing && existing.path === projectPath) {
|
|
122
|
+
// Already registered, just update version and lastSeen
|
|
123
|
+
registry.projects[projectId].version = version;
|
|
124
|
+
registry.projects[projectId].lastSeen = now;
|
|
125
|
+
this.logger.debug('ProjectRegistry', `Updated existing project: ${projectId}`);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// New registration
|
|
129
|
+
registry.projects[projectId] = {
|
|
130
|
+
path: projectPath,
|
|
131
|
+
registeredAt: now,
|
|
132
|
+
version: version,
|
|
133
|
+
lastSeen: now
|
|
134
|
+
};
|
|
135
|
+
this.logger.info('ProjectRegistry', `Registered new project: ${projectId} at ${projectPath}`);
|
|
136
|
+
}
|
|
137
|
+
this.writeRegistry(registry);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
this.logger.error('ProjectRegistry', `Failed to register project ${projectId}: ${error}`);
|
|
141
|
+
// Don't throw - registration failure shouldn't break MCP server startup
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Unregister a project from the registry
|
|
146
|
+
*/
|
|
147
|
+
unregister(projectId) {
|
|
148
|
+
try {
|
|
149
|
+
const registry = this.readRegistry();
|
|
150
|
+
if (!registry.projects[projectId]) {
|
|
151
|
+
this.logger.warn('ProjectRegistry', `Project not found: ${projectId}`);
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
delete registry.projects[projectId];
|
|
155
|
+
this.writeRegistry(registry);
|
|
156
|
+
this.logger.info('ProjectRegistry', `Unregistered project: ${projectId}`);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
this.logger.error('ProjectRegistry', `Failed to unregister project ${projectId}: ${error}`);
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Update lastSeen timestamp for a project
|
|
166
|
+
*/
|
|
167
|
+
updateLastSeen(projectId) {
|
|
168
|
+
try {
|
|
169
|
+
const registry = this.readRegistry();
|
|
170
|
+
if (!registry.projects[projectId]) {
|
|
171
|
+
this.logger.debug('ProjectRegistry', `Cannot update lastSeen for unknown project: ${projectId}`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
registry.projects[projectId].lastSeen = new Date().toISOString();
|
|
175
|
+
this.writeRegistry(registry);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
this.logger.error('ProjectRegistry', `Failed to update lastSeen for ${projectId}: ${error}`);
|
|
179
|
+
// Don't throw - this is non-critical
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get all registered projects
|
|
184
|
+
*/
|
|
185
|
+
list() {
|
|
186
|
+
const registry = this.readRegistry();
|
|
187
|
+
return Object.entries(registry.projects).map(([projectId, entry]) => ({
|
|
188
|
+
projectId,
|
|
189
|
+
entry
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get a specific project entry
|
|
194
|
+
*/
|
|
195
|
+
get(projectId) {
|
|
196
|
+
const registry = this.readRegistry();
|
|
197
|
+
return registry.projects[projectId] || null;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Clean stale registry entries (not seen in X days)
|
|
201
|
+
* @param daysOld Number of days without activity to consider stale
|
|
202
|
+
* @param dryRun If true, don't actually remove entries
|
|
203
|
+
* @returns Number of entries that would be/were removed
|
|
204
|
+
*/
|
|
205
|
+
clean(daysOld = 30, dryRun = false) {
|
|
206
|
+
try {
|
|
207
|
+
const registry = this.readRegistry();
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
const cutoff = now - (daysOld * 24 * 60 * 60 * 1000);
|
|
210
|
+
let removed = 0;
|
|
211
|
+
for (const [projectId, entry] of Object.entries(registry.projects)) {
|
|
212
|
+
const lastSeenTime = new Date(entry.lastSeen).getTime();
|
|
213
|
+
if (lastSeenTime < cutoff) {
|
|
214
|
+
if (dryRun) {
|
|
215
|
+
this.logger.info('ProjectRegistry', `[DRY RUN] Would remove stale project: ${projectId} (last seen: ${entry.lastSeen})`);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
this.logger.info('ProjectRegistry', `Removing stale project: ${projectId} (last seen: ${entry.lastSeen})`);
|
|
219
|
+
delete registry.projects[projectId];
|
|
220
|
+
}
|
|
221
|
+
removed++;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (!dryRun && removed > 0) {
|
|
225
|
+
this.writeRegistry(registry);
|
|
226
|
+
}
|
|
227
|
+
return removed;
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
this.logger.error('ProjectRegistry', `Failed to clean registry: ${error}`);
|
|
231
|
+
return 0;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get the registry file path (for debugging/inspection)
|
|
236
|
+
*/
|
|
237
|
+
getRegistryPath() {
|
|
238
|
+
return this.registryPath;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
exports.ProjectRegistry = ProjectRegistry;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-recall",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "Persistent memory for Claude Code with automatic capture, failure learning, sophistication tracking, and project
|
|
3
|
+
"version": "0.7.5",
|
|
4
|
+
"description": "Persistent memory for Claude Code with automatic capture, failure learning, sophistication tracking, project scoping, and project registry via MCP server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"claude-recall": "dist/cli/claude-recall-cli.js"
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
".claude/",
|
|
12
12
|
"scripts/postinstall.js",
|
|
13
13
|
"scripts/postinstall-claude-md.js",
|
|
14
|
+
"scripts/uninstall.js",
|
|
14
15
|
"README.md",
|
|
15
16
|
"LICENSE",
|
|
16
17
|
"docs/"
|
|
@@ -89,7 +90,6 @@
|
|
|
89
90
|
"dependencies": {
|
|
90
91
|
"better-sqlite3": "^12.2.0",
|
|
91
92
|
"chalk": "^5.5.0",
|
|
92
|
-
"claude-recall": "^0.7.3",
|
|
93
93
|
"commander": "^12.1.0"
|
|
94
94
|
}
|
|
95
95
|
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -56,6 +56,53 @@ try {
|
|
|
56
56
|
// Don't fail installation if CLAUDE.md update fails
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// Auto-register project if this is a local install
|
|
60
|
+
try {
|
|
61
|
+
// Detect if we're in a project (not global install, not claude-recall itself)
|
|
62
|
+
const cwd = process.cwd();
|
|
63
|
+
const projectName = path.basename(cwd);
|
|
64
|
+
|
|
65
|
+
// Skip registration if:
|
|
66
|
+
// 1. We're inside claude-recall itself
|
|
67
|
+
// 2. We're in node_modules (global install)
|
|
68
|
+
if (projectName !== 'claude-recall' && !cwd.includes('node_modules/.pnpm') && !cwd.includes('node_modules/claude-recall')) {
|
|
69
|
+
const registryPath = path.join(dbDir, 'projects.json');
|
|
70
|
+
|
|
71
|
+
// Read or create registry
|
|
72
|
+
let registry = { version: 1, projects: {} };
|
|
73
|
+
if (fs.existsSync(registryPath)) {
|
|
74
|
+
const registryContent = fs.readFileSync(registryPath, 'utf8');
|
|
75
|
+
registry = JSON.parse(registryContent);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Get version from package.json
|
|
79
|
+
const packageJsonPath = path.join(__dirname, '../package.json');
|
|
80
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
81
|
+
const version = packageJson.version;
|
|
82
|
+
|
|
83
|
+
// Register project
|
|
84
|
+
const now = new Date().toISOString();
|
|
85
|
+
const existing = registry.projects[projectName];
|
|
86
|
+
|
|
87
|
+
registry.projects[projectName] = {
|
|
88
|
+
path: cwd,
|
|
89
|
+
registeredAt: existing ? existing.registeredAt : now,
|
|
90
|
+
version: version,
|
|
91
|
+
lastSeen: now
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Write registry atomically
|
|
95
|
+
const tempPath = registryPath + '.tmp';
|
|
96
|
+
fs.writeFileSync(tempPath, JSON.stringify(registry, null, 2));
|
|
97
|
+
fs.renameSync(tempPath, registryPath);
|
|
98
|
+
|
|
99
|
+
console.log(`š Registered project: ${projectName}`);
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
// Don't fail installation if registration fails
|
|
103
|
+
console.log('ā ļø Failed to register project (non-fatal):', error.message);
|
|
104
|
+
}
|
|
105
|
+
|
|
59
106
|
console.log('\nš Installation complete!');
|
|
60
107
|
console.log(' Claude Recall MCP server is now configured.');
|
|
61
108
|
console.log(' Restart your terminal to activate the memory system.');
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
console.log('\nš Uninstalling Claude Recall...\n');
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const dbDir = path.join(os.homedir(), '.claude-recall');
|
|
11
|
+
const registryPath = path.join(dbDir, 'projects.json');
|
|
12
|
+
const cwd = process.cwd();
|
|
13
|
+
const projectName = path.basename(cwd);
|
|
14
|
+
|
|
15
|
+
// Remove project from registry
|
|
16
|
+
if (fs.existsSync(registryPath)) {
|
|
17
|
+
try {
|
|
18
|
+
const registryContent = fs.readFileSync(registryPath, 'utf8');
|
|
19
|
+
const registry = JSON.parse(registryContent);
|
|
20
|
+
|
|
21
|
+
if (registry.projects && registry.projects[projectName]) {
|
|
22
|
+
delete registry.projects[projectName];
|
|
23
|
+
|
|
24
|
+
// Write registry atomically
|
|
25
|
+
const tempPath = registryPath + '.tmp';
|
|
26
|
+
fs.writeFileSync(tempPath, JSON.stringify(registry, null, 2));
|
|
27
|
+
fs.renameSync(tempPath, registryPath);
|
|
28
|
+
|
|
29
|
+
console.log(`š Removed project from registry: ${projectName}`);
|
|
30
|
+
} else {
|
|
31
|
+
console.log(`ā ļø Project not found in registry: ${projectName}`);
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.log('ā ļø Failed to update registry (non-fatal):', error.message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Stop MCP server if running
|
|
39
|
+
const pidDir = path.join(dbDir, 'pids');
|
|
40
|
+
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
41
|
+
const pidFile = path.join(pidDir, `mcp-${safeName}.pid`);
|
|
42
|
+
|
|
43
|
+
if (fs.existsSync(pidFile)) {
|
|
44
|
+
try {
|
|
45
|
+
const pidContent = fs.readFileSync(pidFile, 'utf8').trim();
|
|
46
|
+
const pid = parseInt(pidContent, 10);
|
|
47
|
+
|
|
48
|
+
if (!isNaN(pid) && pid > 0) {
|
|
49
|
+
try {
|
|
50
|
+
// Check if process exists
|
|
51
|
+
process.kill(pid, 0);
|
|
52
|
+
// If we get here, process exists - kill it
|
|
53
|
+
process.kill(pid, 'SIGTERM');
|
|
54
|
+
console.log(`š Stopped MCP server (PID: ${pid})`);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
// Process doesn't exist or no permission
|
|
57
|
+
if (error.code === 'ESRCH') {
|
|
58
|
+
console.log('ā ļø MCP server was not running');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Remove PID file
|
|
64
|
+
fs.unlinkSync(pidFile);
|
|
65
|
+
console.log('š Removed PID file');
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.log('ā ļø Failed to stop MCP server (non-fatal):', error.message);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log('\nā
Uninstallation cleanup complete!\n');
|
|
72
|
+
|
|
73
|
+
console.log('š Note:');
|
|
74
|
+
console.log(' - Your memory database is still intact at ~/.claude-recall/');
|
|
75
|
+
console.log(' - MCP server configuration in ~/.claude.json is still present');
|
|
76
|
+
console.log(' - Other projects using Claude Recall are unaffected\n');
|
|
77
|
+
|
|
78
|
+
console.log('šļø To completely remove Claude Recall:');
|
|
79
|
+
console.log(' 1. Remove MCP server from ~/.claude.json');
|
|
80
|
+
console.log(' 2. Delete ~/.claude-recall/ directory');
|
|
81
|
+
console.log(' 3. Uninstall globally: npm uninstall -g claude-recall\n');
|
|
82
|
+
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('ā Error during uninstall:', error.message);
|
|
85
|
+
console.log('\nš” You may need to manually clean up:');
|
|
86
|
+
console.log(' - Remove project from ~/.claude-recall/projects.json');
|
|
87
|
+
console.log(' - Stop MCP server: npx claude-recall mcp stop');
|
|
88
|
+
console.log(' - Remove PID file from ~/.claude-recall/pids/\n');
|
|
89
|
+
}
|