From f923c0cdd51c793677c8463817dccdc2ed02e86e Mon Sep 17 00:00:00 2001 From: Alex Newman Date: Fri, 12 Dec 2025 19:23:35 -0500 Subject: [PATCH] fix: complete better-sqlite3 to bun:sqlite migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Must Fix: - Remove better-sqlite3 logic from smart-install.js (5 sections) - Update all documentation to reference bun:sqlite (7 files) Should Fix: - Add defensive break statement in worker-cli.ts:38 Nice to Have: - Add port validation in ProcessManager.start() (1024-65535) - Add one-time marker for PM2 cleanup migration - Verify clearPortCache() wiring (already correct) Addresses PR #248 review feedback (comment #3648517713) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- docs/public/architecture-evolution.mdx | 2 +- docs/public/architecture/database.mdx | 8 +- docs/public/architecture/overview.mdx | 4 +- docs/public/hooks-architecture.mdx | 1 - plugin/scripts/cleanup-hook.js | 12 +- plugin/scripts/context-hook.js | 6 +- plugin/scripts/new-hook.js | 8 +- plugin/scripts/save-hook.js | 12 +- plugin/scripts/smart-install.js | 123 ------------------ plugin/scripts/summary-hook.js | 16 +-- plugin/scripts/user-message-hook.js | 8 +- plugin/scripts/worker-cli.js | 6 +- .../troubleshoot/operations/database.md | 2 +- .../troubleshoot/operations/diagnostics.md | 1 - .../skills/troubleshoot/operations/worker.md | 1 - src/cli/worker-cli.ts | 1 + src/services/process/ProcessManager.ts | 8 ++ src/shared/worker-utils.ts | 11 +- src/types/database.ts | 2 +- 19 files changed, 61 insertions(+), 171 deletions(-) diff --git a/docs/public/architecture-evolution.mdx b/docs/public/architecture-evolution.mdx index ac833b50..aa64733f 100644 --- a/docs/public/architecture-evolution.mdx +++ b/docs/public/architecture-evolution.mdx @@ -151,7 +151,7 @@ if (currentVersion !== installedVersion) { **Cached Check Logic**: 1. Does `node_modules` exist? 2. Does `.install-version` match `package.json` version? -3. Is `better-sqlite3` present? +3. Is `better-sqlite3` present? (Legacy: now uses bun:sqlite which requires no installation) **Impact**: - SessionStart hook: 2-5 seconds → 10ms (99.5% faster) diff --git a/docs/public/architecture/database.mdx b/docs/public/architecture/database.mdx index a859f83d..7f6a155c 100644 --- a/docs/public/architecture/database.mdx +++ b/docs/public/architecture/database.mdx @@ -5,7 +5,7 @@ description: "SQLite schema, FTS5 search, and data storage" # Database Architecture -Claude-Mem uses SQLite 3 with the better-sqlite3 native module for persistent storage and FTS5 for full-text search. +Claude-Mem uses SQLite 3 with the bun:sqlite native module for persistent storage and FTS5 for full-text search. ## Database Location @@ -15,7 +15,7 @@ Claude-Mem uses SQLite 3 with the better-sqlite3 native module for persistent st ## Database Implementation -**Primary Implementation**: better-sqlite3 (native SQLite module) +**Primary Implementation**: bun:sqlite (native SQLite module) - Used by: SessionStore and SessionSearch - Format: Synchronous API with better performance - **Note**: Database.ts (using bun:sqlite) is legacy code @@ -301,8 +301,8 @@ Database schema is managed via migrations in `src/services/sqlite/migrations.ts` - **Indexes**: All foreign keys and frequently queried columns are indexed - **FTS5**: Full-text search is significantly faster than LIKE queries - **Triggers**: Automatic synchronization has minimal overhead -- **Connection Pooling**: better-sqlite3 reuses connections efficiently -- **Synchronous API**: better-sqlite3 uses synchronous API for better performance +- **Connection Pooling**: bun:sqlite reuses connections efficiently +- **Synchronous API**: bun:sqlite uses synchronous API for better performance ## Troubleshooting diff --git a/docs/public/architecture/overview.mdx b/docs/public/architecture/overview.mdx index 48039195..57486131 100644 --- a/docs/public/architecture/overview.mdx +++ b/docs/public/architecture/overview.mdx @@ -22,7 +22,7 @@ Claude-Mem operates as a Claude Code plugin with five core components: |------------------------|-------------------------------------------| | **Language** | TypeScript (ES2022, ESNext modules) | | **Runtime** | Node.js 18+ | -| **Database** | SQLite 3 with better-sqlite3 driver | +| **Database** | SQLite 3 with bun:sqlite driver | | **Vector Store** | ChromaDB (optional, for semantic search) | | **HTTP Server** | Express.js 4.18 | | **Real-time** | Server-Sent Events (SSE) | @@ -205,7 +205,7 @@ Express.js HTTP server on port 37777 (configurable) with: See [Worker Service](/architecture/worker-service) for HTTP API and endpoints. ### 3. Database Layer -SQLite3 with better-sqlite3 driver featuring: +SQLite3 with bun:sqlite driver featuring: - FTS5 virtual tables for full-text search - SessionStore for CRUD operations - SessionSearch for FTS5 queries diff --git a/docs/public/hooks-architecture.mdx b/docs/public/hooks-architecture.mdx index d61a2ee6..9f81b773 100644 --- a/docs/public/hooks-architecture.mdx +++ b/docs/public/hooks-architecture.mdx @@ -85,7 +85,6 @@ Claude-Mem uses 6 lifecycle hook scripts across 5 lifecycle events, plus 1 pre-h 2. Only runs `npm install` when necessary: - First-time installation - Version changed in package.json - - Critical dependency missing (better-sqlite3) 3. Provides Windows-specific error messages 4. Starts Bun worker service diff --git a/plugin/scripts/cleanup-hook.js b/plugin/scripts/cleanup-hook.js index 3446106c..267a87d0 100755 --- a/plugin/scripts/cleanup-hook.js +++ b/plugin/scripts/cleanup-hook.js @@ -1,9 +1,9 @@ #!/usr/bin/env bun -import{stdin as M}from"process";import x from"path";import{homedir as lt}from"os";import{spawnSync as _t}from"child_process";import{readFileSync as G,writeFileSync as V,existsSync as X}from"fs";import{join as j}from"path";import{homedir as Y}from"os";var K=["bugfix","feature","refactor","discovery","decision","change"],B=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var U=K.join(","),P=B.join(",");var p=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:j(Y(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:U,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:P,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!X(t))return this.getAllDefaults();let e=G(t,"utf-8"),n=JSON.parse(e),r=n;if(n.env&&typeof n.env=="object"){r=n.env;try{V(t,JSON.stringify(r,null,2),"utf-8"),E.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(i){E.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},i)}}let o={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))r[i]!==void 0&&(o[i]=r[i]);return o}};var A=(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))(A||{}),C=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=p.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=A[t]??1}return this.level}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message} -${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let n=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&n.command){let r=n.command.length>50?n.command.substring(0,50)+"...":n.command;return`${t}(${r})`}if(t==="Read"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Edit"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Write"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}return t}catch{return t}}log(t,e,n,r,o){if(t0&&(I=` {${Object.entries(R).map(([F,W])=>`${F}=${W}`).join(", ")}}`)}let y=`[${i}] [${u}] [${f}] ${l}${n}${I}${_}`;t===3?console.error(y):console.log(y)}debug(t,e,n,r){this.log(0,t,e,n,r)}info(t,e,n,r){this.log(1,t,e,n,r)}warn(t,e,n,r){this.log(2,t,e,n,r)}error(t,e,n,r){this.log(3,t,e,n,r)}dataIn(t,e,n,r){this.info(t,`\u2192 ${e}`,n,r)}dataOut(t,e,n,r){this.info(t,`\u2190 ${e}`,n,r)}success(t,e,n,r){this.info(t,`\u2713 ${e}`,n,r)}failure(t,e,n,r){this.error(t,`\u2717 ${e}`,n,r)}timing(t,e,n,r){this.info(t,`\u23F1 ${e}`,r,{duration:`${n}ms`})}},E=new C;var m={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(s){return process.platform==="win32"?Math.round(s*m.WINDOWS_MULTIPLIER):s}import{existsSync as D,readFileSync as Z,writeFileSync as tt,unlinkSync as et,mkdirSync as v}from"fs";import{createWriteStream as rt}from"fs";import{join as d}from"path";import{spawn as nt,spawnSync as ot}from"child_process";import{homedir as st}from"os";import{join as a,dirname as J,basename as wt}from"path";import{homedir as q}from"os";import{fileURLToPath as Q}from"url";function z(){return typeof __dirname<"u"?__dirname:J(Q(import.meta.url))}var Ft=z(),c=p.get("CLAUDE_MEM_DATA_DIR"),L=process.env.CLAUDE_CONFIG_DIR||a(q(),".claude"),Wt=a(c,"archives"),Kt=a(c,"logs"),Bt=a(c,"trash"),Gt=a(c,"backups"),Vt=a(c,"settings.json"),Xt=a(c,"claude-mem.db"),jt=a(c,"vector-db"),Yt=a(L,"settings.json"),Jt=a(L,"commands"),qt=a(L,"CLAUDE.md");var S=d(c,"worker.pid"),b=d(c,"logs"),w=d(st(),".claude","plugins","marketplaces","thedotmack"),it=5e3,at=1e4,ct=200,ut=1e3,pt=100,h=class{static async start(t){if(await this.isRunning())return{success:!0,pid:this.getPidInfo()?.pid};v(b,{recursive:!0});let e=d(w,"plugin","scripts","worker-service.cjs");if(!D(e))return{success:!1,error:`Worker script not found at ${e}`};let n=this.getLogFilePath();return this.startWithBun(e,n,t)}static isBunAvailable(){try{return ot("bun",["--version"],{stdio:"pipe",timeout:5e3}).status===0}catch{return!1}}static async startWithBun(t,e,n){if(!this.isBunAvailable())return{success:!1,error:"Bun is required but not found in PATH. Install from https://bun.sh"};try{let r=process.platform==="win32",o=nt("bun",[t],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(n)},cwd:w,...r&&{windowsHide:!0}}),i=rt(e,{flags:"a"});return o.stdout?.pipe(i),o.stderr?.pipe(i),o.unref(),o.pid?(this.writePidFile({pid:o.pid,port:n,startedAt:new Date().toISOString(),version:process.env.npm_package_version||"unknown"}),this.waitForHealth(o.pid,n)):{success:!1,error:"Failed to get PID from spawned process"}}catch(r){return{success:!1,error:r instanceof Error?r.message:String(r)}}}static async stop(t=it){let e=this.getPidInfo();if(!e)return!0;try{process.kill(e.pid,"SIGTERM"),await this.waitForExit(e.pid,t)}catch{try{process.kill(e.pid,"SIGKILL")}catch{}}return this.removePidFile(),!0}static async restart(t){return await this.stop(),this.start(t)}static async status(){let t=this.getPidInfo();if(!t)return{running:!1};let e=this.isProcessAlive(t.pid);return{running:e,pid:e?t.pid:void 0,port:e?t.port:void 0,uptime:e?this.formatUptime(t.startedAt):void 0}}static async isRunning(){let t=this.getPidInfo();if(!t)return!1;let e=this.isProcessAlive(t.pid);return e||this.removePidFile(),e}static getPidInfo(){try{if(!D(S))return null;let t=Z(S,"utf-8"),e=JSON.parse(t);return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}static writePidFile(t){v(c,{recursive:!0}),tt(S,JSON.stringify(t,null,2))}static removePidFile(){try{D(S)&&et(S)}catch{}}static isProcessAlive(t){try{return process.kill(t,0),!0}catch{return!1}}static async waitForHealth(t,e,n=at){let r=Date.now();for(;Date.now()-rsetTimeout(o,ct))}return{success:!1,error:"Health check timed out"}}static async waitForExit(t,e){let n=Date.now();for(;Date.now()-nsetTimeout(r,pt))}throw new Error("Process did not exit within timeout")}static getLogFilePath(){let t=new Date().toISOString().slice(0,10);return d(b,`worker-${t}.log`)}static formatUptime(t){let e=new Date(t).getTime(),r=Date.now()-e,o=Math.floor(r/1e3),i=Math.floor(o/60),u=Math.floor(i/60),f=Math.floor(u/24);return f>0?`${f}d ${u%24}h`:u>0?`${u}h ${i%60}m`:i>0?`${i}m ${o%60}s`:`${o}s`}};var Et=x.join(lt(),".claude","plugins","marketplaces","thedotmack"),ft=N(m.HEALTH_CHECK),g=null;function T(){if(g!==null)return g;try{let s=x.join(p.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=p.loadFromFile(s);return g=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),g}catch(s){return E.debug("SYSTEM","Failed to load port from settings, using default",{error:s}),g=parseInt(p.get("CLAUDE_MEM_WORKER_PORT"),10),g}}async function k(){try{let s=T();return(await fetch(`http://127.0.0.1:${s}/health`,{signal:AbortSignal.timeout(ft)})).ok}catch(s){return E.debug("SYSTEM","Worker health check failed",{error:s instanceof Error?s.message:String(s),errorType:s?.constructor?.name}),!1}}async function gt(){if(process.platform!=="win32")try{_t("pm2",["delete","claude-mem-worker"],{stdio:"ignore"})}catch{}let s=T(),t=await h.start(s);return t.success||E.error("SYSTEM","Failed to start worker",{platform:process.platform,port:s,error:t.error,marketplaceRoot:Et}),t.success}async function H(){if(await k())return;let s=await gt();if(!(!s&&await k())&&!s){let t=T();throw new Error(`Worker service failed to start on port ${t}. +import{stdin as I}from"process";import M from"path";import{homedir as _t}from"os";import{spawnSync as Et}from"child_process";import{existsSync as ft,writeFileSync as x}from"fs";import{readFileSync as V,writeFileSync as j,existsSync as X}from"fs";import{join as Y}from"path";import{homedir as J}from"os";var B=["bugfix","feature","refactor","discovery","decision","change"],G=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var P=B.join(","),N=G.join(",");var a=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:Y(J(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:P,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:N,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!X(t))return this.getAllDefaults();let e=V(t,"utf-8"),n=JSON.parse(e),r=n;if(n.env&&typeof n.env=="object"){r=n.env;try{j(t,JSON.stringify(r,null,2),"utf-8"),_.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(i){_.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},i)}}let o={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))r[i]!==void 0&&(o[i]=r[i]);return o}};var h=(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))(h||{}),C=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=a.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=h[t]??1}return this.level}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message} +${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let n=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&n.command){let r=n.command.length>50?n.command.substring(0,50)+"...":n.command;return`${t}(${r})`}if(t==="Read"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Edit"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Write"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}return t}catch{return t}}log(t,e,n,r,o){if(t0&&(y=` {${Object.entries(U).map(([W,K])=>`${W}=${K}`).join(", ")}}`)}let R=`[${i}] [${p}] [${f}] ${l}${n}${y}${E}`;t===3?console.error(R):console.log(R)}debug(t,e,n,r){this.log(0,t,e,n,r)}info(t,e,n,r){this.log(1,t,e,n,r)}warn(t,e,n,r){this.log(2,t,e,n,r)}error(t,e,n,r){this.log(3,t,e,n,r)}dataIn(t,e,n,r){this.info(t,`\u2192 ${e}`,n,r)}dataOut(t,e,n,r){this.info(t,`\u2190 ${e}`,n,r)}success(t,e,n,r){this.info(t,`\u2713 ${e}`,n,r)}failure(t,e,n,r){this.error(t,`\u2717 ${e}`,n,r)}timing(t,e,n,r){this.info(t,`\u23F1 ${e}`,r,{duration:`${n}ms`})}},_=new C;var m={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function v(s){return process.platform==="win32"?Math.round(s*m.WINDOWS_MULTIPLIER):s}import{existsSync as L,readFileSync as tt,writeFileSync as et,unlinkSync as rt,mkdirSync as w}from"fs";import{createWriteStream as nt}from"fs";import{join as d}from"path";import{spawn as ot,spawnSync as st}from"child_process";import{homedir as it}from"os";import{join as c,dirname as q,basename as xt}from"path";import{homedir as Q}from"os";import{fileURLToPath as z}from"url";function Z(){return typeof __dirname<"u"?__dirname:q(z(import.meta.url))}var Kt=Z(),u=a.get("CLAUDE_MEM_DATA_DIR"),D=process.env.CLAUDE_CONFIG_DIR||c(Q(),".claude"),Bt=c(u,"archives"),Gt=c(u,"logs"),Vt=c(u,"trash"),jt=c(u,"backups"),Xt=c(u,"settings.json"),Yt=c(u,"claude-mem.db"),Jt=c(u,"vector-db"),qt=c(D,"settings.json"),Qt=c(D,"commands"),zt=c(D,"CLAUDE.md");var S=d(u,"worker.pid"),b=d(u,"logs"),k=d(it(),".claude","plugins","marketplaces","thedotmack"),at=5e3,ct=1e4,ut=200,pt=1e3,lt=100,A=class{static async start(t){if(isNaN(t)||t<1024||t>65535)return{success:!1,error:`Invalid port ${t}. Must be between 1024 and 65535`};if(await this.isRunning())return{success:!0,pid:this.getPidInfo()?.pid};w(b,{recursive:!0});let e=d(k,"plugin","scripts","worker-service.cjs");if(!L(e))return{success:!1,error:`Worker script not found at ${e}`};let n=this.getLogFilePath();return this.startWithBun(e,n,t)}static isBunAvailable(){try{return st("bun",["--version"],{stdio:"pipe",timeout:5e3}).status===0}catch{return!1}}static async startWithBun(t,e,n){if(!this.isBunAvailable())return{success:!1,error:"Bun is required but not found in PATH. Install from https://bun.sh"};try{let r=process.platform==="win32",o=ot("bun",[t],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(n)},cwd:k,...r&&{windowsHide:!0}}),i=nt(e,{flags:"a"});return o.stdout?.pipe(i),o.stderr?.pipe(i),o.unref(),o.pid?(this.writePidFile({pid:o.pid,port:n,startedAt:new Date().toISOString(),version:process.env.npm_package_version||"unknown"}),this.waitForHealth(o.pid,n)):{success:!1,error:"Failed to get PID from spawned process"}}catch(r){return{success:!1,error:r instanceof Error?r.message:String(r)}}}static async stop(t=at){let e=this.getPidInfo();if(!e)return!0;try{process.kill(e.pid,"SIGTERM"),await this.waitForExit(e.pid,t)}catch{try{process.kill(e.pid,"SIGKILL")}catch{}}return this.removePidFile(),!0}static async restart(t){return await this.stop(),this.start(t)}static async status(){let t=this.getPidInfo();if(!t)return{running:!1};let e=this.isProcessAlive(t.pid);return{running:e,pid:e?t.pid:void 0,port:e?t.port:void 0,uptime:e?this.formatUptime(t.startedAt):void 0}}static async isRunning(){let t=this.getPidInfo();if(!t)return!1;let e=this.isProcessAlive(t.pid);return e||this.removePidFile(),e}static getPidInfo(){try{if(!L(S))return null;let t=tt(S,"utf-8"),e=JSON.parse(t);return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}static writePidFile(t){w(u,{recursive:!0}),et(S,JSON.stringify(t,null,2))}static removePidFile(){try{L(S)&&rt(S)}catch{}}static isProcessAlive(t){try{return process.kill(t,0),!0}catch{return!1}}static async waitForHealth(t,e,n=ct){let r=Date.now();for(;Date.now()-rsetTimeout(o,ut))}return{success:!1,error:"Health check timed out"}}static async waitForExit(t,e){let n=Date.now();for(;Date.now()-nsetTimeout(r,lt))}throw new Error("Process did not exit within timeout")}static getLogFilePath(){let t=new Date().toISOString().slice(0,10);return d(b,`worker-${t}.log`)}static formatUptime(t){let e=new Date(t).getTime(),r=Date.now()-e,o=Math.floor(r/1e3),i=Math.floor(o/60),p=Math.floor(i/60),f=Math.floor(p/24);return f>0?`${f}d ${p%24}h`:p>0?`${p}h ${i%60}m`:i>0?`${i}m ${o%60}s`:`${o}s`}};var gt=M.join(_t(),".claude","plugins","marketplaces","thedotmack"),mt=v(m.HEALTH_CHECK),g=null;function T(){if(g!==null)return g;try{let s=M.join(a.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=a.loadFromFile(s);return g=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),g}catch(s){return _.debug("SYSTEM","Failed to load port from settings, using default",{error:s}),g=parseInt(a.get("CLAUDE_MEM_WORKER_PORT"),10),g}}async function $(){try{let s=T();return(await fetch(`http://127.0.0.1:${s}/health`,{signal:AbortSignal.timeout(mt)})).ok}catch(s){return _.debug("SYSTEM","Worker health check failed",{error:s instanceof Error?s.message:String(s),errorType:s?.constructor?.name}),!1}}async function St(){let s=M.join(a.get("CLAUDE_MEM_DATA_DIR"),".pm2-migrated");if(process.platform!=="win32"&&!ft(s))try{Et("pm2",["delete","claude-mem-worker"],{stdio:"ignore"}),x(s,new Date().toISOString(),"utf-8"),_.debug("SYSTEM","PM2 cleanup completed and marked")}catch{x(s,new Date().toISOString(),"utf-8")}let t=T(),e=await A.start(t);return e.success||_.error("SYSTEM","Failed to start worker",{platform:process.platform,port:t,error:e.error,marketplaceRoot:gt}),e.success}async function H(){if(await $())return;let s=await St();if(!(!s&&await $())&&!s){let t=T();throw new Error(`Worker service failed to start on port ${t}. To start manually, run: npm run worker:start -If already running, try: npm run worker:restart`)}}import{appendFileSync as mt}from"fs";import{homedir as St}from"os";import{join as dt}from"path";var Tt=dt(St(),".claude-mem","silent.log");function O(s,t,e=""){let n=new Date().toISOString(),u=((new Error().stack||"").split(` -`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),f=u?`${u[1].split("/").pop()}:${u[2]}`:"unknown",l=`[${n}] [HAPPY-PATH-ERROR] [${f}] ${s}`;if(t!==void 0)try{l+=` ${JSON.stringify(t)}`}catch(_){l+=` [stringify error: ${_}]`}l+=` -`;try{mt(Tt,l)}catch(_){console.error("[silent-debug] Failed to write to log:",_)}return e}async function $(s){if(await H(),O("[cleanup-hook] Hook fired",{session_id:s?.session_id,reason:s?.reason}),!s)throw new Error("cleanup-hook requires input from Claude Code");let{session_id:t,reason:e}=s,n=T();try{let r=await fetch(`http://127.0.0.1:${n}/api/sessions/complete`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,reason:e}),signal:AbortSignal.timeout(m.DEFAULT)});if(r.ok){let o=await r.json();O("[cleanup-hook] Session cleanup completed",o)}else O("[cleanup-hook] Session not found or already cleaned up")}catch(r){O("[cleanup-hook] Worker not reachable (non-critical)",{error:r.message})}console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}if(M.isTTY)$(void 0);else{let s="";M.on("data",t=>s+=t),M.on("end",async()=>{let t=s?JSON.parse(s):void 0;await $(t)})} +If already running, try: npm run worker:restart`)}}import{appendFileSync as dt}from"fs";import{homedir as Tt}from"os";import{join as Ot}from"path";var At=Ot(Tt(),".claude-mem","silent.log");function O(s,t,e=""){let n=new Date().toISOString(),p=((new Error().stack||"").split(` +`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),f=p?`${p[1].split("/").pop()}:${p[2]}`:"unknown",l=`[${n}] [HAPPY-PATH-ERROR] [${f}] ${s}`;if(t!==void 0)try{l+=` ${JSON.stringify(t)}`}catch(E){l+=` [stringify error: ${E}]`}l+=` +`;try{dt(At,l)}catch(E){console.error("[silent-debug] Failed to write to log:",E)}return e}async function F(s){if(await H(),O("[cleanup-hook] Hook fired",{session_id:s?.session_id,reason:s?.reason}),!s)throw new Error("cleanup-hook requires input from Claude Code");let{session_id:t,reason:e}=s,n=T();try{let r=await fetch(`http://127.0.0.1:${n}/api/sessions/complete`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,reason:e}),signal:AbortSignal.timeout(m.DEFAULT)});if(r.ok){let o=await r.json();O("[cleanup-hook] Session cleanup completed",o)}else O("[cleanup-hook] Session not found or already cleaned up")}catch(r){O("[cleanup-hook] Worker not reachable (non-critical)",{error:r.message})}console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}if(I.isTTY)F(void 0);else{let s="";I.on("data",t=>s+=t),I.on("end",async()=>{let t=s?JSON.parse(s):void 0;await F(t)})} diff --git a/plugin/scripts/context-hook.js b/plugin/scripts/context-hook.js index cfb612b4..023b9b06 100755 --- a/plugin/scripts/context-hook.js +++ b/plugin/scripts/context-hook.js @@ -1,7 +1,7 @@ #!/usr/bin/env bun -import mt from"path";import{stdin as L}from"process";import k from"path";import{homedir as Et}from"os";import{spawnSync as lt}from"child_process";import{readFileSync as G,writeFileSync as V,existsSync as j}from"fs";import{join as X}from"path";import{homedir as Y}from"os";var K=["bugfix","feature","refactor","discovery","decision","change"],B=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var y=K.join(","),U=B.join(",");var u=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:X(Y(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:y,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:U,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!j(t))return this.getAllDefaults();let e=G(t,"utf-8"),n=JSON.parse(e),r=n;if(n.env&&typeof n.env=="object"){r=n.env;try{V(t,JSON.stringify(r,null,2),"utf-8"),E.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(i){E.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},i)}}let o={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))r[i]!==void 0&&(o[i]=r[i]);return o}};var A=(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))(A||{}),C=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=u.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=A[t]??1}return this.level}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message} +import Tt from"path";import{stdin as L}from"process";import M from"path";import{homedir as lt}from"os";import{spawnSync as _t}from"child_process";import{existsSync as ft,writeFileSync as k}from"fs";import{readFileSync as j,writeFileSync as V,existsSync as X}from"fs";import{join as Y}from"path";import{homedir as J}from"os";var B=["bugfix","feature","refactor","discovery","decision","change"],G=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var U=B.join(","),w=G.join(",");var a=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:Y(J(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:U,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:w,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!X(t))return this.getAllDefaults();let e=j(t,"utf-8"),n=JSON.parse(e),r=n;if(n.env&&typeof n.env=="object"){r=n.env;try{V(t,JSON.stringify(r,null,2),"utf-8"),E.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(i){E.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},i)}}let o={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))r[i]!==void 0&&(o[i]=r[i]);return o}};var A=(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))(A||{}),C=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=a.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=A[t]??1}return this.level}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message} ${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let n=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&n.command){let r=n.command.length>50?n.command.substring(0,50)+"...":n.command;return`${t}(${r})`}if(t==="Read"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Edit"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Write"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}return t}catch{return t}}log(t,e,n,r,o){if(t0&&(M=` {${Object.entries(R).map(([$,W])=>`${$}=${W}`).join(", ")}}`)}let I=`[${i}] [${p}] [${S}] ${d}${n}${M}${O}`;t===3?console.error(I):console.log(I)}debug(t,e,n,r){this.log(0,t,e,n,r)}info(t,e,n,r){this.log(1,t,e,n,r)}warn(t,e,n,r){this.log(2,t,e,n,r)}error(t,e,n,r){this.log(3,t,e,n,r)}dataIn(t,e,n,r){this.info(t,`\u2192 ${e}`,n,r)}dataOut(t,e,n,r){this.info(t,`\u2190 ${e}`,n,r)}success(t,e,n,r){this.info(t,`\u2713 ${e}`,n,r)}failure(t,e,n,r){this.error(t,`\u2717 ${e}`,n,r)}timing(t,e,n,r){this.info(t,`\u23F1 ${e}`,r,{duration:`${n}ms`})}},E=new C;var _={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(s){return process.platform==="win32"?Math.round(s*_.WINDOWS_MULTIPLIER):s}import{existsSync as D,readFileSync as Z,writeFileSync as tt,unlinkSync as et,mkdirSync as v}from"fs";import{createWriteStream as rt}from"fs";import{join as g}from"path";import{spawn as nt,spawnSync as ot}from"child_process";import{homedir as st}from"os";import{join as a,dirname as J,basename as wt}from"path";import{homedir as q}from"os";import{fileURLToPath as Q}from"url";function z(){return typeof __dirname<"u"?__dirname:J(Q(import.meta.url))}var Ht=z(),c=u.get("CLAUDE_MEM_DATA_DIR"),h=process.env.CLAUDE_CONFIG_DIR||a(q(),".claude"),Ft=a(c,"archives"),$t=a(c,"logs"),Wt=a(c,"trash"),Kt=a(c,"backups"),Bt=a(c,"settings.json"),Gt=a(c,"claude-mem.db"),Vt=a(c,"vector-db"),jt=a(h,"settings.json"),Xt=a(h,"commands"),Yt=a(h,"CLAUDE.md");var f=g(c,"worker.pid"),w=g(c,"logs"),P=g(st(),".claude","plugins","marketplaces","thedotmack"),it=5e3,at=1e4,ct=200,ut=1e3,pt=100,T=class{static async start(t){if(await this.isRunning())return{success:!0,pid:this.getPidInfo()?.pid};v(w,{recursive:!0});let e=g(P,"plugin","scripts","worker-service.cjs");if(!D(e))return{success:!1,error:`Worker script not found at ${e}`};let n=this.getLogFilePath();return this.startWithBun(e,n,t)}static isBunAvailable(){try{return ot("bun",["--version"],{stdio:"pipe",timeout:5e3}).status===0}catch{return!1}}static async startWithBun(t,e,n){if(!this.isBunAvailable())return{success:!1,error:"Bun is required but not found in PATH. Install from https://bun.sh"};try{let r=process.platform==="win32",o=nt("bun",[t],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(n)},cwd:P,...r&&{windowsHide:!0}}),i=rt(e,{flags:"a"});return o.stdout?.pipe(i),o.stderr?.pipe(i),o.unref(),o.pid?(this.writePidFile({pid:o.pid,port:n,startedAt:new Date().toISOString(),version:process.env.npm_package_version||"unknown"}),this.waitForHealth(o.pid,n)):{success:!1,error:"Failed to get PID from spawned process"}}catch(r){return{success:!1,error:r instanceof Error?r.message:String(r)}}}static async stop(t=it){let e=this.getPidInfo();if(!e)return!0;try{process.kill(e.pid,"SIGTERM"),await this.waitForExit(e.pid,t)}catch{try{process.kill(e.pid,"SIGKILL")}catch{}}return this.removePidFile(),!0}static async restart(t){return await this.stop(),this.start(t)}static async status(){let t=this.getPidInfo();if(!t)return{running:!1};let e=this.isProcessAlive(t.pid);return{running:e,pid:e?t.pid:void 0,port:e?t.port:void 0,uptime:e?this.formatUptime(t.startedAt):void 0}}static async isRunning(){let t=this.getPidInfo();if(!t)return!1;let e=this.isProcessAlive(t.pid);return e||this.removePidFile(),e}static getPidInfo(){try{if(!D(f))return null;let t=Z(f,"utf-8"),e=JSON.parse(t);return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}static writePidFile(t){v(c,{recursive:!0}),tt(f,JSON.stringify(t,null,2))}static removePidFile(){try{D(f)&&et(f)}catch{}}static isProcessAlive(t){try{return process.kill(t,0),!0}catch{return!1}}static async waitForHealth(t,e,n=at){let r=Date.now();for(;Date.now()-rsetTimeout(o,ct))}return{success:!1,error:"Health check timed out"}}static async waitForExit(t,e){let n=Date.now();for(;Date.now()-nsetTimeout(r,pt))}throw new Error("Process did not exit within timeout")}static getLogFilePath(){let t=new Date().toISOString().slice(0,10);return g(w,`worker-${t}.log`)}static formatUptime(t){let e=new Date(t).getTime(),r=Date.now()-e,o=Math.floor(r/1e3),i=Math.floor(o/60),p=Math.floor(i/60),S=Math.floor(p/24);return S>0?`${S}d ${p%24}h`:p>0?`${p}h ${i%60}m`:i>0?`${i}m ${o%60}s`:`${o}s`}};var _t=k.join(Et(),".claude","plugins","marketplaces","thedotmack"),ft=N(_.HEALTH_CHECK),l=null;function m(){if(l!==null)return l;try{let s=k.join(u.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=u.loadFromFile(s);return l=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),l}catch(s){return E.debug("SYSTEM","Failed to load port from settings, using default",{error:s}),l=parseInt(u.get("CLAUDE_MEM_WORKER_PORT"),10),l}}async function b(){try{let s=m();return(await fetch(`http://127.0.0.1:${s}/health`,{signal:AbortSignal.timeout(ft)})).ok}catch(s){return E.debug("SYSTEM","Worker health check failed",{error:s instanceof Error?s.message:String(s),errorType:s?.constructor?.name}),!1}}async function gt(){if(process.platform!=="win32")try{lt("pm2",["delete","claude-mem-worker"],{stdio:"ignore"})}catch{}let s=m(),t=await T.start(s);return t.success||E.error("SYSTEM","Failed to start worker",{platform:process.platform,port:s,error:t.error,marketplaceRoot:_t}),t.success}async function x(){if(await b())return;let s=await gt();if(!(!s&&await b())&&!s){let t=m();throw new Error(`Worker service failed to start on port ${t}. +`+JSON.stringify(o,null,2):O=" "+this.formatData(o));let I="";if(r){let{sessionId:Ot,sdkSessionId:At,correlationId:Ct,...y}=r;Object.keys(y).length>0&&(I=` {${Object.entries(y).map(([W,K])=>`${W}=${K}`).join(", ")}}`)}let R=`[${i}] [${p}] [${S}] ${d}${n}${I}${O}`;t===3?console.error(R):console.log(R)}debug(t,e,n,r){this.log(0,t,e,n,r)}info(t,e,n,r){this.log(1,t,e,n,r)}warn(t,e,n,r){this.log(2,t,e,n,r)}error(t,e,n,r){this.log(3,t,e,n,r)}dataIn(t,e,n,r){this.info(t,`\u2192 ${e}`,n,r)}dataOut(t,e,n,r){this.info(t,`\u2190 ${e}`,n,r)}success(t,e,n,r){this.info(t,`\u2713 ${e}`,n,r)}failure(t,e,n,r){this.error(t,`\u2717 ${e}`,n,r)}timing(t,e,n,r){this.info(t,`\u23F1 ${e}`,r,{duration:`${n}ms`})}},E=new C;var _={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(s){return process.platform==="win32"?Math.round(s*_.WINDOWS_MULTIPLIER):s}import{existsSync as D,readFileSync as tt,writeFileSync as et,unlinkSync as rt,mkdirSync as v}from"fs";import{createWriteStream as nt}from"fs";import{join as g}from"path";import{spawn as ot,spawnSync as st}from"child_process";import{homedir as it}from"os";import{join as c,dirname as q,basename as bt}from"path";import{homedir as Q}from"os";import{fileURLToPath as z}from"url";function Z(){return typeof __dirname<"u"?__dirname:q(z(import.meta.url))}var $t=Z(),u=a.get("CLAUDE_MEM_DATA_DIR"),h=process.env.CLAUDE_CONFIG_DIR||c(Q(),".claude"),Wt=c(u,"archives"),Kt=c(u,"logs"),Bt=c(u,"trash"),Gt=c(u,"backups"),jt=c(u,"settings.json"),Vt=c(u,"claude-mem.db"),Xt=c(u,"vector-db"),Yt=c(h,"settings.json"),Jt=c(h,"commands"),qt=c(h,"CLAUDE.md");var f=g(u,"worker.pid"),P=g(u,"logs"),b=g(it(),".claude","plugins","marketplaces","thedotmack"),at=5e3,ct=1e4,ut=200,pt=1e3,Et=100,T=class{static async start(t){if(isNaN(t)||t<1024||t>65535)return{success:!1,error:`Invalid port ${t}. Must be between 1024 and 65535`};if(await this.isRunning())return{success:!0,pid:this.getPidInfo()?.pid};v(P,{recursive:!0});let e=g(b,"plugin","scripts","worker-service.cjs");if(!D(e))return{success:!1,error:`Worker script not found at ${e}`};let n=this.getLogFilePath();return this.startWithBun(e,n,t)}static isBunAvailable(){try{return st("bun",["--version"],{stdio:"pipe",timeout:5e3}).status===0}catch{return!1}}static async startWithBun(t,e,n){if(!this.isBunAvailable())return{success:!1,error:"Bun is required but not found in PATH. Install from https://bun.sh"};try{let r=process.platform==="win32",o=ot("bun",[t],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(n)},cwd:b,...r&&{windowsHide:!0}}),i=nt(e,{flags:"a"});return o.stdout?.pipe(i),o.stderr?.pipe(i),o.unref(),o.pid?(this.writePidFile({pid:o.pid,port:n,startedAt:new Date().toISOString(),version:process.env.npm_package_version||"unknown"}),this.waitForHealth(o.pid,n)):{success:!1,error:"Failed to get PID from spawned process"}}catch(r){return{success:!1,error:r instanceof Error?r.message:String(r)}}}static async stop(t=at){let e=this.getPidInfo();if(!e)return!0;try{process.kill(e.pid,"SIGTERM"),await this.waitForExit(e.pid,t)}catch{try{process.kill(e.pid,"SIGKILL")}catch{}}return this.removePidFile(),!0}static async restart(t){return await this.stop(),this.start(t)}static async status(){let t=this.getPidInfo();if(!t)return{running:!1};let e=this.isProcessAlive(t.pid);return{running:e,pid:e?t.pid:void 0,port:e?t.port:void 0,uptime:e?this.formatUptime(t.startedAt):void 0}}static async isRunning(){let t=this.getPidInfo();if(!t)return!1;let e=this.isProcessAlive(t.pid);return e||this.removePidFile(),e}static getPidInfo(){try{if(!D(f))return null;let t=tt(f,"utf-8"),e=JSON.parse(t);return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}static writePidFile(t){v(u,{recursive:!0}),et(f,JSON.stringify(t,null,2))}static removePidFile(){try{D(f)&&rt(f)}catch{}}static isProcessAlive(t){try{return process.kill(t,0),!0}catch{return!1}}static async waitForHealth(t,e,n=ct){let r=Date.now();for(;Date.now()-rsetTimeout(o,ut))}return{success:!1,error:"Health check timed out"}}static async waitForExit(t,e){let n=Date.now();for(;Date.now()-nsetTimeout(r,Et))}throw new Error("Process did not exit within timeout")}static getLogFilePath(){let t=new Date().toISOString().slice(0,10);return g(P,`worker-${t}.log`)}static formatUptime(t){let e=new Date(t).getTime(),r=Date.now()-e,o=Math.floor(r/1e3),i=Math.floor(o/60),p=Math.floor(i/60),S=Math.floor(p/24);return S>0?`${S}d ${p%24}h`:p>0?`${p}h ${i%60}m`:i>0?`${i}m ${o%60}s`:`${o}s`}};var gt=M.join(lt(),".claude","plugins","marketplaces","thedotmack"),mt=N(_.HEALTH_CHECK),l=null;function m(){if(l!==null)return l;try{let s=M.join(a.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=a.loadFromFile(s);return l=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),l}catch(s){return E.debug("SYSTEM","Failed to load port from settings, using default",{error:s}),l=parseInt(a.get("CLAUDE_MEM_WORKER_PORT"),10),l}}async function x(){try{let s=m();return(await fetch(`http://127.0.0.1:${s}/health`,{signal:AbortSignal.timeout(mt)})).ok}catch(s){return E.debug("SYSTEM","Worker health check failed",{error:s instanceof Error?s.message:String(s),errorType:s?.constructor?.name}),!1}}async function St(){let s=M.join(a.get("CLAUDE_MEM_DATA_DIR"),".pm2-migrated");if(process.platform!=="win32"&&!ft(s))try{_t("pm2",["delete","claude-mem-worker"],{stdio:"ignore"}),k(s,new Date().toISOString(),"utf-8"),E.debug("SYSTEM","PM2 cleanup completed and marked")}catch{k(s,new Date().toISOString(),"utf-8")}let t=m(),e=await T.start(t);return e.success||E.error("SYSTEM","Failed to start worker",{platform:process.platform,port:t,error:e.error,marketplaceRoot:gt}),e.success}async function H(){if(await x())return;let s=await St();if(!(!s&&await x())&&!s){let t=m();throw new Error(`Worker service failed to start on port ${t}. To start manually, run: npm run worker:start -If already running, try: npm run worker:restart`)}}function H(s){throw s.cause?.code==="ECONNREFUSED"||s.name==="TimeoutError"||s.message?.includes("fetch failed")?new Error("There's a problem with the worker. Try: npm run worker:restart"):s}async function F(s){await x();let t=s?.cwd??process.cwd(),e=t?mt.basename(t):"unknown-project",r=`http://127.0.0.1:${m()}/api/context/inject?project=${encodeURIComponent(e)}`;try{let o=await fetch(r,{signal:AbortSignal.timeout(_.DEFAULT)});if(!o.ok){let p=await o.text();throw new Error(`Failed to fetch context: ${o.status} ${p}`)}return(await o.text()).trim()}catch(o){H(o)}}var St=process.argv.includes("--colors");if(L.isTTY||St)F(void 0).then(s=>{console.log(s),process.exit(0)});else{let s="";L.on("data",t=>s+=t),L.on("end",async()=>{let t=s.trim()?JSON.parse(s):void 0,e=await F(t);console.log(JSON.stringify({hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e}})),process.exit(0)})} +If already running, try: npm run worker:restart`)}}function F(s){throw s.cause?.code==="ECONNREFUSED"||s.name==="TimeoutError"||s.message?.includes("fetch failed")?new Error("There's a problem with the worker. Try: npm run worker:restart"):s}async function $(s){await H();let t=s?.cwd??process.cwd(),e=t?Tt.basename(t):"unknown-project",r=`http://127.0.0.1:${m()}/api/context/inject?project=${encodeURIComponent(e)}`;try{let o=await fetch(r,{signal:AbortSignal.timeout(_.DEFAULT)});if(!o.ok){let p=await o.text();throw new Error(`Failed to fetch context: ${o.status} ${p}`)}return(await o.text()).trim()}catch(o){F(o)}}var dt=process.argv.includes("--colors");if(L.isTTY||dt)$(void 0).then(s=>{console.log(s),process.exit(0)});else{let s="";L.on("data",t=>s+=t),L.on("end",async()=>{let t=s.trim()?JSON.parse(s):void 0,e=await $(t);console.log(JSON.stringify({hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e}})),process.exit(0)})} diff --git a/plugin/scripts/new-hook.js b/plugin/scripts/new-hook.js index 08d40082..797d29cf 100755 --- a/plugin/scripts/new-hook.js +++ b/plugin/scripts/new-hook.js @@ -1,9 +1,9 @@ #!/usr/bin/env bun -import Ct from"path";import{stdin as W}from"process";function G(o,t,e){return o==="SessionStart"?t&&e.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:t,suppressOutput:!0,...e.reason&&!t?{stopReason:e.reason}:{}}}function h(o,t,e={}){let r=G(o,t,e);return JSON.stringify(r)}import H from"path";import{homedir as _t}from"os";import{spawnSync as mt}from"child_process";import{readFileSync as X,writeFileSync as Y,existsSync as J}from"fs";import{join as q}from"path";import{homedir as z}from"os";var j=["bugfix","feature","refactor","discovery","decision","change"],V=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var w=j.join(","),P=V.join(",");var E=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:q(z(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:w,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:P,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!J(t))return this.getAllDefaults();let e=X(t,"utf-8"),r=JSON.parse(e),n=r;if(r.env&&typeof r.env=="object"){n=r.env;try{Y(t,JSON.stringify(n,null,2),"utf-8"),_.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(i){_.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},i)}}let s={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))n[i]!==void 0&&(s[i]=n[i]);return s}};var A=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(A||{}),C=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=E.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=A[t]??1}return this.level}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message} +import Lt from"path";import{stdin as K}from"process";function j(o,t,e){return o==="SessionStart"?t&&e.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:t,suppressOutput:!0,...e.reason&&!t?{stopReason:e.reason}:{}}}function h(o,t,e={}){let r=j(o,t,e);return JSON.stringify(r)}import R from"path";import{homedir as _t}from"os";import{spawnSync as gt}from"child_process";import{existsSync as St,writeFileSync as $}from"fs";import{readFileSync as Y,writeFileSync as J,existsSync as q}from"fs";import{join as z}from"path";import{homedir as Q}from"os";var V=["bugfix","feature","refactor","discovery","decision","change"],X=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var P=V.join(","),U=X.join(",");var p=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:z(Q(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:P,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:U,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!q(t))return this.getAllDefaults();let e=Y(t,"utf-8"),r=JSON.parse(e),n=r;if(r.env&&typeof r.env=="object"){n=r.env;try{J(t,JSON.stringify(n,null,2),"utf-8"),m.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(i){m.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},i)}}let s={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))n[i]!==void 0&&(s[i]=n[i]);return s}};var A=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(A||{}),C=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=p.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=A[t]??1}return this.level}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message} ${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let r=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&r.command){let n=r.command.length>50?r.command.substring(0,50)+"...":r.command;return`${t}(${n})`}if(t==="Read"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Edit"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Write"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}return t}catch{return t}}log(t,e,r,n,s){if(t0&&(T=` {${Object.entries(b).map(([K,B])=>`${K}=${B}`).join(", ")}}`)}let I=`[${i}] [${c}] [${f}] ${a}${r}${T}${u}`;t===3?console.error(I):console.log(I)}debug(t,e,r,n){this.log(0,t,e,r,n)}info(t,e,r,n){this.log(1,t,e,r,n)}warn(t,e,r,n){this.log(2,t,e,r,n)}error(t,e,r,n){this.log(3,t,e,r,n)}dataIn(t,e,r,n){this.info(t,`\u2192 ${e}`,r,n)}dataOut(t,e,r,n){this.info(t,`\u2190 ${e}`,r,n)}success(t,e,r,n){this.info(t,`\u2713 ${e}`,r,n)}failure(t,e,r,n){this.error(t,`\u2717 ${e}`,r,n)}timing(t,e,r,n){this.info(t,`\u23F1 ${e}`,n,{duration:`${r}ms`})}},_=new C;var D={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function U(o){return process.platform==="win32"?Math.round(o*D.WINDOWS_MULTIPLIER):o}import{existsSync as M,readFileSync as rt,writeFileSync as nt,unlinkSync as ot,mkdirSync as N}from"fs";import{createWriteStream as st}from"fs";import{join as S}from"path";import{spawn as it,spawnSync as at}from"child_process";import{homedir as ct}from"os";import{join as p,dirname as Q,basename as Wt}from"path";import{homedir as Z}from"os";import{fileURLToPath as tt}from"url";function et(){return typeof __dirname<"u"?__dirname:Q(tt(import.meta.url))}var Vt=et(),l=E.get("CLAUDE_MEM_DATA_DIR"),L=process.env.CLAUDE_CONFIG_DIR||p(Z(),".claude"),Xt=p(l,"archives"),Yt=p(l,"logs"),Jt=p(l,"trash"),qt=p(l,"backups"),zt=p(l,"settings.json"),Qt=p(l,"claude-mem.db"),Zt=p(l,"vector-db"),te=p(L,"settings.json"),ee=p(L,"commands"),re=p(L,"CLAUDE.md");var g=S(l,"worker.pid"),k=S(l,"logs"),v=S(ct(),".claude","plugins","marketplaces","thedotmack"),ut=5e3,pt=1e4,lt=200,Et=1e3,ft=100,O=class{static async start(t){if(await this.isRunning())return{success:!0,pid:this.getPidInfo()?.pid};N(k,{recursive:!0});let e=S(v,"plugin","scripts","worker-service.cjs");if(!M(e))return{success:!1,error:`Worker script not found at ${e}`};let r=this.getLogFilePath();return this.startWithBun(e,r,t)}static isBunAvailable(){try{return at("bun",["--version"],{stdio:"pipe",timeout:5e3}).status===0}catch{return!1}}static async startWithBun(t,e,r){if(!this.isBunAvailable())return{success:!1,error:"Bun is required but not found in PATH. Install from https://bun.sh"};try{let n=process.platform==="win32",s=it("bun",[t],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(r)},cwd:v,...n&&{windowsHide:!0}}),i=st(e,{flags:"a"});return s.stdout?.pipe(i),s.stderr?.pipe(i),s.unref(),s.pid?(this.writePidFile({pid:s.pid,port:r,startedAt:new Date().toISOString(),version:process.env.npm_package_version||"unknown"}),this.waitForHealth(s.pid,r)):{success:!1,error:"Failed to get PID from spawned process"}}catch(n){return{success:!1,error:n instanceof Error?n.message:String(n)}}}static async stop(t=ut){let e=this.getPidInfo();if(!e)return!0;try{process.kill(e.pid,"SIGTERM"),await this.waitForExit(e.pid,t)}catch{try{process.kill(e.pid,"SIGKILL")}catch{}}return this.removePidFile(),!0}static async restart(t){return await this.stop(),this.start(t)}static async status(){let t=this.getPidInfo();if(!t)return{running:!1};let e=this.isProcessAlive(t.pid);return{running:e,pid:e?t.pid:void 0,port:e?t.port:void 0,uptime:e?this.formatUptime(t.startedAt):void 0}}static async isRunning(){let t=this.getPidInfo();if(!t)return!1;let e=this.isProcessAlive(t.pid);return e||this.removePidFile(),e}static getPidInfo(){try{if(!M(g))return null;let t=rt(g,"utf-8"),e=JSON.parse(t);return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}static writePidFile(t){N(l,{recursive:!0}),nt(g,JSON.stringify(t,null,2))}static removePidFile(){try{M(g)&&ot(g)}catch{}}static isProcessAlive(t){try{return process.kill(t,0),!0}catch{return!1}}static async waitForHealth(t,e,r=pt){let n=Date.now();for(;Date.now()-nsetTimeout(s,lt))}return{success:!1,error:"Health check timed out"}}static async waitForExit(t,e){let r=Date.now();for(;Date.now()-rsetTimeout(n,ft))}throw new Error("Process did not exit within timeout")}static getLogFilePath(){let t=new Date().toISOString().slice(0,10);return S(k,`worker-${t}.log`)}static formatUptime(t){let e=new Date(t).getTime(),n=Date.now()-e,s=Math.floor(n/1e3),i=Math.floor(s/60),c=Math.floor(i/60),f=Math.floor(c/24);return f>0?`${f}d ${c%24}h`:c>0?`${c}h ${i%60}m`:i>0?`${i}m ${s%60}s`:`${s}s`}};var gt=H.join(_t(),".claude","plugins","marketplaces","thedotmack"),St=U(D.HEALTH_CHECK),m=null;function d(){if(m!==null)return m;try{let o=H.join(E.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=E.loadFromFile(o);return m=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),m}catch(o){return _.debug("SYSTEM","Failed to load port from settings, using default",{error:o}),m=parseInt(E.get("CLAUDE_MEM_WORKER_PORT"),10),m}}async function x(){try{let o=d();return(await fetch(`http://127.0.0.1:${o}/health`,{signal:AbortSignal.timeout(St)})).ok}catch(o){return _.debug("SYSTEM","Worker health check failed",{error:o instanceof Error?o.message:String(o),errorType:o?.constructor?.name}),!1}}async function dt(){if(process.platform!=="win32")try{mt("pm2",["delete","claude-mem-worker"],{stdio:"ignore"})}catch{}let o=d(),t=await O.start(o);return t.success||_.error("SYSTEM","Failed to start worker",{platform:process.platform,port:o,error:t.error,marketplaceRoot:gt}),t.success}async function $(){if(await x())return;let o=await dt();if(!(!o&&await x())&&!o){let t=d();throw new Error(`Worker service failed to start on port ${t}. +`+JSON.stringify(s,null,2):u=" "+this.formatData(s));let T="";if(n){let{sessionId:Rt,sdkSessionId:yt,correlationId:It,...w}=n;Object.keys(w).length>0&&(T=` {${Object.entries(w).map(([B,G])=>`${B}=${G}`).join(", ")}}`)}let b=`[${i}] [${c}] [${f}] ${a}${r}${T}${u}`;t===3?console.error(b):console.log(b)}debug(t,e,r,n){this.log(0,t,e,r,n)}info(t,e,r,n){this.log(1,t,e,r,n)}warn(t,e,r,n){this.log(2,t,e,r,n)}error(t,e,r,n){this.log(3,t,e,r,n)}dataIn(t,e,r,n){this.info(t,`\u2192 ${e}`,r,n)}dataOut(t,e,r,n){this.info(t,`\u2190 ${e}`,r,n)}success(t,e,r,n){this.info(t,`\u2713 ${e}`,r,n)}failure(t,e,r,n){this.error(t,`\u2717 ${e}`,r,n)}timing(t,e,r,n){this.info(t,`\u23F1 ${e}`,n,{duration:`${r}ms`})}},m=new C;var D={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(o){return process.platform==="win32"?Math.round(o*D.WINDOWS_MULTIPLIER):o}import{existsSync as M,readFileSync as nt,writeFileSync as ot,unlinkSync as st,mkdirSync as k}from"fs";import{createWriteStream as it}from"fs";import{join as S}from"path";import{spawn as at,spawnSync as ct}from"child_process";import{homedir as ut}from"os";import{join as l,dirname as Z,basename as Bt}from"path";import{homedir as tt}from"os";import{fileURLToPath as et}from"url";function rt(){return typeof __dirname<"u"?__dirname:Z(et(import.meta.url))}var Yt=rt(),E=p.get("CLAUDE_MEM_DATA_DIR"),L=process.env.CLAUDE_CONFIG_DIR||l(tt(),".claude"),Jt=l(E,"archives"),qt=l(E,"logs"),zt=l(E,"trash"),Qt=l(E,"backups"),Zt=l(E,"settings.json"),te=l(E,"claude-mem.db"),ee=l(E,"vector-db"),re=l(L,"settings.json"),ne=l(L,"commands"),oe=l(L,"CLAUDE.md");var g=S(E,"worker.pid"),v=S(E,"logs"),x=S(ut(),".claude","plugins","marketplaces","thedotmack"),pt=5e3,lt=1e4,Et=200,ft=1e3,mt=100,O=class{static async start(t){if(isNaN(t)||t<1024||t>65535)return{success:!1,error:`Invalid port ${t}. Must be between 1024 and 65535`};if(await this.isRunning())return{success:!0,pid:this.getPidInfo()?.pid};k(v,{recursive:!0});let e=S(x,"plugin","scripts","worker-service.cjs");if(!M(e))return{success:!1,error:`Worker script not found at ${e}`};let r=this.getLogFilePath();return this.startWithBun(e,r,t)}static isBunAvailable(){try{return ct("bun",["--version"],{stdio:"pipe",timeout:5e3}).status===0}catch{return!1}}static async startWithBun(t,e,r){if(!this.isBunAvailable())return{success:!1,error:"Bun is required but not found in PATH. Install from https://bun.sh"};try{let n=process.platform==="win32",s=at("bun",[t],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(r)},cwd:x,...n&&{windowsHide:!0}}),i=it(e,{flags:"a"});return s.stdout?.pipe(i),s.stderr?.pipe(i),s.unref(),s.pid?(this.writePidFile({pid:s.pid,port:r,startedAt:new Date().toISOString(),version:process.env.npm_package_version||"unknown"}),this.waitForHealth(s.pid,r)):{success:!1,error:"Failed to get PID from spawned process"}}catch(n){return{success:!1,error:n instanceof Error?n.message:String(n)}}}static async stop(t=pt){let e=this.getPidInfo();if(!e)return!0;try{process.kill(e.pid,"SIGTERM"),await this.waitForExit(e.pid,t)}catch{try{process.kill(e.pid,"SIGKILL")}catch{}}return this.removePidFile(),!0}static async restart(t){return await this.stop(),this.start(t)}static async status(){let t=this.getPidInfo();if(!t)return{running:!1};let e=this.isProcessAlive(t.pid);return{running:e,pid:e?t.pid:void 0,port:e?t.port:void 0,uptime:e?this.formatUptime(t.startedAt):void 0}}static async isRunning(){let t=this.getPidInfo();if(!t)return!1;let e=this.isProcessAlive(t.pid);return e||this.removePidFile(),e}static getPidInfo(){try{if(!M(g))return null;let t=nt(g,"utf-8"),e=JSON.parse(t);return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}static writePidFile(t){k(E,{recursive:!0}),ot(g,JSON.stringify(t,null,2))}static removePidFile(){try{M(g)&&st(g)}catch{}}static isProcessAlive(t){try{return process.kill(t,0),!0}catch{return!1}}static async waitForHealth(t,e,r=lt){let n=Date.now();for(;Date.now()-nsetTimeout(s,Et))}return{success:!1,error:"Health check timed out"}}static async waitForExit(t,e){let r=Date.now();for(;Date.now()-rsetTimeout(n,mt))}throw new Error("Process did not exit within timeout")}static getLogFilePath(){let t=new Date().toISOString().slice(0,10);return S(v,`worker-${t}.log`)}static formatUptime(t){let e=new Date(t).getTime(),n=Date.now()-e,s=Math.floor(n/1e3),i=Math.floor(s/60),c=Math.floor(i/60),f=Math.floor(c/24);return f>0?`${f}d ${c%24}h`:c>0?`${c}h ${i%60}m`:i>0?`${i}m ${s%60}s`:`${s}s`}};var dt=R.join(_t(),".claude","plugins","marketplaces","thedotmack"),Tt=N(D.HEALTH_CHECK),_=null;function d(){if(_!==null)return _;try{let o=R.join(p.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=p.loadFromFile(o);return _=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),_}catch(o){return m.debug("SYSTEM","Failed to load port from settings, using default",{error:o}),_=parseInt(p.get("CLAUDE_MEM_WORKER_PORT"),10),_}}async function H(){try{let o=d();return(await fetch(`http://127.0.0.1:${o}/health`,{signal:AbortSignal.timeout(Tt)})).ok}catch(o){return m.debug("SYSTEM","Worker health check failed",{error:o instanceof Error?o.message:String(o),errorType:o?.constructor?.name}),!1}}async function Ot(){let o=R.join(p.get("CLAUDE_MEM_DATA_DIR"),".pm2-migrated");if(process.platform!=="win32"&&!St(o))try{gt("pm2",["delete","claude-mem-worker"],{stdio:"ignore"}),$(o,new Date().toISOString(),"utf-8"),m.debug("SYSTEM","PM2 cleanup completed and marked")}catch{$(o,new Date().toISOString(),"utf-8")}let t=d(),e=await O.start(t);return e.success||m.error("SYSTEM","Failed to start worker",{platform:process.platform,port:t,error:e.error,marketplaceRoot:dt}),e.success}async function F(){if(await H())return;let o=await Ot();if(!(!o&&await H())&&!o){let t=d();throw new Error(`Worker service failed to start on port ${t}. To start manually, run: npm run worker:start -If already running, try: npm run worker:restart`)}}import{appendFileSync as Tt}from"fs";import{homedir as Ot}from"os";import{join as ht}from"path";var At=ht(Ot(),".claude-mem","silent.log");function F(o,t,e=""){let r=new Date().toISOString(),c=((new Error().stack||"").split(` +If already running, try: npm run worker:restart`)}}import{appendFileSync as ht}from"fs";import{homedir as At}from"os";import{join as Ct}from"path";var Dt=Ct(At(),".claude-mem","silent.log");function W(o,t,e=""){let r=new Date().toISOString(),c=((new Error().stack||"").split(` `)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),f=c?`${c[1].split("/").pop()}:${c[2]}`:"unknown",a=`[${r}] [HAPPY-PATH-ERROR] [${f}] ${o}`;if(t!==void 0)try{a+=` ${JSON.stringify(t)}`}catch(u){a+=` [stringify error: ${u}]`}a+=` -`;try{Tt(At,a)}catch(u){console.error("[silent-debug] Failed to write to log:",u)}return e}function R(o){throw o.cause?.code==="ECONNREFUSED"||o.name==="TimeoutError"||o.message?.includes("fetch failed")?new Error("There's a problem with the worker. Try: npm run worker:restart"):o}async function Dt(o){if(await $(),!o)throw new Error("newHook requires input");let{session_id:t,cwd:e,prompt:r}=o,n=Ct.basename(e);F("[new-hook] Input received",{session_id:t,project:n,prompt_length:r?.length});let s=d(),i,c;try{let a=await fetch(`http://127.0.0.1:${s}/api/sessions/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,project:n,prompt:r}),signal:AbortSignal.timeout(5e3)});if(!a.ok){let T=await a.text();throw new Error(`Failed to initialize session: ${a.status} ${T}`)}let u=await a.json();if(i=u.sessionDbId,c=u.promptNumber,u.skipped&&u.reason==="private"){console.error(`[new-hook] Session ${i}, prompt #${c} (fully private - skipped)`),console.log(h("UserPromptSubmit",!0));return}console.error(`[new-hook] Session ${i}, prompt #${c}`)}catch(a){R(a)}let f=r.startsWith("/")?r.substring(1):r;try{let a=await fetch(`http://127.0.0.1:${s}/sessions/${i}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userPrompt:f,promptNumber:c}),signal:AbortSignal.timeout(5e3)});if(!a.ok){let u=await a.text();throw new Error(`Failed to start SDK agent: ${a.status} ${u}`)}}catch(a){R(a)}console.log(h("UserPromptSubmit",!0))}var y="";W.on("data",o=>y+=o);W.on("end",async()=>{let o=y?JSON.parse(y):void 0;await Dt(o)}); +`;try{ht(Dt,a)}catch(u){console.error("[silent-debug] Failed to write to log:",u)}return e}function y(o){throw o.cause?.code==="ECONNREFUSED"||o.name==="TimeoutError"||o.message?.includes("fetch failed")?new Error("There's a problem with the worker. Try: npm run worker:restart"):o}async function Mt(o){if(await F(),!o)throw new Error("newHook requires input");let{session_id:t,cwd:e,prompt:r}=o,n=Lt.basename(e);W("[new-hook] Input received",{session_id:t,project:n,prompt_length:r?.length});let s=d(),i,c;try{let a=await fetch(`http://127.0.0.1:${s}/api/sessions/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,project:n,prompt:r}),signal:AbortSignal.timeout(5e3)});if(!a.ok){let T=await a.text();throw new Error(`Failed to initialize session: ${a.status} ${T}`)}let u=await a.json();if(i=u.sessionDbId,c=u.promptNumber,u.skipped&&u.reason==="private"){console.error(`[new-hook] Session ${i}, prompt #${c} (fully private - skipped)`),console.log(h("UserPromptSubmit",!0));return}console.error(`[new-hook] Session ${i}, prompt #${c}`)}catch(a){y(a)}let f=r.startsWith("/")?r.substring(1):r;try{let a=await fetch(`http://127.0.0.1:${s}/sessions/${i}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userPrompt:f,promptNumber:c}),signal:AbortSignal.timeout(5e3)});if(!a.ok){let u=await a.text();throw new Error(`Failed to start SDK agent: ${a.status} ${u}`)}}catch(a){y(a)}console.log(h("UserPromptSubmit",!0))}var I="";K.on("data",o=>I+=o);K.on("end",async()=>{let o=I?JSON.parse(I):void 0;await Mt(o)}); diff --git a/plugin/scripts/save-hook.js b/plugin/scripts/save-hook.js index ba3f5367..00959320 100755 --- a/plugin/scripts/save-hook.js +++ b/plugin/scripts/save-hook.js @@ -1,9 +1,9 @@ #!/usr/bin/env bun -import{stdin as W}from"process";function G(n,t,e){return n==="SessionStart"?t&&e.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e.context}}:{continue:!0,suppressOutput:!0}:n==="UserPromptSubmit"||n==="PostToolUse"?{continue:!0,suppressOutput:!0}:n==="Stop"?{continue:!0,suppressOutput:!0}:{continue:t,suppressOutput:!0,...e.reason&&!t?{stopReason:e.reason}:{}}}function I(n,t,e={}){let r=G(n,t,e);return JSON.stringify(r)}import{readFileSync as j,writeFileSync as Y,existsSync as J}from"fs";import{join as q}from"path";import{homedir as Q}from"os";var V=["bugfix","feature","refactor","discovery","decision","change"],X=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var U=V.join(","),P=X.join(",");var _=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:q(Q(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:U,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:P,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!J(t))return this.getAllDefaults();let e=j(t,"utf-8"),r=JSON.parse(e),o=r;if(r.env&&typeof r.env=="object"){o=r.env;try{Y(t,JSON.stringify(o,null,2),"utf-8"),u.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(i){u.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},i)}}let s={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))o[i]!==void 0&&(s[i]=o[i]);return s}};var h=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(h||{}),A=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=_.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=h[t]??1}return this.level}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message} -${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let r=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&r.command){let o=r.command.length>50?r.command.substring(0,50)+"...":r.command;return`${t}(${o})`}if(t==="Read"&&r.file_path){let o=r.file_path.split("/").pop()||r.file_path;return`${t}(${o})`}if(t==="Edit"&&r.file_path){let o=r.file_path.split("/").pop()||r.file_path;return`${t}(${o})`}if(t==="Write"&&r.file_path){let o=r.file_path.split("/").pop()||r.file_path;return`${t}(${o})`}return t}catch{return t}}log(t,e,r,o,s){if(t0&&(M=` {${Object.entries(y).map(([K,B])=>`${K}=${B}`).join(", ")}}`)}let R=`[${i}] [${c}] [${a}] ${p}${r}${M}${f}`;t===3?console.error(R):console.log(R)}debug(t,e,r,o){this.log(0,t,e,r,o)}info(t,e,r,o){this.log(1,t,e,r,o)}warn(t,e,r,o){this.log(2,t,e,r,o)}error(t,e,r,o){this.log(3,t,e,r,o)}dataIn(t,e,r,o){this.info(t,`\u2192 ${e}`,r,o)}dataOut(t,e,r,o){this.info(t,`\u2190 ${e}`,r,o)}success(t,e,r,o){this.info(t,`\u2713 ${e}`,r,o)}failure(t,e,r,o){this.error(t,`\u2717 ${e}`,r,o)}timing(t,e,r,o){this.info(t,`\u23F1 ${e}`,o,{duration:`${r}ms`})}},u=new A;import x from"path";import{homedir as ft}from"os";import{spawnSync as mt}from"child_process";var g={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function w(n){return process.platform==="win32"?Math.round(n*g.WINDOWS_MULTIPLIER):n}import{existsSync as L,readFileSync as rt,writeFileSync as ot,unlinkSync as nt,mkdirSync as b}from"fs";import{createWriteStream as st}from"fs";import{join as T}from"path";import{spawn as it,spawnSync as at}from"child_process";import{homedir as ct}from"os";import{join as l,dirname as z,basename as Ft}from"path";import{homedir as Z}from"os";import{fileURLToPath as tt}from"url";function et(){return typeof __dirname<"u"?__dirname:z(tt(import.meta.url))}var Vt=et(),E=_.get("CLAUDE_MEM_DATA_DIR"),C=process.env.CLAUDE_CONFIG_DIR||l(Z(),".claude"),Xt=l(E,"archives"),jt=l(E,"logs"),Yt=l(E,"trash"),Jt=l(E,"backups"),qt=l(E,"settings.json"),Qt=l(E,"claude-mem.db"),zt=l(E,"vector-db"),Zt=l(C,"settings.json"),te=l(C,"commands"),ee=l(C,"CLAUDE.md");var S=T(E,"worker.pid"),v=T(E,"logs"),N=T(ct(),".claude","plugins","marketplaces","thedotmack"),ut=5e3,pt=1e4,lt=200,Et=1e3,_t=100,O=class{static async start(t){if(await this.isRunning())return{success:!0,pid:this.getPidInfo()?.pid};b(v,{recursive:!0});let e=T(N,"plugin","scripts","worker-service.cjs");if(!L(e))return{success:!1,error:`Worker script not found at ${e}`};let r=this.getLogFilePath();return this.startWithBun(e,r,t)}static isBunAvailable(){try{return at("bun",["--version"],{stdio:"pipe",timeout:5e3}).status===0}catch{return!1}}static async startWithBun(t,e,r){if(!this.isBunAvailable())return{success:!1,error:"Bun is required but not found in PATH. Install from https://bun.sh"};try{let o=process.platform==="win32",s=it("bun",[t],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(r)},cwd:N,...o&&{windowsHide:!0}}),i=st(e,{flags:"a"});return s.stdout?.pipe(i),s.stderr?.pipe(i),s.unref(),s.pid?(this.writePidFile({pid:s.pid,port:r,startedAt:new Date().toISOString(),version:process.env.npm_package_version||"unknown"}),this.waitForHealth(s.pid,r)):{success:!1,error:"Failed to get PID from spawned process"}}catch(o){return{success:!1,error:o instanceof Error?o.message:String(o)}}}static async stop(t=ut){let e=this.getPidInfo();if(!e)return!0;try{process.kill(e.pid,"SIGTERM"),await this.waitForExit(e.pid,t)}catch{try{process.kill(e.pid,"SIGKILL")}catch{}}return this.removePidFile(),!0}static async restart(t){return await this.stop(),this.start(t)}static async status(){let t=this.getPidInfo();if(!t)return{running:!1};let e=this.isProcessAlive(t.pid);return{running:e,pid:e?t.pid:void 0,port:e?t.port:void 0,uptime:e?this.formatUptime(t.startedAt):void 0}}static async isRunning(){let t=this.getPidInfo();if(!t)return!1;let e=this.isProcessAlive(t.pid);return e||this.removePidFile(),e}static getPidInfo(){try{if(!L(S))return null;let t=rt(S,"utf-8"),e=JSON.parse(t);return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}static writePidFile(t){b(E,{recursive:!0}),ot(S,JSON.stringify(t,null,2))}static removePidFile(){try{L(S)&&nt(S)}catch{}}static isProcessAlive(t){try{return process.kill(t,0),!0}catch{return!1}}static async waitForHealth(t,e,r=pt){let o=Date.now();for(;Date.now()-osetTimeout(s,lt))}return{success:!1,error:"Health check timed out"}}static async waitForExit(t,e){let r=Date.now();for(;Date.now()-rsetTimeout(o,_t))}throw new Error("Process did not exit within timeout")}static getLogFilePath(){let t=new Date().toISOString().slice(0,10);return T(v,`worker-${t}.log`)}static formatUptime(t){let e=new Date(t).getTime(),o=Date.now()-e,s=Math.floor(o/1e3),i=Math.floor(s/60),c=Math.floor(i/60),a=Math.floor(c/24);return a>0?`${a}d ${c%24}h`:c>0?`${c}h ${i%60}m`:i>0?`${i}m ${s%60}s`:`${s}s`}};var gt=x.join(ft(),".claude","plugins","marketplaces","thedotmack"),St=w(g.HEALTH_CHECK),m=null;function d(){if(m!==null)return m;try{let n=x.join(_.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=_.loadFromFile(n);return m=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),m}catch(n){return u.debug("SYSTEM","Failed to load port from settings, using default",{error:n}),m=parseInt(_.get("CLAUDE_MEM_WORKER_PORT"),10),m}}async function k(){try{let n=d();return(await fetch(`http://127.0.0.1:${n}/health`,{signal:AbortSignal.timeout(St)})).ok}catch(n){return u.debug("SYSTEM","Worker health check failed",{error:n instanceof Error?n.message:String(n),errorType:n?.constructor?.name}),!1}}async function Tt(){if(process.platform!=="win32")try{mt("pm2",["delete","claude-mem-worker"],{stdio:"ignore"})}catch{}let n=d(),t=await O.start(n);return t.success||u.error("SYSTEM","Failed to start worker",{platform:process.platform,port:n,error:t.error,marketplaceRoot:gt}),t.success}async function H(){if(await k())return;let n=await Tt();if(!(!n&&await k())&&!n){let t=d();throw new Error(`Worker service failed to start on port ${t}. +import{stdin as K}from"process";function V(n,t,e){return n==="SessionStart"?t&&e.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e.context}}:{continue:!0,suppressOutput:!0}:n==="UserPromptSubmit"||n==="PostToolUse"?{continue:!0,suppressOutput:!0}:n==="Stop"?{continue:!0,suppressOutput:!0}:{continue:t,suppressOutput:!0,...e.reason&&!t?{stopReason:e.reason}:{}}}function U(n,t,e={}){let r=V(n,t,e);return JSON.stringify(r)}import{readFileSync as Y,writeFileSync as J,existsSync as q}from"fs";import{join as Q}from"path";import{homedir as z}from"os";var X=["bugfix","feature","refactor","discovery","decision","change"],j=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var P=X.join(","),w=j.join(",");var l=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:Q(z(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:P,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:w,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!q(t))return this.getAllDefaults();let e=Y(t,"utf-8"),r=JSON.parse(e),o=r;if(r.env&&typeof r.env=="object"){o=r.env;try{J(t,JSON.stringify(o,null,2),"utf-8"),c.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(i){c.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},i)}}let s={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))o[i]!==void 0&&(s[i]=o[i]);return s}};var h=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(h||{}),A=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=l.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=h[t]??1}return this.level}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message} +${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let r=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&r.command){let o=r.command.length>50?r.command.substring(0,50)+"...":r.command;return`${t}(${o})`}if(t==="Read"&&r.file_path){let o=r.file_path.split("/").pop()||r.file_path;return`${t}(${o})`}if(t==="Edit"&&r.file_path){let o=r.file_path.split("/").pop()||r.file_path;return`${t}(${o})`}if(t==="Write"&&r.file_path){let o=r.file_path.split("/").pop()||r.file_path;return`${t}(${o})`}return t}catch{return t}}log(t,e,r,o,s){if(t0&&(R=` {${Object.entries(y).map(([B,G])=>`${B}=${G}`).join(", ")}}`)}let I=`[${i}] [${u}] [${a}] ${p}${r}${R}${_}`;t===3?console.error(I):console.log(I)}debug(t,e,r,o){this.log(0,t,e,r,o)}info(t,e,r,o){this.log(1,t,e,r,o)}warn(t,e,r,o){this.log(2,t,e,r,o)}error(t,e,r,o){this.log(3,t,e,r,o)}dataIn(t,e,r,o){this.info(t,`\u2192 ${e}`,r,o)}dataOut(t,e,r,o){this.info(t,`\u2190 ${e}`,r,o)}success(t,e,r,o){this.info(t,`\u2713 ${e}`,r,o)}failure(t,e,r,o){this.error(t,`\u2717 ${e}`,r,o)}timing(t,e,r,o){this.info(t,`\u23F1 ${e}`,o,{duration:`${r}ms`})}},c=new A;import L from"path";import{homedir as mt}from"os";import{spawnSync as gt}from"child_process";import{existsSync as St,writeFileSync as x}from"fs";var g={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function b(n){return process.platform==="win32"?Math.round(n*g.WINDOWS_MULTIPLIER):n}import{existsSync as D,readFileSync as ot,writeFileSync as nt,unlinkSync as st,mkdirSync as v}from"fs";import{createWriteStream as it}from"fs";import{join as d}from"path";import{spawn as at,spawnSync as ct}from"child_process";import{homedir as ut}from"os";import{join as E,dirname as Z,basename as Kt}from"path";import{homedir as tt}from"os";import{fileURLToPath as et}from"url";function rt(){return typeof __dirname<"u"?__dirname:Z(et(import.meta.url))}var jt=rt(),f=l.get("CLAUDE_MEM_DATA_DIR"),C=process.env.CLAUDE_CONFIG_DIR||E(tt(),".claude"),Yt=E(f,"archives"),Jt=E(f,"logs"),qt=E(f,"trash"),Qt=E(f,"backups"),zt=E(f,"settings.json"),Zt=E(f,"claude-mem.db"),te=E(f,"vector-db"),ee=E(C,"settings.json"),re=E(C,"commands"),oe=E(C,"CLAUDE.md");var S=d(f,"worker.pid"),N=d(f,"logs"),k=d(ut(),".claude","plugins","marketplaces","thedotmack"),pt=5e3,lt=1e4,Et=200,ft=1e3,_t=100,O=class{static async start(t){if(isNaN(t)||t<1024||t>65535)return{success:!1,error:`Invalid port ${t}. Must be between 1024 and 65535`};if(await this.isRunning())return{success:!0,pid:this.getPidInfo()?.pid};v(N,{recursive:!0});let e=d(k,"plugin","scripts","worker-service.cjs");if(!D(e))return{success:!1,error:`Worker script not found at ${e}`};let r=this.getLogFilePath();return this.startWithBun(e,r,t)}static isBunAvailable(){try{return ct("bun",["--version"],{stdio:"pipe",timeout:5e3}).status===0}catch{return!1}}static async startWithBun(t,e,r){if(!this.isBunAvailable())return{success:!1,error:"Bun is required but not found in PATH. Install from https://bun.sh"};try{let o=process.platform==="win32",s=at("bun",[t],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(r)},cwd:k,...o&&{windowsHide:!0}}),i=it(e,{flags:"a"});return s.stdout?.pipe(i),s.stderr?.pipe(i),s.unref(),s.pid?(this.writePidFile({pid:s.pid,port:r,startedAt:new Date().toISOString(),version:process.env.npm_package_version||"unknown"}),this.waitForHealth(s.pid,r)):{success:!1,error:"Failed to get PID from spawned process"}}catch(o){return{success:!1,error:o instanceof Error?o.message:String(o)}}}static async stop(t=pt){let e=this.getPidInfo();if(!e)return!0;try{process.kill(e.pid,"SIGTERM"),await this.waitForExit(e.pid,t)}catch{try{process.kill(e.pid,"SIGKILL")}catch{}}return this.removePidFile(),!0}static async restart(t){return await this.stop(),this.start(t)}static async status(){let t=this.getPidInfo();if(!t)return{running:!1};let e=this.isProcessAlive(t.pid);return{running:e,pid:e?t.pid:void 0,port:e?t.port:void 0,uptime:e?this.formatUptime(t.startedAt):void 0}}static async isRunning(){let t=this.getPidInfo();if(!t)return!1;let e=this.isProcessAlive(t.pid);return e||this.removePidFile(),e}static getPidInfo(){try{if(!D(S))return null;let t=ot(S,"utf-8"),e=JSON.parse(t);return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}static writePidFile(t){v(f,{recursive:!0}),nt(S,JSON.stringify(t,null,2))}static removePidFile(){try{D(S)&&st(S)}catch{}}static isProcessAlive(t){try{return process.kill(t,0),!0}catch{return!1}}static async waitForHealth(t,e,r=lt){let o=Date.now();for(;Date.now()-osetTimeout(s,Et))}return{success:!1,error:"Health check timed out"}}static async waitForExit(t,e){let r=Date.now();for(;Date.now()-rsetTimeout(o,_t))}throw new Error("Process did not exit within timeout")}static getLogFilePath(){let t=new Date().toISOString().slice(0,10);return d(N,`worker-${t}.log`)}static formatUptime(t){let e=new Date(t).getTime(),o=Date.now()-e,s=Math.floor(o/1e3),i=Math.floor(s/60),u=Math.floor(i/60),a=Math.floor(u/24);return a>0?`${a}d ${u%24}h`:u>0?`${u}h ${i%60}m`:i>0?`${i}m ${s%60}s`:`${s}s`}};var dt=L.join(mt(),".claude","plugins","marketplaces","thedotmack"),Tt=b(g.HEALTH_CHECK),m=null;function T(){if(m!==null)return m;try{let n=L.join(l.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=l.loadFromFile(n);return m=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),m}catch(n){return c.debug("SYSTEM","Failed to load port from settings, using default",{error:n}),m=parseInt(l.get("CLAUDE_MEM_WORKER_PORT"),10),m}}async function H(){try{let n=T();return(await fetch(`http://127.0.0.1:${n}/health`,{signal:AbortSignal.timeout(Tt)})).ok}catch(n){return c.debug("SYSTEM","Worker health check failed",{error:n instanceof Error?n.message:String(n),errorType:n?.constructor?.name}),!1}}async function Ot(){let n=L.join(l.get("CLAUDE_MEM_DATA_DIR"),".pm2-migrated");if(process.platform!=="win32"&&!St(n))try{gt("pm2",["delete","claude-mem-worker"],{stdio:"ignore"}),x(n,new Date().toISOString(),"utf-8"),c.debug("SYSTEM","PM2 cleanup completed and marked")}catch{x(n,new Date().toISOString(),"utf-8")}let t=T(),e=await O.start(t);return e.success||c.error("SYSTEM","Failed to start worker",{platform:process.platform,port:t,error:e.error,marketplaceRoot:dt}),e.success}async function $(){if(await H())return;let n=await Ot();if(!(!n&&await H())&&!n){let t=T();throw new Error(`Worker service failed to start on port ${t}. To start manually, run: npm run worker:start -If already running, try: npm run worker:restart`)}}import{appendFileSync as dt}from"fs";import{homedir as Ot}from"os";import{join as ht}from"path";var At=ht(Ot(),".claude-mem","silent.log");function $(n,t,e=""){let r=new Date().toISOString(),c=((new Error().stack||"").split(` -`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),a=c?`${c[1].split("/").pop()}:${c[2]}`:"unknown",p=`[${r}] [HAPPY-PATH-ERROR] [${a}] ${n}`;if(t!==void 0)try{p+=` ${JSON.stringify(t)}`}catch(f){p+=` [stringify error: ${f}]`}p+=` -`;try{dt(At,p)}catch(f){console.error("[silent-debug] Failed to write to log:",f)}return e}function F(n){throw n.cause?.code==="ECONNREFUSED"||n.name==="TimeoutError"||n.message?.includes("fetch failed")?new Error("There's a problem with the worker. Try: npm run worker:restart"):n}async function Ct(n){if(await H(),!n)throw new Error("saveHook requires input");let{session_id:t,cwd:e,tool_name:r,tool_input:o,tool_response:s}=n,i=d(),c=u.formatTool(r,o);u.dataIn("HOOK",`PostToolUse: ${c}`,{workerPort:i});try{let a=await fetch(`http://127.0.0.1:${i}/api/sessions/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,tool_name:r,tool_input:o,tool_response:s,cwd:$("Missing cwd in PostToolUse hook input",{session_id:t,tool_name:r},e||"")}),signal:AbortSignal.timeout(g.DEFAULT)});if(!a.ok){let p=await a.text();throw u.failure("HOOK","Failed to send observation",{status:a.status},p),new Error(`Failed to send observation to worker: ${a.status} ${p}`)}u.debug("HOOK","Observation sent successfully",{toolName:r})}catch(a){F(a)}console.log(I("PostToolUse",!0))}var D="";W.on("data",n=>D+=n);W.on("end",async()=>{let n=D?JSON.parse(D):void 0;await Ct(n)}); +If already running, try: npm run worker:restart`)}}import{appendFileSync as ht}from"fs";import{homedir as At}from"os";import{join as Ct}from"path";var Dt=Ct(At(),".claude-mem","silent.log");function F(n,t,e=""){let r=new Date().toISOString(),u=((new Error().stack||"").split(` +`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),a=u?`${u[1].split("/").pop()}:${u[2]}`:"unknown",p=`[${r}] [HAPPY-PATH-ERROR] [${a}] ${n}`;if(t!==void 0)try{p+=` ${JSON.stringify(t)}`}catch(_){p+=` [stringify error: ${_}]`}p+=` +`;try{ht(Dt,p)}catch(_){console.error("[silent-debug] Failed to write to log:",_)}return e}function W(n){throw n.cause?.code==="ECONNREFUSED"||n.name==="TimeoutError"||n.message?.includes("fetch failed")?new Error("There's a problem with the worker. Try: npm run worker:restart"):n}async function Lt(n){if(await $(),!n)throw new Error("saveHook requires input");let{session_id:t,cwd:e,tool_name:r,tool_input:o,tool_response:s}=n,i=T(),u=c.formatTool(r,o);c.dataIn("HOOK",`PostToolUse: ${u}`,{workerPort:i});try{let a=await fetch(`http://127.0.0.1:${i}/api/sessions/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,tool_name:r,tool_input:o,tool_response:s,cwd:F("Missing cwd in PostToolUse hook input",{session_id:t,tool_name:r},e||"")}),signal:AbortSignal.timeout(g.DEFAULT)});if(!a.ok){let p=await a.text();throw c.failure("HOOK","Failed to send observation",{status:a.status},p),new Error(`Failed to send observation to worker: ${a.status} ${p}`)}c.debug("HOOK","Observation sent successfully",{toolName:r})}catch(a){W(a)}console.log(U("PostToolUse",!0))}var M="";K.on("data",n=>M+=n);K.on("end",async()=>{let n=M?JSON.parse(M):void 0;await Lt(n)}); diff --git a/plugin/scripts/smart-install.js b/plugin/scripts/smart-install.js index c1416d84..da46f41b 100644 --- a/plugin/scripts/smart-install.js +++ b/plugin/scripts/smart-install.js @@ -17,7 +17,6 @@ import { existsSync, readFileSync, writeFileSync } from 'fs'; import { execSync } from 'child_process'; import { join, dirname } from 'path'; import { homedir } from 'os'; -import { createRequire } from 'module'; import { fileURLToPath } from 'url'; // Determine the directory where THIS script is running from @@ -41,7 +40,6 @@ const PLUGIN_ROOT = IS_RUNNING_FROM_CACHE const PACKAGE_JSON_PATH = join(PLUGIN_ROOT, 'package.json'); const VERSION_MARKER_PATH = join(PLUGIN_ROOT, '.install-version'); const NODE_MODULES_PATH = join(PLUGIN_ROOT, 'node_modules'); -const BETTER_SQLITE3_PATH = join(NODE_MODULES_PATH, 'better-sqlite3'); // Colors for output const colors = { @@ -126,12 +124,6 @@ function needsInstall() { return true; } - // Check if better-sqlite3 is installed - if (!existsSync(BETTER_SQLITE3_PATH)) { - log('📦 better-sqlite3 missing - reinstalling', colors.cyan); - return true; - } - // Check version marker const currentPackageVersion = getPackageVersion(); const currentNodeVersion = getNodeVersion(); @@ -165,101 +157,6 @@ function needsInstall() { return false; } -/** - * Verify that better-sqlite3 native module loads correctly - * This catches ABI mismatches and corrupted builds - */ -async function verifyNativeModules() { - try { - log('🔍 Verifying native modules...', colors.dim); - - // Use createRequire() to resolve from PLUGIN_ROOT's node_modules - const require = createRequire(join(PLUGIN_ROOT, 'package.json')); - const Database = require('better-sqlite3'); - - // Try to create a test in-memory database - const db = new Database(':memory:'); - - // Run a simple query to ensure it works - const result = db.prepare('SELECT 1 + 1 as result').get(); - - // Clean up - db.close(); - - if (result.result !== 2) { - throw new Error('SQLite math check failed'); - } - - log('✓ Native modules verified', colors.dim); - return true; - - } catch (error) { - if (error.code === 'ERR_DLOPEN_FAILED') { - log('⚠️ Native module ABI mismatch detected', colors.yellow); - return false; - } - - // Other errors are unexpected - log and fail - log(`❌ Native module verification failed: ${error.message}`, colors.red); - return false; - } -} - -function getWindowsErrorHelp(errorOutput) { - // Detect Python version at runtime - let pythonStatus = ' Python not detected or version unknown'; - try { - const pythonVersion = execSync('python --version', { encoding: 'utf-8', stdio: 'pipe' }).trim(); - const versionMatch = pythonVersion.match(/Python\s+([\d.]+)/); - if (versionMatch) { - pythonStatus = ` You have ${versionMatch[0]} installed ✓`; - } - } catch (error) { - // Python not available or failed to detect - use default message - } - - const help = [ - '', - '╔══════════════════════════════════════════════════════════════════════╗', - '║ Windows Installation Help ║', - '╚══════════════════════════════════════════════════════════════════════╝', - '', - '📋 better-sqlite3 requires build tools to compile native modules.', - '', - '🔧 Option 1: Install Visual Studio Build Tools (Recommended)', - ' 1. Download: https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022', - ' 2. Install "Desktop development with C++"', - ' 3. Restart your terminal', - ' 4. Try again', - '', - '🔧 Option 2: Install via npm (automated)', - ' Run as Administrator:', - ' npm install --global windows-build-tools', - '', - '🐍 Python Requirement:', - ' Python 3.6+ is required.', - pythonStatus, - '', - ]; - - // Check for specific error patterns - if (errorOutput.includes('MSBuild.exe')) { - help.push('❌ MSBuild not found - install Visual Studio Build Tools'); - } - if (errorOutput.includes('MSVS')) { - help.push('❌ Visual Studio not detected - install Build Tools'); - } - if (errorOutput.includes('permission') || errorOutput.includes('EPERM')) { - help.push('❌ Permission denied - try running as Administrator'); - } - - help.push(''); - help.push('📖 Full documentation: https://github.com/WiseLibs/better-sqlite3/blob/master/docs/troubleshooting.md'); - help.push(''); - - return help.join('\n'); -} - async function runNpmInstall() { const isWindows = process.platform === 'win32'; @@ -310,11 +207,6 @@ async function runNpmInstall() { log('❌ Installation failed after retrying!', colors.bright); log('', colors.reset); - // Provide Windows-specific help - if (isWindows && lastError && lastError.message && lastError.message.includes('better-sqlite3')) { - log(getWindowsErrorHelp(lastError.message), colors.yellow); - } - // Show generic error info with troubleshooting steps if (lastError) { if (lastError.stderr) { @@ -360,21 +252,6 @@ async function main() { log('', colors.reset); process.exit(1); } - } else { - // Even if install not needed, verify native modules work - const nativeModulesWork = await verifyNativeModules(); - - if (!nativeModulesWork) { - log('📦 Native modules need rebuild - reinstalling', colors.cyan); - const installSuccess = await runNpmInstall(); - - if (!installSuccess) { - log('', colors.red); - log('⚠️ Native module rebuild failed', colors.yellow); - log('', colors.reset); - process.exit(1); - } - } } // NOTE: Worker auto-start disabled in smart-install.js diff --git a/plugin/scripts/summary-hook.js b/plugin/scripts/summary-hook.js index c80559c6..0281b2e9 100755 --- a/plugin/scripts/summary-hook.js +++ b/plugin/scripts/summary-hook.js @@ -1,13 +1,13 @@ #!/usr/bin/env bun -import{stdin as K}from"process";function V(o,t,e){return o==="SessionStart"?t&&e.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:t,suppressOutput:!0,...e.reason&&!t?{stopReason:e.reason}:{}}}function U(o,t,e={}){let r=V(o,t,e);return JSON.stringify(r)}import{readFileSync as Y,writeFileSync as J,existsSync as q}from"fs";import{join as z}from"path";import{homedir as Q}from"os";var j=["bugfix","feature","refactor","discovery","decision","change"],X=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var w=j.join(","),P=X.join(",");var m=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:z(Q(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:w,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:P,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!q(t))return this.getAllDefaults();let e=Y(t,"utf-8"),r=JSON.parse(e),n=r;if(r.env&&typeof r.env=="object"){n=r.env;try{J(t,JSON.stringify(n,null,2),"utf-8"),c.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(i){c.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},i)}}let s={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))n[i]!==void 0&&(s[i]=n[i]);return s}};var h=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(h||{}),A=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=m.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=h[t]??1}return this.level}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message} -${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let r=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&r.command){let n=r.command.length>50?r.command.substring(0,50)+"...":r.command;return`${t}(${n})`}if(t==="Read"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Edit"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Write"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}return t}catch{return t}}log(t,e,r,n,s){if(t0&&(D=` {${Object.entries(I).map(([B,G])=>`${B}=${G}`).join(", ")}}`)}let R=`[${i}] [${a}] [${p}] ${u}${r}${D}${E}`;t===3?console.error(R):console.log(R)}debug(t,e,r,n){this.log(0,t,e,r,n)}info(t,e,r,n){this.log(1,t,e,r,n)}warn(t,e,r,n){this.log(2,t,e,r,n)}error(t,e,r,n){this.log(3,t,e,r,n)}dataIn(t,e,r,n){this.info(t,`\u2192 ${e}`,r,n)}dataOut(t,e,r,n){this.info(t,`\u2190 ${e}`,r,n)}success(t,e,r,n){this.info(t,`\u2713 ${e}`,r,n)}failure(t,e,r,n){this.error(t,`\u2717 ${e}`,r,n)}timing(t,e,r,n){this.info(t,`\u23F1 ${e}`,n,{duration:`${r}ms`})}},c=new A;import H from"path";import{homedir as _t}from"os";import{spawnSync as gt}from"child_process";var g={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(o){return process.platform==="win32"?Math.round(o*g.WINDOWS_MULTIPLIER):o}import{existsSync as L,readFileSync as nt,writeFileSync as ot,unlinkSync as st,mkdirSync as b}from"fs";import{createWriteStream as it}from"fs";import{join as d}from"path";import{spawn as at,spawnSync as ct}from"child_process";import{homedir as ut}from"os";import{join as l,dirname as Z,basename as Bt}from"path";import{homedir as tt}from"os";import{fileURLToPath as et}from"url";function rt(){return typeof __dirname<"u"?__dirname:Z(et(import.meta.url))}var Yt=rt(),f=m.get("CLAUDE_MEM_DATA_DIR"),C=process.env.CLAUDE_CONFIG_DIR||l(tt(),".claude"),Jt=l(f,"archives"),qt=l(f,"logs"),zt=l(f,"trash"),Qt=l(f,"backups"),Zt=l(f,"settings.json"),te=l(f,"claude-mem.db"),ee=l(f,"vector-db"),re=l(C,"settings.json"),ne=l(C,"commands"),oe=l(C,"CLAUDE.md");var S=d(f,"worker.pid"),k=d(f,"logs"),v=d(ut(),".claude","plugins","marketplaces","thedotmack"),pt=5e3,lt=1e4,ft=200,mt=1e3,Et=100,O=class{static async start(t){if(await this.isRunning())return{success:!0,pid:this.getPidInfo()?.pid};b(k,{recursive:!0});let e=d(v,"plugin","scripts","worker-service.cjs");if(!L(e))return{success:!1,error:`Worker script not found at ${e}`};let r=this.getLogFilePath();return this.startWithBun(e,r,t)}static isBunAvailable(){try{return ct("bun",["--version"],{stdio:"pipe",timeout:5e3}).status===0}catch{return!1}}static async startWithBun(t,e,r){if(!this.isBunAvailable())return{success:!1,error:"Bun is required but not found in PATH. Install from https://bun.sh"};try{let n=process.platform==="win32",s=at("bun",[t],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(r)},cwd:v,...n&&{windowsHide:!0}}),i=it(e,{flags:"a"});return s.stdout?.pipe(i),s.stderr?.pipe(i),s.unref(),s.pid?(this.writePidFile({pid:s.pid,port:r,startedAt:new Date().toISOString(),version:process.env.npm_package_version||"unknown"}),this.waitForHealth(s.pid,r)):{success:!1,error:"Failed to get PID from spawned process"}}catch(n){return{success:!1,error:n instanceof Error?n.message:String(n)}}}static async stop(t=pt){let e=this.getPidInfo();if(!e)return!0;try{process.kill(e.pid,"SIGTERM"),await this.waitForExit(e.pid,t)}catch{try{process.kill(e.pid,"SIGKILL")}catch{}}return this.removePidFile(),!0}static async restart(t){return await this.stop(),this.start(t)}static async status(){let t=this.getPidInfo();if(!t)return{running:!1};let e=this.isProcessAlive(t.pid);return{running:e,pid:e?t.pid:void 0,port:e?t.port:void 0,uptime:e?this.formatUptime(t.startedAt):void 0}}static async isRunning(){let t=this.getPidInfo();if(!t)return!1;let e=this.isProcessAlive(t.pid);return e||this.removePidFile(),e}static getPidInfo(){try{if(!L(S))return null;let t=nt(S,"utf-8"),e=JSON.parse(t);return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}static writePidFile(t){b(f,{recursive:!0}),ot(S,JSON.stringify(t,null,2))}static removePidFile(){try{L(S)&&st(S)}catch{}}static isProcessAlive(t){try{return process.kill(t,0),!0}catch{return!1}}static async waitForHealth(t,e,r=lt){let n=Date.now();for(;Date.now()-nsetTimeout(s,ft))}return{success:!1,error:"Health check timed out"}}static async waitForExit(t,e){let r=Date.now();for(;Date.now()-rsetTimeout(n,Et))}throw new Error("Process did not exit within timeout")}static getLogFilePath(){let t=new Date().toISOString().slice(0,10);return d(k,`worker-${t}.log`)}static formatUptime(t){let e=new Date(t).getTime(),n=Date.now()-e,s=Math.floor(n/1e3),i=Math.floor(s/60),a=Math.floor(i/60),p=Math.floor(a/24);return p>0?`${p}d ${a%24}h`:a>0?`${a}h ${i%60}m`:i>0?`${i}m ${s%60}s`:`${s}s`}};var St=H.join(_t(),".claude","plugins","marketplaces","thedotmack"),dt=N(g.HEALTH_CHECK),_=null;function T(){if(_!==null)return _;try{let o=H.join(m.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=m.loadFromFile(o);return _=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),_}catch(o){return c.debug("SYSTEM","Failed to load port from settings, using default",{error:o}),_=parseInt(m.get("CLAUDE_MEM_WORKER_PORT"),10),_}}async function x(){try{let o=T();return(await fetch(`http://127.0.0.1:${o}/health`,{signal:AbortSignal.timeout(dt)})).ok}catch(o){return c.debug("SYSTEM","Worker health check failed",{error:o instanceof Error?o.message:String(o),errorType:o?.constructor?.name}),!1}}async function Tt(){if(process.platform!=="win32")try{gt("pm2",["delete","claude-mem-worker"],{stdio:"ignore"})}catch{}let o=T(),t=await O.start(o);return t.success||c.error("SYSTEM","Failed to start worker",{platform:process.platform,port:o,error:t.error,marketplaceRoot:St}),t.success}async function $(){if(await x())return;let o=await Tt();if(!(!o&&await x())&&!o){let t=T();throw new Error(`Worker service failed to start on port ${t}. +import{stdin as B}from"process";function V(s,t,e){return s==="SessionStart"?t&&e.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e.context}}:{continue:!0,suppressOutput:!0}:s==="UserPromptSubmit"||s==="PostToolUse"?{continue:!0,suppressOutput:!0}:s==="Stop"?{continue:!0,suppressOutput:!0}:{continue:t,suppressOutput:!0,...e.reason&&!t?{stopReason:e.reason}:{}}}function w(s,t,e={}){let r=V(s,t,e);return JSON.stringify(r)}import{readFileSync as J,writeFileSync as q,existsSync as z}from"fs";import{join as Q}from"path";import{homedir as Z}from"os";var X=["bugfix","feature","refactor","discovery","decision","change"],Y=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var P=X.join(","),b=Y.join(",");var l=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:Q(Z(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:P,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:b,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!z(t))return this.getAllDefaults();let e=J(t,"utf-8"),r=JSON.parse(e),n=r;if(r.env&&typeof r.env=="object"){n=r.env;try{q(t,JSON.stringify(n,null,2),"utf-8"),c.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(i){c.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},i)}}let o={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))n[i]!==void 0&&(o[i]=n[i]);return o}};var h=(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))(h||{}),A=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=l.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=h[t]??1}return this.level}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message} +${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let r=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&r.command){let n=r.command.length>50?r.command.substring(0,50)+"...":r.command;return`${t}(${n})`}if(t==="Read"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Edit"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Write"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}return t}catch{return t}}log(t,e,r,n,o){if(t0&&(R=` {${Object.entries(U).map(([G,j])=>`${G}=${j}`).join(", ")}}`)}let I=`[${i}] [${a}] [${p}] ${u}${r}${R}${E}`;t===3?console.error(I):console.log(I)}debug(t,e,r,n){this.log(0,t,e,r,n)}info(t,e,r,n){this.log(1,t,e,r,n)}warn(t,e,r,n){this.log(2,t,e,r,n)}error(t,e,r,n){this.log(3,t,e,r,n)}dataIn(t,e,r,n){this.info(t,`\u2192 ${e}`,r,n)}dataOut(t,e,r,n){this.info(t,`\u2190 ${e}`,r,n)}success(t,e,r,n){this.info(t,`\u2713 ${e}`,r,n)}failure(t,e,r,n){this.error(t,`\u2717 ${e}`,r,n)}timing(t,e,r,n){this.info(t,`\u23F1 ${e}`,n,{duration:`${r}ms`})}},c=new A;import L from"path";import{homedir as _t}from"os";import{spawnSync as St}from"child_process";import{existsSync as dt,writeFileSync as H}from"fs";var _={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(s){return process.platform==="win32"?Math.round(s*_.WINDOWS_MULTIPLIER):s}import{existsSync as M,readFileSync as st,writeFileSync as ot,unlinkSync as it,mkdirSync as k}from"fs";import{createWriteStream as at}from"fs";import{join as d}from"path";import{spawn as ct,spawnSync as ut}from"child_process";import{homedir as pt}from"os";import{join as f,dirname as tt,basename as jt}from"path";import{homedir as et}from"os";import{fileURLToPath as rt}from"url";function nt(){return typeof __dirname<"u"?__dirname:tt(rt(import.meta.url))}var qt=nt(),m=l.get("CLAUDE_MEM_DATA_DIR"),C=process.env.CLAUDE_CONFIG_DIR||f(et(),".claude"),zt=f(m,"archives"),Qt=f(m,"logs"),Zt=f(m,"trash"),te=f(m,"backups"),ee=f(m,"settings.json"),re=f(m,"claude-mem.db"),ne=f(m,"vector-db"),se=f(C,"settings.json"),oe=f(C,"commands"),ie=f(C,"CLAUDE.md");var S=d(m,"worker.pid"),v=d(m,"logs"),x=d(pt(),".claude","plugins","marketplaces","thedotmack"),lt=5e3,ft=1e4,mt=200,Et=1e3,gt=100,O=class{static async start(t){if(isNaN(t)||t<1024||t>65535)return{success:!1,error:`Invalid port ${t}. Must be between 1024 and 65535`};if(await this.isRunning())return{success:!0,pid:this.getPidInfo()?.pid};k(v,{recursive:!0});let e=d(x,"plugin","scripts","worker-service.cjs");if(!M(e))return{success:!1,error:`Worker script not found at ${e}`};let r=this.getLogFilePath();return this.startWithBun(e,r,t)}static isBunAvailable(){try{return ut("bun",["--version"],{stdio:"pipe",timeout:5e3}).status===0}catch{return!1}}static async startWithBun(t,e,r){if(!this.isBunAvailable())return{success:!1,error:"Bun is required but not found in PATH. Install from https://bun.sh"};try{let n=process.platform==="win32",o=ct("bun",[t],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(r)},cwd:x,...n&&{windowsHide:!0}}),i=at(e,{flags:"a"});return o.stdout?.pipe(i),o.stderr?.pipe(i),o.unref(),o.pid?(this.writePidFile({pid:o.pid,port:r,startedAt:new Date().toISOString(),version:process.env.npm_package_version||"unknown"}),this.waitForHealth(o.pid,r)):{success:!1,error:"Failed to get PID from spawned process"}}catch(n){return{success:!1,error:n instanceof Error?n.message:String(n)}}}static async stop(t=lt){let e=this.getPidInfo();if(!e)return!0;try{process.kill(e.pid,"SIGTERM"),await this.waitForExit(e.pid,t)}catch{try{process.kill(e.pid,"SIGKILL")}catch{}}return this.removePidFile(),!0}static async restart(t){return await this.stop(),this.start(t)}static async status(){let t=this.getPidInfo();if(!t)return{running:!1};let e=this.isProcessAlive(t.pid);return{running:e,pid:e?t.pid:void 0,port:e?t.port:void 0,uptime:e?this.formatUptime(t.startedAt):void 0}}static async isRunning(){let t=this.getPidInfo();if(!t)return!1;let e=this.isProcessAlive(t.pid);return e||this.removePidFile(),e}static getPidInfo(){try{if(!M(S))return null;let t=st(S,"utf-8"),e=JSON.parse(t);return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}static writePidFile(t){k(m,{recursive:!0}),ot(S,JSON.stringify(t,null,2))}static removePidFile(){try{M(S)&&it(S)}catch{}}static isProcessAlive(t){try{return process.kill(t,0),!0}catch{return!1}}static async waitForHealth(t,e,r=ft){let n=Date.now();for(;Date.now()-nsetTimeout(o,mt))}return{success:!1,error:"Health check timed out"}}static async waitForExit(t,e){let r=Date.now();for(;Date.now()-rsetTimeout(n,gt))}throw new Error("Process did not exit within timeout")}static getLogFilePath(){let t=new Date().toISOString().slice(0,10);return d(v,`worker-${t}.log`)}static formatUptime(t){let e=new Date(t).getTime(),n=Date.now()-e,o=Math.floor(n/1e3),i=Math.floor(o/60),a=Math.floor(i/60),p=Math.floor(a/24);return p>0?`${p}d ${a%24}h`:a>0?`${a}h ${i%60}m`:i>0?`${i}m ${o%60}s`:`${o}s`}};var Tt=L.join(_t(),".claude","plugins","marketplaces","thedotmack"),Ot=N(_.HEALTH_CHECK),g=null;function T(){if(g!==null)return g;try{let s=L.join(l.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=l.loadFromFile(s);return g=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),g}catch(s){return c.debug("SYSTEM","Failed to load port from settings, using default",{error:s}),g=parseInt(l.get("CLAUDE_MEM_WORKER_PORT"),10),g}}async function $(){try{let s=T();return(await fetch(`http://127.0.0.1:${s}/health`,{signal:AbortSignal.timeout(Ot)})).ok}catch(s){return c.debug("SYSTEM","Worker health check failed",{error:s instanceof Error?s.message:String(s),errorType:s?.constructor?.name}),!1}}async function ht(){let s=L.join(l.get("CLAUDE_MEM_DATA_DIR"),".pm2-migrated");if(process.platform!=="win32"&&!dt(s))try{St("pm2",["delete","claude-mem-worker"],{stdio:"ignore"}),H(s,new Date().toISOString(),"utf-8"),c.debug("SYSTEM","PM2 cleanup completed and marked")}catch{H(s,new Date().toISOString(),"utf-8")}let t=T(),e=await O.start(t);return e.success||c.error("SYSTEM","Failed to start worker",{platform:process.platform,port:t,error:e.error,marketplaceRoot:Tt}),e.success}async function F(){if(await $())return;let s=await ht();if(!(!s&&await $())&&!s){let t=T();throw new Error(`Worker service failed to start on port ${t}. To start manually, run: npm run worker:start -If already running, try: npm run worker:restart`)}}import{appendFileSync as Ot}from"fs";import{homedir as ht}from"os";import{join as At}from"path";var Ct=At(ht(),".claude-mem","silent.log");function F(o,t,e=""){let r=new Date().toISOString(),a=((new Error().stack||"").split(` -`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),p=a?`${a[1].split("/").pop()}:${a[2]}`:"unknown",u=`[${r}] [HAPPY-PATH-ERROR] [${p}] ${o}`;if(t!==void 0)try{u+=` ${JSON.stringify(t)}`}catch(E){u+=` [stringify error: ${E}]`}u+=` -`;try{Ot(Ct,u)}catch(E){console.error("[silent-debug] Failed to write to log:",E)}return e}function W(o){throw o.cause?.code==="ECONNREFUSED"||o.name==="TimeoutError"||o.message?.includes("fetch failed")?new Error("There's a problem with the worker. Try: npm run worker:restart"):o}import{readFileSync as Lt,existsSync as yt}from"fs";function y(o,t,e=!1){if(!o||!yt(o))return"";try{let r=Lt(o,"utf-8").trim();if(!r)return"";let n=r.split(` -`);for(let s=n.length-1;s>=0;s--)try{let i=JSON.parse(n[s]);if(i.type===t&&i.message?.content){let a="",p=i.message.content;return typeof p=="string"?a=p:Array.isArray(p)&&(a=p.filter(u=>u.type==="text").map(u=>u.text).join(` +If already running, try: npm run worker:restart`)}}import{appendFileSync as At}from"fs";import{homedir as Ct}from"os";import{join as Mt}from"path";var Lt=Mt(Ct(),".claude-mem","silent.log");function W(s,t,e=""){let r=new Date().toISOString(),a=((new Error().stack||"").split(` +`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),p=a?`${a[1].split("/").pop()}:${a[2]}`:"unknown",u=`[${r}] [HAPPY-PATH-ERROR] [${p}] ${s}`;if(t!==void 0)try{u+=` ${JSON.stringify(t)}`}catch(E){u+=` [stringify error: ${E}]`}u+=` +`;try{At(Lt,u)}catch(E){console.error("[silent-debug] Failed to write to log:",E)}return e}function K(s){throw s.cause?.code==="ECONNREFUSED"||s.name==="TimeoutError"||s.message?.includes("fetch failed")?new Error("There's a problem with the worker. Try: npm run worker:restart"):s}import{readFileSync as yt,existsSync as Dt}from"fs";function y(s,t,e=!1){if(!s||!Dt(s))return"";try{let r=yt(s,"utf-8").trim();if(!r)return"";let n=r.split(` +`);for(let o=n.length-1;o>=0;o--)try{let i=JSON.parse(n[o]);if(i.type===t&&i.message?.content){let a="",p=i.message.content;return typeof p=="string"?a=p:Array.isArray(p)&&(a=p.filter(u=>u.type==="text").map(u=>u.text).join(` `)),e&&(a=a.replace(/[\s\S]*?<\/system-reminder>/g,""),a=a.replace(/\n{3,}/g,` -`).trim()),a}}catch{continue}}catch(r){c.error("HOOK","Failed to read transcript",{transcriptPath:o},r)}return""}async function Mt(o){if(await $(),!o)throw new Error("summaryHook requires input");let{session_id:t}=o,e=T(),r=F("Missing transcript_path in Stop hook input",{session_id:t},o.transcript_path||""),n=y(r,"user"),s=y(r,"assistant",!0);c.dataIn("HOOK","Stop: Requesting summary",{workerPort:e,hasLastUserMessage:!!n,hasLastAssistantMessage:!!s});try{let i=await fetch(`http://127.0.0.1:${e}/api/sessions/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,last_user_message:n,last_assistant_message:s}),signal:AbortSignal.timeout(g.DEFAULT)});if(!i.ok){let a=await i.text();throw c.failure("HOOK","Failed to generate summary",{status:i.status},a),new Error(`Failed to request summary from worker: ${i.status} ${a}`)}c.debug("HOOK","Summary request sent successfully")}catch(i){W(i)}finally{try{let i=await fetch(`http://127.0.0.1:${e}/api/processing`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({isProcessing:!1}),signal:AbortSignal.timeout(2e3)});i.ok||c.warn("HOOK","Failed to stop spinner",{status:i.status})}catch(i){c.warn("HOOK","Could not stop spinner",{error:i.message})}}console.log(U("Stop",!0))}var M="";K.on("data",o=>M+=o);K.on("end",async()=>{let o=M?JSON.parse(M):void 0;await Mt(o)}); +`).trim()),a}}catch{continue}}catch(r){c.error("HOOK","Failed to read transcript",{transcriptPath:s},r)}return""}async function Rt(s){if(await F(),!s)throw new Error("summaryHook requires input");let{session_id:t}=s,e=T(),r=W("Missing transcript_path in Stop hook input",{session_id:t},s.transcript_path||""),n=y(r,"user"),o=y(r,"assistant",!0);c.dataIn("HOOK","Stop: Requesting summary",{workerPort:e,hasLastUserMessage:!!n,hasLastAssistantMessage:!!o});try{let i=await fetch(`http://127.0.0.1:${e}/api/sessions/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,last_user_message:n,last_assistant_message:o}),signal:AbortSignal.timeout(_.DEFAULT)});if(!i.ok){let a=await i.text();throw c.failure("HOOK","Failed to generate summary",{status:i.status},a),new Error(`Failed to request summary from worker: ${i.status} ${a}`)}c.debug("HOOK","Summary request sent successfully")}catch(i){K(i)}finally{try{let i=await fetch(`http://127.0.0.1:${e}/api/processing`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({isProcessing:!1}),signal:AbortSignal.timeout(2e3)});i.ok||c.warn("HOOK","Failed to stop spinner",{status:i.status})}catch(i){c.warn("HOOK","Could not stop spinner",{error:i.message})}}console.log(w("Stop",!0))}var D="";B.on("data",s=>D+=s);B.on("end",async()=>{let s=D?JSON.parse(D):void 0;await Rt(s)}); diff --git a/plugin/scripts/user-message-hook.js b/plugin/scripts/user-message-hook.js index 8f34953c..d4e60c96 100755 --- a/plugin/scripts/user-message-hook.js +++ b/plugin/scripts/user-message-hook.js @@ -1,10 +1,10 @@ #!/usr/bin/env bun -import{basename as ft}from"path";import k from"path";import{homedir as ut}from"os";import{spawnSync as pt}from"child_process";import{readFileSync as K,writeFileSync as B,existsSync as G}from"fs";import{join as V}from"path";import{homedir as X}from"os";var F=["bugfix","feature","refactor","discovery","decision","change"],W=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var R=F.join(","),y=W.join(",");var u=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:V(X(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:R,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:y,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!G(t))return this.getAllDefaults();let e=K(t,"utf-8"),r=JSON.parse(e),n=r;if(r.env&&typeof r.env=="object"){n=r.env;try{B(t,JSON.stringify(n,null,2),"utf-8"),p.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(i){p.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},i)}}let o={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))n[i]!==void 0&&(o[i]=n[i]);return o}};var O=(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))(O||{}),C=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=u.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=O[t]??1}return this.level}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message} +import{basename as mt}from"path";import M from"path";import{homedir as pt}from"os";import{spawnSync as lt}from"child_process";import{existsSync as Et,writeFileSync as k}from"fs";import{readFileSync as B,writeFileSync as G,existsSync as j}from"fs";import{join as V}from"path";import{homedir as X}from"os";var W=["bugfix","feature","refactor","discovery","decision","change"],K=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var y=W.join(","),U=K.join(",");var a=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:V(X(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:y,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:U,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!j(t))return this.getAllDefaults();let e=B(t,"utf-8"),r=JSON.parse(e),n=r;if(r.env&&typeof r.env=="object"){n=r.env;try{G(t,JSON.stringify(n,null,2),"utf-8"),p.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(i){p.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},i)}}let o={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))n[i]!==void 0&&(o[i]=n[i]);return o}};var O=(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))(O||{}),C=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=a.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=O[t]??1}return this.level}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message} ${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let r=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&r.command){let n=r.command.length>50?r.command.substring(0,50)+"...":r.command;return`${t}(${n})`}if(t==="Read"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Edit"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}if(t==="Write"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${t}(${n})`}return t}catch{return t}}log(t,e,r,n,o){if(t0&&(M=` {${Object.entries(I).map(([H,$])=>`${H}=${$}`).join(", ")}}`)}let L=`[${i}] [${l}] [${m}] ${S}${r}${M}${d}`;t===3?console.error(L):console.log(L)}debug(t,e,r,n){this.log(0,t,e,r,n)}info(t,e,r,n){this.log(1,t,e,r,n)}warn(t,e,r,n){this.log(2,t,e,r,n)}error(t,e,r,n){this.log(3,t,e,r,n)}dataIn(t,e,r,n){this.info(t,`\u2192 ${e}`,r,n)}dataOut(t,e,r,n){this.info(t,`\u2190 ${e}`,r,n)}success(t,e,r,n){this.info(t,`\u2713 ${e}`,r,n)}failure(t,e,r,n){this.error(t,`\u2717 ${e}`,r,n)}timing(t,e,r,n){this.info(t,`\u23F1 ${e}`,n,{duration:`${r}ms`})}},p=new C;var h={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5},U={SUCCESS:0,FAILURE:1,USER_MESSAGE_ONLY:3};function v(s){return process.platform==="win32"?Math.round(s*h.WINDOWS_MULTIPLIER):s}import{existsSync as D,readFileSync as Q,writeFileSync as z,unlinkSync as Z,mkdirSync as N}from"fs";import{createWriteStream as tt}from"fs";import{join as f}from"path";import{spawn as et,spawnSync as rt}from"child_process";import{homedir as nt}from"os";import{join as a,dirname as j,basename as Ut}from"path";import{homedir as Y}from"os";import{fileURLToPath as J}from"url";function q(){return typeof __dirname<"u"?__dirname:j(J(import.meta.url))}var bt=q(),c=u.get("CLAUDE_MEM_DATA_DIR"),A=process.env.CLAUDE_CONFIG_DIR||a(Y(),".claude"),kt=a(c,"archives"),xt=a(c,"logs"),Ht=a(c,"trash"),$t=a(c,"backups"),Ft=a(c,"settings.json"),Wt=a(c,"claude-mem.db"),Kt=a(c,"vector-db"),Bt=a(A,"settings.json"),Gt=a(A,"commands"),Vt=a(A,"CLAUDE.md");var _=f(c,"worker.pid"),P=f(c,"logs"),w=f(nt(),".claude","plugins","marketplaces","thedotmack"),ot=5e3,st=1e4,it=200,at=1e3,ct=100,T=class{static async start(t){if(await this.isRunning())return{success:!0,pid:this.getPidInfo()?.pid};N(P,{recursive:!0});let e=f(w,"plugin","scripts","worker-service.cjs");if(!D(e))return{success:!1,error:`Worker script not found at ${e}`};let r=this.getLogFilePath();return this.startWithBun(e,r,t)}static isBunAvailable(){try{return rt("bun",["--version"],{stdio:"pipe",timeout:5e3}).status===0}catch{return!1}}static async startWithBun(t,e,r){if(!this.isBunAvailable())return{success:!1,error:"Bun is required but not found in PATH. Install from https://bun.sh"};try{let n=process.platform==="win32",o=et("bun",[t],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(r)},cwd:w,...n&&{windowsHide:!0}}),i=tt(e,{flags:"a"});return o.stdout?.pipe(i),o.stderr?.pipe(i),o.unref(),o.pid?(this.writePidFile({pid:o.pid,port:r,startedAt:new Date().toISOString(),version:process.env.npm_package_version||"unknown"}),this.waitForHealth(o.pid,r)):{success:!1,error:"Failed to get PID from spawned process"}}catch(n){return{success:!1,error:n instanceof Error?n.message:String(n)}}}static async stop(t=ot){let e=this.getPidInfo();if(!e)return!0;try{process.kill(e.pid,"SIGTERM"),await this.waitForExit(e.pid,t)}catch{try{process.kill(e.pid,"SIGKILL")}catch{}}return this.removePidFile(),!0}static async restart(t){return await this.stop(),this.start(t)}static async status(){let t=this.getPidInfo();if(!t)return{running:!1};let e=this.isProcessAlive(t.pid);return{running:e,pid:e?t.pid:void 0,port:e?t.port:void 0,uptime:e?this.formatUptime(t.startedAt):void 0}}static async isRunning(){let t=this.getPidInfo();if(!t)return!1;let e=this.isProcessAlive(t.pid);return e||this.removePidFile(),e}static getPidInfo(){try{if(!D(_))return null;let t=Q(_,"utf-8"),e=JSON.parse(t);return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}static writePidFile(t){N(c,{recursive:!0}),z(_,JSON.stringify(t,null,2))}static removePidFile(){try{D(_)&&Z(_)}catch{}}static isProcessAlive(t){try{return process.kill(t,0),!0}catch{return!1}}static async waitForHealth(t,e,r=st){let n=Date.now();for(;Date.now()-nsetTimeout(o,it))}return{success:!1,error:"Health check timed out"}}static async waitForExit(t,e){let r=Date.now();for(;Date.now()-rsetTimeout(n,ct))}throw new Error("Process did not exit within timeout")}static getLogFilePath(){let t=new Date().toISOString().slice(0,10);return f(P,`worker-${t}.log`)}static formatUptime(t){let e=new Date(t).getTime(),n=Date.now()-e,o=Math.floor(n/1e3),i=Math.floor(o/60),l=Math.floor(i/60),m=Math.floor(l/24);return m>0?`${m}d ${l%24}h`:l>0?`${l}h ${i%60}m`:i>0?`${i}m ${o%60}s`:`${o}s`}};var lt=k.join(ut(),".claude","plugins","marketplaces","thedotmack"),Et=v(h.HEALTH_CHECK),E=null;function g(){if(E!==null)return E;try{let s=k.join(u.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=u.loadFromFile(s);return E=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),E}catch(s){return p.debug("SYSTEM","Failed to load port from settings, using default",{error:s}),E=parseInt(u.get("CLAUDE_MEM_WORKER_PORT"),10),E}}async function b(){try{let s=g();return(await fetch(`http://127.0.0.1:${s}/health`,{signal:AbortSignal.timeout(Et)})).ok}catch(s){return p.debug("SYSTEM","Worker health check failed",{error:s instanceof Error?s.message:String(s),errorType:s?.constructor?.name}),!1}}async function _t(){if(process.platform!=="win32")try{pt("pm2",["delete","claude-mem-worker"],{stdio:"ignore"})}catch{}let s=g(),t=await T.start(s);return t.success||p.error("SYSTEM","Failed to start worker",{platform:process.platform,port:s,error:t.error,marketplaceRoot:lt}),t.success}async function x(){if(await b())return;let s=await _t();if(!(!s&&await b())&&!s){let t=g();throw new Error(`Worker service failed to start on port ${t}. +`+JSON.stringify(o,null,2):d=" "+this.formatData(o));let L="";if(n){let{sessionId:Tt,sdkSessionId:St,correlationId:dt,...R}=n;Object.keys(R).length>0&&(L=` {${Object.entries(R).map(([$,F])=>`${$}=${F}`).join(", ")}}`)}let I=`[${i}] [${l}] [${m}] ${S}${r}${L}${d}`;t===3?console.error(I):console.log(I)}debug(t,e,r,n){this.log(0,t,e,r,n)}info(t,e,r,n){this.log(1,t,e,r,n)}warn(t,e,r,n){this.log(2,t,e,r,n)}error(t,e,r,n){this.log(3,t,e,r,n)}dataIn(t,e,r,n){this.info(t,`\u2192 ${e}`,r,n)}dataOut(t,e,r,n){this.info(t,`\u2190 ${e}`,r,n)}success(t,e,r,n){this.info(t,`\u2713 ${e}`,r,n)}failure(t,e,r,n){this.error(t,`\u2717 ${e}`,r,n)}timing(t,e,r,n){this.info(t,`\u23F1 ${e}`,n,{duration:`${r}ms`})}},p=new C;var A={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5},v={SUCCESS:0,FAILURE:1,USER_MESSAGE_ONLY:3};function w(s){return process.platform==="win32"?Math.round(s*A.WINDOWS_MULTIPLIER):s}import{existsSync as D,readFileSync as z,writeFileSync as Z,unlinkSync as tt,mkdirSync as N}from"fs";import{createWriteStream as et}from"fs";import{join as f}from"path";import{spawn as rt,spawnSync as nt}from"child_process";import{homedir as ot}from"os";import{join as c,dirname as Y,basename as wt}from"path";import{homedir as J}from"os";import{fileURLToPath as q}from"url";function Q(){return typeof __dirname<"u"?__dirname:Y(q(import.meta.url))}var xt=Q(),u=a.get("CLAUDE_MEM_DATA_DIR"),h=process.env.CLAUDE_CONFIG_DIR||c(J(),".claude"),Ht=c(u,"archives"),$t=c(u,"logs"),Ft=c(u,"trash"),Wt=c(u,"backups"),Kt=c(u,"settings.json"),Bt=c(u,"claude-mem.db"),Gt=c(u,"vector-db"),jt=c(h,"settings.json"),Vt=c(h,"commands"),Xt=c(h,"CLAUDE.md");var _=f(u,"worker.pid"),P=f(u,"logs"),b=f(ot(),".claude","plugins","marketplaces","thedotmack"),st=5e3,it=1e4,at=200,ct=1e3,ut=100,T=class{static async start(t){if(isNaN(t)||t<1024||t>65535)return{success:!1,error:`Invalid port ${t}. Must be between 1024 and 65535`};if(await this.isRunning())return{success:!0,pid:this.getPidInfo()?.pid};N(P,{recursive:!0});let e=f(b,"plugin","scripts","worker-service.cjs");if(!D(e))return{success:!1,error:`Worker script not found at ${e}`};let r=this.getLogFilePath();return this.startWithBun(e,r,t)}static isBunAvailable(){try{return nt("bun",["--version"],{stdio:"pipe",timeout:5e3}).status===0}catch{return!1}}static async startWithBun(t,e,r){if(!this.isBunAvailable())return{success:!1,error:"Bun is required but not found in PATH. Install from https://bun.sh"};try{let n=process.platform==="win32",o=rt("bun",[t],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(r)},cwd:b,...n&&{windowsHide:!0}}),i=et(e,{flags:"a"});return o.stdout?.pipe(i),o.stderr?.pipe(i),o.unref(),o.pid?(this.writePidFile({pid:o.pid,port:r,startedAt:new Date().toISOString(),version:process.env.npm_package_version||"unknown"}),this.waitForHealth(o.pid,r)):{success:!1,error:"Failed to get PID from spawned process"}}catch(n){return{success:!1,error:n instanceof Error?n.message:String(n)}}}static async stop(t=st){let e=this.getPidInfo();if(!e)return!0;try{process.kill(e.pid,"SIGTERM"),await this.waitForExit(e.pid,t)}catch{try{process.kill(e.pid,"SIGKILL")}catch{}}return this.removePidFile(),!0}static async restart(t){return await this.stop(),this.start(t)}static async status(){let t=this.getPidInfo();if(!t)return{running:!1};let e=this.isProcessAlive(t.pid);return{running:e,pid:e?t.pid:void 0,port:e?t.port:void 0,uptime:e?this.formatUptime(t.startedAt):void 0}}static async isRunning(){let t=this.getPidInfo();if(!t)return!1;let e=this.isProcessAlive(t.pid);return e||this.removePidFile(),e}static getPidInfo(){try{if(!D(_))return null;let t=z(_,"utf-8"),e=JSON.parse(t);return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}static writePidFile(t){N(u,{recursive:!0}),Z(_,JSON.stringify(t,null,2))}static removePidFile(){try{D(_)&&tt(_)}catch{}}static isProcessAlive(t){try{return process.kill(t,0),!0}catch{return!1}}static async waitForHealth(t,e,r=it){let n=Date.now();for(;Date.now()-nsetTimeout(o,at))}return{success:!1,error:"Health check timed out"}}static async waitForExit(t,e){let r=Date.now();for(;Date.now()-rsetTimeout(n,ut))}throw new Error("Process did not exit within timeout")}static getLogFilePath(){let t=new Date().toISOString().slice(0,10);return f(P,`worker-${t}.log`)}static formatUptime(t){let e=new Date(t).getTime(),n=Date.now()-e,o=Math.floor(n/1e3),i=Math.floor(o/60),l=Math.floor(i/60),m=Math.floor(l/24);return m>0?`${m}d ${l%24}h`:l>0?`${l}h ${i%60}m`:i>0?`${i}m ${o%60}s`:`${o}s`}};var _t=M.join(pt(),".claude","plugins","marketplaces","thedotmack"),ft=w(A.HEALTH_CHECK),E=null;function g(){if(E!==null)return E;try{let s=M.join(a.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=a.loadFromFile(s);return E=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),E}catch(s){return p.debug("SYSTEM","Failed to load port from settings, using default",{error:s}),E=parseInt(a.get("CLAUDE_MEM_WORKER_PORT"),10),E}}async function x(){try{let s=g();return(await fetch(`http://127.0.0.1:${s}/health`,{signal:AbortSignal.timeout(ft)})).ok}catch(s){return p.debug("SYSTEM","Worker health check failed",{error:s instanceof Error?s.message:String(s),errorType:s?.constructor?.name}),!1}}async function gt(){let s=M.join(a.get("CLAUDE_MEM_DATA_DIR"),".pm2-migrated");if(process.platform!=="win32"&&!Et(s))try{lt("pm2",["delete","claude-mem-worker"],{stdio:"ignore"}),k(s,new Date().toISOString(),"utf-8"),p.debug("SYSTEM","PM2 cleanup completed and marked")}catch{k(s,new Date().toISOString(),"utf-8")}let t=g(),e=await T.start(t);return e.success||p.error("SYSTEM","Failed to start worker",{platform:process.platform,port:t,error:e.error,marketplaceRoot:_t}),e.success}async function H(){if(await x())return;let s=await gt();if(!(!s&&await x())&&!s){let t=g();throw new Error(`Worker service failed to start on port ${t}. To start manually, run: npm run worker:start -If already running, try: npm run worker:restart`)}}try{await x();let s=g(),t=ft(process.cwd()),e=await fetch(`http://127.0.0.1:${s}/api/context/inject?project=${encodeURIComponent(t)}&colors=true`,{method:"GET",signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Worker error ${e.status}`);let r=await e.text();console.error(` +If already running, try: npm run worker:restart`)}}try{await H();let s=g(),t=mt(process.cwd()),e=await fetch(`http://127.0.0.1:${s}/api/context/inject?project=${encodeURIComponent(t)}&colors=true`,{method:"GET",signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Worker error ${e.status}`);let r=await e.text();console.error(` \u{1F4DD} Claude-Mem Context Loaded \u2139\uFE0F Note: This appears as stderr but is informational only @@ -33,4 +33,4 @@ Dependencies are installing in the background. This only happens once. Thank you for installing Claude-Mem! This message was not added to your startup context, so you can continue working as normal. -`)}process.exit(U.USER_MESSAGE_ONLY); +`)}process.exit(v.USER_MESSAGE_ONLY); diff --git a/plugin/scripts/worker-cli.js b/plugin/scripts/worker-cli.js index 9b5933e9..46202861 100755 --- a/plugin/scripts/worker-cli.js +++ b/plugin/scripts/worker-cli.js @@ -1,4 +1,4 @@ #!/usr/bin/env bun -import{existsSync as C,readFileSync as J,writeFileSync as q,unlinkSync as Q,mkdirSync as y}from"fs";import{createWriteStream as z}from"fs";import{join as g}from"path";import{spawn as Z,spawnSync as tt}from"child_process";import{homedir as et}from"os";import{join as c,dirname as V,basename as ht}from"path";import{homedir as X}from"os";import{fileURLToPath as j}from"url";import{readFileSync as F,writeFileSync as W,existsSync as K}from"fs";import{join as B}from"path";import{homedir as G}from"os";var $=["bugfix","feature","refactor","discovery","decision","change"],H=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var I=$.join(","),R=H.join(",");var d=(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))(d||{}),O=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=u.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=d[t]??1}return this.level}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message} -${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let s=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&s.command){let r=s.command.length>50?s.command.substring(0,50)+"...":s.command;return`${t}(${r})`}if(t==="Read"&&s.file_path){let r=s.file_path.split("/").pop()||s.file_path;return`${t}(${r})`}if(t==="Edit"&&s.file_path){let r=s.file_path.split("/").pop()||s.file_path;return`${t}(${r})`}if(t==="Write"&&s.file_path){let r=s.file_path.split("/").pop()||s.file_path;return`${t}(${r})`}return t}catch{return t}}log(t,e,s,r,o){if(t0&&(h=` {${Object.entries(M).map(([k,x])=>`${k}=${x}`).join(", ")}}`)}let L=`[${i}] [${p}] [${m}] ${T}${s}${h}${S}`;t===3?console.error(L):console.log(L)}debug(t,e,s,r){this.log(0,t,e,s,r)}info(t,e,s,r){this.log(1,t,e,s,r)}warn(t,e,s,r){this.log(2,t,e,s,r)}error(t,e,s,r){this.log(3,t,e,s,r)}dataIn(t,e,s,r){this.info(t,`\u2192 ${e}`,s,r)}dataOut(t,e,s,r){this.info(t,`\u2190 ${e}`,s,r)}success(t,e,s,r){this.info(t,`\u2713 ${e}`,s,r)}failure(t,e,s,r){this.error(t,`\u2717 ${e}`,s,r)}timing(t,e,s,r){this.info(t,`\u23F1 ${e}`,r,{duration:`${s}ms`})}},_=new O;var u=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:B(G(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:I,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:R,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!K(t))return this.getAllDefaults();let e=F(t,"utf-8"),s=JSON.parse(e),r=s;if(s.env&&typeof s.env=="object"){r=s.env;try{W(t,JSON.stringify(r,null,2),"utf-8"),_.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(i){_.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},i)}}let o={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))r[i]!==void 0&&(o[i]=r[i]);return o}};function Y(){return typeof __dirname<"u"?__dirname:V(j(import.meta.url))}var yt=Y(),a=u.get("CLAUDE_MEM_DATA_DIR"),A=process.env.CLAUDE_CONFIG_DIR||c(X(),".claude"),Ut=c(a,"archives"),Pt=c(a,"logs"),vt=c(a,"trash"),Nt=c(a,"backups"),bt=c(a,"settings.json"),wt=c(a,"claude-mem.db"),kt=c(a,"vector-db"),xt=c(A,"settings.json"),$t=c(A,"commands"),Ht=c(A,"CLAUDE.md");var f=g(a,"worker.pid"),U=g(a,"logs"),P=g(et(),".claude","plugins","marketplaces","thedotmack"),rt=5e3,st=1e4,ot=200,nt=1e3,it=100,l=class{static async start(t){if(await this.isRunning())return{success:!0,pid:this.getPidInfo()?.pid};y(U,{recursive:!0});let e=g(P,"plugin","scripts","worker-service.cjs");if(!C(e))return{success:!1,error:`Worker script not found at ${e}`};let s=this.getLogFilePath();return this.startWithBun(e,s,t)}static isBunAvailable(){try{return tt("bun",["--version"],{stdio:"pipe",timeout:5e3}).status===0}catch{return!1}}static async startWithBun(t,e,s){if(!this.isBunAvailable())return{success:!1,error:"Bun is required but not found in PATH. Install from https://bun.sh"};try{let r=process.platform==="win32",o=Z("bun",[t],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(s)},cwd:P,...r&&{windowsHide:!0}}),i=z(e,{flags:"a"});return o.stdout?.pipe(i),o.stderr?.pipe(i),o.unref(),o.pid?(this.writePidFile({pid:o.pid,port:s,startedAt:new Date().toISOString(),version:process.env.npm_package_version||"unknown"}),this.waitForHealth(o.pid,s)):{success:!1,error:"Failed to get PID from spawned process"}}catch(r){return{success:!1,error:r instanceof Error?r.message:String(r)}}}static async stop(t=rt){let e=this.getPidInfo();if(!e)return!0;try{process.kill(e.pid,"SIGTERM"),await this.waitForExit(e.pid,t)}catch{try{process.kill(e.pid,"SIGKILL")}catch{}}return this.removePidFile(),!0}static async restart(t){return await this.stop(),this.start(t)}static async status(){let t=this.getPidInfo();if(!t)return{running:!1};let e=this.isProcessAlive(t.pid);return{running:e,pid:e?t.pid:void 0,port:e?t.port:void 0,uptime:e?this.formatUptime(t.startedAt):void 0}}static async isRunning(){let t=this.getPidInfo();if(!t)return!1;let e=this.isProcessAlive(t.pid);return e||this.removePidFile(),e}static getPidInfo(){try{if(!C(f))return null;let t=J(f,"utf-8"),e=JSON.parse(t);return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}static writePidFile(t){y(a,{recursive:!0}),q(f,JSON.stringify(t,null,2))}static removePidFile(){try{C(f)&&Q(f)}catch{}}static isProcessAlive(t){try{return process.kill(t,0),!0}catch{return!1}}static async waitForHealth(t,e,s=st){let r=Date.now();for(;Date.now()-rsetTimeout(o,ot))}return{success:!1,error:"Health check timed out"}}static async waitForExit(t,e){let s=Date.now();for(;Date.now()-ssetTimeout(r,it))}throw new Error("Process did not exit within timeout")}static getLogFilePath(){let t=new Date().toISOString().slice(0,10);return g(U,`worker-${t}.log`)}static formatUptime(t){let e=new Date(t).getTime(),r=Date.now()-e,o=Math.floor(r/1e3),i=Math.floor(o/60),p=Math.floor(i/60),m=Math.floor(p/24);return m>0?`${m}d ${p%24}h`:p>0?`${p}h ${i%60}m`:i>0?`${i}m ${o%60}s`:`${o}s`}};import N from"path";import{homedir as ct}from"os";var D={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function v(n){return process.platform==="win32"?Math.round(n*D.WINDOWS_MULTIPLIER):n}var ee=N.join(ct(),".claude","plugins","marketplaces","thedotmack"),re=v(D.HEALTH_CHECK),E=null;function b(){if(E!==null)return E;try{let n=N.join(u.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=u.loadFromFile(n);return E=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),E}catch(n){return _.debug("SYSTEM","Failed to load port from settings, using default",{error:n}),E=parseInt(u.get("CLAUDE_MEM_WORKER_PORT"),10),E}}var at=process.argv[2],w=b();async function ut(){switch(at){case"start":{let n=await l.start(w);if(n.success){console.log(`Worker started (PID: ${n.pid})`);let t=new Date().toISOString().slice(0,10);console.log(`Logs: ~/.claude-mem/logs/worker-${t}.log`),process.exit(0)}else console.error(`Failed to start: ${n.error}`),process.exit(1);break}case"stop":await l.stop(),console.log("Worker stopped"),process.exit(0);case"restart":{let n=await l.restart(w);n.success?(console.log(`Worker restarted (PID: ${n.pid})`),process.exit(0)):(console.error(`Failed to restart: ${n.error}`),process.exit(1))}case"status":{let n=await l.status();n.running?(console.log("Worker is running"),console.log(` PID: ${n.pid}`),console.log(` Port: ${n.port}`),console.log(` Uptime: ${n.uptime}`)):console.log("Worker is not running"),process.exit(0)}default:console.log("Usage: worker-cli.js "),process.exit(1)}}ut().catch(n=>{console.error(n),process.exit(1)}); +import{existsSync as C,readFileSync as J,writeFileSync as q,unlinkSync as Q,mkdirSync as y}from"fs";import{createWriteStream as z}from"fs";import{join as g}from"path";import{spawn as Z,spawnSync as tt}from"child_process";import{homedir as et}from"os";import{join as a,dirname as V,basename as Mt}from"path";import{homedir as X}from"os";import{fileURLToPath as j}from"url";import{readFileSync as F,writeFileSync as W,existsSync as K}from"fs";import{join as B}from"path";import{homedir as G}from"os";var $=["bugfix","feature","refactor","discovery","decision","change"],H=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var I=$.join(","),R=H.join(",");var d=(s=>(s[s.DEBUG=0]="DEBUG",s[s.INFO=1]="INFO",s[s.WARN=2]="WARN",s[s.ERROR=3]="ERROR",s[s.SILENT=4]="SILENT",s))(d||{}),O=class{level=null;useColor;constructor(){this.useColor=process.stdout.isTTY??!1}getLevel(){if(this.level===null){let t=u.get("CLAUDE_MEM_LOG_LEVEL").toUpperCase();this.level=d[t]??1}return this.level}correlationId(t,e){return`obs-${t}-${e}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message} +${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let e=Object.keys(t);return e.length===0?"{}":e.length<=3?JSON.stringify(t):`{${e.length} keys: ${e.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,e){if(!e)return t;try{let n=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&n.command){let r=n.command.length>50?n.command.substring(0,50)+"...":n.command;return`${t}(${r})`}if(t==="Read"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Edit"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}if(t==="Write"&&n.file_path){let r=n.file_path.split("/").pop()||n.file_path;return`${t}(${r})`}return t}catch{return t}}log(t,e,n,r,s){if(t0&&(M=` {${Object.entries(L).map(([k,x])=>`${k}=${x}`).join(", ")}}`)}let h=`[${i}] [${p}] [${m}] ${S}${n}${M}${T}`;t===3?console.error(h):console.log(h)}debug(t,e,n,r){this.log(0,t,e,n,r)}info(t,e,n,r){this.log(1,t,e,n,r)}warn(t,e,n,r){this.log(2,t,e,n,r)}error(t,e,n,r){this.log(3,t,e,n,r)}dataIn(t,e,n,r){this.info(t,`\u2192 ${e}`,n,r)}dataOut(t,e,n,r){this.info(t,`\u2190 ${e}`,n,r)}success(t,e,n,r){this.info(t,`\u2713 ${e}`,n,r)}failure(t,e,n,r){this.error(t,`\u2717 ${e}`,n,r)}timing(t,e,n,r){this.info(t,`\u23F1 ${e}`,r,{duration:`${n}ms`})}},_=new O;var u=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_DATA_DIR:B(G(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:I,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:R,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let e=this.get(t);return parseInt(e,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!K(t))return this.getAllDefaults();let e=F(t,"utf-8"),n=JSON.parse(e),r=n;if(n.env&&typeof n.env=="object"){r=n.env;try{W(t,JSON.stringify(r,null,2),"utf-8"),_.info("SETTINGS","Migrated settings file from nested to flat schema",{settingsPath:t})}catch(i){_.warn("SETTINGS","Failed to auto-migrate settings file",{settingsPath:t},i)}}let s={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))r[i]!==void 0&&(s[i]=r[i]);return s}};function Y(){return typeof __dirname<"u"?__dirname:V(j(import.meta.url))}var yt=Y(),c=u.get("CLAUDE_MEM_DATA_DIR"),A=process.env.CLAUDE_CONFIG_DIR||a(X(),".claude"),Ut=a(c,"archives"),Pt=a(c,"logs"),Nt=a(c,"trash"),vt=a(c,"backups"),bt=a(c,"settings.json"),wt=a(c,"claude-mem.db"),kt=a(c,"vector-db"),xt=a(A,"settings.json"),$t=a(A,"commands"),Ht=a(A,"CLAUDE.md");var f=g(c,"worker.pid"),U=g(c,"logs"),P=g(et(),".claude","plugins","marketplaces","thedotmack"),rt=5e3,nt=1e4,st=200,ot=1e3,it=100,l=class{static async start(t){if(isNaN(t)||t<1024||t>65535)return{success:!1,error:`Invalid port ${t}. Must be between 1024 and 65535`};if(await this.isRunning())return{success:!0,pid:this.getPidInfo()?.pid};y(U,{recursive:!0});let e=g(P,"plugin","scripts","worker-service.cjs");if(!C(e))return{success:!1,error:`Worker script not found at ${e}`};let n=this.getLogFilePath();return this.startWithBun(e,n,t)}static isBunAvailable(){try{return tt("bun",["--version"],{stdio:"pipe",timeout:5e3}).status===0}catch{return!1}}static async startWithBun(t,e,n){if(!this.isBunAvailable())return{success:!1,error:"Bun is required but not found in PATH. Install from https://bun.sh"};try{let r=process.platform==="win32",s=Z("bun",[t],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,CLAUDE_MEM_WORKER_PORT:String(n)},cwd:P,...r&&{windowsHide:!0}}),i=z(e,{flags:"a"});return s.stdout?.pipe(i),s.stderr?.pipe(i),s.unref(),s.pid?(this.writePidFile({pid:s.pid,port:n,startedAt:new Date().toISOString(),version:process.env.npm_package_version||"unknown"}),this.waitForHealth(s.pid,n)):{success:!1,error:"Failed to get PID from spawned process"}}catch(r){return{success:!1,error:r instanceof Error?r.message:String(r)}}}static async stop(t=rt){let e=this.getPidInfo();if(!e)return!0;try{process.kill(e.pid,"SIGTERM"),await this.waitForExit(e.pid,t)}catch{try{process.kill(e.pid,"SIGKILL")}catch{}}return this.removePidFile(),!0}static async restart(t){return await this.stop(),this.start(t)}static async status(){let t=this.getPidInfo();if(!t)return{running:!1};let e=this.isProcessAlive(t.pid);return{running:e,pid:e?t.pid:void 0,port:e?t.port:void 0,uptime:e?this.formatUptime(t.startedAt):void 0}}static async isRunning(){let t=this.getPidInfo();if(!t)return!1;let e=this.isProcessAlive(t.pid);return e||this.removePidFile(),e}static getPidInfo(){try{if(!C(f))return null;let t=J(f,"utf-8"),e=JSON.parse(t);return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}static writePidFile(t){y(c,{recursive:!0}),q(f,JSON.stringify(t,null,2))}static removePidFile(){try{C(f)&&Q(f)}catch{}}static isProcessAlive(t){try{return process.kill(t,0),!0}catch{return!1}}static async waitForHealth(t,e,n=nt){let r=Date.now();for(;Date.now()-rsetTimeout(s,st))}return{success:!1,error:"Health check timed out"}}static async waitForExit(t,e){let n=Date.now();for(;Date.now()-nsetTimeout(r,it))}throw new Error("Process did not exit within timeout")}static getLogFilePath(){let t=new Date().toISOString().slice(0,10);return g(U,`worker-${t}.log`)}static formatUptime(t){let e=new Date(t).getTime(),r=Date.now()-e,s=Math.floor(r/1e3),i=Math.floor(s/60),p=Math.floor(i/60),m=Math.floor(p/24);return m>0?`${m}d ${p%24}h`:p>0?`${p}h ${i%60}m`:i>0?`${i}m ${s%60}s`:`${s}s`}};import v from"path";import{homedir as at}from"os";var D={DEFAULT:5e3,HEALTH_CHECK:1e3,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:15,WINDOWS_MULTIPLIER:1.5};function N(o){return process.platform==="win32"?Math.round(o*D.WINDOWS_MULTIPLIER):o}var ee=v.join(at(),".claude","plugins","marketplaces","thedotmack"),re=N(D.HEALTH_CHECK),E=null;function b(){if(E!==null)return E;try{let o=v.join(u.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=u.loadFromFile(o);return E=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),E}catch(o){return _.debug("SYSTEM","Failed to load port from settings, using default",{error:o}),E=parseInt(u.get("CLAUDE_MEM_WORKER_PORT"),10),E}}var ct=process.argv[2],w=b();async function ut(){switch(ct){case"start":{let o=await l.start(w);if(o.success){console.log(`Worker started (PID: ${o.pid})`);let t=new Date().toISOString().slice(0,10);console.log(`Logs: ~/.claude-mem/logs/worker-${t}.log`),process.exit(0)}else console.error(`Failed to start: ${o.error}`),process.exit(1);break}case"stop":await l.stop(),console.log("Worker stopped"),process.exit(0);case"restart":{let o=await l.restart(w);o.success?(console.log(`Worker restarted (PID: ${o.pid})`),process.exit(0)):(console.error(`Failed to restart: ${o.error}`),process.exit(1));break}case"status":{let o=await l.status();o.running?(console.log("Worker is running"),console.log(` PID: ${o.pid}`),console.log(` Port: ${o.port}`),console.log(` Uptime: ${o.uptime}`)):console.log("Worker is not running"),process.exit(0)}default:console.log("Usage: worker-cli.js "),process.exit(1)}}ut().catch(o=>{console.error(o),process.exit(1)}); diff --git a/plugin/skills/troubleshoot/operations/database.md b/plugin/skills/troubleshoot/operations/database.md index d57c736a..2179630f 100644 --- a/plugin/skills/troubleshoot/operations/database.md +++ b/plugin/skills/troubleshoot/operations/database.md @@ -6,7 +6,7 @@ SQLite database troubleshooting for claude-mem. Claude-mem uses SQLite3 for persistent storage: - **Location:** `~/.claude-mem/claude-mem.db` -- **Library:** better-sqlite3 (synchronous, not bun:sqlite) +- **Library:** bun:sqlite (native Bun SQLite, synchronous) - **Features:** FTS5 full-text search, triggers, indexes - **Tables:** observations, sessions, user_prompts, observations_fts, sessions_fts, prompts_fts diff --git a/plugin/skills/troubleshoot/operations/diagnostics.md b/plugin/skills/troubleshoot/operations/diagnostics.md index fc14c733..0256047b 100644 --- a/plugin/skills/troubleshoot/operations/diagnostics.md +++ b/plugin/skills/troubleshoot/operations/diagnostics.md @@ -97,7 +97,6 @@ cd ~/.claude/plugins/marketplaces/thedotmack/ # Check for critical packages ls node_modules/@anthropic-ai/claude-agent-sdk 2>&1 | head -1 -ls node_modules/better-sqlite3 2>&1 | head -1 ls node_modules/express 2>&1 | head -1 ls node_modules/pm2 2>&1 | head -1 ``` diff --git a/plugin/skills/troubleshoot/operations/worker.md b/plugin/skills/troubleshoot/operations/worker.md index 894db91b..1a23102e 100644 --- a/plugin/skills/troubleshoot/operations/worker.md +++ b/plugin/skills/troubleshoot/operations/worker.md @@ -187,7 +187,6 @@ pm2 delete claude-mem-worker ```bash cd ~/.claude/plugins/marketplaces/thedotmack/ ls node_modules/@anthropic-ai/claude-agent-sdk - ls node_modules/better-sqlite3 ls node_modules/express ls node_modules/pm2 ``` diff --git a/src/cli/worker-cli.ts b/src/cli/worker-cli.ts index d96b80e6..6d147c2d 100644 --- a/src/cli/worker-cli.ts +++ b/src/cli/worker-cli.ts @@ -35,6 +35,7 @@ async function main() { console.error(`Failed to restart: ${result.error}`); process.exit(1); } + break; } case 'status': { diff --git a/src/services/process/ProcessManager.ts b/src/services/process/ProcessManager.ts index 822da8f3..9ea4bc3e 100644 --- a/src/services/process/ProcessManager.ts +++ b/src/services/process/ProcessManager.ts @@ -25,6 +25,14 @@ interface PidInfo { export class ProcessManager { static async start(port: number): Promise<{ success: boolean; pid?: number; error?: string }> { + // Validate port range + if (isNaN(port) || port < 1024 || port > 65535) { + return { + success: false, + error: `Invalid port ${port}. Must be between 1024 and 65535` + }; + } + // Check if already running if (await this.isRunning()) { const info = this.getPidInfo(); diff --git a/src/shared/worker-utils.ts b/src/shared/worker-utils.ts index 0a3c9a4a..36219ac8 100644 --- a/src/shared/worker-utils.ts +++ b/src/shared/worker-utils.ts @@ -1,6 +1,7 @@ import path from "path"; import { homedir } from "os"; import { spawnSync } from "child_process"; +import { existsSync, writeFileSync } from "fs"; import { logger } from "../utils/logger.js"; import { HOOK_TIMEOUTS, getTimeout } from "./hook-constants.js"; import { ProcessManager } from "../services/process/ProcessManager.js"; @@ -70,11 +71,17 @@ async function isWorkerHealthy(): Promise { */ async function startWorker(): Promise { // Clean up legacy PM2 (one-time migration) - if (process.platform !== 'win32') { + const pm2MigratedMarker = path.join(SettingsDefaultsManager.get('CLAUDE_MEM_DATA_DIR'), '.pm2-migrated'); + + if (process.platform !== 'win32' && !existsSync(pm2MigratedMarker)) { try { spawnSync('pm2', ['delete', 'claude-mem-worker'], { stdio: 'ignore' }); + // Mark migration as complete + writeFileSync(pm2MigratedMarker, new Date().toISOString(), 'utf-8'); + logger.debug('SYSTEM', 'PM2 cleanup completed and marked'); } catch { - // PM2 not installed or process doesn't exist - ignore + // PM2 not installed or process doesn't exist - still mark as migrated + writeFileSync(pm2MigratedMarker, new Date().toISOString(), 'utf-8'); } } diff --git a/src/types/database.ts b/src/types/database.ts index 54cdb161..d01bb26a 100644 --- a/src/types/database.ts +++ b/src/types/database.ts @@ -1,6 +1,6 @@ /** * TypeScript types for database query results - * Provides type safety for better-sqlite3 query results + * Provides type safety for bun:sqlite query results */ /**