Compare commits

...

19 Commits

Author SHA1 Message Date
Alex Newman 861cd20adf chore: Update version to 4.1.0 in plugin.json 2025-10-21 01:08:10 -04:00
Alex Newman d275715974 Release v4.1.0 - Graceful session cleanup and MCP search server restoration
### 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

### Dependencies
- Updated @anthropic-ai/claude-agent-sdk to ^0.1.23
- Added @modelcontextprotocol/sdk ^1.20.1
- Added zod-to-json-schema ^3.24.6

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 01:07:56 -04:00
Alex Newman fd4684dcb3 Refactor search server to use Model Context Protocol SDK; update tool handling and response formatting
- Replaced `createSdkMcpServer` with `Server` from `@modelcontextprotocol/sdk/server/index.js`.
- Updated tool definitions to use structured input schemas with Zod.
- Enhanced response formatting for search results, combining multiple results into a single text response.
- Added new tools for advanced search and recent session context retrieval.
- Improved error handling and logging throughout the server.
2025-10-21 01:03:35 -04:00
Alex Newman c54e50ec0c refactor: Update cleanupHook documentation to reflect session completion logic 2025-10-21 01:01:11 -04:00
Alex Newman c42444e06c Refactor cleanupHook to remove HTTP session deletion and mark session as completed in the database 2025-10-21 00:59:06 -04:00
Alex Newman cb2f2a0432 fix: Update command in SessionStart hook for correct node_modules path and remove package.json 2025-10-20 18:18:07 -04:00
Alex Newman 692bb07dfe fix: Add pm2 dependency to package.json for improved process management 2025-10-20 18:07:46 -04:00
Alex Newman 58e9dcc0fc fix: Update hook commands and reduce timeout values for improved performance 2025-10-20 17:59:43 -04:00
Alex Newman f60ee3b4f5 fix: Update version to 4.0.6 and remove redundant npm install commands from hooks 2025-10-20 17:57:23 -04:00
Alex Newman 7ed166323c fix: Update UserPromptSubmit hook to install better-sqlite3 with a message 2025-10-20 17:55:39 -04:00
Alex Newman 651989c423 refactor: Replace TypeScript bootstrap with bash dependency checks
Simplified dependency installation by moving from TypeScript runtime bootstrap to bash-based checks in plugin manifest. This reduces complexity and code size while maintaining the same functionality.

Changes:
- Added bash conditional dependency checks to all 5 hooks in hooks.json
- Check runs before each hook: [ ! -d "${CLAUDE_PLUGIN_ROOT}/scripts/node_modules" ] && cd "${CLAUDE_PLUGIN_ROOT}/scripts" && npm install || true
- Reverted all hook TypeScript files to use simple static imports (removed dynamic imports)
- Removed src/shared/bootstrap.ts (44 lines)
- Removed ensureDependencies() calls from all hook entry points

Benefits:
- Simpler architecture using native bash instead of TypeScript
- Net reduction of 157 lines of code
- No runtime overhead when dependencies already installed
- Uses plugin manifest's command hook feature as intended
- Faster and more efficient

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 17:01:24 -04:00
Alex Newman 9dd8b180ac fix: Use dynamic imports in hooks to fix better-sqlite3 loading
Changed all hook entry points to use dynamic imports after bootstrap runs. This ensures that better-sqlite3 is installed before Node.js attempts to resolve the import.

