From f7f11b2a4be543c224963eba51195628e39ec5c5 Mon Sep 17 00:00:00 2001 From: Alex Newman Date: Mon, 29 Dec 2025 23:12:15 -0500 Subject: [PATCH] chore(release): v8.5.0 - Cursor Support Now Available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit πŸŽ‰ Major release introducing full Cursor IDE support New Features: - Cursor IDE integration with native hook system - Interactive setup wizard (bun run cursor:setup) - Works without Claude Code using Gemini (free) or OpenRouter - Cross-platform: macOS, Linux, Windows (PowerShell) - Context injection via .cursor/rules directory - Project registry for multi-workspace support - MCP search tools for Cursor Documentation: - Full docs at docs.claude-mem.ai/cursor - Gemini and OpenRouter setup guides πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .claude-plugin/marketplace.json | 2 +- CHANGELOG.md | 326 ++++++++++++++++++++---------- package.json | 2 +- plugin/.claude-plugin/plugin.json | 2 +- plugin/package.json | 2 +- plugin/scripts/worker-service.cjs | 2 +- 6 files changed, 226 insertions(+), 110 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index f14b53dc..8a55e683 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "plugins": [ { "name": "claude-mem", - "version": "8.2.10", + "version": "8.5.0", "source": "./plugin", "description": "Persistent memory system for Claude Code - context compression across sessions" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 69b88065..c65dfe87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,122 @@ 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/). +## [8.5.0] - 2025-12-30 + +# Cursor Support Now Available + +This is a major release introducing **full Cursor IDE support**. Claude-mem now works with Cursor, bringing persistent AI memory to Cursor users with or without a Claude Code subscription. + +## Highlights + +**Give Cursor persistent memory.** Every Cursor session starts fresh - your AI doesn't remember what it worked on yesterday. Claude-mem changes that. Your agent builds cumulative knowledge about your codebase, decisions, and patterns over time. + +### Works Without Claude Code + +You can now use claude-mem with Cursor using free AI providers: +- **Gemini** (recommended): 1,500 free requests/day, no credit card required +- **OpenRouter**: Access to 100+ models including free options +- **Claude SDK**: For Claude Code subscribers + +### Cross-Platform Support + +Full support for all major platforms: +- **macOS**: Bash scripts with `jq` and `curl` +- **Linux**: Same toolchain as macOS +- **Windows**: Native PowerShell scripts, no WSL required + +## New Features + +### Interactive Setup Wizard (`bun run cursor:setup`) +A guided installer that: +- Detects your environment (Claude Code present or not) +- Helps you choose and configure an AI provider +- Installs Cursor hooks automatically +- Starts the worker service +- Verifies everything is working + +### Cursor Lifecycle Hooks +Complete hook integration with Cursor's native hook system: +- `session-init.sh/.ps1` - Session start with context injection +- `user-message.sh/.ps1` - User prompt capture +- `save-observation.sh/.ps1` - Tool usage logging +- `save-file-edit.sh/.ps1` - File edit tracking +- `session-summary.sh/.ps1` - Session end summary +- `context-inject.sh/.ps1` - Load relevant history + +### Context Injection via `.cursor/rules` +Relevant past context is automatically injected into Cursor sessions via the `.cursor/rules/claude-mem-context.mdc` file, giving your AI immediate awareness of prior work. + +### Project Registry +Multi-project support with automatic project detection: +- Projects registered in `~/.claude-mem/cursor-projects.json` +- Context automatically scoped to current project +- Works across multiple workspaces simultaneously + +### MCP Search Tools +Full MCP server integration for Cursor: +- `search` - Find observations by query, date, type +- `timeline` - Get context around specific observations +- `get_observations` - Fetch full details for filtered IDs + +## New Commands + +| Command | Description | +|---------|-------------| +| `bun run cursor:setup` | Interactive setup wizard | +| `bun run cursor:install` | Install Cursor hooks | +| `bun run cursor:uninstall` | Remove Cursor hooks | +| `bun run cursor:status` | Check hook installation status | + +## Documentation + +Full documentation available at [docs.claude-mem.ai/cursor](https://docs.claude-mem.ai/cursor): +- Cursor Integration Overview +- Gemini Setup Guide (free tier) +- OpenRouter Setup Guide +- Troubleshooting + +## Technical Improvements + +### New Utilities Module (`src/utils/cursor-utils.ts`) +Extracted testable pure functions for: +- Project registry management +- Context file operations +- MCP configuration +- JSON utilities with array access support +- Cross-platform path handling + +### Comprehensive Test Suite +New test files validating Cursor integration: +- `cursor-registry.test.ts` - Project registry operations +- `cursor-context-update.test.ts` - Context file management +- `cursor-hooks-json-utils.test.ts` - JSON parsing utilities +- `cursor-mcp-config.test.ts` - MCP configuration +- `cursor-hook-outputs.test.ts` - Hook script output validation + +### Atomic File Writes +Context files use temp-file-plus-rename pattern to prevent corruption during updates. + +## Getting Started + +### For Cursor-Only Users (No Claude Code) + +```bash +git clone https://github.com/thedotmack/claude-mem.git +cd claude-mem && bun install && bun run build +bun run cursor:setup +``` + +### For Claude Code Users + +```bash +/plugin marketplace add thedotmack/claude-mem +/plugin install claude-mem +claude-mem cursor install +``` + +--- + ## [8.2.10] - 2025-12-30 ## Bug Fixes @@ -252,98 +368,98 @@ Huge thanks to **Alexander Knigge** ([@AlexanderKnigge](https://x.com/AlexanderK ## [8.1.0] - 2025-12-25 -## The 3-Month Battle Against Complexity - -**TL;DR:** For three months, Claude's instinct to add code instead of delete it caused the same bugs to recur. What should have been 5 lines of code became ~1000 lines, 11 useless methods, and 7+ failed "fixes." The timestamp corruption that finally broke things was just a symptom. The real achievement: **984 lines of code deleted.** - ---- - -## What Actually Happened - -Every Claude Code hook receives a session ID. That's all you need. - -But Claude built an entire redundant session management system on top: -- An `sdk_sessions` table with status tracking, port assignment, and prompt counting -- 11 methods in `SessionStore` to manage this artificial complexity -- Auto-creation logic scattered across 3 locations -- A cleanup hook that "completed" sessions at the end - -**Why?** Because it seemed "robust." Because "what if the session doesn't exist?" - -But the edge cases didn't exist. Hooks ALWAYS provide session IDs. The "defensive" code was solving imaginary problems while creating real ones. - ---- - -## The Pattern of Failure - -Every time a bug appeared, Claude's instinct was to **ADD** more code: - -| Bug | What Claude Added | What Should Have Happened | -|-----|------------------|--------------------------| -| Race conditions | Auto-create fallbacks | Delete the auto-create logic | -| Duplicate observations | Validation layers | Delete the code path allowing duplicates | -| UNIQUE constraint violations | Try-catch with fallbacks | Use `INSERT OR IGNORE` (5 characters) | -| Session not found | Silent auto-creation | **FAIL LOUDLY** (it's a hook bug) | - ---- - -## The 7+ Failed Attempts - -- **Nov 4**: "Always store session data regardless of pre-existence." Complexity planted. -- **Nov 11**: `INSERT OR IGNORE` recognized. But complexity documented, not removed. -- **Nov 21**: Duplicate observations bug. Fixed. Then broken again by endless mode. -- **Dec 5**: "6 hours of work delivered zero value." User requests self-audit. -- **Dec 20**: "Phase 2: Eliminated Race Conditions" β€” felt like progress. Complexity remained. -- **Dec 24**: Finally, forced deletion. - -The user stated "hooks provide session IDs, no extra management needed" **seven times** across months. Claude didn't listen. - ---- - -## The Fix - -### Deleted (984 lines): -- 11 `SessionStore` methods: `incrementPromptCounter`, `getPromptCounter`, `setWorkerPort`, `getWorkerPort`, `markSessionCompleted`, `markSessionFailed`, `reactivateSession`, `findActiveSDKSession`, `findAnySDKSession`, `updateSDKSessionId` -- Auto-create logic from `storeObservation` and `storeSummary` -- The entire cleanup hook (was aborting SDK agent and causing data loss) -- 117 lines from `worker-utils.ts` - -### What remains (~10 lines): -```javascript -createSDKSession(sessionId) { - db.run('INSERT OR IGNORE INTO sdk_sessions (...) VALUES (...)'); - return db.query('SELECT id FROM sdk_sessions WHERE ...').get(sessionId); -} -``` - -**That's it.** - ---- - -## Behavior Change - -- **Before:** Missing session? Auto-create silently. Bug hidden. -- **After:** Missing session? Storage fails. Bug visible immediately. - ---- - -## New Tools - -Since we're now explicit about recovery instead of silently papering over problems: - -- `GET /api/pending-queue` - See what's stuck -- `POST /api/pending-queue/process` - Manually trigger recovery -- `npm run queue:check` / `npm run queue:process` - CLI equivalents - ---- - -## Dependencies -- Upgraded `@anthropic-ai/claude-agent-sdk` from `^0.1.67` to `^0.1.76` - ---- - -**PR #437:** https://github.com/thedotmack/claude-mem/pull/437 - +## The 3-Month Battle Against Complexity + +**TL;DR:** For three months, Claude's instinct to add code instead of delete it caused the same bugs to recur. What should have been 5 lines of code became ~1000 lines, 11 useless methods, and 7+ failed "fixes." The timestamp corruption that finally broke things was just a symptom. The real achievement: **984 lines of code deleted.** + +--- + +## What Actually Happened + +Every Claude Code hook receives a session ID. That's all you need. + +But Claude built an entire redundant session management system on top: +- An `sdk_sessions` table with status tracking, port assignment, and prompt counting +- 11 methods in `SessionStore` to manage this artificial complexity +- Auto-creation logic scattered across 3 locations +- A cleanup hook that "completed" sessions at the end + +**Why?** Because it seemed "robust." Because "what if the session doesn't exist?" + +But the edge cases didn't exist. Hooks ALWAYS provide session IDs. The "defensive" code was solving imaginary problems while creating real ones. + +--- + +## The Pattern of Failure + +Every time a bug appeared, Claude's instinct was to **ADD** more code: + +| Bug | What Claude Added | What Should Have Happened | +|-----|------------------|--------------------------| +| Race conditions | Auto-create fallbacks | Delete the auto-create logic | +| Duplicate observations | Validation layers | Delete the code path allowing duplicates | +| UNIQUE constraint violations | Try-catch with fallbacks | Use `INSERT OR IGNORE` (5 characters) | +| Session not found | Silent auto-creation | **FAIL LOUDLY** (it's a hook bug) | + +--- + +## The 7+ Failed Attempts + +- **Nov 4**: "Always store session data regardless of pre-existence." Complexity planted. +- **Nov 11**: `INSERT OR IGNORE` recognized. But complexity documented, not removed. +- **Nov 21**: Duplicate observations bug. Fixed. Then broken again by endless mode. +- **Dec 5**: "6 hours of work delivered zero value." User requests self-audit. +- **Dec 20**: "Phase 2: Eliminated Race Conditions" β€” felt like progress. Complexity remained. +- **Dec 24**: Finally, forced deletion. + +The user stated "hooks provide session IDs, no extra management needed" **seven times** across months. Claude didn't listen. + +--- + +## The Fix + +### Deleted (984 lines): +- 11 `SessionStore` methods: `incrementPromptCounter`, `getPromptCounter`, `setWorkerPort`, `getWorkerPort`, `markSessionCompleted`, `markSessionFailed`, `reactivateSession`, `findActiveSDKSession`, `findAnySDKSession`, `updateSDKSessionId` +- Auto-create logic from `storeObservation` and `storeSummary` +- The entire cleanup hook (was aborting SDK agent and causing data loss) +- 117 lines from `worker-utils.ts` + +### What remains (~10 lines): +```javascript +createSDKSession(sessionId) { + db.run('INSERT OR IGNORE INTO sdk_sessions (...) VALUES (...)'); + return db.query('SELECT id FROM sdk_sessions WHERE ...').get(sessionId); +} +``` + +**That's it.** + +--- + +## Behavior Change + +- **Before:** Missing session? Auto-create silently. Bug hidden. +- **After:** Missing session? Storage fails. Bug visible immediately. + +--- + +## New Tools + +Since we're now explicit about recovery instead of silently papering over problems: + +- `GET /api/pending-queue` - See what's stuck +- `POST /api/pending-queue/process` - Manually trigger recovery +- `npm run queue:check` / `npm run queue:process` - CLI equivalents + +--- + +## Dependencies +- Upgraded `@anthropic-ai/claude-agent-sdk` from `^0.1.67` to `^0.1.76` + +--- + +**PR #437:** https://github.com/thedotmack/claude-mem/pull/437 + *The evidence: Observations #3646, #6738, #7598, #12860, #12866, #13046, #15259, #20995, #21055, #30524, #31080, #32114, #32116, #32125, #32126, #32127, #32146, #32324β€”the complete record of a 3-month battle.* ## [8.0.6] - 2025-12-24 @@ -570,13 +686,13 @@ This represents a major reliability improvement for Windows users, eliminating c ## [7.3.5] - 2025-12-17 -## What's Changed -* fix(windows): solve zombie port problem with wrapper architecture by @ToxMox in https://github.com/thedotmack/claude-mem/pull/372 -* chore: bump version to 7.3.5 by @thedotmack in https://github.com/thedotmack/claude-mem/pull/375 - -## New Contributors -* @ToxMox made their first contribution in https://github.com/thedotmack/claude-mem/pull/372 - +## What's Changed +* fix(windows): solve zombie port problem with wrapper architecture by @ToxMox in https://github.com/thedotmack/claude-mem/pull/372 +* chore: bump version to 7.3.5 by @thedotmack in https://github.com/thedotmack/claude-mem/pull/375 + +## New Contributors +* @ToxMox made their first contribution in https://github.com/thedotmack/claude-mem/pull/372 + **Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.3.4...v7.3.5 ## [7.3.4] - 2025-12-17 @@ -3106,12 +3222,12 @@ None (patch version) ## [4.3.0] - 2025-10-25 -## What's Changed -* feat: Enhanced context hook with session observations and cross-platform improvements by @thedotmack in https://github.com/thedotmack/claude-mem/pull/25 - -## New Contributors -* @thedotmack made their first contribution in https://github.com/thedotmack/claude-mem/pull/25 - +## What's Changed +* feat: Enhanced context hook with session observations and cross-platform improvements by @thedotmack in https://github.com/thedotmack/claude-mem/pull/25 + +## New Contributors +* @thedotmack made their first contribution in https://github.com/thedotmack/claude-mem/pull/25 + **Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v4.2.11...v4.3.0 ## [4.2.10] - 2025-10-25 diff --git a/package.json b/package.json index b98599c2..6086e48b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claude-mem", - "version": "8.2.10", + "version": "8.5.0", "description": "Memory compression system for Claude Code - persist context across sessions", "keywords": [ "claude", diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index 7671819a..f06e22d8 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "claude-mem", - "version": "8.2.10", + "version": "8.5.0", "description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions", "author": { "name": "Alex Newman" diff --git a/plugin/package.json b/plugin/package.json index a3686312..c3d46a35 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -1,6 +1,6 @@ { "name": "claude-mem-plugin", - "version": "8.2.10", + "version": "8.5.0", "private": true, "description": "Runtime dependencies for claude-mem bundled hooks", "type": "module", diff --git a/plugin/scripts/worker-service.cjs b/plugin/scripts/worker-service.cjs index d2fc04ed..c6465d94 100755 --- a/plugin/scripts/worker-service.cjs +++ b/plugin/scripts/worker-service.cjs @@ -1093,7 +1093,7 @@ Tips: WHERE project IS NOT NULL GROUP BY project ORDER BY MAX(created_at_epoch) DESC - `).all().map(o=>o.project);a.json({projects:i})});handleGetProcessingStatus=this.wrapHandler((r,a)=>{let n=this.sessionManager.isAnySessionProcessing(),s=this.sessionManager.getTotalActiveWork();a.json({isProcessing:n,queueDepth:s})});handleSetProcessing=this.wrapHandler((r,a)=>{this.workerService.broadcastProcessingStatus();let n=this.sessionManager.isAnySessionProcessing(),s=this.sessionManager.getTotalQueueDepth(),i=this.sessionManager.getActiveSessionCount();a.json({status:"ok",isProcessing:n,queueDepth:s,activeSessions:i})});parsePaginationParams(r){let a=parseInt(r.query.offset,10)||0,n=Math.min(parseInt(r.query.limit,10)||20,100),s=r.query.project;return{offset:a,limit:n,project:s}}handleImport=this.wrapHandler((r,a)=>{let{sessions:n,summaries:s,observations:i,prompts:o}=r.body,c={sessionsImported:0,sessionsSkipped:0,summariesImported:0,summariesSkipped:0,observationsImported:0,observationsSkipped:0,promptsImported:0,promptsSkipped:0},u=this.dbManager.getSessionStore();if(Array.isArray(n))for(let l of n)u.importSdkSession(l).imported?c.sessionsImported++:c.sessionsSkipped++;if(Array.isArray(s))for(let l of s)u.importSessionSummary(l).imported?c.summariesImported++:c.summariesSkipped++;if(Array.isArray(i))for(let l of i)u.importObservation(l).imported?c.observationsImported++:c.observationsSkipped++;if(Array.isArray(o))for(let l of o)u.importUserPrompt(l).imported?c.promptsImported++:c.promptsSkipped++;a.json({success:!0,stats:c})});handleGetPendingQueue=this.wrapHandler((r,a)=>{let{PendingMessageStore:n}=(Oo(),Vh(tl)),s=new n(this.dbManager.getSessionStore().db,3),i=s.getQueueMessages(),o=s.getRecentlyProcessed(20,30),c=s.getStuckCount(300*1e3),u=s.getSessionsWithPendingMessages();a.json({queue:{messages:i,totalPending:i.filter(l=>l.status==="pending").length,totalProcessing:i.filter(l=>l.status==="processing").length,totalFailed:i.filter(l=>l.status==="failed").length,stuckCount:c},recentlyProcessed:o,sessionsWithPendingWork:u})});handleProcessPendingQueue=this.wrapHandler(async(r,a)=>{let n=Math.min(Math.max(parseInt(r.body.sessionLimit,10)||10,1),100),s=await this.workerService.processPendingQueues(n);a.json({success:!0,...s})})};var cd=class extends jr{constructor(r){super();this.searchManager=r}setupRoutes(r){r.get("/api/search",this.handleUnifiedSearch.bind(this)),r.get("/api/timeline",this.handleUnifiedTimeline.bind(this)),r.get("/api/decisions",this.handleDecisions.bind(this)),r.get("/api/changes",this.handleChanges.bind(this)),r.get("/api/how-it-works",this.handleHowItWorks.bind(this)),r.get("/api/search/observations",this.handleSearchObservations.bind(this)),r.get("/api/search/sessions",this.handleSearchSessions.bind(this)),r.get("/api/search/prompts",this.handleSearchPrompts.bind(this)),r.get("/api/search/by-concept",this.handleSearchByConcept.bind(this)),r.get("/api/search/by-file",this.handleSearchByFile.bind(this)),r.get("/api/search/by-type",this.handleSearchByType.bind(this)),r.get("/api/context/recent",this.handleGetRecentContext.bind(this)),r.get("/api/context/timeline",this.handleGetContextTimeline.bind(this)),r.get("/api/context/preview",this.handleContextPreview.bind(this)),r.get("/api/context/inject",this.handleContextInject.bind(this)),r.get("/api/timeline/by-query",this.handleGetTimelineByQuery.bind(this)),r.get("/api/search/help",this.handleSearchHelp.bind(this))}handleUnifiedSearch=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.search(r.query);a.json(n)});handleUnifiedTimeline=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.timeline(r.query);a.json(n)});handleDecisions=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.decisions(r.query);a.json(n)});handleChanges=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.changes(r.query);a.json(n)});handleHowItWorks=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.howItWorks(r.query);a.json(n)});handleSearchObservations=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.searchObservations(r.query);a.json(n)});handleSearchSessions=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.searchSessions(r.query);a.json(n)});handleSearchPrompts=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.searchUserPrompts(r.query);a.json(n)});handleSearchByConcept=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.findByConcept(r.query);a.json(n)});handleSearchByFile=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.findByFile(r.query);a.json(n)});handleSearchByType=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.findByType(r.query);a.json(n)});handleGetRecentContext=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.getRecentContext(r.query);a.json(n)});handleGetContextTimeline=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.getContextTimeline(r.query);a.json(n)});handleContextPreview=this.wrapHandler(async(r,a)=>{let n=r.query.project;if(!n){this.badRequest(a,"Project parameter is required");return}let{generateContext:s}=await Promise.resolve().then(()=>(Sh(),bh)),i=`/preview/${n}`,o=await s({session_id:"preview-"+Date.now(),cwd:i},!0);a.setHeader("Content-Type","text/plain; charset=utf-8"),a.send(o)});handleContextInject=this.wrapHandler(async(r,a)=>{let n=r.query.project,s=r.query.colors==="true";if(!n){this.badRequest(a,"Project parameter is required");return}let{generateContext:i}=await Promise.resolve().then(()=>(Sh(),bh)),o=`/context/${n}`,c=await i({session_id:"context-inject-"+Date.now(),cwd:o},s);a.setHeader("Content-Type","text/plain; charset=utf-8"),a.send(c)});handleGetTimelineByQuery=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.getTimelineByQuery(r.query);a.json(n)});handleSearchHelp=this.wrapHandler((r,a)=>{a.json({title:"Claude-Mem Search API",description:"HTTP API for searching persistent memory",endpoints:[{path:"/api/search/observations",method:"GET",description:"Search observations using full-text search",parameters:{query:"Search query (required)",limit:"Number of results (default: 20)",project:"Filter by project name (optional)"}},{path:"/api/search/sessions",method:"GET",description:"Search session summaries using full-text search",parameters:{query:"Search query (required)",limit:"Number of results (default: 20)"}},{path:"/api/search/prompts",method:"GET",description:"Search user prompts using full-text search",parameters:{query:"Search query (required)",limit:"Number of results (default: 20)",project:"Filter by project name (optional)"}},{path:"/api/search/by-concept",method:"GET",description:"Find observations by concept tag",parameters:{concept:"Concept tag (required): discovery, decision, bugfix, feature, refactor",limit:"Number of results (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/by-file",method:"GET",description:"Find observations and sessions by file path",parameters:{filePath:"File path or partial path (required)",limit:"Number of results per type (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/by-type",method:"GET",description:"Find observations by type",parameters:{type:"Observation type (required): discovery, decision, bugfix, feature, refactor",limit:"Number of results (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/context/recent",method:"GET",description:"Get recent session context including summaries and observations",parameters:{project:"Project name (default: current directory)",limit:"Number of recent sessions (default: 3)"}},{path:"/api/context/timeline",method:"GET",description:"Get unified timeline around a specific point in time",parameters:{anchor:'Anchor point: observation ID, session ID (e.g., "S123"), or ISO timestamp (required)',depth_before:"Number of records before anchor (default: 10)",depth_after:"Number of records after anchor (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/timeline/by-query",method:"GET",description:"Search for best match, then get timeline around it",parameters:{query:"Search query (required)",mode:'Search mode: "auto", "observations", or "sessions" (default: "auto")',depth_before:"Number of records before match (default: 10)",depth_after:"Number of records after match (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/help",method:"GET",description:"Get this help documentation"}],examples:['curl "http://localhost:37777/api/search/observations?query=authentication&limit=5"','curl "http://localhost:37777/api/search/by-type?type=bugfix&limit=10"','curl "http://localhost:37777/api/context/recent?project=claude-mem&limit=3"','curl "http://localhost:37777/api/context/timeline?anchor=123&depth_before=5&depth_after=5"']})})};var Ja=xt(require("path"),1),zt=require("fs"),Th=require("os");mr();ot();var wh=require("child_process"),Xa=require("fs"),UR=require("os"),rc=require("path");ot();var nc=(0,rc.join)((0,UR.homedir)(),".claude","plugins","marketplaces","thedotmack");function xh(t){return!t||typeof t!="string"?!1:/^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/.test(t)&&!t.includes("..")}var RV=3e5,Eh=6e5;function Dr(t){let e=(0,wh.spawnSync)("git",t,{cwd:nc,encoding:"utf-8",timeout:RV,windowsHide:!0,shell:!1});if(e.error)throw e.error;if(e.status!==0)throw new Error(e.stderr||e.stdout||"Git command failed");return e.stdout.trim()}function zR(t,e=Eh){let a=process.platform==="win32"?"npm.cmd":"npm",n=(0,wh.spawnSync)(a,t,{cwd:nc,encoding:"utf-8",timeout:e,windowsHide:!0,shell:!1});if(n.error)throw n.error;if(n.status!==0)throw new Error(n.stderr||n.stdout||"npm command failed");return n.stdout.trim()}function ud(){let t=(0,rc.join)(nc,".git");if(!(0,Xa.existsSync)(t))return{branch:null,isBeta:!1,isGitRepo:!1,isDirty:!1,canSwitch:!1,error:"Installed plugin is not a git repository"};try{let e=Dr(["rev-parse","--abbrev-ref","HEAD"]),a=Dr(["status","--porcelain"]).length>0,n=e.startsWith("beta");return{branch:e,isBeta:n,isGitRepo:!0,isDirty:a,canSwitch:!0}}catch(e){return P.error("BRANCH","Failed to get branch info",{},e),{branch:null,isBeta:!1,isGitRepo:!0,isDirty:!1,canSwitch:!1,error:e.message}}}async function HR(t){if(!xh(t))return{success:!1,error:`Invalid branch name: ${t}. Branch names must be alphanumeric with hyphens, underscores, slashes, or dots.`};let e=ud();if(!e.isGitRepo)return{success:!1,error:"Installed plugin is not a git repository. Please reinstall."};if(e.branch===t)return{success:!0,branch:t,message:`Already on branch ${t}`};try{P.info("BRANCH","Starting branch switch",{from:e.branch,to:t}),P.debug("BRANCH","Discarding local changes"),Dr(["checkout","--","."]),Dr(["clean","-fd"]),P.debug("BRANCH","Fetching from origin"),Dr(["fetch","origin"]),P.debug("BRANCH","Checking out branch",{branch:t});try{Dr(["checkout",t])}catch{Dr(["checkout","-b",t,`origin/${t}`])}P.debug("BRANCH","Pulling latest"),Dr(["pull","origin",t]);let r=(0,rc.join)(nc,".install-version");return(0,Xa.existsSync)(r)&&(0,Xa.unlinkSync)(r),P.debug("BRANCH","Running npm install"),zR(["install"],Eh),P.success("BRANCH","Branch switch complete",{branch:t}),{success:!0,branch:t,message:`Switched to ${t}. Worker will restart automatically.`}}catch(r){P.error("BRANCH","Branch switch failed",{targetBranch:t},r);try{e.branch&&xh(e.branch)&&Dr(["checkout",e.branch])}catch{}return{success:!1,error:`Branch switch failed: ${r.message}`}}}async function BR(){let t=ud();if(!t.isGitRepo||!t.branch)return{success:!1,error:"Cannot pull updates: not a git repository"};try{if(!xh(t.branch))return{success:!1,error:`Invalid current branch name: ${t.branch}`};P.info("BRANCH","Pulling updates",{branch:t.branch}),Dr(["checkout","--","."]),Dr(["fetch","origin"]),Dr(["pull","origin",t.branch]);let e=(0,rc.join)(nc,".install-version");return(0,Xa.existsSync)(e)&&(0,Xa.unlinkSync)(e),zR(["install"],Eh),P.success("BRANCH","Updates pulled",{branch:t.branch}),{success:!0,branch:t.branch,message:`Updated ${t.branch}. Worker will restart automatically.`}}catch(e){return P.error("BRANCH","Pull failed",{},e),{success:!1,error:`Pull failed: ${e.message}`}}}Or();var ld=class extends jr{constructor(r){super();this.settingsManager=r}setupRoutes(r){r.get("/api/settings",this.handleGetSettings.bind(this)),r.post("/api/settings",this.handleUpdateSettings.bind(this)),r.get("/api/mcp/status",this.handleGetMcpStatus.bind(this)),r.post("/api/mcp/toggle",this.handleToggleMcp.bind(this)),r.get("/api/branch/status",this.handleGetBranchStatus.bind(this)),r.post("/api/branch/switch",this.handleSwitchBranch.bind(this)),r.post("/api/branch/update",this.handleUpdateBranch.bind(this))}handleGetSettings=this.wrapHandler((r,a)=>{let n=Ja.default.join((0,Th.homedir)(),".claude-mem","settings.json");this.ensureSettingsFile(n);let s=Ze.loadFromFile(n);a.json(s)});handleUpdateSettings=this.wrapHandler((r,a)=>{let n=this.validateSettings(r.body);if(!n.valid){a.status(400).json({success:!1,error:n.error});return}let s=Ja.default.join((0,Th.homedir)(),".claude-mem","settings.json");this.ensureSettingsFile(s);let i={};if((0,zt.existsSync)(s)){let c=(0,zt.readFileSync)(s,"utf-8");try{i=JSON.parse(c)}catch(u){P.error("SETTINGS","Failed to parse settings file",{settingsPath:s},u),a.status(500).json({success:!1,error:"Settings file is corrupted. Delete ~/.claude-mem/settings.json to reset."});return}}let o=["CLAUDE_MEM_MODEL","CLAUDE_MEM_CONTEXT_OBSERVATIONS","CLAUDE_MEM_WORKER_PORT","CLAUDE_MEM_WORKER_HOST","CLAUDE_MEM_PROVIDER","CLAUDE_MEM_GEMINI_API_KEY","CLAUDE_MEM_GEMINI_MODEL","CLAUDE_MEM_GEMINI_RATE_LIMITING_ENABLED","CLAUDE_MEM_OPENROUTER_API_KEY","CLAUDE_MEM_OPENROUTER_MODEL","CLAUDE_MEM_OPENROUTER_SITE_URL","CLAUDE_MEM_OPENROUTER_APP_NAME","CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES","CLAUDE_MEM_OPENROUTER_MAX_TOKENS","CLAUDE_MEM_DATA_DIR","CLAUDE_MEM_LOG_LEVEL","CLAUDE_MEM_PYTHON_VERSION","CLAUDE_CODE_PATH","CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT","CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES","CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS","CLAUDE_MEM_CONTEXT_FULL_COUNT","CLAUDE_MEM_CONTEXT_FULL_FIELD","CLAUDE_MEM_CONTEXT_SESSION_COUNT","CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY","CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE"];for(let c of o)r.body[c]!==void 0&&(i[c]=r.body[c]);(0,zt.writeFileSync)(s,JSON.stringify(i,null,2),"utf-8"),E1(),P.info("WORKER","Settings updated"),a.json({success:!0,message:"Settings updated successfully"})});handleGetMcpStatus=this.wrapHandler((r,a)=>{let n=this.isMcpEnabled();a.json({enabled:n})});handleToggleMcp=this.wrapHandler((r,a)=>{let{enabled:n}=r.body;if(typeof n!="boolean"){this.badRequest(a,"enabled must be a boolean");return}this.toggleMcp(n),a.json({success:!0,enabled:this.isMcpEnabled()})});handleGetBranchStatus=this.wrapHandler((r,a)=>{let n=ud();a.json(n)});handleSwitchBranch=this.wrapHandler(async(r,a)=>{let{branch:n}=r.body;if(!n){a.status(400).json({success:!1,error:"Missing branch parameter"});return}let s=["main","beta/7.0","feature/bun-executable"];if(!s.includes(n)){a.status(400).json({success:!1,error:`Invalid branch. Allowed: ${s.join(", ")}`});return}P.info("WORKER","Branch switch requested",{branch:n});let i=await HR(n);i.success&&setTimeout(()=>{P.info("WORKER","Restarting worker after branch switch"),process.exit(0)},1e3),a.json(i)});handleUpdateBranch=this.wrapHandler(async(r,a)=>{P.info("WORKER","Branch update requested");let n=await BR();n.success&&setTimeout(()=>{P.info("WORKER","Restarting worker after branch update"),process.exit(0)},1e3),a.json(n)});validateSettings(r){if(r.CLAUDE_MEM_PROVIDER&&!["claude","gemini","openrouter"].includes(r.CLAUDE_MEM_PROVIDER))return{valid:!1,error:'CLAUDE_MEM_PROVIDER must be "claude", "gemini", or "openrouter"'};if(r.CLAUDE_MEM_GEMINI_MODEL&&!["gemini-2.5-flash-lite","gemini-2.5-flash","gemini-3-flash"].includes(r.CLAUDE_MEM_GEMINI_MODEL))return{valid:!1,error:"CLAUDE_MEM_GEMINI_MODEL must be one of: gemini-2.5-flash-lite, gemini-2.5-flash, gemini-3-flash"};if(r.CLAUDE_MEM_CONTEXT_OBSERVATIONS){let n=parseInt(r.CLAUDE_MEM_CONTEXT_OBSERVATIONS,10);if(isNaN(n)||n<1||n>200)return{valid:!1,error:"CLAUDE_MEM_CONTEXT_OBSERVATIONS must be between 1 and 200"}}if(r.CLAUDE_MEM_WORKER_PORT){let n=parseInt(r.CLAUDE_MEM_WORKER_PORT,10);if(isNaN(n)||n<1024||n>65535)return{valid:!1,error:"CLAUDE_MEM_WORKER_PORT must be between 1024 and 65535"}}if(r.CLAUDE_MEM_WORKER_HOST){let n=r.CLAUDE_MEM_WORKER_HOST;if(!/^(127\.0\.0\.1|0\.0\.0\.0|localhost|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.test(n))return{valid:!1,error:"CLAUDE_MEM_WORKER_HOST must be a valid IP address (e.g., 127.0.0.1, 0.0.0.0)"}}if(r.CLAUDE_MEM_LOG_LEVEL&&!["DEBUG","INFO","WARN","ERROR","SILENT"].includes(r.CLAUDE_MEM_LOG_LEVEL.toUpperCase()))return{valid:!1,error:"CLAUDE_MEM_LOG_LEVEL must be one of: DEBUG, INFO, WARN, ERROR, SILENT"};if(r.CLAUDE_MEM_PYTHON_VERSION&&!/^3\.\d{1,2}$/.test(r.CLAUDE_MEM_PYTHON_VERSION))return{valid:!1,error:'CLAUDE_MEM_PYTHON_VERSION must be in format "3.X" or "3.XX" (e.g., "3.13")'};let a=["CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT","CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY","CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE"];for(let n of a)if(r[n]&&!["true","false"].includes(r[n]))return{valid:!1,error:`${n} must be "true" or "false"`};if(r.CLAUDE_MEM_CONTEXT_FULL_COUNT){let n=parseInt(r.CLAUDE_MEM_CONTEXT_FULL_COUNT,10);if(isNaN(n)||n<0||n>20)return{valid:!1,error:"CLAUDE_MEM_CONTEXT_FULL_COUNT must be between 0 and 20"}}if(r.CLAUDE_MEM_CONTEXT_SESSION_COUNT){let n=parseInt(r.CLAUDE_MEM_CONTEXT_SESSION_COUNT,10);if(isNaN(n)||n<1||n>50)return{valid:!1,error:"CLAUDE_MEM_CONTEXT_SESSION_COUNT must be between 1 and 50"}}if(r.CLAUDE_MEM_CONTEXT_FULL_FIELD&&!["narrative","facts"].includes(r.CLAUDE_MEM_CONTEXT_FULL_FIELD))return{valid:!1,error:'CLAUDE_MEM_CONTEXT_FULL_FIELD must be "narrative" or "facts"'};if(r.CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES){let n=parseInt(r.CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES,10);if(isNaN(n)||n<1||n>100)return{valid:!1,error:"CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES must be between 1 and 100"}}if(r.CLAUDE_MEM_OPENROUTER_MAX_TOKENS){let n=parseInt(r.CLAUDE_MEM_OPENROUTER_MAX_TOKENS,10);if(isNaN(n)||n<1e3||n>1e6)return{valid:!1,error:"CLAUDE_MEM_OPENROUTER_MAX_TOKENS must be between 1000 and 1000000"}}if(r.CLAUDE_MEM_OPENROUTER_SITE_URL)try{new URL(r.CLAUDE_MEM_OPENROUTER_SITE_URL)}catch{return{valid:!1,error:"CLAUDE_MEM_OPENROUTER_SITE_URL must be a valid URL"}}return{valid:!0}}isMcpEnabled(){let r=or(),a=Ja.default.join(r,"plugin",".mcp.json");return(0,zt.existsSync)(a)}toggleMcp(r){let a=or(),n=Ja.default.join(a,"plugin",".mcp.json"),s=Ja.default.join(a,"plugin",".mcp.json.disabled");r&&(0,zt.existsSync)(s)?((0,zt.renameSync)(s,n),P.info("WORKER","MCP search server enabled")):!r&&(0,zt.existsSync)(n)?((0,zt.renameSync)(n,s),P.info("WORKER","MCP search server disabled")):P.debug("WORKER","MCP toggle no-op (already in desired state)",{enabled:r})}ensureSettingsFile(r){if(!(0,zt.existsSync)(r)){let a=Ze.getAllDefaults(),n=Ja.default.dirname(r);(0,zt.existsSync)(n)||(0,zt.mkdirSync)(n,{recursive:!0}),(0,zt.writeFileSync)(r,JSON.stringify(a,null,2),"utf-8"),P.info("SETTINGS","Created settings file with defaults",{settingsPath:r})}}};var ac=(0,ZR.promisify)(ra.exec),PV="8.2.10",Ch=xe.default.join((0,Wr.homedir)(),".claude-mem"),Qa=xe.default.join(Ch,"worker.pid"),WR=xe.default.join(Ch,"cursor-projects.json");function $h(t){(0,Ae.mkdirSync)(Ch,{recursive:!0}),(0,Ae.writeFileSync)(Qa,JSON.stringify(t,null,2))}function $V(){try{return(0,Ae.existsSync)(Qa)?JSON.parse((0,Ae.readFileSync)(Qa,"utf-8")):null}catch(t){return P.warn("SYSTEM","Failed to read PID file",{path:Qa,error:t.message}),null}}function ta(){try{(0,Ae.existsSync)(Qa)&&(0,Ae.unlinkSync)(Qa)}catch(t){P.warn("SYSTEM","Failed to remove PID file",{path:Qa,error:t.message})}}function Ih(){return T1(WR)}function KR(t){k1(WR,t)}function OV(t,e){let r=Ih();r[t]={workspacePath:e,installedAt:new Date().toISOString()},KR(r),P.info("CURSOR","Registered project for auto-context updates",{projectName:t,workspacePath:e})}function CV(t){let e=Ih();e[t]&&(delete e[t],KR(e),P.info("CURSOR","Unregistered project",{projectName:t}))}async function Za(t,e){let a=Ih()[t];if(a)try{let n=await fetch(`http://127.0.0.1:${e}/api/context/inject?project=${encodeURIComponent(t)}`);if(!n.ok)return;let s=await n.text();if(!s||!s.trim())return;R1(a.workspacePath,s),P.debug("CURSOR","Updated context file",{projectName:t,workspacePath:a.workspacePath})}catch(n){P.warn("CURSOR","Failed to update context file",{projectName:t,error:n.message})}}function Ya(t){return process.platform==="win32"?Math.round(t*2):t}async function Oh(t){try{return(await fetch(`http://127.0.0.1:${t}/api/health`)).ok}catch{return!1}}async function Pi(t,e=3e4){let r=Date.now();for(;Date.now()-rsetTimeout(a,500))}return!1}async function kh(t){try{let e=await fetch(`http://127.0.0.1:${t}/api/admin/shutdown`,{method:"POST"});return e.ok?!0:(P.warn("SYSTEM","Shutdown request returned error",{port:t,status:e.status}),!1)}catch(e){return e.message?.includes("ECONNREFUSED")||P.warn("SYSTEM","Shutdown request failed",{port:t,error:e.message}),!1}}async function Rh(t,e=1e4){let r=Date.now();for(;Date.now()-rsetTimeout(a,500))}return!1}function IV(){let t=xe.default.join((0,Wr.homedir)(),".claude","plugins","marketplaces","thedotmack"),e=xe.default.join(t,"package.json");return JSON.parse((0,Ae.readFileSync)(e,"utf-8")).version}async function AV(t){try{let e=await fetch(`http://127.0.0.1:${t}/api/version`);return e.ok?(await e.json()).version:null}catch{return null}}async function NV(t){let e=IV(),r=await AV(t);return r?{matches:e===r,pluginVersion:e,workerVersion:r}:{matches:!0,pluginVersion:e,workerVersion:r}}var dd=class{app;server=null;startTime=Date.now();mcpClient;mcpReady=!1;initializationCompleteFlag=!1;isShuttingDown=!1;dbManager;sessionManager;sseBroadcaster;sdkAgent;geminiAgent;openRouterAgent;paginationHelper;settingsManager;sessionEventBroadcaster;viewerRoutes;sessionRoutes;dataRoutes;searchRoutes;settingsRoutes;initializationComplete;resolveInitialization;constructor(){this.app=(0,VR.default)(),this.initializationComplete=new Promise(e=>{this.resolveInitialization=e}),this.dbManager=new el,this.sessionManager=new nl(this.dbManager),this.sseBroadcaster=new al,this.sdkAgent=new Ul(this.dbManager,this.sessionManager),this.geminiAgent=new zl(this.dbManager,this.sessionManager),this.geminiAgent.setFallbackAgent(this.sdkAgent),this.openRouterAgent=new Vl(this.dbManager,this.sessionManager),this.openRouterAgent.setFallbackAgent(this.sdkAgent),this.paginationHelper=new Gl(this.dbManager),this.settingsManager=new Zl(this.dbManager),this.sessionEventBroadcaster=new Yl(this.sseBroadcaster,this),this.sessionManager.setOnSessionDeleted(()=>{this.broadcastProcessingStatus()}),this.mcpClient=new Hs({name:"worker-search-proxy",version:"1.0.0"},{capabilities:{}}),this.viewerRoutes=new ed(this.sseBroadcaster,this.dbManager,this.sessionManager),this.sessionRoutes=new nd(this.sessionManager,this.dbManager,this.sdkAgent,this.geminiAgent,this.openRouterAgent,this.sessionEventBroadcaster,this),this.dataRoutes=new ad(this.paginationHelper,this.dbManager,this.sessionManager,this.sseBroadcaster,this,this.startTime),this.searchRoutes=null,this.settingsRoutes=new ld(this.settingsManager),this.setupMiddleware(),this.setupRoutes(),this.registerSignalHandlers()}registerSignalHandlers(){let e=async r=>{if(this.isShuttingDown){P.warn("SYSTEM",`Received ${r} but shutdown already in progress`);return}this.isShuttingDown=!0,P.info("SYSTEM",`Received ${r}, shutting down...`);try{await this.shutdown(),process.exit(0)}catch(a){P.error("SYSTEM","Error during shutdown",{},a),process.exit(1)}};process.on("SIGTERM",()=>e("SIGTERM")),process.on("SIGINT",()=>e("SIGINT"))}setupMiddleware(){OR(this.summarizeRequestBody.bind(this)).forEach(r=>this.app.use(r))}setupRoutes(){let e="TEST-008-wrapper-ipc";this.app.get("/api/health",(r,a)=>{a.status(200).json({status:"ok",build:e,managed:process.env.CLAUDE_MEM_MANAGED==="true",hasIpc:typeof process.send=="function",platform:process.platform,pid:process.pid,initialized:this.initializationCompleteFlag,mcpReady:this.mcpReady})}),this.app.get("/api/readiness",(r,a)=>{this.initializationCompleteFlag?a.status(200).json({status:"ready",mcpReady:this.mcpReady}):a.status(503).json({status:"initializing",message:"Worker is still initializing, please retry"})}),this.app.get("/api/version",(r,a)=>{a.status(200).json({version:PV})}),this.app.get("/api/instructions",async(r,a)=>{let n=r.query.topic||"all",s=r.query.operation;try{let i;if(s){let o=xe.default.join(__dirname,"../skills/mem-search/operations",`${s}.md`);i=await Ph.promises.readFile(o,"utf-8")}else{let o=xe.default.join(__dirname,"../skills/mem-search/SKILL.md"),c=await Ph.promises.readFile(o,"utf-8");i=this.extractInstructionSection(c,n)}a.json({content:[{type:"text",text:i}]})}catch(i){P.error("WORKER","Failed to load instructions",{topic:n,operation:s},i),a.status(500).json({content:[{type:"text",text:`Error loading instructions: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0})}}),this.app.post("/api/admin/restart",gh,async(r,a)=>{a.json({status:"restarting"}),process.platform==="win32"&&process.env.CLAUDE_MEM_MANAGED==="true"&&process.send?(P.info("SYSTEM","Sending restart request to wrapper"),process.send({type:"restart"})):setTimeout(async()=>{await this.shutdown(),process.exit(0)},100)}),this.app.post("/api/admin/shutdown",gh,async(r,a)=>{a.json({status:"shutting_down"}),process.platform==="win32"&&process.env.CLAUDE_MEM_MANAGED==="true"&&process.send?(P.info("SYSTEM","Sending shutdown request to wrapper"),process.send({type:"shutdown"})):setTimeout(async()=>{await this.shutdown(),process.exit(0)},100)}),this.viewerRoutes.setupRoutes(this.app),this.sessionRoutes.setupRoutes(this.app),this.dataRoutes.setupRoutes(this.app),this.settingsRoutes.setupRoutes(this.app),this.app.get("/api/context/inject",async(r,a,n)=>{try{let i=new Promise((o,c)=>setTimeout(()=>c(new Error("Initialization timeout")),3e5));if(await Promise.race([this.initializationComplete,i]),!this.searchRoutes){a.status(503).json({error:"Search routes not initialized"});return}n()}catch(s){P.error("WORKER","Context inject handler failed",{},s),a.headersSent||a.status(500).json({error:s instanceof Error?s.message:"Internal server error"})}})}async cleanupOrphanedProcesses(){let e=process.platform==="win32",r=[];if(e){let a=`powershell -Command "Get-CimInstance Win32_Process | Where-Object { $_.Name -like '*python*' -and $_.CommandLine -like '*chroma-mcp*' } | Select-Object -ExpandProperty ProcessId"`,{stdout:n}=await ac(a,{timeout:6e4});if(!n.trim()){P.debug("SYSTEM","No orphaned chroma-mcp processes found (Windows)");return}let s=n.trim().split(` + `).all().map(o=>o.project);a.json({projects:i})});handleGetProcessingStatus=this.wrapHandler((r,a)=>{let n=this.sessionManager.isAnySessionProcessing(),s=this.sessionManager.getTotalActiveWork();a.json({isProcessing:n,queueDepth:s})});handleSetProcessing=this.wrapHandler((r,a)=>{this.workerService.broadcastProcessingStatus();let n=this.sessionManager.isAnySessionProcessing(),s=this.sessionManager.getTotalQueueDepth(),i=this.sessionManager.getActiveSessionCount();a.json({status:"ok",isProcessing:n,queueDepth:s,activeSessions:i})});parsePaginationParams(r){let a=parseInt(r.query.offset,10)||0,n=Math.min(parseInt(r.query.limit,10)||20,100),s=r.query.project;return{offset:a,limit:n,project:s}}handleImport=this.wrapHandler((r,a)=>{let{sessions:n,summaries:s,observations:i,prompts:o}=r.body,c={sessionsImported:0,sessionsSkipped:0,summariesImported:0,summariesSkipped:0,observationsImported:0,observationsSkipped:0,promptsImported:0,promptsSkipped:0},u=this.dbManager.getSessionStore();if(Array.isArray(n))for(let l of n)u.importSdkSession(l).imported?c.sessionsImported++:c.sessionsSkipped++;if(Array.isArray(s))for(let l of s)u.importSessionSummary(l).imported?c.summariesImported++:c.summariesSkipped++;if(Array.isArray(i))for(let l of i)u.importObservation(l).imported?c.observationsImported++:c.observationsSkipped++;if(Array.isArray(o))for(let l of o)u.importUserPrompt(l).imported?c.promptsImported++:c.promptsSkipped++;a.json({success:!0,stats:c})});handleGetPendingQueue=this.wrapHandler((r,a)=>{let{PendingMessageStore:n}=(Oo(),Vh(tl)),s=new n(this.dbManager.getSessionStore().db,3),i=s.getQueueMessages(),o=s.getRecentlyProcessed(20,30),c=s.getStuckCount(300*1e3),u=s.getSessionsWithPendingMessages();a.json({queue:{messages:i,totalPending:i.filter(l=>l.status==="pending").length,totalProcessing:i.filter(l=>l.status==="processing").length,totalFailed:i.filter(l=>l.status==="failed").length,stuckCount:c},recentlyProcessed:o,sessionsWithPendingWork:u})});handleProcessPendingQueue=this.wrapHandler(async(r,a)=>{let n=Math.min(Math.max(parseInt(r.body.sessionLimit,10)||10,1),100),s=await this.workerService.processPendingQueues(n);a.json({success:!0,...s})})};var cd=class extends jr{constructor(r){super();this.searchManager=r}setupRoutes(r){r.get("/api/search",this.handleUnifiedSearch.bind(this)),r.get("/api/timeline",this.handleUnifiedTimeline.bind(this)),r.get("/api/decisions",this.handleDecisions.bind(this)),r.get("/api/changes",this.handleChanges.bind(this)),r.get("/api/how-it-works",this.handleHowItWorks.bind(this)),r.get("/api/search/observations",this.handleSearchObservations.bind(this)),r.get("/api/search/sessions",this.handleSearchSessions.bind(this)),r.get("/api/search/prompts",this.handleSearchPrompts.bind(this)),r.get("/api/search/by-concept",this.handleSearchByConcept.bind(this)),r.get("/api/search/by-file",this.handleSearchByFile.bind(this)),r.get("/api/search/by-type",this.handleSearchByType.bind(this)),r.get("/api/context/recent",this.handleGetRecentContext.bind(this)),r.get("/api/context/timeline",this.handleGetContextTimeline.bind(this)),r.get("/api/context/preview",this.handleContextPreview.bind(this)),r.get("/api/context/inject",this.handleContextInject.bind(this)),r.get("/api/timeline/by-query",this.handleGetTimelineByQuery.bind(this)),r.get("/api/search/help",this.handleSearchHelp.bind(this))}handleUnifiedSearch=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.search(r.query);a.json(n)});handleUnifiedTimeline=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.timeline(r.query);a.json(n)});handleDecisions=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.decisions(r.query);a.json(n)});handleChanges=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.changes(r.query);a.json(n)});handleHowItWorks=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.howItWorks(r.query);a.json(n)});handleSearchObservations=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.searchObservations(r.query);a.json(n)});handleSearchSessions=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.searchSessions(r.query);a.json(n)});handleSearchPrompts=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.searchUserPrompts(r.query);a.json(n)});handleSearchByConcept=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.findByConcept(r.query);a.json(n)});handleSearchByFile=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.findByFile(r.query);a.json(n)});handleSearchByType=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.findByType(r.query);a.json(n)});handleGetRecentContext=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.getRecentContext(r.query);a.json(n)});handleGetContextTimeline=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.getContextTimeline(r.query);a.json(n)});handleContextPreview=this.wrapHandler(async(r,a)=>{let n=r.query.project;if(!n){this.badRequest(a,"Project parameter is required");return}let{generateContext:s}=await Promise.resolve().then(()=>(Sh(),bh)),i=`/preview/${n}`,o=await s({session_id:"preview-"+Date.now(),cwd:i},!0);a.setHeader("Content-Type","text/plain; charset=utf-8"),a.send(o)});handleContextInject=this.wrapHandler(async(r,a)=>{let n=r.query.project,s=r.query.colors==="true";if(!n){this.badRequest(a,"Project parameter is required");return}let{generateContext:i}=await Promise.resolve().then(()=>(Sh(),bh)),o=`/context/${n}`,c=await i({session_id:"context-inject-"+Date.now(),cwd:o},s);a.setHeader("Content-Type","text/plain; charset=utf-8"),a.send(c)});handleGetTimelineByQuery=this.wrapHandler(async(r,a)=>{let n=await this.searchManager.getTimelineByQuery(r.query);a.json(n)});handleSearchHelp=this.wrapHandler((r,a)=>{a.json({title:"Claude-Mem Search API",description:"HTTP API for searching persistent memory",endpoints:[{path:"/api/search/observations",method:"GET",description:"Search observations using full-text search",parameters:{query:"Search query (required)",limit:"Number of results (default: 20)",project:"Filter by project name (optional)"}},{path:"/api/search/sessions",method:"GET",description:"Search session summaries using full-text search",parameters:{query:"Search query (required)",limit:"Number of results (default: 20)"}},{path:"/api/search/prompts",method:"GET",description:"Search user prompts using full-text search",parameters:{query:"Search query (required)",limit:"Number of results (default: 20)",project:"Filter by project name (optional)"}},{path:"/api/search/by-concept",method:"GET",description:"Find observations by concept tag",parameters:{concept:"Concept tag (required): discovery, decision, bugfix, feature, refactor",limit:"Number of results (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/by-file",method:"GET",description:"Find observations and sessions by file path",parameters:{filePath:"File path or partial path (required)",limit:"Number of results per type (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/by-type",method:"GET",description:"Find observations by type",parameters:{type:"Observation type (required): discovery, decision, bugfix, feature, refactor",limit:"Number of results (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/context/recent",method:"GET",description:"Get recent session context including summaries and observations",parameters:{project:"Project name (default: current directory)",limit:"Number of recent sessions (default: 3)"}},{path:"/api/context/timeline",method:"GET",description:"Get unified timeline around a specific point in time",parameters:{anchor:'Anchor point: observation ID, session ID (e.g., "S123"), or ISO timestamp (required)',depth_before:"Number of records before anchor (default: 10)",depth_after:"Number of records after anchor (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/timeline/by-query",method:"GET",description:"Search for best match, then get timeline around it",parameters:{query:"Search query (required)",mode:'Search mode: "auto", "observations", or "sessions" (default: "auto")',depth_before:"Number of records before match (default: 10)",depth_after:"Number of records after match (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/help",method:"GET",description:"Get this help documentation"}],examples:['curl "http://localhost:37777/api/search/observations?query=authentication&limit=5"','curl "http://localhost:37777/api/search/by-type?type=bugfix&limit=10"','curl "http://localhost:37777/api/context/recent?project=claude-mem&limit=3"','curl "http://localhost:37777/api/context/timeline?anchor=123&depth_before=5&depth_after=5"']})})};var Ja=xt(require("path"),1),zt=require("fs"),Th=require("os");mr();ot();var wh=require("child_process"),Xa=require("fs"),UR=require("os"),rc=require("path");ot();var nc=(0,rc.join)((0,UR.homedir)(),".claude","plugins","marketplaces","thedotmack");function xh(t){return!t||typeof t!="string"?!1:/^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/.test(t)&&!t.includes("..")}var RV=3e5,Eh=6e5;function Dr(t){let e=(0,wh.spawnSync)("git",t,{cwd:nc,encoding:"utf-8",timeout:RV,windowsHide:!0,shell:!1});if(e.error)throw e.error;if(e.status!==0)throw new Error(e.stderr||e.stdout||"Git command failed");return e.stdout.trim()}function zR(t,e=Eh){let a=process.platform==="win32"?"npm.cmd":"npm",n=(0,wh.spawnSync)(a,t,{cwd:nc,encoding:"utf-8",timeout:e,windowsHide:!0,shell:!1});if(n.error)throw n.error;if(n.status!==0)throw new Error(n.stderr||n.stdout||"npm command failed");return n.stdout.trim()}function ud(){let t=(0,rc.join)(nc,".git");if(!(0,Xa.existsSync)(t))return{branch:null,isBeta:!1,isGitRepo:!1,isDirty:!1,canSwitch:!1,error:"Installed plugin is not a git repository"};try{let e=Dr(["rev-parse","--abbrev-ref","HEAD"]),a=Dr(["status","--porcelain"]).length>0,n=e.startsWith("beta");return{branch:e,isBeta:n,isGitRepo:!0,isDirty:a,canSwitch:!0}}catch(e){return P.error("BRANCH","Failed to get branch info",{},e),{branch:null,isBeta:!1,isGitRepo:!0,isDirty:!1,canSwitch:!1,error:e.message}}}async function HR(t){if(!xh(t))return{success:!1,error:`Invalid branch name: ${t}. Branch names must be alphanumeric with hyphens, underscores, slashes, or dots.`};let e=ud();if(!e.isGitRepo)return{success:!1,error:"Installed plugin is not a git repository. Please reinstall."};if(e.branch===t)return{success:!0,branch:t,message:`Already on branch ${t}`};try{P.info("BRANCH","Starting branch switch",{from:e.branch,to:t}),P.debug("BRANCH","Discarding local changes"),Dr(["checkout","--","."]),Dr(["clean","-fd"]),P.debug("BRANCH","Fetching from origin"),Dr(["fetch","origin"]),P.debug("BRANCH","Checking out branch",{branch:t});try{Dr(["checkout",t])}catch{Dr(["checkout","-b",t,`origin/${t}`])}P.debug("BRANCH","Pulling latest"),Dr(["pull","origin",t]);let r=(0,rc.join)(nc,".install-version");return(0,Xa.existsSync)(r)&&(0,Xa.unlinkSync)(r),P.debug("BRANCH","Running npm install"),zR(["install"],Eh),P.success("BRANCH","Branch switch complete",{branch:t}),{success:!0,branch:t,message:`Switched to ${t}. Worker will restart automatically.`}}catch(r){P.error("BRANCH","Branch switch failed",{targetBranch:t},r);try{e.branch&&xh(e.branch)&&Dr(["checkout",e.branch])}catch{}return{success:!1,error:`Branch switch failed: ${r.message}`}}}async function BR(){let t=ud();if(!t.isGitRepo||!t.branch)return{success:!1,error:"Cannot pull updates: not a git repository"};try{if(!xh(t.branch))return{success:!1,error:`Invalid current branch name: ${t.branch}`};P.info("BRANCH","Pulling updates",{branch:t.branch}),Dr(["checkout","--","."]),Dr(["fetch","origin"]),Dr(["pull","origin",t.branch]);let e=(0,rc.join)(nc,".install-version");return(0,Xa.existsSync)(e)&&(0,Xa.unlinkSync)(e),zR(["install"],Eh),P.success("BRANCH","Updates pulled",{branch:t.branch}),{success:!0,branch:t.branch,message:`Updated ${t.branch}. Worker will restart automatically.`}}catch(e){return P.error("BRANCH","Pull failed",{},e),{success:!1,error:`Pull failed: ${e.message}`}}}Or();var ld=class extends jr{constructor(r){super();this.settingsManager=r}setupRoutes(r){r.get("/api/settings",this.handleGetSettings.bind(this)),r.post("/api/settings",this.handleUpdateSettings.bind(this)),r.get("/api/mcp/status",this.handleGetMcpStatus.bind(this)),r.post("/api/mcp/toggle",this.handleToggleMcp.bind(this)),r.get("/api/branch/status",this.handleGetBranchStatus.bind(this)),r.post("/api/branch/switch",this.handleSwitchBranch.bind(this)),r.post("/api/branch/update",this.handleUpdateBranch.bind(this))}handleGetSettings=this.wrapHandler((r,a)=>{let n=Ja.default.join((0,Th.homedir)(),".claude-mem","settings.json");this.ensureSettingsFile(n);let s=Ze.loadFromFile(n);a.json(s)});handleUpdateSettings=this.wrapHandler((r,a)=>{let n=this.validateSettings(r.body);if(!n.valid){a.status(400).json({success:!1,error:n.error});return}let s=Ja.default.join((0,Th.homedir)(),".claude-mem","settings.json");this.ensureSettingsFile(s);let i={};if((0,zt.existsSync)(s)){let c=(0,zt.readFileSync)(s,"utf-8");try{i=JSON.parse(c)}catch(u){P.error("SETTINGS","Failed to parse settings file",{settingsPath:s},u),a.status(500).json({success:!1,error:"Settings file is corrupted. Delete ~/.claude-mem/settings.json to reset."});return}}let o=["CLAUDE_MEM_MODEL","CLAUDE_MEM_CONTEXT_OBSERVATIONS","CLAUDE_MEM_WORKER_PORT","CLAUDE_MEM_WORKER_HOST","CLAUDE_MEM_PROVIDER","CLAUDE_MEM_GEMINI_API_KEY","CLAUDE_MEM_GEMINI_MODEL","CLAUDE_MEM_GEMINI_RATE_LIMITING_ENABLED","CLAUDE_MEM_OPENROUTER_API_KEY","CLAUDE_MEM_OPENROUTER_MODEL","CLAUDE_MEM_OPENROUTER_SITE_URL","CLAUDE_MEM_OPENROUTER_APP_NAME","CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES","CLAUDE_MEM_OPENROUTER_MAX_TOKENS","CLAUDE_MEM_DATA_DIR","CLAUDE_MEM_LOG_LEVEL","CLAUDE_MEM_PYTHON_VERSION","CLAUDE_CODE_PATH","CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT","CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES","CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS","CLAUDE_MEM_CONTEXT_FULL_COUNT","CLAUDE_MEM_CONTEXT_FULL_FIELD","CLAUDE_MEM_CONTEXT_SESSION_COUNT","CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY","CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE"];for(let c of o)r.body[c]!==void 0&&(i[c]=r.body[c]);(0,zt.writeFileSync)(s,JSON.stringify(i,null,2),"utf-8"),E1(),P.info("WORKER","Settings updated"),a.json({success:!0,message:"Settings updated successfully"})});handleGetMcpStatus=this.wrapHandler((r,a)=>{let n=this.isMcpEnabled();a.json({enabled:n})});handleToggleMcp=this.wrapHandler((r,a)=>{let{enabled:n}=r.body;if(typeof n!="boolean"){this.badRequest(a,"enabled must be a boolean");return}this.toggleMcp(n),a.json({success:!0,enabled:this.isMcpEnabled()})});handleGetBranchStatus=this.wrapHandler((r,a)=>{let n=ud();a.json(n)});handleSwitchBranch=this.wrapHandler(async(r,a)=>{let{branch:n}=r.body;if(!n){a.status(400).json({success:!1,error:"Missing branch parameter"});return}let s=["main","beta/7.0","feature/bun-executable"];if(!s.includes(n)){a.status(400).json({success:!1,error:`Invalid branch. Allowed: ${s.join(", ")}`});return}P.info("WORKER","Branch switch requested",{branch:n});let i=await HR(n);i.success&&setTimeout(()=>{P.info("WORKER","Restarting worker after branch switch"),process.exit(0)},1e3),a.json(i)});handleUpdateBranch=this.wrapHandler(async(r,a)=>{P.info("WORKER","Branch update requested");let n=await BR();n.success&&setTimeout(()=>{P.info("WORKER","Restarting worker after branch update"),process.exit(0)},1e3),a.json(n)});validateSettings(r){if(r.CLAUDE_MEM_PROVIDER&&!["claude","gemini","openrouter"].includes(r.CLAUDE_MEM_PROVIDER))return{valid:!1,error:'CLAUDE_MEM_PROVIDER must be "claude", "gemini", or "openrouter"'};if(r.CLAUDE_MEM_GEMINI_MODEL&&!["gemini-2.5-flash-lite","gemini-2.5-flash","gemini-3-flash"].includes(r.CLAUDE_MEM_GEMINI_MODEL))return{valid:!1,error:"CLAUDE_MEM_GEMINI_MODEL must be one of: gemini-2.5-flash-lite, gemini-2.5-flash, gemini-3-flash"};if(r.CLAUDE_MEM_CONTEXT_OBSERVATIONS){let n=parseInt(r.CLAUDE_MEM_CONTEXT_OBSERVATIONS,10);if(isNaN(n)||n<1||n>200)return{valid:!1,error:"CLAUDE_MEM_CONTEXT_OBSERVATIONS must be between 1 and 200"}}if(r.CLAUDE_MEM_WORKER_PORT){let n=parseInt(r.CLAUDE_MEM_WORKER_PORT,10);if(isNaN(n)||n<1024||n>65535)return{valid:!1,error:"CLAUDE_MEM_WORKER_PORT must be between 1024 and 65535"}}if(r.CLAUDE_MEM_WORKER_HOST){let n=r.CLAUDE_MEM_WORKER_HOST;if(!/^(127\.0\.0\.1|0\.0\.0\.0|localhost|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.test(n))return{valid:!1,error:"CLAUDE_MEM_WORKER_HOST must be a valid IP address (e.g., 127.0.0.1, 0.0.0.0)"}}if(r.CLAUDE_MEM_LOG_LEVEL&&!["DEBUG","INFO","WARN","ERROR","SILENT"].includes(r.CLAUDE_MEM_LOG_LEVEL.toUpperCase()))return{valid:!1,error:"CLAUDE_MEM_LOG_LEVEL must be one of: DEBUG, INFO, WARN, ERROR, SILENT"};if(r.CLAUDE_MEM_PYTHON_VERSION&&!/^3\.\d{1,2}$/.test(r.CLAUDE_MEM_PYTHON_VERSION))return{valid:!1,error:'CLAUDE_MEM_PYTHON_VERSION must be in format "3.X" or "3.XX" (e.g., "3.13")'};let a=["CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT","CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY","CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE"];for(let n of a)if(r[n]&&!["true","false"].includes(r[n]))return{valid:!1,error:`${n} must be "true" or "false"`};if(r.CLAUDE_MEM_CONTEXT_FULL_COUNT){let n=parseInt(r.CLAUDE_MEM_CONTEXT_FULL_COUNT,10);if(isNaN(n)||n<0||n>20)return{valid:!1,error:"CLAUDE_MEM_CONTEXT_FULL_COUNT must be between 0 and 20"}}if(r.CLAUDE_MEM_CONTEXT_SESSION_COUNT){let n=parseInt(r.CLAUDE_MEM_CONTEXT_SESSION_COUNT,10);if(isNaN(n)||n<1||n>50)return{valid:!1,error:"CLAUDE_MEM_CONTEXT_SESSION_COUNT must be between 1 and 50"}}if(r.CLAUDE_MEM_CONTEXT_FULL_FIELD&&!["narrative","facts"].includes(r.CLAUDE_MEM_CONTEXT_FULL_FIELD))return{valid:!1,error:'CLAUDE_MEM_CONTEXT_FULL_FIELD must be "narrative" or "facts"'};if(r.CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES){let n=parseInt(r.CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES,10);if(isNaN(n)||n<1||n>100)return{valid:!1,error:"CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES must be between 1 and 100"}}if(r.CLAUDE_MEM_OPENROUTER_MAX_TOKENS){let n=parseInt(r.CLAUDE_MEM_OPENROUTER_MAX_TOKENS,10);if(isNaN(n)||n<1e3||n>1e6)return{valid:!1,error:"CLAUDE_MEM_OPENROUTER_MAX_TOKENS must be between 1000 and 1000000"}}if(r.CLAUDE_MEM_OPENROUTER_SITE_URL)try{new URL(r.CLAUDE_MEM_OPENROUTER_SITE_URL)}catch{return{valid:!1,error:"CLAUDE_MEM_OPENROUTER_SITE_URL must be a valid URL"}}return{valid:!0}}isMcpEnabled(){let r=or(),a=Ja.default.join(r,"plugin",".mcp.json");return(0,zt.existsSync)(a)}toggleMcp(r){let a=or(),n=Ja.default.join(a,"plugin",".mcp.json"),s=Ja.default.join(a,"plugin",".mcp.json.disabled");r&&(0,zt.existsSync)(s)?((0,zt.renameSync)(s,n),P.info("WORKER","MCP search server enabled")):!r&&(0,zt.existsSync)(n)?((0,zt.renameSync)(n,s),P.info("WORKER","MCP search server disabled")):P.debug("WORKER","MCP toggle no-op (already in desired state)",{enabled:r})}ensureSettingsFile(r){if(!(0,zt.existsSync)(r)){let a=Ze.getAllDefaults(),n=Ja.default.dirname(r);(0,zt.existsSync)(n)||(0,zt.mkdirSync)(n,{recursive:!0}),(0,zt.writeFileSync)(r,JSON.stringify(a,null,2),"utf-8"),P.info("SETTINGS","Created settings file with defaults",{settingsPath:r})}}};var ac=(0,ZR.promisify)(ra.exec),PV="8.5.0",Ch=xe.default.join((0,Wr.homedir)(),".claude-mem"),Qa=xe.default.join(Ch,"worker.pid"),WR=xe.default.join(Ch,"cursor-projects.json");function $h(t){(0,Ae.mkdirSync)(Ch,{recursive:!0}),(0,Ae.writeFileSync)(Qa,JSON.stringify(t,null,2))}function $V(){try{return(0,Ae.existsSync)(Qa)?JSON.parse((0,Ae.readFileSync)(Qa,"utf-8")):null}catch(t){return P.warn("SYSTEM","Failed to read PID file",{path:Qa,error:t.message}),null}}function ta(){try{(0,Ae.existsSync)(Qa)&&(0,Ae.unlinkSync)(Qa)}catch(t){P.warn("SYSTEM","Failed to remove PID file",{path:Qa,error:t.message})}}function Ih(){return T1(WR)}function KR(t){k1(WR,t)}function OV(t,e){let r=Ih();r[t]={workspacePath:e,installedAt:new Date().toISOString()},KR(r),P.info("CURSOR","Registered project for auto-context updates",{projectName:t,workspacePath:e})}function CV(t){let e=Ih();e[t]&&(delete e[t],KR(e),P.info("CURSOR","Unregistered project",{projectName:t}))}async function Za(t,e){let a=Ih()[t];if(a)try{let n=await fetch(`http://127.0.0.1:${e}/api/context/inject?project=${encodeURIComponent(t)}`);if(!n.ok)return;let s=await n.text();if(!s||!s.trim())return;R1(a.workspacePath,s),P.debug("CURSOR","Updated context file",{projectName:t,workspacePath:a.workspacePath})}catch(n){P.warn("CURSOR","Failed to update context file",{projectName:t,error:n.message})}}function Ya(t){return process.platform==="win32"?Math.round(t*2):t}async function Oh(t){try{return(await fetch(`http://127.0.0.1:${t}/api/health`)).ok}catch{return!1}}async function Pi(t,e=3e4){let r=Date.now();for(;Date.now()-rsetTimeout(a,500))}return!1}async function kh(t){try{let e=await fetch(`http://127.0.0.1:${t}/api/admin/shutdown`,{method:"POST"});return e.ok?!0:(P.warn("SYSTEM","Shutdown request returned error",{port:t,status:e.status}),!1)}catch(e){return e.message?.includes("ECONNREFUSED")||P.warn("SYSTEM","Shutdown request failed",{port:t,error:e.message}),!1}}async function Rh(t,e=1e4){let r=Date.now();for(;Date.now()-rsetTimeout(a,500))}return!1}function IV(){let t=xe.default.join((0,Wr.homedir)(),".claude","plugins","marketplaces","thedotmack"),e=xe.default.join(t,"package.json");return JSON.parse((0,Ae.readFileSync)(e,"utf-8")).version}async function AV(t){try{let e=await fetch(`http://127.0.0.1:${t}/api/version`);return e.ok?(await e.json()).version:null}catch{return null}}async function NV(t){let e=IV(),r=await AV(t);return r?{matches:e===r,pluginVersion:e,workerVersion:r}:{matches:!0,pluginVersion:e,workerVersion:r}}var dd=class{app;server=null;startTime=Date.now();mcpClient;mcpReady=!1;initializationCompleteFlag=!1;isShuttingDown=!1;dbManager;sessionManager;sseBroadcaster;sdkAgent;geminiAgent;openRouterAgent;paginationHelper;settingsManager;sessionEventBroadcaster;viewerRoutes;sessionRoutes;dataRoutes;searchRoutes;settingsRoutes;initializationComplete;resolveInitialization;constructor(){this.app=(0,VR.default)(),this.initializationComplete=new Promise(e=>{this.resolveInitialization=e}),this.dbManager=new el,this.sessionManager=new nl(this.dbManager),this.sseBroadcaster=new al,this.sdkAgent=new Ul(this.dbManager,this.sessionManager),this.geminiAgent=new zl(this.dbManager,this.sessionManager),this.geminiAgent.setFallbackAgent(this.sdkAgent),this.openRouterAgent=new Vl(this.dbManager,this.sessionManager),this.openRouterAgent.setFallbackAgent(this.sdkAgent),this.paginationHelper=new Gl(this.dbManager),this.settingsManager=new Zl(this.dbManager),this.sessionEventBroadcaster=new Yl(this.sseBroadcaster,this),this.sessionManager.setOnSessionDeleted(()=>{this.broadcastProcessingStatus()}),this.mcpClient=new Hs({name:"worker-search-proxy",version:"1.0.0"},{capabilities:{}}),this.viewerRoutes=new ed(this.sseBroadcaster,this.dbManager,this.sessionManager),this.sessionRoutes=new nd(this.sessionManager,this.dbManager,this.sdkAgent,this.geminiAgent,this.openRouterAgent,this.sessionEventBroadcaster,this),this.dataRoutes=new ad(this.paginationHelper,this.dbManager,this.sessionManager,this.sseBroadcaster,this,this.startTime),this.searchRoutes=null,this.settingsRoutes=new ld(this.settingsManager),this.setupMiddleware(),this.setupRoutes(),this.registerSignalHandlers()}registerSignalHandlers(){let e=async r=>{if(this.isShuttingDown){P.warn("SYSTEM",`Received ${r} but shutdown already in progress`);return}this.isShuttingDown=!0,P.info("SYSTEM",`Received ${r}, shutting down...`);try{await this.shutdown(),process.exit(0)}catch(a){P.error("SYSTEM","Error during shutdown",{},a),process.exit(1)}};process.on("SIGTERM",()=>e("SIGTERM")),process.on("SIGINT",()=>e("SIGINT"))}setupMiddleware(){OR(this.summarizeRequestBody.bind(this)).forEach(r=>this.app.use(r))}setupRoutes(){let e="TEST-008-wrapper-ipc";this.app.get("/api/health",(r,a)=>{a.status(200).json({status:"ok",build:e,managed:process.env.CLAUDE_MEM_MANAGED==="true",hasIpc:typeof process.send=="function",platform:process.platform,pid:process.pid,initialized:this.initializationCompleteFlag,mcpReady:this.mcpReady})}),this.app.get("/api/readiness",(r,a)=>{this.initializationCompleteFlag?a.status(200).json({status:"ready",mcpReady:this.mcpReady}):a.status(503).json({status:"initializing",message:"Worker is still initializing, please retry"})}),this.app.get("/api/version",(r,a)=>{a.status(200).json({version:PV})}),this.app.get("/api/instructions",async(r,a)=>{let n=r.query.topic||"all",s=r.query.operation;try{let i;if(s){let o=xe.default.join(__dirname,"../skills/mem-search/operations",`${s}.md`);i=await Ph.promises.readFile(o,"utf-8")}else{let o=xe.default.join(__dirname,"../skills/mem-search/SKILL.md"),c=await Ph.promises.readFile(o,"utf-8");i=this.extractInstructionSection(c,n)}a.json({content:[{type:"text",text:i}]})}catch(i){P.error("WORKER","Failed to load instructions",{topic:n,operation:s},i),a.status(500).json({content:[{type:"text",text:`Error loading instructions: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0})}}),this.app.post("/api/admin/restart",gh,async(r,a)=>{a.json({status:"restarting"}),process.platform==="win32"&&process.env.CLAUDE_MEM_MANAGED==="true"&&process.send?(P.info("SYSTEM","Sending restart request to wrapper"),process.send({type:"restart"})):setTimeout(async()=>{await this.shutdown(),process.exit(0)},100)}),this.app.post("/api/admin/shutdown",gh,async(r,a)=>{a.json({status:"shutting_down"}),process.platform==="win32"&&process.env.CLAUDE_MEM_MANAGED==="true"&&process.send?(P.info("SYSTEM","Sending shutdown request to wrapper"),process.send({type:"shutdown"})):setTimeout(async()=>{await this.shutdown(),process.exit(0)},100)}),this.viewerRoutes.setupRoutes(this.app),this.sessionRoutes.setupRoutes(this.app),this.dataRoutes.setupRoutes(this.app),this.settingsRoutes.setupRoutes(this.app),this.app.get("/api/context/inject",async(r,a,n)=>{try{let i=new Promise((o,c)=>setTimeout(()=>c(new Error("Initialization timeout")),3e5));if(await Promise.race([this.initializationComplete,i]),!this.searchRoutes){a.status(503).json({error:"Search routes not initialized"});return}n()}catch(s){P.error("WORKER","Context inject handler failed",{},s),a.headersSent||a.status(500).json({error:s instanceof Error?s.message:"Internal server error"})}})}async cleanupOrphanedProcesses(){let e=process.platform==="win32",r=[];if(e){let a=`powershell -Command "Get-CimInstance Win32_Process | Where-Object { $_.Name -like '*python*' -and $_.CommandLine -like '*chroma-mcp*' } | Select-Object -ExpandProperty ProcessId"`,{stdout:n}=await ac(a,{timeout:6e4});if(!n.trim()){P.debug("SYSTEM","No orphaned chroma-mcp processes found (Windows)");return}let s=n.trim().split(` `);for(let i of s){let o=parseInt(i.trim(),10);!isNaN(o)&&Number.isInteger(o)&&o>0&&r.push(o)}}else{let{stdout:a}=await ac('ps aux | grep "chroma-mcp" | grep -v grep || true');if(!a.trim()){P.debug("SYSTEM","No orphaned chroma-mcp processes found (Unix)");return}let n=a.trim().split(` `);for(let s of n){let i=s.trim().split(/\s+/);if(i.length>1){let o=parseInt(i[1],10);!isNaN(o)&&Number.isInteger(o)&&o>0&&r.push(o)}}}if(r.length!==0){if(P.info("SYSTEM","Cleaning up orphaned chroma-mcp processes",{platform:e?"Windows":"Unix",count:r.length,pids:r}),e)for(let a of r){if(!Number.isInteger(a)||a<=0){P.warn("SYSTEM","Skipping invalid PID",{pid:a});continue}try{(0,ra.execSync)(`taskkill /PID ${a} /T /F`,{timeout:6e4,stdio:"ignore"})}catch{}}else for(let a of r)try{process.kill(a,"SIGKILL")}catch{}P.info("SYSTEM","Orphaned processes cleaned up",{count:r.length})}}async start(){let e=Xt(),r=w1();this.server=await new Promise((a,n)=>{let s=this.app.listen(e,r,()=>a(s));s.on("error",n)}),P.info("SYSTEM","Worker started",{host:r,port:e,pid:process.pid}),this.initializeBackground().catch(a=>{P.error("SYSTEM","Background initialization failed",{},a)})}async initializeBackground(){try{await this.cleanupOrphanedProcesses();let{ModeManager:e}=await Promise.resolve().then(()=>(ln(),z1)),{SettingsDefaultsManager:r}=await Promise.resolve().then(()=>(Or(),b1)),{USER_SETTINGS_PATH:a}=await Promise.resolve().then(()=>(mr(),j1)),s=r.loadFromFile(a).CLAUDE_MEM_MODE;e.getInstance().loadMode(s),P.info("SYSTEM",`Mode loaded: ${s}`),await this.dbManager.initialize();let{PendingMessageStore:i}=await Promise.resolve().then(()=>(Oo(),tl)),o=new i(this.dbManager.getSessionStore().db,3),c=300*1e3,u=o.resetStuckMessages(c);u>0&&P.info("SYSTEM",`Recovered ${u} stuck messages from previous session`,{thresholdMinutes:5});let l=new Xl,d=new Jl,p=new Kl(this.dbManager.getSessionSearch(),this.dbManager.getSessionStore(),this.dbManager.getChromaSync(),l,d);this.searchRoutes=new cd(p),this.searchRoutes.setupRoutes(this.app),P.info("WORKER","SearchManager initialized and search routes registered");let m=xe.default.join(__dirname,"mcp-server.cjs"),g=new Gs({command:"node",args:[m],env:process.env}),_=3e5,f=this.mcpClient.connect(g),h=new Promise((y,v)=>setTimeout(()=>v(new Error("MCP connection timeout after 5 minutes")),_));await Promise.race([f,h]),this.mcpReady=!0,P.success("WORKER","Connected to MCP server"),this.initializationCompleteFlag=!0,this.resolveInitialization(),P.info("SYSTEM","Background initialization complete"),this.processPendingQueues(50).then(y=>{y.sessionsStarted>0&&P.info("SYSTEM",`Auto-recovered ${y.sessionsStarted} sessions with pending work`,{totalPending:y.totalPendingSessions,started:y.sessionsStarted,sessionIds:y.startedSessionIds})}).catch(y=>{P.warn("SYSTEM","Auto-recovery of pending queues failed",{},y)})}catch(e){throw P.error("SYSTEM","Background initialization failed",{},e),e}}startSessionProcessor(e,r){if(!e)return;let a=e.sessionDbId;P.info("SYSTEM",`Starting generator (${r})`,{sessionId:a}),e.generatorPromise=this.sdkAgent.startSession(e,this).catch(n=>{e.abortController.signal.aborted||P.error("SYSTEM",`Generator failed (${r})`,{sessionId:a,error:n.message},n)}).finally(()=>{e.generatorPromise=null,this.broadcastProcessingStatus(),e.abortController.signal.aborted||P.warn("SYSTEM","Session processor exited unexpectedly",{sessionId:a})})}async processPendingQueues(e=10){let{PendingMessageStore:r}=await Promise.resolve().then(()=>(Oo(),tl)),a=new r(this.dbManager.getSessionStore().db,3),n=a.getSessionsWithPendingMessages(),s={totalPendingSessions:n.length,sessionsStarted:0,sessionsSkipped:0,startedSessionIds:[]};if(n.length===0)return s;P.info("SYSTEM",`Processing up to ${e} of ${n.length} pending session queues`);for(let i of n){if(s.sessionsStarted>=e)break;try{if(this.sessionManager.getSession(i)?.generatorPromise){s.sessionsSkipped++;continue}let c=this.sessionManager.initializeSession(i);P.info("SYSTEM",`Starting processor for session ${i}`,{project:c.project,pendingCount:a.getPendingCount(i)}),this.startSessionProcessor(c,"startup-recovery"),s.sessionsStarted++,s.startedSessionIds.push(i),await new Promise(u=>setTimeout(u,100))}catch(o){P.warn("SYSTEM",`Failed to process session ${i}`,{},o),s.sessionsSkipped++}}return s}extractInstructionSection(e,r){let a={workflow:this.extractBetween(e,"## The Workflow","## Search Parameters"),search_params:this.extractBetween(e,"## Search Parameters","## Examples"),examples:this.extractBetween(e,"## Examples","## Why This Workflow"),all:e};return a[r]||a.all}extractBetween(e,r,a){let n=e.indexOf(r),s=e.indexOf(a);return n===-1?e:s===-1?e.substring(n):e.substring(n,s).trim()}async shutdown(){P.info("SYSTEM","Shutdown initiated"),ta();let e=await this.getChildProcesses(process.pid);if(P.info("SYSTEM","Found child processes",{count:e.length,pids:e}),this.server&&(this.server.closeAllConnections(),process.platform==="win32"&&await new Promise(r=>setTimeout(r,500)),await new Promise((r,a)=>{this.server.close(n=>n?a(n):r())}),this.server=null,P.info("SYSTEM","HTTP server closed"),process.platform==="win32"&&(await new Promise(r=>setTimeout(r,500)),P.info("SYSTEM","Waited for Windows port cleanup"))),await this.sessionManager.shutdownAll(),this.mcpClient&&(await this.mcpClient.close(),P.info("SYSTEM","MCP client closed")),await this.dbManager.close(),e.length>0){P.info("SYSTEM","Force killing remaining children");for(let r of e)await this.forceKillProcess(r);await this.waitForProcessesExit(e,5e3)}P.info("SYSTEM","Worker shutdown complete")}async getChildProcesses(e){if(process.platform!=="win32")return[];if(!Number.isInteger(e)||e<=0)return P.warn("SYSTEM","Invalid parent PID for child process enumeration",{parentPid:e}),[];try{let r=`powershell -Command "Get-CimInstance Win32_Process | Where-Object { $_.ParentProcessId -eq ${e} } | Select-Object -ExpandProperty ProcessId"`,{stdout:a}=await ac(r,{timeout:6e4});return a.trim().split(` `).map(n=>parseInt(n.trim(),10)).filter(n=>!isNaN(n)&&Number.isInteger(n)&&n>0)}catch(r){return P.warn("SYSTEM","Failed to enumerate child processes",{parentPid:e,error:r.message}),[]}}async forceKillProcess(e){if(!Number.isInteger(e)||e<=0){P.warn("SYSTEM","Invalid PID for force kill",{pid:e});return}try{process.platform==="win32"?await ac(`taskkill /PID ${e} /T /F`,{timeout:6e4}):process.kill(e,"SIGKILL"),P.info("SYSTEM","Killed process",{pid:e})}catch{P.debug("SYSTEM","Process already exited during force kill",{pid:e})}}async waitForProcessesExit(e,r){let a=Date.now();for(;Date.now()-a{try{return process.kill(s,0),!0}catch{return!1}});if(n.length===0){P.info("SYSTEM","All child processes exited");return}P.debug("SYSTEM","Waiting for processes to exit",{stillAlive:n}),await new Promise(s=>setTimeout(s,100))}P.warn("SYSTEM","Timeout waiting for child processes to exit")}summarizeRequestBody(e,r,a){return CR(e,r,a)}broadcastProcessingStatus(){let e=this.sessionManager.isAnySessionProcessing(),r=this.sessionManager.getTotalActiveWork(),a=this.sessionManager.getActiveSessionCount();P.info("WORKER","Broadcasting processing status",{isProcessing:e,queueDepth:r,activeSessions:a}),this.sseBroadcaster.broadcast({type:"processing_status",isProcessing:e,queueDepth:r})}};async function MV(){let t=GR.createInterface({input:process.stdin,output:process.stdout}),e=r=>new Promise(a=>t.question(r,a));console.log(`