Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 861cd20adf | |||
| d275715974 | |||
| fd4684dcb3 | |||
| c54e50ec0c | |||
| c42444e06c | |||
| cb2f2a0432 | |||
| 692bb07dfe | |||
| 58e9dcc0fc | |||
| f60ee3b4f5 | |||
| 7ed166323c | |||
| 651989c423 | |||
| 9dd8b180ac | |||
| 9b1d801a46 | |||
| 6fb9570291 | |||
| ae90b26995 |
@@ -4,18 +4,15 @@
|
||||
"name": "Alex Newman"
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Alex Newman's Claude Code plugins",
|
||||
"version": "1.0.0"
|
||||
"description": "Plugins by Alex Newman (thedotmack)",
|
||||
"homepage": "https://github.com/thedotmack/claude-mem"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "4.1.0",
|
||||
"source": "./plugin",
|
||||
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
|
||||
"version": "4.0.3",
|
||||
"author": {
|
||||
"name": "Alex Newman"
|
||||
}
|
||||
"description": "Persistent memory system for Claude Code - context compression across sessions"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
|
||||
## [4.1.0] - 2025-10-21
|
||||
|
||||
### Changed
|
||||
- **Graceful session cleanup**: Cleanup hook now marks sessions as completed instead of sending DELETE requests to worker
|
||||
- **Natural worker shutdown**: Workers now finish pending operations (like summary generation) before terminating
|
||||
- **Restored MCP search server**: Re-enabled full-text search capabilities from backup
|
||||
|
||||
### Fixed
|
||||
- Session summaries no longer interrupted by aggressive cleanup during session end
|
||||
- Workers can now complete final operations before shutdown
|
||||
|
||||
|
||||
## [4.0.2] - 2025-10-19
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
Claude-Mem seamlessly preserves context across sessions by automatically capturing tool usage observations, generating semantic summaries, and making them available to future sessions. This enables Claude to maintain continuity of knowledge about projects even after sessions end or reconnect.
|
||||
|
||||
[](LICENSE)
|
||||
[](package.json)
|
||||
[](package.json)
|
||||
[](package.json)
|
||||
|
||||
---
|
||||
@@ -184,27 +184,44 @@ SQLite database (`${CLAUDE_PLUGIN_ROOT}/data/claude-mem.db`) with tables:
|
||||
node --version # Should be >= 18.0.0
|
||||
```
|
||||
|
||||
### Method 1: Claude Code Marketplace (Recommended)
|
||||
### Method 1: GitHub Marketplace (Recommended)
|
||||
|
||||
Install directly from Claude Code using the plugin marketplace:
|
||||
Install directly from GitHub:
|
||||
|
||||
```bash
|
||||
# Add the marketplace
|
||||
/plugin marketplace add thedotmack/claude-mem
|
||||
/plugin marketplace add https://github.com/thedotmack/claude-mem
|
||||
|
||||
# Install the plugin
|
||||
/plugin install claude-mem
|
||||
```
|
||||
|
||||
The plugin will:
|
||||
- Automatically install all dependencies (including PM2)
|
||||
- Automatically download prebuilt binaries (no compilation needed)
|
||||
- Install all dependencies (including PM2 and SQLite binaries)
|
||||
- Configure hooks for session lifecycle management
|
||||
- Set up the MCP search server
|
||||
- Auto-start the worker service on first session
|
||||
|
||||
**That's it!** The plugin is ready to use. Start a new Claude Code session and you'll see context from previous sessions automatically loaded.
|
||||
|
||||
### Method 2: Clone and Build (For Development)
|
||||
### Method 2: Local Marketplace Installation
|
||||
|
||||
Install using the local marketplace file (useful for development or testing):
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/thedotmack/claude-mem.git
|
||||
cd claude-mem
|
||||
|
||||
# Add the local marketplace to Claude Code
|
||||
/plugin marketplace add .claude-plugin/marketplace.json
|
||||
|
||||
# Install the plugin
|
||||
/plugin install claude-mem
|
||||
```
|
||||
|
||||
### Method 3: Clone and Build (For Development)
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
@@ -225,7 +242,7 @@ npm run worker:start
|
||||
npm run worker:status
|
||||
```
|
||||
|
||||
### Method 3: NPM Package (Coming Soon)
|
||||
### Method 4: NPM Package (Coming Soon)
|
||||
|
||||
```bash
|
||||
# Install from NPM (when published)
|
||||
|
||||
Generated
+693
-162
File diff suppressed because it is too large
Load Diff
+5
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "4.0.3",
|
||||
"version": "4.1.0",
|
||||
"description": "Memory compression system for Claude Code - persist context across sessions",
|
||||
"keywords": [
|
||||
"claude",
|
||||
@@ -50,12 +50,14 @@
|
||||
"worker:status": "npx pm2 status claude-mem-worker"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.1.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.1.23",
|
||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||
"better-sqlite3": "^11.0.0",
|
||||
"express": "^4.18.2",
|
||||
"glob": "^11.0.3",
|
||||
"handlebars": "^4.7.8",
|
||||
"pm2": "^5.3.0"
|
||||
"pm2": "^5.3.0",
|
||||
"zod-to-json-schema": "^3.24.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.8",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "4.0.2",
|
||||
"version": "4.1.0",
|
||||
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
|
||||
"author": {
|
||||
"name": "Alex Newman"
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js",
|
||||
"timeout": 180000
|
||||
"command": "[ ! -d \"${CLAUDE_PLUGIN_ROOT}/../node_modules\" ] && cd \"${CLAUDE_PLUGIN_ROOT}/..\" && npm install && node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js || node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/new-hook.js",
|
||||
"timeout": 60000
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -30,7 +30,7 @@
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/save-hook.js",
|
||||
"timeout": 180000
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/summary-hook.js",
|
||||
"timeout": 60000
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -52,7 +52,7 @@
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/cleanup-hook.js",
|
||||
"timeout": 60000
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
import F from"better-sqlite3";import{join as a,dirname as U,basename as q}from"path";import{homedir as R}from"os";import{existsSync as z,mkdirSync as w}from"fs";import{fileURLToPath as X}from"url";function M(){return typeof __dirname<"u"?__dirname:U(X(import.meta.url))}var P=M(),d=process.env.CLAUDE_MEM_DATA_DIR||a(R(),".claude-mem"),u=process.env.CLAUDE_CONFIG_DIR||a(R(),".claude"),ee=a(d,"archives"),se=a(d,"logs"),te=a(d,"trash"),re=a(d,"backups"),ne=a(d,"settings.json"),I=a(d,"claude-mem.db"),oe=a(u,"settings.json"),ie=a(u,"commands"),ae=a(u,"CLAUDE.md");function O(o){w(o,{recursive:!0})}function L(){return a(P,"..","..")}var _=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(_||{}),T=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=_[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,t){return`obs-${e}-${t}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
|
||||
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let t=Object.keys(e);return t.length===0?"{}":t.length<=3?JSON.stringify(e):`{${t.length} keys: ${t.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,t){if(!t)return e;try{let s=typeof t=="string"?JSON.parse(t):t;if(e==="Bash"&&s.command){let r=s.command.length>50?s.command.substring(0,50)+"...":s.command;return`${e}(${r})`}if(e==="Read"&&s.file_path){let r=s.file_path.split("/").pop()||s.file_path;return`${e}(${r})`}if(e==="Edit"&&s.file_path){let r=s.file_path.split("/").pop()||s.file_path;return`${e}(${r})`}if(e==="Write"&&s.file_path){let r=s.file_path.split("/").pop()||s.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,t,s,r,n){if(e<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),p=_[e].padEnd(5),c=t.padEnd(6),m="";r?.correlationId?m=`[${r.correlationId}] `:r?.sessionId&&(m=`[session-${r.sessionId}] `);let E="";n!=null&&(this.level===0&&typeof n=="object"?E=`
|
||||
`+JSON.stringify(n,null,2):E=" "+this.formatData(n));let h="";if(r){let{sessionId:$,sdkSessionId:B,correlationId:G,...f}=r;Object.keys(f).length>0&&(h=` {${Object.entries(f).map(([y,x])=>`${y}=${x}`).join(", ")}}`)}let N=`[${i}] [${p}] [${c}] ${m}${s}${h}${E}`;e===3?console.error(N):console.log(N)}debug(e,t,s,r){this.log(0,e,t,s,r)}info(e,t,s,r){this.log(1,e,t,s,r)}warn(e,t,s,r){this.log(2,e,t,s,r)}error(e,t,s,r){this.log(3,e,t,s,r)}dataIn(e,t,s,r){this.info(e,`\u2192 ${t}`,s,r)}dataOut(e,t,s,r){this.info(e,`\u2190 ${t}`,s,r)}success(e,t,s,r){this.info(e,`\u2713 ${t}`,s,r)}failure(e,t,s,r){this.error(e,`\u2717 ${t}`,s,r)}timing(e,t,s,r){this.info(e,`\u23F1 ${t}`,r,{duration:`${s}ms`})}},v=new T;var l=class{db;constructor(){O(d),this.db=new F(I),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(`
|
||||
import F from"better-sqlite3";import{join as i,dirname as U,basename as q}from"path";import{homedir as f}from"os";import{existsSync as z,mkdirSync as X}from"fs";import{fileURLToPath as w}from"url";function M(){return typeof __dirname<"u"?__dirname:U(w(import.meta.url))}var P=M(),d=process.env.CLAUDE_MEM_DATA_DIR||i(f(),".claude-mem"),l=process.env.CLAUDE_CONFIG_DIR||i(f(),".claude"),ee=i(d,"archives"),se=i(d,"logs"),te=i(d,"trash"),re=i(d,"backups"),ne=i(d,"settings.json"),I=i(d,"claude-mem.db"),oe=i(l,"settings.json"),ie=i(l,"commands"),ae=i(l,"CLAUDE.md");function O(o){X(o,{recursive:!0})}function L(){return i(P,"..","..")}var _=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(_||{}),T=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=_[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
|
||||
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let a=new Date().toISOString().replace("T"," ").substring(0,23),p=_[e].padEnd(5),c=s.padEnd(6),m="";r?.correlationId?m=`[${r.correlationId}] `:r?.sessionId&&(m=`[session-${r.sessionId}] `);let u="";n!=null&&(this.level===0&&typeof n=="object"?u=`
|
||||
`+JSON.stringify(n,null,2):u=" "+this.formatData(n));let N="";if(r){let{sessionId:B,sdkSessionId:G,correlationId:$,...R}=r;Object.keys(R).length>0&&(N=` {${Object.entries(R).map(([y,x])=>`${y}=${x}`).join(", ")}}`)}let h=`[${a}] [${p}] [${c}] ${m}${t}${N}${u}`;e===3?console.error(h):console.log(h)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},v=new T;var E=class{db;constructor(){O(d),this.db=new F(I),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS schema_versions (
|
||||
id INTEGER PRIMARY KEY,
|
||||
version INTEGER UNIQUE NOT NULL,
|
||||
applied_at TEXT NOT NULL
|
||||
)
|
||||
`);let e=this.db.prepare("SELECT version FROM schema_versions ORDER BY version").all();(e.length>0?Math.max(...e.map(s=>s.version)):0)===0&&(console.error("[SessionStore] Initializing fresh database with migration004..."),this.db.exec(`
|
||||
`);let e=this.db.prepare("SELECT version FROM schema_versions ORDER BY version").all();(e.length>0?Math.max(...e.map(t=>t.version)):0)===0&&(console.error("[SessionStore] Initializing fresh database with migration004..."),this.db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS sdk_sessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
claude_session_id TEXT UNIQUE NOT NULL,
|
||||
@@ -99,7 +99,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let t=Obje
|
||||
ALTER TABLE observations ADD COLUMN concepts TEXT;
|
||||
ALTER TABLE observations ADD COLUMN files_read TEXT;
|
||||
ALTER TABLE observations ADD COLUMN files_modified TEXT;
|
||||
`),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(8,new Date().toISOString()),console.error("[SessionStore] Successfully added hierarchical fields to observations table")}catch(e){console.error("[SessionStore] Migration error (add hierarchical fields):",e.message)}}makeObservationsTextNullable(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(9))return;let s=this.db.pragma("table_info(observations)").find(r=>r.name==="text");if(!s||s.notnull===0){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(9,new Date().toISOString());return}console.error("[SessionStore] Making observations.text nullable..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
|
||||
`),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(8,new Date().toISOString()),console.error("[SessionStore] Successfully added hierarchical fields to observations table")}catch(e){console.error("[SessionStore] Migration error (add hierarchical fields):",e.message)}}makeObservationsTextNullable(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(9))return;let t=this.db.pragma("table_info(observations)").find(r=>r.name==="text");if(!t||t.notnull===0){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(9,new Date().toISOString());return}console.error("[SessionStore] Making observations.text nullable..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
|
||||
CREATE TABLE observations_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sdk_session_id TEXT NOT NULL,
|
||||
@@ -129,7 +129,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let t=Obje
|
||||
CREATE INDEX idx_observations_project ON observations(project);
|
||||
CREATE INDEX idx_observations_type ON observations(type);
|
||||
CREATE INDEX idx_observations_created ON observations(created_at_epoch DESC);
|
||||
`),this.db.exec("COMMIT"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(9,new Date().toISOString()),console.error("[SessionStore] Successfully made observations.text nullable")}catch(r){throw this.db.exec("ROLLBACK"),r}}catch(e){console.error("[SessionStore] Migration error (make text nullable):",e.message)}}getRecentSummaries(e,t=10){return this.db.prepare(`
|
||||
`),this.db.exec("COMMIT"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(9,new Date().toISOString()),console.error("[SessionStore] Successfully made observations.text nullable")}catch(r){throw this.db.exec("ROLLBACK"),r}}catch(e){console.error("[SessionStore] Migration error (make text nullable):",e.message)}}getRecentSummaries(e,s=10){return this.db.prepare(`
|
||||
SELECT
|
||||
request, investigated, learned, completed, next_steps,
|
||||
files_read, files_edited, notes, prompt_number, created_at
|
||||
@@ -137,13 +137,13 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let t=Obje
|
||||
WHERE project = ?
|
||||
ORDER BY created_at_epoch DESC
|
||||
LIMIT ?
|
||||
`).all(e,t)}getRecentObservations(e,t=20){return this.db.prepare(`
|
||||
`).all(e,s)}getRecentObservations(e,s=20){return this.db.prepare(`
|
||||
SELECT type, text, prompt_number, created_at
|
||||
FROM observations
|
||||
WHERE project = ?
|
||||
ORDER BY created_at_epoch DESC
|
||||
LIMIT ?
|
||||
`).all(e,t)}getRecentSessionsWithStatus(e,t=3){return this.db.prepare(`
|
||||
`).all(e,s)}getRecentSessionsWithStatus(e,s=3){return this.db.prepare(`
|
||||
SELECT * FROM (
|
||||
SELECT
|
||||
s.sdk_session_id,
|
||||
@@ -160,7 +160,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let t=Obje
|
||||
LIMIT ?
|
||||
)
|
||||
ORDER BY started_at_epoch ASC
|
||||
`).all(e,t)}getObservationsForSession(e){return this.db.prepare(`
|
||||
`).all(e,s)}getObservationsForSession(e){return this.db.prepare(`
|
||||
SELECT title, subtitle, type, prompt_number
|
||||
FROM observations
|
||||
WHERE sdk_session_id = ?
|
||||
@@ -188,11 +188,11 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let t=Obje
|
||||
FROM sdk_sessions
|
||||
WHERE claude_session_id = ?
|
||||
LIMIT 1
|
||||
`).get(e)||null}reactivateSession(e,t){this.db.prepare(`
|
||||
`).get(e)||null}reactivateSession(e,s){this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET status = 'active', user_prompt = ?, worker_port = NULL
|
||||
WHERE id = ?
|
||||
`).run(t,e)}incrementPromptCounter(e){return this.db.prepare(`
|
||||
`).run(s,e)}incrementPromptCounter(e){return this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET prompt_counter = COALESCE(prompt_counter, 0) + 1
|
||||
WHERE id = ?
|
||||
@@ -200,44 +200,44 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let t=Obje
|
||||
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
|
||||
`).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(`
|
||||
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
|
||||
`).get(e)?.prompt_counter||0}createSDKSession(e,t,s){let r=new Date,n=r.getTime();return this.db.prepare(`
|
||||
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime();return this.db.prepare(`
|
||||
INSERT INTO sdk_sessions
|
||||
(claude_session_id, project, user_prompt, started_at, started_at_epoch, status)
|
||||
VALUES (?, ?, ?, ?, ?, 'active')
|
||||
`).run(e,t,s,r.toISOString(),n).lastInsertRowid}updateSDKSessionId(e,t){return this.db.prepare(`
|
||||
`).run(e,s,t,r.toISOString(),n).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET sdk_session_id = ?
|
||||
WHERE id = ? AND sdk_session_id IS NULL
|
||||
`).run(t,e).changes===0?(v.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:t}),!1):!0}setWorkerPort(e,t){this.db.prepare(`
|
||||
`).run(s,e).changes===0?(v.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET worker_port = ?
|
||||
WHERE id = ?
|
||||
`).run(t,e)}getWorkerPort(e){return this.db.prepare(`
|
||||
`).run(s,e)}getWorkerPort(e){return this.db.prepare(`
|
||||
SELECT worker_port
|
||||
FROM sdk_sessions
|
||||
WHERE id = ?
|
||||
LIMIT 1
|
||||
`).get(e)?.worker_port||null}storeObservation(e,t,s,r){let n=new Date,i=n.getTime();this.db.prepare(`
|
||||
`).get(e)?.worker_port||null}storeObservation(e,s,t,r){let n=new Date,a=n.getTime();this.db.prepare(`
|
||||
INSERT INTO observations
|
||||
(sdk_session_id, project, type, title, subtitle, facts, narrative, concepts,
|
||||
files_read, files_modified, prompt_number, created_at, created_at_epoch)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(e,t,s.type,s.title,s.subtitle,JSON.stringify(s.facts),s.narrative,JSON.stringify(s.concepts),JSON.stringify(s.files_read),JSON.stringify(s.files_modified),r||null,n.toISOString(),i)}storeSummary(e,t,s,r){let n=new Date,i=n.getTime();this.db.prepare(`
|
||||
`).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,n.toISOString(),a)}storeSummary(e,s,t,r){let n=new Date,a=n.getTime();this.db.prepare(`
|
||||
INSERT INTO session_summaries
|
||||
(sdk_session_id, project, request, investigated, learned, completed,
|
||||
next_steps, notes, prompt_number, created_at, created_at_epoch)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(e,t,s.request,s.investigated,s.learned,s.completed,s.next_steps,s.notes,r||null,n.toISOString(),i)}markSessionCompleted(e){let t=new Date,s=t.getTime();this.db.prepare(`
|
||||
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,n.toISOString(),a)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET status = 'completed', completed_at = ?, completed_at_epoch = ?
|
||||
WHERE id = ?
|
||||
`).run(t.toISOString(),s,e)}markSessionFailed(e){let t=new Date,s=t.getTime();this.db.prepare(`
|
||||
`).run(s.toISOString(),t,e)}markSessionFailed(e){let s=new Date,t=s.getTime();this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
|
||||
WHERE id = ?
|
||||
`).run(t.toISOString(),s,e)}cleanupOrphanedSessions(){let e=new Date,t=e.getTime();return this.db.prepare(`
|
||||
`).run(s.toISOString(),t,e)}cleanupOrphanedSessions(){let e=new Date,s=e.getTime();return this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
|
||||
WHERE status = 'active'
|
||||
`).run(e.toISOString(),t).changes}close(){this.db.close()}};import g from"path";import{existsSync as S}from"fs";import{spawn as H}from"child_process";var W=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),j=`http://127.0.0.1:${W}/health`;async function A(){try{return(await fetch(j,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function k(){try{if(await A())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=L(),e=g.join(o,"plugin","scripts","worker-service.cjs");if(!S(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let t=g.join(o,"ecosystem.config.cjs"),s=g.join(o,"node_modules",".bin","pm2");if(!S(s))throw new Error(`PM2 binary not found at ${s}. This is a bundled dependency - try running: npm install`);if(!S(t))throw new Error(`PM2 ecosystem config not found at ${t}. Plugin installation may be corrupted.`);let r=H(s,["start",t],{detached:!0,stdio:"ignore",cwd:o});r.on("error",n=>{throw new Error(`Failed to spawn PM2: ${n.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let n=0;n<3;n++)if(await new Promise(i=>setTimeout(i,500)),await A())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(o){return console.error(`[claude-mem] Failed to start worker: ${o.message}`),!1}}async function C(o){try{console.error("[claude-mem cleanup] Hook fired",{input:o?{session_id:o.session_id,cwd:o.cwd,reason:o.reason}:null}),o||(console.log("No input provided - this script is designed to run as a Claude Code SessionEnd hook"),console.log(`
|
||||
Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string",transcript_path:"string",hook_event_name:"SessionEnd",reason:"exit"},null,2)),process.exit(0));let{session_id:e,reason:t}=o;console.error("[claude-mem cleanup] Searching for active SDK session",{session_id:e,reason:t});let s=await k();s||console.error("[claude-mem cleanup] Worker not available - skipping HTTP cleanup");let r=new l,n=r.findActiveSDKSession(e);if(n||(console.error("[claude-mem cleanup] No active SDK session found",{session_id:e}),r.close(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)),console.error("[claude-mem cleanup] Active SDK session found",{session_id:n.id,sdk_session_id:n.sdk_session_id,project:n.project,worker_port:n.worker_port}),n.worker_port&&s)try{let i=await fetch(`http://127.0.0.1:${n.worker_port}/sessions/${n.id}`,{method:"DELETE",signal:AbortSignal.timeout(5e3)});i.ok?console.error("[claude-mem cleanup] Session deleted successfully via HTTP"):console.error("[claude-mem cleanup] Failed to delete session:",await i.text())}catch(i){console.error("[claude-mem cleanup] HTTP DELETE error:",i.message)}else console.error("[claude-mem cleanup] No worker available or no worker port, skipping HTTP cleanup");try{r.markSessionFailed(n.id),console.error("[claude-mem cleanup] Session marked as failed in database")}catch(i){console.error("[claude-mem cleanup] Failed to mark session as failed:",i)}r.close(),console.error("[claude-mem cleanup] Cleanup completed successfully"),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}catch(e){console.error("[claude-mem cleanup] Unexpected error in hook",{error:e.message,stack:e.stack,name:e.name}),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}}import{stdin as D}from"process";var b="";D.on("data",o=>b+=o);D.on("end",async()=>{try{let o=b.trim()?JSON.parse(b):void 0;await C(o)}catch(o){console.error(`[claude-mem cleanup-hook error: ${o.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}});
|
||||
`).run(e.toISOString(),s).changes}close(){this.db.close()}};import g from"path";import{existsSync as S}from"fs";import{spawn as H}from"child_process";var W=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),j=`http://127.0.0.1:${W}/health`;async function A(){try{return(await fetch(j,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function k(){try{if(await A())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=L(),e=g.join(o,"plugin","scripts","worker-service.cjs");if(!S(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=g.join(o,"ecosystem.config.cjs"),t=g.join(o,"node_modules",".bin","pm2");if(!S(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!S(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=H(t,["start",s],{detached:!0,stdio:"ignore",cwd:o});r.on("error",n=>{throw new Error(`Failed to spawn PM2: ${n.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let n=0;n<3;n++)if(await new Promise(a=>setTimeout(a,500)),await A())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(o){return console.error(`[claude-mem] Failed to start worker: ${o.message}`),!1}}async function C(o){try{console.error("[claude-mem cleanup] Hook fired",{input:o?{session_id:o.session_id,cwd:o.cwd,reason:o.reason}:null}),o||(console.log("No input provided - this script is designed to run as a Claude Code SessionEnd hook"),console.log(`
|
||||
Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string",transcript_path:"string",hook_event_name:"SessionEnd",reason:"exit"},null,2)),process.exit(0));let{session_id:e,reason:s}=o;console.error("[claude-mem cleanup] Searching for active SDK session",{session_id:e,reason:s}),await k()||console.error("[claude-mem cleanup] Worker not available - skipping HTTP cleanup");let r=new E,n=r.findActiveSDKSession(e);n||(console.error("[claude-mem cleanup] No active SDK session found",{session_id:e}),r.close(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)),console.error("[claude-mem cleanup] Active SDK session found",{session_id:n.id,sdk_session_id:n.sdk_session_id,project:n.project,worker_port:n.worker_port});try{r.markSessionCompleted(n.id),console.error("[claude-mem cleanup] Session marked as completed in database")}catch(a){console.error("[claude-mem cleanup] Failed to mark session as completed:",a)}r.close(),console.error("[claude-mem cleanup] Cleanup completed successfully"),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}catch(e){console.error("[claude-mem cleanup] Unexpected error in hook",{error:e.message,stack:e.stack,name:e.name}),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}}import{stdin as D}from"process";var b="";D.on("data",o=>b+=o);D.on("end",async()=>{try{let o=b.trim()?JSON.parse(b):void 0;await C(o)}catch(o){console.error(`[claude-mem cleanup-hook error: ${o.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}});
|
||||
|
||||
Generated
+463
@@ -0,0 +1,463 @@
|
||||
{
|
||||
"name": "claude-mem-scripts",
|
||||
"version": "4.0.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-mem-scripts",
|
||||
"version": "4.0.5",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/better-sqlite3": {
|
||||
"version": "11.10.0",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
|
||||
"integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"prebuild-install": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-response": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||
"license": "(MIT OR WTFPL)",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.78.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.78.0.tgz",
|
||||
"integrity": "sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^2.0.0",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"bin": {
|
||||
"prebuild-install": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
|
||||
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||
"dependencies": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"rc": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/simple-get": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"decompress-response": "^6.0.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
}
|
||||
}
|
||||
}
|
||||
+302
-35
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -133,6 +133,7 @@ async function buildHooks() {
|
||||
console.log(` - Hooks: *-hook.js`);
|
||||
console.log(` - Worker: worker-service.cjs`);
|
||||
console.log(` - Search: search-server.js`);
|
||||
console.log('\n💡 Note: Dependencies will be auto-installed on first hook execution');
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ Build failed:', error.message);
|
||||
|
||||
+7
-27
@@ -11,12 +11,12 @@ export interface SessionEndInput {
|
||||
|
||||
/**
|
||||
* Cleanup Hook - SessionEnd
|
||||
* Cleans up worker session via HTTP DELETE
|
||||
* Marks session as completed when Claude Code session ends
|
||||
*
|
||||
* This hook runs when a Claude Code session ends. It:
|
||||
* 1. Finds active SDK session for this Claude session
|
||||
* 2. Sends DELETE request to worker service
|
||||
* 3. Marks session as failed if not already completed
|
||||
* 2. Marks session as completed in database
|
||||
* 3. Allows worker to finish pending operations naturally
|
||||
*/
|
||||
export async function cleanupHook(input?: SessionEndInput): Promise<void> {
|
||||
try {
|
||||
@@ -71,32 +71,12 @@ export async function cleanupHook(input?: SessionEndInput): Promise<void> {
|
||||
worker_port: session.worker_port
|
||||
});
|
||||
|
||||
// 1. Delete session via HTTP
|
||||
if (session.worker_port && workerReady) {
|
||||
try {
|
||||
const response = await fetch(`http://127.0.0.1:${session.worker_port}/sessions/${session.id}`, {
|
||||
method: 'DELETE',
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
console.error('[claude-mem cleanup] Session deleted successfully via HTTP');
|
||||
} else {
|
||||
console.error('[claude-mem cleanup] Failed to delete session:', await response.text());
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('[claude-mem cleanup] HTTP DELETE error:', error.message);
|
||||
}
|
||||
} else {
|
||||
console.error('[claude-mem cleanup] No worker available or no worker port, skipping HTTP cleanup');
|
||||
}
|
||||
|
||||
// 2. Mark session as failed in DB (if not already completed)
|
||||
// 1. Mark session as completed in DB (if not already completed)
|
||||
try {
|
||||
db.markSessionFailed(session.id);
|
||||
console.error('[claude-mem cleanup] Session marked as failed in database');
|
||||
db.markSessionCompleted(session.id);
|
||||
console.error('[claude-mem cleanup] Session marked as completed in database');
|
||||
} catch (markErr: any) {
|
||||
console.error('[claude-mem cleanup] Failed to mark session as failed:', markErr);
|
||||
console.error('[claude-mem cleanup] Failed to mark session as completed:', markErr);
|
||||
}
|
||||
|
||||
db.close();
|
||||
|
||||
+533
-299
@@ -1,44 +1,57 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Claude-mem MCP Search Server
|
||||
* Exposes SessionSearch capabilities as MCP tools with search_result formatting
|
||||
*/
|
||||
|
||||
import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import { z } from 'zod';
|
||||
import { zodToJsonSchema } from 'zod-to-json-schema';
|
||||
import { basename } from 'path';
|
||||
import { SessionSearch } from '../services/sqlite/SessionSearch.js';
|
||||
import { SessionStore } from '../services/sqlite/SessionStore.js';
|
||||
import { ObservationSearchResult, SessionSummarySearchResult } from '../services/sqlite/types.js';
|
||||
|
||||
// Initialize search instance
|
||||
let search: SessionSearch;
|
||||
let store: SessionStore;
|
||||
try {
|
||||
search = new SessionSearch();
|
||||
store = new SessionStore();
|
||||
} catch (error: any) {
|
||||
console.error('[search-server] Failed to initialize search:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format observation as search_result with citations
|
||||
* Format observation as text content with metadata
|
||||
*/
|
||||
function formatObservationResult(obs: ObservationSearchResult, index: number) {
|
||||
const source = `claude-mem://observation/${obs.id}`;
|
||||
function formatObservationResult(obs: ObservationSearchResult, index: number): string {
|
||||
const title = obs.title || `Observation #${obs.id}`;
|
||||
|
||||
// Build content from available fields
|
||||
const contentParts: string[] = [];
|
||||
contentParts.push(`## ${title}`);
|
||||
contentParts.push(`*Source: claude-mem://observation/${obs.id}*`);
|
||||
contentParts.push('');
|
||||
|
||||
if (obs.subtitle) {
|
||||
contentParts.push(`**${obs.subtitle}**`);
|
||||
contentParts.push('');
|
||||
}
|
||||
|
||||
if (obs.narrative) {
|
||||
contentParts.push(obs.narrative);
|
||||
contentParts.push('');
|
||||
}
|
||||
|
||||
if (obs.text) {
|
||||
contentParts.push(obs.text);
|
||||
contentParts.push('');
|
||||
}
|
||||
|
||||
// Add metadata
|
||||
@@ -81,51 +94,48 @@ function formatObservationResult(obs: ObservationSearchResult, index: number) {
|
||||
}
|
||||
|
||||
if (metadata.length > 0) {
|
||||
contentParts.push(`\n---\n${metadata.join(' | ')}`);
|
||||
contentParts.push('---');
|
||||
contentParts.push(metadata.join(' | '));
|
||||
}
|
||||
|
||||
const content = contentParts.join('\n\n');
|
||||
|
||||
return {
|
||||
type: 'search_result' as const,
|
||||
source,
|
||||
title,
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: content || 'No content available'
|
||||
}],
|
||||
citations: { enabled: true }
|
||||
};
|
||||
return contentParts.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format session summary as search_result with citations
|
||||
* Format session summary as text content with metadata
|
||||
*/
|
||||
function formatSessionResult(session: SessionSummarySearchResult, index: number) {
|
||||
const source = `claude-mem://session/${session.sdk_session_id}`;
|
||||
function formatSessionResult(session: SessionSummarySearchResult, index: number): string {
|
||||
const title = session.request || `Session ${session.sdk_session_id.substring(0, 8)}`;
|
||||
|
||||
// Build content from available fields
|
||||
const contentParts: string[] = [];
|
||||
contentParts.push(`## ${title}`);
|
||||
contentParts.push(`*Source: claude-mem://session/${session.sdk_session_id}*`);
|
||||
contentParts.push('');
|
||||
|
||||
if (session.completed) {
|
||||
contentParts.push(`**Completed:** ${session.completed}`);
|
||||
contentParts.push('');
|
||||
}
|
||||
|
||||
if (session.learned) {
|
||||
contentParts.push(`**Learned:** ${session.learned}`);
|
||||
contentParts.push('');
|
||||
}
|
||||
|
||||
if (session.investigated) {
|
||||
contentParts.push(`**Investigated:** ${session.investigated}`);
|
||||
contentParts.push('');
|
||||
}
|
||||
|
||||
if (session.next_steps) {
|
||||
contentParts.push(`**Next Steps:** ${session.next_steps}`);
|
||||
contentParts.push('');
|
||||
}
|
||||
|
||||
if (session.notes) {
|
||||
contentParts.push(`**Notes:** ${session.notes}`);
|
||||
contentParts.push('');
|
||||
}
|
||||
|
||||
// Add metadata
|
||||
@@ -152,21 +162,11 @@ function formatSessionResult(session: SessionSummarySearchResult, index: number)
|
||||
metadata.push(`Date: ${date}`);
|
||||
|
||||
if (metadata.length > 0) {
|
||||
contentParts.push(`\n---\n${metadata.join(' | ')}`);
|
||||
contentParts.push('---');
|
||||
contentParts.push(metadata.join(' | '));
|
||||
}
|
||||
|
||||
const content = contentParts.join('\n\n');
|
||||
|
||||
return {
|
||||
type: 'search_result' as const,
|
||||
source,
|
||||
title,
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: content || 'No content available'
|
||||
}],
|
||||
citations: { enabled: true }
|
||||
};
|
||||
return contentParts.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,277 +189,511 @@ const filterSchema = z.object({
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('relevance').describe('Sort order')
|
||||
});
|
||||
|
||||
// Define tool schemas
|
||||
const tools = [
|
||||
{
|
||||
name: 'search_observations',
|
||||
description: 'Search observations using full-text search across titles, narratives, facts, and concepts',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('Search query for FTS5 full-text search'),
|
||||
...filterSchema.shape
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
try {
|
||||
const { query, ...options } = args;
|
||||
const results = search.searchObservations(query, options);
|
||||
|
||||
if (results.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `No observations found matching "${query}"`
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// Combine all results into a single text response
|
||||
const formattedResults = results.map((obs, i) => formatObservationResult(obs, i));
|
||||
const combinedText = formattedResults.join('\n\n---\n\n');
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: combinedText
|
||||
}]
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Search failed: ${error.message}`
|
||||
}],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'search_sessions',
|
||||
description: 'Search session summaries using full-text search across requests, completions, learnings, and notes',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('Search query for FTS5 full-text search'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateRange: z.object({
|
||||
start: z.union([z.string(), z.number()]).optional(),
|
||||
end: z.union([z.string(), z.number()]).optional()
|
||||
}).optional().describe('Filter by date range'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('relevance').describe('Sort order')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
try {
|
||||
const { query, ...options } = args;
|
||||
const results = search.searchSessions(query, options);
|
||||
|
||||
if (results.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `No sessions found matching "${query}"`
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// Combine all results into a single text response
|
||||
const formattedResults = results.map((session, i) => formatSessionResult(session, i));
|
||||
const combinedText = formattedResults.join('\n\n---\n\n');
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: combinedText
|
||||
}]
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Search failed: ${error.message}`
|
||||
}],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'find_by_concept',
|
||||
description: 'Find observations tagged with a specific concept',
|
||||
inputSchema: z.object({
|
||||
concept: z.string().describe('Concept tag to search for'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateRange: z.object({
|
||||
start: z.union([z.string(), z.number()]).optional(),
|
||||
end: z.union([z.string(), z.number()]).optional()
|
||||
}).optional().describe('Filter by date range'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
try {
|
||||
const { concept, ...filters } = args;
|
||||
const results = search.findByConcept(concept, filters);
|
||||
|
||||
if (results.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `No observations found with concept "${concept}"`
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// Combine all results into a single text response
|
||||
const formattedResults = results.map((obs, i) => formatObservationResult(obs, i));
|
||||
const combinedText = formattedResults.join('\n\n---\n\n');
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: combinedText
|
||||
}]
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Search failed: ${error.message}`
|
||||
}],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'find_by_file',
|
||||
description: 'Find observations and sessions that reference a specific file path',
|
||||
inputSchema: z.object({
|
||||
filePath: z.string().describe('File path to search for (supports partial matching)'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateRange: z.object({
|
||||
start: z.union([z.string(), z.number()]).optional(),
|
||||
end: z.union([z.string(), z.number()]).optional()
|
||||
}).optional().describe('Filter by date range'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
try {
|
||||
const { filePath, ...filters } = args;
|
||||
const results = search.findByFile(filePath, filters);
|
||||
|
||||
const totalResults = results.observations.length + results.sessions.length;
|
||||
|
||||
if (totalResults === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `No results found for file "${filePath}"`
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
const formattedResults: string[] = [];
|
||||
|
||||
// Add observations
|
||||
results.observations.forEach((obs, i) => {
|
||||
formattedResults.push(formatObservationResult(obs, i));
|
||||
});
|
||||
|
||||
// Add sessions
|
||||
results.sessions.forEach((session, i) => {
|
||||
formattedResults.push(formatSessionResult(session, i + results.observations.length));
|
||||
});
|
||||
|
||||
const combinedText = formattedResults.join('\n\n---\n\n');
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: combinedText
|
||||
}]
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Search failed: ${error.message}`
|
||||
}],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'find_by_type',
|
||||
description: 'Find observations of a specific type (decision, bugfix, feature, refactor, discovery, change)',
|
||||
inputSchema: z.object({
|
||||
type: z.union([
|
||||
z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']),
|
||||
z.array(z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']))
|
||||
]).describe('Observation type(s) to filter by'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateRange: z.object({
|
||||
start: z.union([z.string(), z.number()]).optional(),
|
||||
end: z.union([z.string(), z.number()]).optional()
|
||||
}).optional().describe('Filter by date range'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
try {
|
||||
const { type, ...filters } = args;
|
||||
const results = search.findByType(type, filters);
|
||||
|
||||
if (results.length === 0) {
|
||||
const typeStr = Array.isArray(type) ? type.join(', ') : type;
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `No observations found with type "${typeStr}"`
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// Combine all results into a single text response
|
||||
const formattedResults = results.map((obs, i) => formatObservationResult(obs, i));
|
||||
const combinedText = formattedResults.join('\n\n---\n\n');
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: combinedText
|
||||
}]
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Search failed: ${error.message}`
|
||||
}],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_recent_context',
|
||||
description: 'Get recent session context including summaries and observations for a project',
|
||||
inputSchema: z.object({
|
||||
project: z.string().optional().describe('Project name (defaults to current working directory basename)'),
|
||||
limit: z.number().min(1).max(10).default(3).describe('Number of recent sessions to retrieve')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
try {
|
||||
const project = args.project || basename(process.cwd());
|
||||
const limit = args.limit || 3;
|
||||
|
||||
const sessions = store.getRecentSessionsWithStatus(project, limit);
|
||||
|
||||
if (sessions.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `# Recent Session Context\n\nNo previous sessions found for project "${project}".`
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
lines.push('# Recent Session Context');
|
||||
lines.push('');
|
||||
lines.push(`Showing last ${sessions.length} session(s) for **${project}**:`);
|
||||
lines.push('');
|
||||
|
||||
for (const session of sessions) {
|
||||
if (!session.sdk_session_id) continue;
|
||||
|
||||
lines.push('---');
|
||||
lines.push('');
|
||||
|
||||
if (session.has_summary) {
|
||||
const summary = store.getSummaryForSession(session.sdk_session_id);
|
||||
if (summary) {
|
||||
const promptLabel = summary.prompt_number ? ` (Prompt #${summary.prompt_number})` : '';
|
||||
lines.push(`**Summary${promptLabel}**`);
|
||||
lines.push('');
|
||||
|
||||
if (summary.request) lines.push(`**Request:** ${summary.request}`);
|
||||
if (summary.completed) lines.push(`**Completed:** ${summary.completed}`);
|
||||
if (summary.learned) lines.push(`**Learned:** ${summary.learned}`);
|
||||
if (summary.next_steps) lines.push(`**Next Steps:** ${summary.next_steps}`);
|
||||
|
||||
// Handle files_read
|
||||
if (summary.files_read) {
|
||||
try {
|
||||
const filesRead = JSON.parse(summary.files_read);
|
||||
if (Array.isArray(filesRead) && filesRead.length > 0) {
|
||||
lines.push(`**Files Read:** ${filesRead.join(', ')}`);
|
||||
}
|
||||
} catch {
|
||||
if (summary.files_read.trim()) {
|
||||
lines.push(`**Files Read:** ${summary.files_read}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle files_edited
|
||||
if (summary.files_edited) {
|
||||
try {
|
||||
const filesEdited = JSON.parse(summary.files_edited);
|
||||
if (Array.isArray(filesEdited) && filesEdited.length > 0) {
|
||||
lines.push(`**Files Edited:** ${filesEdited.join(', ')}`);
|
||||
}
|
||||
} catch {
|
||||
if (summary.files_edited.trim()) {
|
||||
lines.push(`**Files Edited:** ${summary.files_edited}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const date = new Date(summary.created_at).toLocaleString();
|
||||
lines.push(`**Date:** ${date}`);
|
||||
}
|
||||
} else if (session.status === 'active') {
|
||||
lines.push('**In Progress**');
|
||||
lines.push('');
|
||||
|
||||
if (session.user_prompt) {
|
||||
lines.push(`**Request:** ${session.user_prompt}`);
|
||||
}
|
||||
|
||||
const observations = store.getObservationsForSession(session.sdk_session_id);
|
||||
if (observations.length > 0) {
|
||||
lines.push('');
|
||||
lines.push(`**Observations (${observations.length}):**`);
|
||||
for (const obs of observations) {
|
||||
lines.push(`- ${obs.title}`);
|
||||
}
|
||||
} else {
|
||||
lines.push('');
|
||||
lines.push('*No observations yet*');
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
lines.push('**Status:** Active - summary pending');
|
||||
|
||||
const date = new Date(session.started_at).toLocaleString();
|
||||
lines.push(`**Date:** ${date}`);
|
||||
} else {
|
||||
lines.push(`**${session.status.charAt(0).toUpperCase() + session.status.slice(1)}**`);
|
||||
lines.push('');
|
||||
|
||||
if (session.user_prompt) {
|
||||
lines.push(`**Request:** ${session.user_prompt}`);
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
lines.push(`**Status:** ${session.status} - no summary available`);
|
||||
|
||||
const date = new Date(session.started_at).toLocaleString();
|
||||
lines.push(`**Date:** ${date}`);
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: lines.join('\n')
|
||||
}]
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Failed to get recent context: ${error.message}`
|
||||
}],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'advanced_search',
|
||||
description: 'Advanced search combining full-text search with structured filters across both observations and sessions',
|
||||
inputSchema: z.object({
|
||||
textQuery: z.string().optional().describe('Optional text query for FTS5 search'),
|
||||
searchSessions: z.boolean().default(true).describe('Include session summaries in results'),
|
||||
...filterSchema.shape
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
try {
|
||||
const results = search.advancedSearch(args);
|
||||
|
||||
const totalResults = results.observations.length + results.sessions.length;
|
||||
|
||||
if (totalResults === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: 'No results found matching the search criteria'
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
const formattedResults: string[] = [];
|
||||
|
||||
// Add observations
|
||||
results.observations.forEach((obs, i) => {
|
||||
formattedResults.push(formatObservationResult(obs, i));
|
||||
});
|
||||
|
||||
// Add sessions
|
||||
results.sessions.forEach((session, i) => {
|
||||
formattedResults.push(formatSessionResult(session, i + results.observations.length));
|
||||
});
|
||||
|
||||
const combinedText = formattedResults.join('\n\n---\n\n');
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: combinedText
|
||||
}]
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Search failed: ${error.message}`
|
||||
}],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Create and start the MCP server
|
||||
*/
|
||||
const server = createSdkMcpServer({
|
||||
name: 'claude-mem-search',
|
||||
version: '1.0.0',
|
||||
tools: [
|
||||
// Tool 1: Search observations
|
||||
tool(
|
||||
'search_observations',
|
||||
'Search observations using full-text search across titles, narratives, facts, and concepts',
|
||||
{
|
||||
query: z.string().describe('Search query for FTS5 full-text search'),
|
||||
...filterSchema.shape
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const { query, ...options } = args;
|
||||
const results = search.searchObservations(query, options);
|
||||
const server = new Server(
|
||||
{
|
||||
name: 'claude-mem-search',
|
||||
version: '1.0.0',
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (results.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `No observations found matching "${query}"`
|
||||
}]
|
||||
};
|
||||
}
|
||||
// Register tools/list handler
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: tools.map(tool => ({
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
inputSchema: zodToJsonSchema(tool.inputSchema) as any
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
content: results.map((obs, i) => formatObservationResult(obs, i))
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Search failed: ${error.message}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
),
|
||||
// Register tools/call handler
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const tool = tools.find(t => t.name === request.params.name);
|
||||
|
||||
// Tool 2: Search sessions
|
||||
tool(
|
||||
'search_sessions',
|
||||
'Search session summaries using full-text search across requests, completions, learnings, and notes',
|
||||
{
|
||||
query: z.string().describe('Search query for FTS5 full-text search'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateRange: z.object({
|
||||
start: z.union([z.string(), z.number()]).optional(),
|
||||
end: z.union([z.string(), z.number()]).optional()
|
||||
}).optional().describe('Filter by date range'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('relevance').describe('Sort order')
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const { query, ...options } = args;
|
||||
const results = search.searchSessions(query, options);
|
||||
if (!tool) {
|
||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||
}
|
||||
|
||||
if (results.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `No sessions found matching "${query}"`
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: results.map((session, i) => formatSessionResult(session, i))
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Search failed: ${error.message}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
// Tool 3: Find by concept
|
||||
tool(
|
||||
'find_by_concept',
|
||||
'Find observations tagged with a specific concept',
|
||||
{
|
||||
concept: z.string().describe('Concept tag to search for'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateRange: z.object({
|
||||
start: z.union([z.string(), z.number()]).optional(),
|
||||
end: z.union([z.string(), z.number()]).optional()
|
||||
}).optional().describe('Filter by date range')
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const { concept, ...filters } = args;
|
||||
const results = search.findByConcept(concept, filters);
|
||||
|
||||
if (results.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `No observations found with concept "${concept}"`
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: results.map((obs, i) => formatObservationResult(obs, i))
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Search failed: ${error.message}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
// Tool 4: Find by file
|
||||
tool(
|
||||
'find_by_file',
|
||||
'Find observations and sessions that reference a specific file path',
|
||||
{
|
||||
filePath: z.string().describe('File path to search for (supports partial matching)'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateRange: z.object({
|
||||
start: z.union([z.string(), z.number()]).optional(),
|
||||
end: z.union([z.string(), z.number()]).optional()
|
||||
}).optional().describe('Filter by date range')
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const { filePath, ...filters } = args;
|
||||
const results = search.findByFile(filePath, filters);
|
||||
|
||||
const totalResults = results.observations.length + results.sessions.length;
|
||||
|
||||
if (totalResults === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `No results found for file "${filePath}"`
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
const content: any[] = [];
|
||||
|
||||
// Add observations
|
||||
results.observations.forEach((obs, i) => {
|
||||
content.push(formatObservationResult(obs, i));
|
||||
});
|
||||
|
||||
// Add sessions
|
||||
results.sessions.forEach((session, i) => {
|
||||
content.push(formatSessionResult(session, i + results.observations.length));
|
||||
});
|
||||
|
||||
return { content };
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Search failed: ${error.message}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
// Tool 5: Find by type
|
||||
tool(
|
||||
'find_by_type',
|
||||
'Find observations of a specific type (decision, bugfix, feature, refactor, discovery, change)',
|
||||
{
|
||||
type: z.union([
|
||||
z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']),
|
||||
z.array(z.enum(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']))
|
||||
]).describe('Observation type(s) to filter by'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateRange: z.object({
|
||||
start: z.union([z.string(), z.number()]).optional(),
|
||||
end: z.union([z.string(), z.number()]).optional()
|
||||
}).optional().describe('Filter by date range')
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const { type, ...filters } = args;
|
||||
const results = search.findByType(type, filters);
|
||||
|
||||
if (results.length === 0) {
|
||||
const typeStr = Array.isArray(type) ? type.join(', ') : type;
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `No observations found with type "${typeStr}"`
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: results.map((obs, i) => formatObservationResult(obs, i))
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Search failed: ${error.message}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
// Tool 6: Advanced search
|
||||
tool(
|
||||
'advanced_search',
|
||||
'Advanced search combining full-text search with structured filters across both observations and sessions',
|
||||
{
|
||||
textQuery: z.string().optional().describe('Optional text query for FTS5 search'),
|
||||
searchSessions: z.boolean().default(true).describe('Include session summaries in results'),
|
||||
...filterSchema.shape
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const results = search.advancedSearch(args);
|
||||
|
||||
const totalResults = results.observations.length + results.sessions.length;
|
||||
|
||||
if (totalResults === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: 'No results found matching the search criteria'
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
const content: any[] = [];
|
||||
|
||||
// Add observations
|
||||
results.observations.forEach((obs, i) => {
|
||||
content.push(formatObservationResult(obs, i));
|
||||
});
|
||||
|
||||
// Add sessions
|
||||
results.sessions.forEach((session, i) => {
|
||||
content.push(formatSessionResult(session, i + results.observations.length));
|
||||
});
|
||||
|
||||
return { content };
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Search failed: ${error.message}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
try {
|
||||
return await tool.handler(request.params.arguments || {});
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Tool execution failed: ${error.message}`
|
||||
}],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Start the server
|
||||
console.error('[search-server] Starting claude-mem search server...');
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error('[search-server] Claude-mem search server started');
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('[search-server] Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { logger } from '../../utils/logger.js';
|
||||
* Provides simple, synchronous CRUD operations for session-based memory
|
||||
*/
|
||||
export class SessionStore {
|
||||
private db: Database;
|
||||
private db: Database.Database;
|
||||
|
||||
constructor() {
|
||||
ensureDir(DATA_DIR);
|
||||
|
||||
Reference in New Issue
Block a user