Changes:
- Modified src/bin/hooks/*.ts to call ensureDependencies() before dynamic import
- Moved from static `import { hook } from '...'` to `const { hook } = await import('...')`
- This delays module resolution until after npm install completes
- Bumped version to 4.0.6

The previous approach failed because static imports are resolved at module link time, before any runtime code (including ensureDependencies) executes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 16:54:28 -04:00
Alex Newman 9b1d801a46 fix: Restore correct marketplace.json source path
The source field must be a relative path starting with ./ not a GitHub URL.
GitHub marketplace installation uses shorthand format:
  /plugin marketplace add thedotmack/claude-mem

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 16:46:28 -04:00
Alex Newman 6fb9570291 feat: Release v4.0.5 - Auto-install dependencies via bootstrap hooks
Fixed better-sqlite3 distribution by implementing self-bootstrapping hooks
that auto-install dependencies on first run. This eliminates the need for
users to have native compilation tools or manually install dependencies.

## Solution

Instead of bundling 25MB of better-sqlite3 binaries in git or requiring
manual npm install, hooks now bootstrap themselves on first execution:

1. Created `src/shared/bootstrap.ts` with `ensureDependencies()` function
2. Added bootstrap calls to all hook entry points (context, new, save, summary, cleanup)
3. Created `plugin/scripts/package.json` declaring better-sqlite3 dependency
4. Bootstrap checks if `node_modules` exists, runs `npm install` if missing
5. npm automatically downloads prebuilt better-sqlite3 binary for user's platform

## Changes

**Core Bootstrap System:**
- Added src/shared/bootstrap.ts: Auto-install dependencies using npm
- Modified all hooks (context, new, save, summary, cleanup) to call ensureDependencies()
- Created plugin/scripts/package.json with better-sqlite3 dependency

**Build & Distribution:**
- Removed node_modules copying logic from build script
- Build output is now compact (hooks + package.json, no binaries)
- Updated marketplace.json to point to GitHub for direct installation

**Documentation:**
- Updated README: GitHub Marketplace installation is now recommended method
- Installation instructions emphasize no compilation needed
- Version bumped to 4.0.5 throughout

## Benefits

-  No git bloat (repo stays small, no 25MB binaries committed)
-  No compilation needed (npm downloads prebuilt binaries)
-  Works on all platforms (npm handles platform-specific binaries)
-  Zero manual steps (hooks bootstrap themselves automatically)
-  Idempotent (skips install if dependencies already exist)

Installation now works via simple:
```
/plugin marketplace add https://github.com/thedotmack/claude-mem
/plugin install claude-mem
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 16:44:26 -04:00
Alex Newman ae90b26995 chore: Release v4.0.4 - Revert to local marketplace installation
Reverted from GitHub-hosted marketplace to local marketplace file installation method. This allows us to resolve issues with better-sqlite3 native module builds before enabling GitHub marketplace distribution.

Changes:
- Simplified .claude-plugin/marketplace.json (removed metadata, version fields)
- Updated README installation instructions to use local .claude-plugin/marketplace.json
- Bumped version to 4.0.4

Installation now requires cloning the repo and using:
/plugin marketplace add .claude-plugin/marketplace.json

Will restore GitHub marketplace method once native module issues are resolved.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 02:13:00 -04:00
Alex Newman 3b0cd0b3f6 chore: Release v4.0.3
Marketplace release for Claude Code plugin
https://github.com/thedotmack/claude-mem
2025-10-19 02:01:58 -04:00
Alex Newman f849a69506 fix: Move ensureWorkerRunning to start of hooks to prevent race condition
All hooks now call ensureWorkerRunning() BEFORE querying the database. This
ensures the worker's orphaned session cleanup runs before hooks check for
active sessions, preventing 404 errors when hooks try to use sessions that
don't exist in worker memory after a restart.

Hook order now:
1. ensureWorkerRunning() - starts worker, runs cleanup
2. Query DB - cleanup already marked orphaned sessions as failed
3. Use session - only valid sessions are processed

Fixed in:
- new-hook: Line 26, before DB queries
- save-hook: Line 37, before DB queries
- summary-hook: Line 24, before DB queries
- cleanup-hook: Line 50, before DB queries

This prevents the race condition where hooks would read session status before
cleanup ran, then get 404 from worker after cleanup marked sessions failed.
2025-10-19 02:01:11 -04:00
Alex Newman daf368e343 Refactor code structure for improved readability and maintainability 2025-10-19 01:45:51 -04:00
Alex Newman cbd43240c7 docs: Add marketplace installation instructions to README
Updates Installation section with:
- Method 1: Claude Code Marketplace (recommended)
  - Simple two-command installation
  - Automatic dependency and hook setup
  - Clear explanation of what gets installed
- Renumbered existing methods (Clone=2, NPM=3)

Users can now easily install via:
  /plugin marketplace add thedotmack/claude-mem
  /plugin install claude-mem

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 01:26:44 -04:00
25 changed files with 2187 additions and 646 deletions
+4 -7
View File
@@ -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.2",
"author": {
"name": "Alex Newman"
}
"description": "Persistent memory system for Claude Code - context compression across sessions"
}
]
}
+12
View File
@@ -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
+42 -7
View File
@@ -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: AGPL-3.0](https://img.shields.io/badge/License-AGPL%203.0-blue.svg)](LICENSE)
[![Version](https://img.shields.io/badge/version-4.0.0-green.svg)](package.json)
[![Version](https://img.shields.io/badge/version-4.0.5-green.svg)](package.json)
[![Node](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)](package.json)
---
@@ -184,7 +184,44 @@ SQLite database (`${CLAUDE_PLUGIN_ROOT}/data/claude-mem.db`) with tables:
node --version # Should be >= 18.0.0
```
### Method 1: Clone and Build (Recommended for Development)
### Method 1: GitHub Marketplace (Recommended)
Install directly from GitHub:
```bash
# Add the marketplace
/plugin marketplace add https://github.com/thedotmack/claude-mem
# Install the plugin
/plugin install claude-mem
```
The plugin will:
- 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: 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
@@ -205,7 +242,7 @@ npm run worker:start
npm run worker:status
```
### Method 2: NPM Package (Coming Soon)
### Method 4: NPM Package (Coming Soon)
```bash
# Install from NPM (when published)
@@ -432,11 +469,9 @@ claude-mem/
│ ├── save-hook.js
│ ├── summary-hook.js
│ ├── cleanup-hook.js
│ ├── worker-service.cjs # Background worker
│ └── search-server.js # MCP search server
├── dist/ # Built output
│ └── worker-service.cjs
├── tests/ # Test suite
├── context/ # Architecture docs
└── ecosystem.config.cjs # PM2 configuration
@@ -569,7 +604,7 @@ The build process:
1. Compiles TypeScript to JavaScript using esbuild
2. Creates standalone executables for each hook in `plugin/scripts/`
3. Bundles MCP search server to `plugin/scripts/search-server.js`
4. Bundles worker service to `dist/worker-service.cjs`
4. Bundles worker service to `plugin/scripts/worker-service.cjs`
### Running Tests
+1 -1
View File
@@ -18,7 +18,7 @@ const logDir = path.join(os.homedir(), '.claude-mem', 'logs');
module.exports = {
apps: [{
name: 'claude-mem-worker',
script: './dist/worker-service.cjs',
script: './plugin/scripts/worker-service.cjs',
interpreter: 'node',
instances: 1,
exec_mode: 'fork',
+693 -162
View File
File diff suppressed because it is too large Load Diff
+6 -5
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem",
"version": "4.0.2",
"version": "4.1.0",
"description": "Memory compression system for Claude Code - persist context across sessions",
"keywords": [
"claude",
@@ -36,7 +36,7 @@
"scripts": {
"build": "node scripts/build-hooks.js",
"build:hooks": "node scripts/build-hooks.js",
"publish:npm": "node scripts/publish.js",
"release": "node scripts/publish.js",
"prepublishOnly": "npm run build",
"test": "node --test tests/",
"test:context": "echo '{\"session_id\":\"test-'$(date +%s)'\",\"cwd\":\"'$(pwd)'\",\"source\":\"startup\"}' | node plugin/scripts/context-hook.js 2>/dev/null",
@@ -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",
@@ -66,7 +68,6 @@
},
"files": [
"plugin",
"dist",
"src",
"scripts",
"docs",
+1 -1
View File
@@ -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 -6
View File
@@ -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
}
]
}
+22 -22
View File
@@ -1,13 +1,13 @@
#!/usr/bin/env node
import x from"better-sqlite3";import{join as i,dirname as C,basename as P}from"path";import{homedir as h}from"os";import{existsSync as j,mkdirSync as D}from"fs";import{fileURLToPath as k}from"url";function y(){return typeof __dirname<"u"?__dirname:C(k(import.meta.url))}var W=y(),a=process.env.CLAUDE_MEM_DATA_DIR||i(h(),".claude-mem"),m=process.env.CLAUDE_CONFIG_DIR||i(h(),".claude"),K=i(a,"archives"),Y=i(a,"logs"),q=i(a,"trash"),V=i(a,"backups"),J=i(a,"settings.json"),I=i(a,"claude-mem.db"),Q=i(m,"settings.json"),z=i(m,"commands"),Z=i(m,"CLAUDE.md");function R(o){D(o,{recursive:!0})}var u=(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))(u||{}),T=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=u[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 d=new Date().toISOString().replace("T"," ").substring(0,23),p=u[e].padEnd(5),c=t.padEnd(6),_="";r?.correlationId?_=`[${r.correlationId}] `:r?.sessionId&&(_=`[session-${r.sessionId}] `);let l="";n!=null&&(this.level===0&&typeof n=="object"?l=`
`+JSON.stringify(n,null,2):l=" "+this.formatData(n));let S="";if(r){let{sessionId:U,sdkSessionId:X,correlationId:w,...b}=r;Object.keys(b).length>0&&(S=` {${Object.entries(b).map(([v,A])=>`${v}=${A}`).join(", ")}}`)}let N=`[${d}] [${p}] [${c}] ${_}${s}${S}${l}`;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`})}},O=new T;var E=class{db;constructor(){R(a),this.db=new x(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?(O.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,d=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(),d)}storeSummary(e,t,s,r){let n=new Date,d=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(),d)}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()}};async function f(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=new E,r=s.findActiveSDKSession(e);if(r||(console.error("[claude-mem cleanup] No active SDK session found",{session_id:e}),s.close(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)),console.error("[claude-mem cleanup] Active SDK session found",{session_id:r.id,sdk_session_id:r.sdk_session_id,project:r.project,worker_port:r.worker_port}),r.worker_port)try{let n=await fetch(`http://127.0.0.1:${r.worker_port}/sessions/${r.id}`,{method:"DELETE",signal:AbortSignal.timeout(5e3)});n.ok?console.error("[claude-mem cleanup] Session deleted successfully via HTTP"):console.error("[claude-mem cleanup] Failed to delete session:",await n.text())}catch(n){console.error("[claude-mem cleanup] HTTP DELETE error:",n.message)}else console.error("[claude-mem cleanup] No worker port, cannot send DELETE request");try{s.markSessionFailed(r.id),console.error("[claude-mem cleanup] Session marked as failed in database")}catch(n){console.error("[claude-mem cleanup] Failed to mark session as failed:",n)}s.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 L}from"process";var g="";L.on("data",o=>g+=o);L.on("end",async()=>{try{let o=g.trim()?JSON.parse(g):void 0;await f(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)}});
+1 -1
View File
@@ -239,7 +239,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let t=Obje
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 h}from"fs";import{spawn as P}from"child_process";var $=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),j=`http://127.0.0.1:${$}/health`;async function C(){try{return(await fetch(j,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function k(){try{if(await C())return!0;console.error("[claude-mem] Worker not responding, starting...");let a=v(),e=g.join(a,"dist","worker-service.cjs");if(!h(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let t=g.join(a,"ecosystem.config.cjs"),s=g.join(a,"node_modules",".bin","pm2");if(!h(s))throw new Error(`PM2 binary not found at ${s}. This is a bundled dependency - try running: npm install`);if(!h(t))throw new Error(`PM2 ecosystem config not found at ${t}. Plugin installation may be corrupted.`);let n=P(s,["start",t],{detached:!0,stdio:"ignore",cwd:a});n.on("error",r=>{throw new Error(`Failed to spawn PM2: ${r.message}`)}),n.unref(),console.error("[claude-mem] Worker started with PM2");for(let r=0;r<3;r++)if(await new Promise(i=>setTimeout(i,500)),await C())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(a){return console.error(`[claude-mem] Failed to start worker: ${a.message}`),!1}}function S(a){k();let e=a?.cwd??process.cwd(),t=e?W.basename(e):"unknown-project",s=new E;try{let n=s.getRecentSessionsWithStatus(t,3);if(n.length===0)return`# Recent Session Context
`).run(e.toISOString(),t).changes}close(){this.db.close()}};import g from"path";import{existsSync as h}from"fs";import{spawn as P}from"child_process";var $=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),j=`http://127.0.0.1:${$}/health`;async function C(){try{return(await fetch(j,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function k(){try{if(await C())return!0;console.error("[claude-mem] Worker not responding, starting...");let a=v(),e=g.join(a,"plugin","scripts","worker-service.cjs");if(!h(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let t=g.join(a,"ecosystem.config.cjs"),s=g.join(a,"node_modules",".bin","pm2");if(!h(s))throw new Error(`PM2 binary not found at ${s}. This is a bundled dependency - try running: npm install`);if(!h(t))throw new Error(`PM2 ecosystem config not found at ${t}. Plugin installation may be corrupted.`);let n=P(s,["start",t],{detached:!0,stdio:"ignore",cwd:a});n.on("error",r=>{throw new Error(`Failed to spawn PM2: ${r.message}`)}),n.unref(),console.error("[claude-mem] Worker started with PM2");for(let r=0;r<3;r++)if(await new Promise(i=>setTimeout(i,500)),await C())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(a){return console.error(`[claude-mem] Failed to start worker: ${a.message}`),!1}}function S(a){k();let e=a?.cwd??process.cwd(),t=e?W.basename(e):"unknown-project",s=new E;try{let n=s.getRecentSessionsWithStatus(t,3);if(n.length===0)return`# Recent Session Context
No previous sessions found for this project yet.`;let r=[];r.push("# Recent Session Context"),r.push(""),r.push(`Showing last ${n.length} session(s) for **${t}**:`),r.push("");for(let i of n)if(i.sdk_session_id){if(r.push("---"),r.push(""),i.has_summary){let o=s.getSummaryForSession(i.sdk_session_id);if(o){let d=o.prompt_number?` (Prompt #${o.prompt_number})`:"";if(r.push(`**Summary${d}**`),r.push(""),o.request&&r.push(`**Request:** ${o.request}`),o.completed&&r.push(`**Completed:** ${o.completed}`),o.learned&&r.push(`**Learned:** ${o.learned}`),o.next_steps&&r.push(`**Next Steps:** ${o.next_steps}`),o.files_read)try{let c=JSON.parse(o.files_read);Array.isArray(c)&&c.length>0&&r.push(`**Files Read:** ${c.join(", ")}`)}catch{o.files_read.trim()&&r.push(`**Files Read:** ${o.files_read}`)}if(o.files_edited)try{let c=JSON.parse(o.files_edited);Array.isArray(c)&&c.length>0&&r.push(`**Files Edited:** ${c.join(", ")}`)}catch{o.files_edited.trim()&&r.push(`**Files Edited:** ${o.files_edited}`)}let m=new Date(o.created_at).toLocaleString();r.push(`**Date:** ${m}`)}}else if(i.status==="active"){r.push("**In Progress**"),r.push(""),i.user_prompt&&r.push(`**Request:** ${i.user_prompt}`);let o=s.getObservationsForSession(i.sdk_session_id);if(o.length>0){r.push(""),r.push(`**Observations (${o.length}):**`);for(let m of o)r.push(`- ${m.title}`)}else r.push(""),r.push("*No observations yet*");r.push(""),r.push("**Status:** Active - summary pending");let d=new Date(i.started_at).toLocaleString();r.push(`**Date:** ${d}`)}else{let o=i.status==="failed"?"stopped":i.status;r.push(`**${o.charAt(0).toUpperCase()+o.slice(1)}**`),r.push(""),i.user_prompt&&r.push(`**Request:** ${i.user_prompt}`),r.push(""),r.push(`**Status:** ${o} - no summary available`);let d=new Date(i.started_at).toLocaleString();r.push(`**Date:** ${d}`)}r.push("")}return r.join(`
`)}finally{s.close()}}import{stdin as f}from"process";try{if(f.isTTY){let e={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:S()}};console.log(JSON.stringify(e)),process.exit(0)}else{let a="";f.on("data",e=>a+=e),f.on("end",()=>{let e=a.trim()?JSON.parse(a):void 0,s={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:S(e)}};console.log(JSON.stringify(s)),process.exit(0)})}}catch(a){console.error(`[claude-mem context-hook error: ${a.message}]`),process.exit(0)}
+4 -4
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import K from"path";import W from"better-sqlite3";import{join as d,dirname as P,basename as z}from"path";import{homedir as R}from"os";import{existsSync as te,mkdirSync as M}from"fs";import{fileURLToPath as F}from"url";function H(){return typeof __dirname<"u"?__dirname:P(F(import.meta.url))}var $=H(),m=process.env.CLAUDE_MEM_DATA_DIR||d(R(),".claude-mem"),T=process.env.CLAUDE_CONFIG_DIR||d(R(),".claude"),ne=d(m,"archives"),oe=d(m,"logs"),ie=d(m,"trash"),ae=d(m,"backups"),de=d(m,"settings.json"),O=d(m,"claude-mem.db"),pe=d(T,"settings.json"),ce=d(T,"commands"),ue=d(T,"CLAUDE.md");function I(o){M(o,{recursive:!0})}function L(){return d($,"..","..")}var g=(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))(g||{}),S=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=g[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),i=g[e].padEnd(5),p=s.padEnd(6),_="";r?.correlationId?_=`[${r.correlationId}] `:r?.sessionId&&(_=`[session-${r.sessionId}] `);let E="";n!=null&&(this.level===0&&typeof n=="object"?E=`
`+JSON.stringify(n,null,2):E=" "+this.formatData(n));let c="";if(r){let{sessionId:Y,sdkSessionId:q,correlationId:V,...h}=r;Object.keys(h).length>0&&(c=` {${Object.entries(h).map(([w,X])=>`${w}=${X}`).join(", ")}}`)}let u=`[${a}] [${i}] [${p}] ${_}${t}${c}${E}`;e===3?console.error(u):console.log(u)}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 S;var l=class{db;constructor(){I(m),this.db=new W(O),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(`
${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),c=g[e].padEnd(5),i=s.padEnd(6),E="";r?.correlationId?E=`[${r.correlationId}] `:r?.sessionId&&(E=`[session-${r.sessionId}] `);let l="";n!=null&&(this.level===0&&typeof n=="object"?l=`
`+JSON.stringify(n,null,2):l=" "+this.formatData(n));let p="";if(r){let{sessionId:Y,sdkSessionId:q,correlationId:V,...h}=r;Object.keys(h).length>0&&(p=` {${Object.entries(h).map(([w,X])=>`${w}=${X}`).join(", ")}}`)}let u=`[${a}] [${c}] [${i}] ${E}${t}${p}${l}`;e===3?console.error(u):console.log(u)}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 S;var _=class{db;constructor(){I(m),this.db=new W(O),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,
@@ -63,7 +63,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
CREATE INDEX IF NOT EXISTS idx_session_summaries_sdk_session ON session_summaries(sdk_session_id);
CREATE INDEX IF NOT EXISTS idx_session_summaries_project ON session_summaries(project);
CREATE INDEX IF NOT EXISTS idx_session_summaries_created ON session_summaries(created_at_epoch DESC);
`),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(p=>p.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(p=>p.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(p=>p.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
`),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(i=>i.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(i=>i.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(i=>i.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
CREATE TABLE session_summaries_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sdk_session_id TEXT NOT NULL,
@@ -239,4 +239,4 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
UPDATE sdk_sessions
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
WHERE status = 'active'
`).run(e.toISOString(),s).changes}close(){this.db.close()}};function j(o,e,s){return o==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:o==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function A(o,e,s={}){let t=j(o,e,s);return JSON.stringify(t)}import b from"path";import{existsSync as N}from"fs";import{spawn as B}from"child_process";var C=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),G=`http://127.0.0.1:${C}/health`;async function k(){try{return(await fetch(G,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function D(){try{if(await k())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=L(),e=b.join(o,"dist","worker-service.cjs");if(!N(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=b.join(o,"ecosystem.config.cjs"),t=b.join(o,"node_modules",".bin","pm2");if(!N(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!N(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=B(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 k())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}}function y(){return C}async function x(o){if(!o)throw new Error("newHook requires input");let{session_id:e,cwd:s,prompt:t}=o,r=K.basename(s),n=new l;try{let a=n.findActiveSDKSession(e),i,p=!1;if(a){i=a.id;let c=n.incrementPromptCounter(i);console.error(`[new-hook] Continuing session ${i}, prompt #${c}`)}else{let c=n.findAnySDKSession(e);if(c){i=c.id,n.reactivateSession(i,t);let u=n.incrementPromptCounter(i);p=!0,console.error(`[new-hook] Reactivated session ${i}, prompt #${u}`)}else{i=n.createSDKSession(e,r,t);let u=n.incrementPromptCounter(i);p=!0,console.error(`[new-hook] Created new session ${i}, prompt #${u}`)}}if(!await D())throw new Error("Worker service failed to start or become healthy");let E=y();if(p){let c=await fetch(`http://127.0.0.1:${E}/sessions/${i}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:r,userPrompt:t}),signal:AbortSignal.timeout(5e3)});if(!c.ok){let u=await c.text();throw new Error(`Failed to initialize session: ${c.status} ${u}`)}}console.log(A("UserPromptSubmit",!0))}finally{n.close()}}import{stdin as U}from"process";var f="";U.on("data",o=>f+=o);U.on("end",async()=>{let o=f.trim()?JSON.parse(f):void 0;await x(o),process.exit(0)});
`).run(e.toISOString(),s).changes}close(){this.db.close()}};function j(o,e,s){return o==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:o==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function A(o,e,s={}){let t=j(o,e,s);return JSON.stringify(t)}import b from"path";import{existsSync as N}from"fs";import{spawn as B}from"child_process";var C=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),G=`http://127.0.0.1:${C}/health`;async function k(){try{return(await fetch(G,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function D(){try{if(await k())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=L(),e=b.join(o,"plugin","scripts","worker-service.cjs");if(!N(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=b.join(o,"ecosystem.config.cjs"),t=b.join(o,"node_modules",".bin","pm2");if(!N(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!N(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=B(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 k())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}}function y(){return C}async function x(o){if(!o)throw new Error("newHook requires input");let{session_id:e,cwd:s,prompt:t}=o,r=K.basename(s);if(!await D())throw new Error("Worker service failed to start or become healthy");let a=new _;try{let c=a.findActiveSDKSession(e),i,E=!1;if(c){i=c.id;let p=a.incrementPromptCounter(i);console.error(`[new-hook] Continuing session ${i}, prompt #${p}`)}else{let p=a.findAnySDKSession(e);if(p){i=p.id,a.reactivateSession(i,t);let u=a.incrementPromptCounter(i);E=!0,console.error(`[new-hook] Reactivated session ${i}, prompt #${u}`)}else{i=a.createSDKSession(e,r,t);let u=a.incrementPromptCounter(i);E=!0,console.error(`[new-hook] Created new session ${i}, prompt #${u}`)}}let l=y();if(E){let p=await fetch(`http://127.0.0.1:${l}/sessions/${i}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:r,userPrompt:t}),signal:AbortSignal.timeout(5e3)});if(!p.ok){let u=await p.text();throw new Error(`Failed to initialize session: ${p.status} ${u}`)}}console.log(A("UserPromptSubmit",!0))}finally{a.close()}}import{stdin as U}from"process";var f="";U.on("data",o=>f+=o);U.on("end",async()=>{let o=f.trim()?JSON.parse(f):void 0;await x(o),process.exit(0)});
+463
View File
@@ -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"
}
}
}
+8 -8
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import U from"better-sqlite3";import{join as a,dirname as k,basename as G}from"path";import{homedir as I}from"os";import{existsSync as K,mkdirSync as D}from"fs";import{fileURLToPath as y}from"url";function x(){return typeof __dirname<"u"?__dirname:k(y(import.meta.url))}var q=x(),c=process.env.CLAUDE_MEM_DATA_DIR||a(I(),".claude-mem"),T=process.env.CLAUDE_CONFIG_DIR||a(I(),".claude"),V=a(c,"archives"),J=a(c,"logs"),Q=a(c,"trash"),z=a(c,"backups"),Z=a(c,"settings.json"),f=a(c,"claude-mem.db"),ee=a(T,"settings.json"),se=a(T,"commands"),te=a(T,"CLAUDE.md");function h(n){D(n,{recursive:!0})}var S=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(S||{}),g=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=S[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,o){if(e<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),u=S[e].padEnd(5),d=s.padEnd(6),p="";r?.correlationId?p=`[${r.correlationId}] `:r?.sessionId&&(p=`[session-${r.sessionId}] `);let _="";o!=null&&(this.level===0&&typeof o=="object"?_=`
`+JSON.stringify(o,null,2):_=" "+this.formatData(o));let N="";if(r){let{sessionId:M,sdkSessionId:P,correlationId:F,...R}=r;Object.keys(R).length>0&&(N=` {${Object.entries(R).map(([A,C])=>`${A}=${C}`).join(", ")}}`)}let O=`[${i}] [${u}] [${d}] ${p}${t}${N}${_}`;e===3?console.error(O):console.log(O)}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`})}},E=new g;var m=class{db;constructor(){h(c),this.db=new U(f),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 H from"better-sqlite3";import{join as d,dirname as w,basename as Q}from"path";import{homedir as I}from"os";import{existsSync as se,mkdirSync as X}from"fs";import{fileURLToPath as P}from"url";function M(){return typeof __dirname<"u"?__dirname:w(P(import.meta.url))}var F=M(),u=process.env.CLAUDE_MEM_DATA_DIR||d(I(),".claude-mem"),g=process.env.CLAUDE_CONFIG_DIR||d(I(),".claude"),re=d(u,"archives"),oe=d(u,"logs"),ne=d(u,"trash"),ie=d(u,"backups"),ae=d(u,"settings.json"),L=d(u,"claude-mem.db"),de=d(g,"settings.json"),pe=d(g,"commands"),ce=d(g,"CLAUDE.md");function v(n){X(n,{recursive:!0})}function A(){return d(F,"..","..")}var S=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(S||{}),b=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=S[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,o){if(e<this.level)return;let a=new Date().toISOString().replace("T"," ").substring(0,23),i=S[e].padEnd(5),p=s.padEnd(6),m="";r?.correlationId?m=`[${r.correlationId}] `:r?.sessionId&&(m=`[session-${r.sessionId}] `);let c="";o!=null&&(this.level===0&&typeof o=="object"?c=`
`+JSON.stringify(o,null,2):c=" "+this.formatData(o));let l="";if(r){let{sessionId:K,sdkSessionId:Y,correlationId:q,...O}=r;Object.keys(O).length>0&&(l=` {${Object.entries(O).map(([x,U])=>`${x}=${U}`).join(", ")}}`)}let h=`[${a}] [${i}] [${p}] ${m}${t}${l}${c}`;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`})}},E=new b;var _=class{db;constructor(){v(u),this.db=new H(L),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,
@@ -63,7 +63,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
CREATE INDEX IF NOT EXISTS idx_session_summaries_sdk_session ON session_summaries(sdk_session_id);
CREATE INDEX IF NOT EXISTS idx_session_summaries_project ON session_summaries(project);
CREATE INDEX IF NOT EXISTS idx_session_summaries_created ON session_summaries(created_at_epoch DESC);
`),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(d=>d.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(d=>d.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(d=>d.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
`),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(p=>p.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(p=>p.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(p=>p.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
CREATE TABLE session_summaries_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sdk_session_id TEXT NOT NULL,
@@ -217,17 +217,17 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
FROM sdk_sessions
WHERE id = ?
LIMIT 1
`).get(e)?.worker_port||null}storeObservation(e,s,t,r){let o=new Date,i=o.getTime();this.db.prepare(`
`).get(e)?.worker_port||null}storeObservation(e,s,t,r){let o=new Date,a=o.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,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,o.toISOString(),i)}storeSummary(e,s,t,r){let o=new Date,i=o.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,o.toISOString(),a)}storeSummary(e,s,t,r){let o=new Date,a=o.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,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,o.toISOString(),i)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(`
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,o.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 = ?
@@ -239,4 +239,4 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
UPDATE sdk_sessions
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
WHERE status = 'active'
`).run(e.toISOString(),s).changes}close(){this.db.close()}};function X(n,e,s){return n==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:n==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:n==="UserPromptSubmit"||n==="PostToolUse"?{continue:!0,suppressOutput:!0}:n==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function l(n,e,s={}){let t=X(n,e,s);return JSON.stringify(t)}var w=new Set(["ListMcpResourcesTool"]);async function L(n){if(!n)throw new Error("saveHook requires input");let{session_id:e,tool_name:s,tool_input:t,tool_output:r}=n;if(w.has(s)){console.log(l("PostToolUse",!0));return}let o=new m,i=o.findActiveSDKSession(e);if(!i){o.close(),console.log(l("PostToolUse",!0));return}if(!i.worker_port)throw o.close(),E.error("HOOK","No worker port for session",{sessionId:i.id}),new Error("No worker port for session - session may not be properly initialized");let u=o.getPromptCounter(i.id);o.close();let d=E.formatTool(s,t);E.dataIn("HOOK",`PostToolUse: ${d}`,{sessionId:i.id,workerPort:i.worker_port});let p=await fetch(`http://127.0.0.1:${i.worker_port}/sessions/${i.id}/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tool_name:s,tool_input:t!==void 0?JSON.stringify(t):"{}",tool_output:r!==void 0?JSON.stringify(r):"{}",prompt_number:u}),signal:AbortSignal.timeout(2e3)});if(!p.ok){let _=await p.text();throw E.failure("HOOK","Failed to send observation",{sessionId:i.id,status:p.status},_),new Error(`Failed to send observation to worker: ${p.status} ${_}`)}E.debug("HOOK","Observation sent successfully",{sessionId:i.id,toolName:s}),console.log(l("PostToolUse",!0))}import{stdin as v}from"process";var b="";v.on("data",n=>b+=n);v.on("end",async()=>{let n=b.trim()?JSON.parse(b):void 0;await L(n),process.exit(0)});
`).run(e.toISOString(),s).changes}close(){this.db.close()}};function W(n,e,s){return n==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:n==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:n==="UserPromptSubmit"||n==="PostToolUse"?{continue:!0,suppressOutput:!0}:n==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function T(n,e,s={}){let t=W(n,e,s);return JSON.stringify(t)}import f from"path";import{existsSync as N}from"fs";import{spawn as $}from"child_process";var j=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),B=`http://127.0.0.1:${j}/health`;async function k(){try{return(await fetch(B,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function C(){try{if(await k())return!0;console.error("[claude-mem] Worker not responding, starting...");let n=A(),e=f.join(n,"plugin","scripts","worker-service.cjs");if(!N(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=f.join(n,"ecosystem.config.cjs"),t=f.join(n,"node_modules",".bin","pm2");if(!N(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!N(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=$(t,["start",s],{detached:!0,stdio:"ignore",cwd:n});r.on("error",o=>{throw new Error(`Failed to spawn PM2: ${o.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let o=0;o<3;o++)if(await new Promise(a=>setTimeout(a,500)),await k())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(n){return console.error(`[claude-mem] Failed to start worker: ${n.message}`),!1}}var G=new Set(["ListMcpResourcesTool"]);async function D(n){if(!n)throw new Error("saveHook requires input");let{session_id:e,tool_name:s,tool_input:t,tool_output:r}=n;if(G.has(s)){console.log(T("PostToolUse",!0));return}if(!await C())throw new Error("Worker service failed to start or become healthy");let a=new _,i=a.findActiveSDKSession(e);if(!i){a.close(),console.log(T("PostToolUse",!0));return}if(!i.worker_port)throw a.close(),E.error("HOOK","No worker port for session",{sessionId:i.id}),new Error("No worker port for session - session may not be properly initialized");let p=a.getPromptCounter(i.id);a.close();let m=E.formatTool(s,t);E.dataIn("HOOK",`PostToolUse: ${m}`,{sessionId:i.id,workerPort:i.worker_port});let c=await fetch(`http://127.0.0.1:${i.worker_port}/sessions/${i.id}/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tool_name:s,tool_input:t!==void 0?JSON.stringify(t):"{}",tool_output:r!==void 0?JSON.stringify(r):"{}",prompt_number:p}),signal:AbortSignal.timeout(2e3)});if(!c.ok){let l=await c.text();throw E.failure("HOOK","Failed to send observation",{sessionId:i.id,status:c.status},l),new Error(`Failed to send observation to worker: ${c.status} ${l}`)}E.debug("HOOK","Observation sent successfully",{sessionId:i.id,toolName:s}),console.log(T("PostToolUse",!0))}import{stdin as y}from"process";var R="";y.on("data",n=>R+=n);y.on("end",async()=>{let n=R.trim()?JSON.parse(R):void 0;await D(n),process.exit(0)});
File diff suppressed because one or more lines are too long
+11 -11
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import U from"better-sqlite3";import{join as i,dirname as k,basename as B}from"path";import{homedir as I}from"os";import{existsSync as W,mkdirSync as D}from"fs";import{fileURLToPath as y}from"url";function x(){return typeof __dirname<"u"?__dirname:k(y(import.meta.url))}var Y=x(),d=process.env.CLAUDE_MEM_DATA_DIR||i(I(),".claude-mem"),l=process.env.CLAUDE_CONFIG_DIR||i(I(),".claude"),q=i(d,"archives"),V=i(d,"logs"),J=i(d,"trash"),Q=i(d,"backups"),z=i(d,"settings.json"),f=i(d,"claude-mem.db"),Z=i(l,"settings.json"),ee=i(l,"commands"),se=i(l,"CLAUDE.md");function h(o){D(o,{recursive:!0})}var T=(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||{}),S=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=T[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),E=T[e].padEnd(5),p=s.padEnd(6),m="";r?.correlationId?m=`[${r.correlationId}] `:r?.sessionId&&(m=`[session-${r.sessionId}] `);let _="";n!=null&&(this.level===0&&typeof n=="object"?_=`
`+JSON.stringify(n,null,2):_=" "+this.formatData(n));let N="";if(r){let{sessionId:w,sdkSessionId:M,correlationId:F,...R}=r;Object.keys(R).length>0&&(N=` {${Object.entries(R).map(([A,C])=>`${A}=${C}`).join(", ")}}`)}let O=`[${a}] [${E}] [${p}] ${m}${t}${N}${_}`;e===3?console.error(O):console.log(O)}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`})}},c=new S;var u=class{db;constructor(){h(d),this.db=new U(f),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 H from"better-sqlite3";import{join as a,dirname as w,basename as J}from"path";import{homedir as I}from"os";import{existsSync as ee,mkdirSync as X}from"fs";import{fileURLToPath as M}from"url";function P(){return typeof __dirname<"u"?__dirname:w(M(import.meta.url))}var F=P(),p=process.env.CLAUDE_MEM_DATA_DIR||a(I(),".claude-mem"),l=process.env.CLAUDE_CONFIG_DIR||a(I(),".claude"),te=a(p,"archives"),re=a(p,"logs"),oe=a(p,"trash"),ne=a(p,"backups"),ie=a(p,"settings.json"),L=a(p,"claude-mem.db"),ae=a(l,"settings.json"),de=a(l,"commands"),pe=a(l,"CLAUDE.md");function v(n){X(n,{recursive:!0})}function A(){return a(F,"..","..")}var T=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(T||{}),g=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=T[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,o){if(e<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),d=T[e].padEnd(5),c=s.padEnd(6),E="";r?.correlationId?E=`[${r.correlationId}] `:r?.sessionId&&(E=`[session-${r.sessionId}] `);let _="";o!=null&&(this.level===0&&typeof o=="object"?_=`
`+JSON.stringify(o,null,2):_=" "+this.formatData(o));let R="";if(r){let{sessionId:G,sdkSessionId:K,correlationId:Y,...O}=r;Object.keys(O).length>0&&(R=` {${Object.entries(O).map(([x,U])=>`${x}=${U}`).join(", ")}}`)}let h=`[${i}] [${d}] [${c}] ${E}${t}${R}${_}`;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`})}},u=new g;var m=class{db;constructor(){v(p),this.db=new H(L),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,
@@ -63,7 +63,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
CREATE INDEX IF NOT EXISTS idx_session_summaries_sdk_session ON session_summaries(sdk_session_id);
CREATE INDEX IF NOT EXISTS idx_session_summaries_project ON session_summaries(project);
CREATE INDEX IF NOT EXISTS idx_session_summaries_created ON session_summaries(created_at_epoch DESC);
`),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(p=>p.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(p=>p.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(p=>p.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
`),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(c=>c.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(c=>c.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(c=>c.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
CREATE TABLE session_summaries_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sdk_session_id TEXT NOT NULL,
@@ -200,15 +200,15 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=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,s,t){let r=new Date,n=r.getTime();return this.db.prepare(`
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,o=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,s,t,r.toISOString(),n).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
`).run(e,s,t,r.toISOString(),o).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
UPDATE sdk_sessions
SET sdk_session_id = ?
WHERE id = ? AND sdk_session_id IS NULL
`).run(s,e).changes===0?(c.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(`
`).run(s,e).changes===0?(u.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 = ?
@@ -217,17 +217,17 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
FROM sdk_sessions
WHERE id = ?
LIMIT 1
`).get(e)?.worker_port||null}storeObservation(e,s,t,r){let n=new Date,a=n.getTime();this.db.prepare(`
`).get(e)?.worker_port||null}storeObservation(e,s,t,r){let o=new Date,i=o.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,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(`
`).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,o.toISOString(),i)}storeSummary(e,s,t,r){let o=new Date,i=o.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,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(`
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,o.toISOString(),i)}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 = ?
@@ -239,4 +239,4 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
UPDATE sdk_sessions
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
WHERE status = 'active'
`).run(e.toISOString(),s).changes}close(){this.db.close()}};function X(o,e,s){return o==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:o==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function g(o,e,s={}){let t=X(o,e,s);return JSON.stringify(t)}async function L(o){if(!o)throw new Error("summaryHook requires input");let{session_id:e}=o,s=new u,t=s.findActiveSDKSession(e);if(!t){s.close(),console.log(g("Stop",!0));return}if(!t.worker_port)throw s.close(),c.error("HOOK","No worker port for session",{sessionId:t.id}),new Error("No worker port for session - session may not be properly initialized");let r=s.getPromptCounter(t.id);s.close(),c.dataIn("HOOK","Stop: Requesting summary",{sessionId:t.id,workerPort:t.worker_port,promptNumber:r});let n=await fetch(`http://127.0.0.1:${t.worker_port}/sessions/${t.id}/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({prompt_number:r}),signal:AbortSignal.timeout(2e3)});if(!n.ok){let a=await n.text();throw c.failure("HOOK","Failed to generate summary",{sessionId:t.id,status:n.status},a),new Error(`Failed to request summary from worker: ${n.status} ${a}`)}c.debug("HOOK","Summary request sent successfully",{sessionId:t.id}),console.log(g("Stop",!0))}import{stdin as v}from"process";var b="";v.on("data",o=>b+=o);v.on("end",async()=>{let o=b.trim()?JSON.parse(b):void 0;await L(o),process.exit(0)});
`).run(e.toISOString(),s).changes}close(){this.db.close()}};function W(n,e,s){return n==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:n==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:n==="UserPromptSubmit"||n==="PostToolUse"?{continue:!0,suppressOutput:!0}:n==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function S(n,e,s={}){let t=W(n,e,s);return JSON.stringify(t)}import b from"path";import{existsSync as N}from"fs";import{spawn as $}from"child_process";var j=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),B=`http://127.0.0.1:${j}/health`;async function k(){try{return(await fetch(B,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function C(){try{if(await k())return!0;console.error("[claude-mem] Worker not responding, starting...");let n=A(),e=b.join(n,"plugin","scripts","worker-service.cjs");if(!N(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=b.join(n,"ecosystem.config.cjs"),t=b.join(n,"node_modules",".bin","pm2");if(!N(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!N(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=$(t,["start",s],{detached:!0,stdio:"ignore",cwd:n});r.on("error",o=>{throw new Error(`Failed to spawn PM2: ${o.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let o=0;o<3;o++)if(await new Promise(i=>setTimeout(i,500)),await k())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(n){return console.error(`[claude-mem] Failed to start worker: ${n.message}`),!1}}async function D(n){if(!n)throw new Error("summaryHook requires input");let{session_id:e}=n;if(!await C())throw new Error("Worker service failed to start or become healthy");let t=new m,r=t.findActiveSDKSession(e);if(!r){t.close(),console.log(S("Stop",!0));return}if(!r.worker_port)throw t.close(),u.error("HOOK","No worker port for session",{sessionId:r.id}),new Error("No worker port for session - session may not be properly initialized");let o=t.getPromptCounter(r.id);t.close(),u.dataIn("HOOK","Stop: Requesting summary",{sessionId:r.id,workerPort:r.worker_port,promptNumber:o});let i=await fetch(`http://127.0.0.1:${r.worker_port}/sessions/${r.id}/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({prompt_number:o}),signal:AbortSignal.timeout(2e3)});if(!i.ok){let d=await i.text();throw u.failure("HOOK","Failed to generate summary",{sessionId:r.id,status:i.status},d),new Error(`Failed to request summary from worker: ${i.status} ${d}`)}u.debug("HOOK","Summary request sent successfully",{sessionId:r.id}),console.log(S("Stop",!0))}import{stdin as y}from"process";var f="";y.on("data",n=>f+=n);y.on("end",async()=>{let n=f.trim()?JSON.parse(f):void 0;await D(n),process.exit(0)});
File diff suppressed because one or more lines are too long
+11 -13
View File
@@ -39,18 +39,14 @@ async function buildHooks() {
const version = packageJson.version;
console.log(`📌 Version: ${version}`);
// Create output directories
console.log('\n📦 Preparing output directories...');
// Create output directory
console.log('\n📦 Preparing output directory...');
const hooksDir = 'plugin/scripts';
const distDir = 'dist';
if (!fs.existsSync(hooksDir)) {
fs.mkdirSync(hooksDir, { recursive: true });
}
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true });
}
console.log('✓ Output directories ready');
console.log('✓ Output directory ready');
// Build worker service
console.log(`\n🔧 Building worker service...`);
@@ -60,7 +56,7 @@ async function buildHooks() {
platform: 'node',
target: 'node18',
format: 'cjs',
outfile: `${distDir}/${WORKER_SERVICE.name}.cjs`,
outfile: `${hooksDir}/${WORKER_SERVICE.name}.cjs`,
minify: true,
external: ['better-sqlite3'],
define: {
@@ -72,8 +68,8 @@ async function buildHooks() {
});
// Make worker service executable
fs.chmodSync(`${distDir}/${WORKER_SERVICE.name}.cjs`, 0o755);
const workerStats = fs.statSync(`${distDir}/${WORKER_SERVICE.name}.cjs`);
fs.chmodSync(`${hooksDir}/${WORKER_SERVICE.name}.cjs`, 0o755);
const workerStats = fs.statSync(`${hooksDir}/${WORKER_SERVICE.name}.cjs`);
console.log(`✓ worker-service built (${(workerStats.size / 1024).toFixed(2)} KB)`);
// Build each hook
@@ -133,9 +129,11 @@ async function buildHooks() {
console.log(`✓ search-server built (${(searchStats.size / 1024).toFixed(2)} KB)`);
console.log('\n✅ All hooks, worker service, and search server built successfully!');
console.log(` Hooks: ${hooksDir}/`);
console.log(` Worker: ${distDir}/worker-service.cjs`);
console.log(` Search: ${hooksDir}/search-server.js`);
console.log(` Output: ${hooksDir}/`);
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);
+20 -21
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env node
/**
* Publish script for claude-mem
* Handles version bumping, building, and publishing to npm
* Release script for claude-mem
* Handles version bumping, building, and creating marketplace releases
*/
import { exec } from 'child_process';
@@ -21,7 +21,7 @@ const question = (query) => new Promise((resolve) => rl.question(query, resolve)
async function publish() {
try {
console.log('📦 Claude-mem Publishing Tool\n');
console.log('📦 Claude-mem Marketplace Release Tool\n');
// Check git status
console.log('🔍 Checking git status...');
@@ -82,15 +82,19 @@ async function publish() {
process.exit(0);
}
// Update package.json version
console.log('\n📝 Updating package.json...');
// Update package.json and marketplace.json versions
console.log('\n📝 Updating package.json and marketplace.json...');
packageJson.version = newVersion;
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2) + '\n');
console.log('✓ Version updated');
const marketplaceJson = JSON.parse(fs.readFileSync('.claude-plugin/marketplace.json', 'utf-8'));
marketplaceJson.plugins[0].version = newVersion;
fs.writeFileSync('.claude-plugin/marketplace.json', JSON.stringify(marketplaceJson, null, 2) + '\n');
console.log('✓ Versions updated in both files');
// Run build
console.log('\n🔨 Building...');
await execAsync('node build.js');
console.log('\n🔨 Building hooks...');
await execAsync('npm run build');
console.log('✓ Build complete');
// Run tests if they exist
@@ -112,31 +116,26 @@ async function publish() {
// Git commit and tag
console.log('\n📌 Creating git commit and tag...');
await execAsync('git add package.json dist/');
await execAsync(`git commit -m "Release v${newVersion}
await execAsync('git add package.json .claude-plugin/marketplace.json plugin/');
await execAsync(`git commit -m "chore: Release v${newVersion}
Published from npm package build
Source: https://github.com/thedotmack/claude-mem"`);
Marketplace release for Claude Code plugin
https://github.com/thedotmack/claude-mem"`);
await execAsync(`git tag v${newVersion}`);
console.log(`✓ Created commit and tag v${newVersion}`);
// Publish to npm
console.log('\n🚀 Publishing to npm...');
await execAsync('npm publish');
console.log('✓ Published to npm');
// Push to git
console.log('\n⬆️ Pushing to git...');
await execAsync('git push');
await execAsync('git push --tags');
console.log('✓ Pushed to git');
console.log(`\n✅ Successfully published v${newVersion}! 🎉`);
console.log(`\n📦 Package: https://www.npmjs.com/package/claude-mem`);
console.log(`🏷️ Tag: https://github.com/thedotmack/claude-mem/releases/tag/v${newVersion}`);
console.log(`\n✅ Successfully released v${newVersion}! 🎉`);
console.log(`\n🏷️ Tag: https://github.com/thedotmack/claude-mem/releases/tag/v${newVersion}`);
console.log(`📦 Marketplace will sync from this tag automatically`);
} catch (error) {
console.error('\n❌ Publish failed:', error.message);
console.error('\n❌ Release failed:', error.message);
if (error.stderr) {
console.error('\nError details:', error.stderr);
}
+14 -27
View File
@@ -1,4 +1,5 @@
import { SessionStore } from '../services/sqlite/SessionStore.js';
import { ensureWorkerRunning } from '../shared/worker-utils.js';
export interface SessionEndInput {
session_id: string;
@@ -10,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 {
@@ -45,6 +46,12 @@ export async function cleanupHook(input?: SessionEndInput): Promise<void> {
const { session_id, reason } = input;
console.error('[claude-mem cleanup] Searching for active SDK session', { session_id, reason });
// Ensure worker is running first (runs cleanup if restarting)
const workerReady = await ensureWorkerRunning();
if (!workerReady) {
console.error('[claude-mem cleanup] Worker not available - skipping HTTP cleanup');
}
// Find active SDK session
const db = new SessionStore();
const session = db.findActiveSDKSession(session_id);
@@ -64,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) {
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 port, cannot send DELETE request');
}
// 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();
+8 -6
View File
@@ -21,9 +21,17 @@ export async function newHook(input?: UserPromptSubmitInput): Promise<void> {
const { session_id, cwd, prompt } = input;
const project = path.basename(cwd);
// Ensure worker is running first (runs cleanup if restarting)
const workerReady = await ensureWorkerRunning();
if (!workerReady) {
throw new Error('Worker service failed to start or become healthy');
}
const db = new SessionStore();
try {
// Check for any existing session (active, failed, or completed)
let existing = db.findActiveSDKSession(session_id);
let sessionDbId: number;
@@ -54,12 +62,6 @@ export async function newHook(input?: UserPromptSubmitInput): Promise<void> {
}
}
// Ensure worker service is running (v4.0.0 auto-start)
const workerReady = await ensureWorkerRunning();
if (!workerReady) {
throw new Error('Worker service failed to start or become healthy');
}
// Get fixed port
const port = getWorkerPort();
+7
View File
@@ -1,6 +1,7 @@
import { SessionStore } from '../services/sqlite/SessionStore.js';
import { createHookResponse } from './hook-response.js';
import { logger } from '../utils/logger.js';
import { ensureWorkerRunning } from '../shared/worker-utils.js';
export interface PostToolUseInput {
session_id: string;
@@ -32,6 +33,12 @@ export async function saveHook(input?: PostToolUseInput): Promise<void> {
return;
}
// Ensure worker is running first (runs cleanup if restarting)
const workerReady = await ensureWorkerRunning();
if (!workerReady) {
throw new Error('Worker service failed to start or become healthy');
}
const db = new SessionStore();
const session = db.findActiveSDKSession(session_id);
+8
View File
@@ -1,6 +1,7 @@
import { SessionStore } from '../services/sqlite/SessionStore.js';
import { createHookResponse } from './hook-response.js';
import { logger } from '../utils/logger.js';
import { ensureWorkerRunning } from '../shared/worker-utils.js';
export interface StopInput {
session_id: string;
@@ -18,6 +19,13 @@ export async function summaryHook(input?: StopInput): Promise<void> {
}
const { session_id } = input;
// Ensure worker is running first (runs cleanup if restarting)
const workerReady = await ensureWorkerRunning();
if (!workerReady) {
throw new Error('Worker service failed to start or become healthy');
}
const db = new SessionStore();
const session = db.findActiveSDKSession(session_id);
+533 -299
View File
@@ -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);
});
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -37,7 +37,7 @@ export async function ensureWorkerRunning(): Promise<boolean> {
// Find worker service path
const packageRoot = getPackageRoot();
const workerPath = path.join(packageRoot, 'dist', 'worker-service.cjs');
const workerPath = path.join(packageRoot, 'plugin', 'scripts', 'worker-service.cjs');
if (!existsSync(workerPath)) {
console.error(`[claude-mem] Worker service not found at ${workerPath}`);