diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index a1e36daf..8af7129a 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -14,6 +14,8 @@ module.exports = { { name: 'claude-mem-worker', script: './plugin/scripts/worker-service.cjs', + // Windows: prevent visible console windows + windowsHide: true, // INTENTIONAL: Watch mode enables auto-restart on plugin updates // // Why this is enabled: diff --git a/plugin/ecosystem.config.cjs b/plugin/ecosystem.config.cjs new file mode 100644 index 00000000..1204c688 --- /dev/null +++ b/plugin/ecosystem.config.cjs @@ -0,0 +1,43 @@ +/** + * PM2 Ecosystem Configuration for claude-mem Worker Service (Packaged Plugin) + * + * NOTE: This config is for the packaged/cache version of the plugin. + * The script path is relative to the cache directory structure. + * + * Usage: + * pm2 start ecosystem.config.cjs + * pm2 stop claude-mem-worker + * pm2 restart claude-mem-worker + * pm2 logs claude-mem-worker + * pm2 status + */ + +module.exports = { + apps: [ + { + name: 'claude-mem-worker', + // Packaged structure: cache/thedotmack/claude-mem/X.X.X/scripts/worker-service.cjs + script: './scripts/worker-service.cjs', + // Windows: prevent visible console windows + windowsHide: true, + // INTENTIONAL: Watch mode enables auto-restart on plugin updates + // + // Why this is enabled: + // - When plugin updates, files change + // - Watch mode detects these changes and auto-restarts the worker + // - Users get the latest code without manually running `pm2 restart` + // + // This is a feature, not a bug - it ensures users always run the + // latest version after plugin updates. + watch: true, + ignore_watch: [ + 'node_modules', + 'logs', + '*.log', + '*.db', + '*.db-*', + '.git' + ] + } + ] +}; diff --git a/plugin/hooks/hooks.json b/plugin/hooks/hooks.json index 99a3a8e0..f183eb99 100644 --- a/plugin/hooks/hooks.json +++ b/plugin/hooks/hooks.json @@ -7,7 +7,7 @@ "hooks": [ { "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/../scripts/smart-install.js\" && node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js", + "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/smart-install.js\" && node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js", "timeout": 300 }, { diff --git a/plugin/scripts/cleanup-hook.js b/plugin/scripts/cleanup-hook.js index 25665f3b..dc616196 100755 --- a/plugin/scripts/cleanup-hook.js +++ b/plugin/scripts/cleanup-hook.js @@ -1,11 +1,11 @@ #!/usr/bin/env node -import{stdin as u}from"process";import T from"path";import{existsSync as f}from"fs";import{homedir as d}from"os";import{spawnSync as U}from"child_process";import{readFileSync as D,existsSync as h}from"fs";var N=["bugfix","feature","refactor","discovery","decision","change"],L=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var p=N.join(","),g=L.join(",");var _=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",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:g,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 process.env[t]||this.DEFAULTS[t]}static getInt(t){let n=this.get(t);return parseInt(n,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!h(t))return this.getAllDefaults();let n=D(t,"utf-8"),o=JSON.parse(n).env||{},s={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))o[i]!==void 0&&(s[i]=o[i]);return s}};var E=T.join(d(),".claude","plugins","marketplaces","thedotmack"),y=100,R=500,I=10;function l(){let e=T.join(d(),".claude-mem","settings.json"),t=_.loadFromFile(e);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function A(){try{let e=l();return(await fetch(`http://127.0.0.1:${e}/health`,{signal:AbortSignal.timeout(y)})).ok}catch{return!1}}async function P(){try{let e=T.join(E,"ecosystem.config.cjs");if(!f(e))throw new Error(`Ecosystem config not found at ${e}`);let t=T.join(E,"node_modules",".bin","pm2"),n=process.platform==="win32"?t+".cmd":t,r=f(n)?n:"pm2",o=U(r,["start",e],{cwd:E,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(o.status!==0)throw new Error(o.stderr||"PM2 start failed");for(let s=0;ssetTimeout(i,R)),await A())return!0;return!1}catch{return!1}}async function C(){if(await A())return;if(!await P()){let t=l();throw new Error(`Worker service failed to start on port ${t}. +import{stdin as p}from"process";import a from"path";import{existsSync as O}from"fs";import{homedir as A}from"os";import{spawnSync as g}from"child_process";import{readFileSync as h,existsSync as y}from"fs";var L=["bugfix","feature","refactor","discovery","decision","change"],D=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var d=L.join(","),f=D.join(",");var T=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",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:d,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:f,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 process.env[t]||this.DEFAULTS[t]}static getInt(t){let o=this.get(t);return parseInt(o,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!y(t))return this.getAllDefaults();let o=h(t,"utf-8"),r=JSON.parse(o).env||{},i={...this.DEFAULTS};for(let E of Object.keys(this.DEFAULTS))r[E]!==void 0&&(i[E]=r[E]);return i}};var n=a.join(A(),".claude","plugins","marketplaces","thedotmack"),U=500,R=1e3,w=15;function l(){let e=a.join(A(),".claude-mem","settings.json"),t=T.loadFromFile(e);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function C(){try{let e=l();return(await fetch(`http://127.0.0.1:${e}/health`,{signal:AbortSignal.timeout(U)})).ok}catch{return!1}}async function I(){try{let e=a.join(n,"plugin","scripts","worker-service.cjs");if(!O(e))throw new Error(`Worker script not found at ${e}`);if(process.platform==="win32"){let t=g("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${e}' -WorkingDirectory '${n}' -WindowStyle Hidden`],{cwd:n,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=a.join(n,"ecosystem.config.cjs");if(!O(t))throw new Error(`Ecosystem config not found at ${t}`);let o=a.join(n,"node_modules",".bin","pm2"),s=O(o)?o:"pm2",r=g(s,["start",t],{cwd:n,stdio:"pipe",encoding:"utf-8"});if(r.status!==0)throw new Error(r.stderr||"PM2 start failed")}for(let t=0;tsetTimeout(o,R)),await C())return!0;return!1}catch{return!1}}async function m(){if(await C())return;if(!await I()){let t=l();throw new Error(`Worker service failed to start on port ${t}. To start manually, run: - cd ${E} + cd ${n} npx pm2 start ecosystem.config.cjs -If already running, try: npx pm2 restart claude-mem-worker`)}}import{appendFileSync as k}from"fs";import{homedir as w}from"os";import{join as x}from"path";var W=x(w(),".claude-mem","silent.log");function a(e,t,n=""){let r=new Date().toISOString(),S=((new Error().stack||"").split(` -`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),M=S?`${S[1].split("/").pop()}:${S[2]}`:"unknown",c=`[${r}] [${M}] ${e}`;if(t!==void 0)try{c+=` ${JSON.stringify(t)}`}catch(O){c+=` [stringify error: ${O}]`}c+=` -`;try{k(W,c)}catch(O){console.error("[silent-debug] Failed to write to log:",O)}return n}async function m(e){await C(),a("[cleanup-hook] Hook fired",{session_id:e?.session_id,cwd:e?.cwd,reason:e?.reason}),e||(console.log("No input provided - this script is designed to run as a Claude Code SessionEnd hook"),console.log(` -Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string",transcript_path:"string",hook_event_name:"SessionEnd",reason:"exit"},null,2)),process.exit(0));let{session_id:t,reason:n}=e,r=l();try{let o=await fetch(`http://127.0.0.1:${r}/api/sessions/complete`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,reason:n}),signal:AbortSignal.timeout(2e3)});if(o.ok){let s=await o.json();a("[cleanup-hook] Session cleanup completed",s)}else a("[cleanup-hook] Session not found or already cleaned up")}catch(o){a("[cleanup-hook] Worker not reachable (non-critical)",{error:o.message})}console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}if(u.isTTY)m(void 0);else{let e="";u.on("data",t=>e+=t),u.on("end",async()=>{let t=e?JSON.parse(e):void 0;await m(t)})} +If already running, try: npx pm2 restart claude-mem-worker`)}}import{appendFileSync as P}from"fs";import{homedir as k}from"os";import{join as W}from"path";var x=W(k(),".claude-mem","silent.log");function c(e,t,o=""){let s=new Date().toISOString(),S=((new Error().stack||"").split(` +`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),N=S?`${S[1].split("/").pop()}:${S[2]}`:"unknown",_=`[${s}] [${N}] ${e}`;if(t!==void 0)try{_+=` ${JSON.stringify(t)}`}catch(u){_+=` [stringify error: ${u}]`}_+=` +`;try{P(x,_)}catch(u){console.error("[silent-debug] Failed to write to log:",u)}return o}async function M(e){await m(),c("[cleanup-hook] Hook fired",{session_id:e?.session_id,cwd:e?.cwd,reason:e?.reason}),e||(console.log("No input provided - this script is designed to run as a Claude Code SessionEnd hook"),console.log(` +Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string",transcript_path:"string",hook_event_name:"SessionEnd",reason:"exit"},null,2)),process.exit(0));let{session_id:t,reason:o}=e,s=l();try{let r=await fetch(`http://127.0.0.1:${s}/api/sessions/complete`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,reason:o}),signal:AbortSignal.timeout(2e3)});if(r.ok){let i=await r.json();c("[cleanup-hook] Session cleanup completed",i)}else c("[cleanup-hook] Session not found or already cleaned up")}catch(r){c("[cleanup-hook] Worker not reachable (non-critical)",{error:r.message})}console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}if(p.isTTY)M(void 0);else{let e="";p.on("data",t=>e+=t),p.on("end",async()=>{let t=e?JSON.parse(e):void 0;await M(t)})} diff --git a/plugin/scripts/context-hook.js b/plugin/scripts/context-hook.js index 5353c5e6..7dd82942 100755 --- a/plugin/scripts/context-hook.js +++ b/plugin/scripts/context-hook.js @@ -1,8 +1,8 @@ #!/usr/bin/env node -import R from"path";import{stdin as T}from"process";import{execSync as y}from"child_process";import _ from"path";import{existsSync as u}from"fs";import{homedir as p}from"os";import{spawnSync as m}from"child_process";import{readFileSync as N,existsSync as g}from"fs";var M=["bugfix","feature","refactor","discovery","decision","change"],l=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var O=M.join(","),S=l.join(",");var i=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",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:O,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:S,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 process.env[t]||this.DEFAULTS[t]}static getInt(t){let r=this.get(t);return parseInt(r,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!g(t))return this.getAllDefaults();let r=N(t,"utf-8"),s=JSON.parse(r).env||{},n={...this.DEFAULTS};for(let o of Object.keys(this.DEFAULTS))s[o]!==void 0&&(n[o]=s[o]);return n}};var E=_.join(p(),".claude","plugins","marketplaces","thedotmack"),d=100,D=500,L=10;function a(){let e=_.join(p(),".claude-mem","settings.json"),t=i.loadFromFile(e);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function C(){try{let e=a();return(await fetch(`http://127.0.0.1:${e}/health`,{signal:AbortSignal.timeout(d)})).ok}catch{return!1}}async function U(){try{let e=_.join(E,"ecosystem.config.cjs");if(!u(e))throw new Error(`Ecosystem config not found at ${e}`);let t=_.join(E,"node_modules",".bin","pm2"),r=process.platform==="win32"?t+".cmd":t,c=u(r)?r:"pm2",s=m(c,["start",e],{cwd:E,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(s.status!==0)throw new Error(s.stderr||"PM2 start failed");for(let n=0;nsetTimeout(o,D)),await C())return!0;return!1}catch{return!1}}async function A(){if(await C())return;if(!await U()){let t=a();throw new Error(`Worker service failed to start on port ${t}. +import R from"path";import{stdin as S}from"process";import{execSync as y}from"child_process";import n from"path";import{existsSync as T}from"fs";import{homedir as f}from"os";import{spawnSync as p}from"child_process";import{readFileSync as N,existsSync as m}from"fs";var M=["bugfix","feature","refactor","discovery","decision","change"],g=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var O=M.join(","),u=g.join(",");var i=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",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:O,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 process.env[t]||this.DEFAULTS[t]}static getInt(t){let r=this.get(t);return parseInt(r,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!m(t))return this.getAllDefaults();let r=N(t,"utf-8"),o=JSON.parse(r).env||{},a={...this.DEFAULTS};for(let c of Object.keys(this.DEFAULTS))o[c]!==void 0&&(a[c]=o[c]);return a}};var s=n.join(f(),".claude","plugins","marketplaces","thedotmack"),d=500,D=1e3,L=15;function E(){let e=n.join(f(),".claude-mem","settings.json"),t=i.loadFromFile(e);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function l(){try{let e=E();return(await fetch(`http://127.0.0.1:${e}/health`,{signal:AbortSignal.timeout(d)})).ok}catch{return!1}}async function U(){try{let e=n.join(s,"plugin","scripts","worker-service.cjs");if(!T(e))throw new Error(`Worker script not found at ${e}`);if(process.platform==="win32"){let t=p("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${e}' -WorkingDirectory '${s}' -WindowStyle Hidden`],{cwd:s,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=n.join(s,"ecosystem.config.cjs");if(!T(t))throw new Error(`Ecosystem config not found at ${t}`);let r=n.join(s,"node_modules",".bin","pm2"),_=T(r)?r:"pm2",o=p(_,["start",t],{cwd:s,stdio:"pipe",encoding:"utf-8"});if(o.status!==0)throw new Error(o.stderr||"PM2 start failed")}for(let t=0;tsetTimeout(r,D)),await l())return!0;return!1}catch{return!1}}async function A(){if(await l())return;if(!await U()){let t=E();throw new Error(`Worker service failed to start on port ${t}. To start manually, run: - cd ${E} + cd ${s} npx pm2 start ecosystem.config.cjs -If already running, try: npx pm2 restart claude-mem-worker`)}}async function f(e){await A();let t=e?.cwd??process.cwd(),r=t?R.basename(t):"unknown-project",s=`http://127.0.0.1:${a()}/api/context/inject?project=${encodeURIComponent(r)}`;return y(`curl -s "${s}"`,{encoding:"utf-8",timeout:5e3}).trim()}var I=process.argv.includes("--colors");if(T.isTTY||I)f(void 0).then(e=>{console.log(e),process.exit(0)});else{let e="";T.on("data",t=>e+=t),T.on("end",async()=>{let t=e.trim()?JSON.parse(e):void 0,r=await f(t);console.log(JSON.stringify({hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:r}})),process.exit(0)})} +If already running, try: npx pm2 restart claude-mem-worker`)}}async function C(e){await A();let t=e?.cwd??process.cwd(),r=t?R.basename(t):"unknown-project",o=`http://127.0.0.1:${E()}/api/context/inject?project=${encodeURIComponent(r)}`;return y(`curl -s "${o}"`,{encoding:"utf-8",timeout:5e3}).trim()}var h=process.argv.includes("--colors");if(S.isTTY||h)C(void 0).then(e=>{console.log(e),process.exit(0)});else{let e="";S.on("data",t=>e+=t),S.on("end",async()=>{let t=e.trim()?JSON.parse(e):void 0,r=await C(t);console.log(JSON.stringify({hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:r}})),process.exit(0)})} diff --git a/plugin/scripts/new-hook.js b/plugin/scripts/new-hook.js index 28adb6e4..507aaafd 100755 --- a/plugin/scripts/new-hook.js +++ b/plugin/scripts/new-hook.js @@ -1,7 +1,7 @@ #!/usr/bin/env node -import _e from"path";import{stdin as W}from"process";import q from"better-sqlite3";import{join as m,dirname as G,basename as le}from"path";import{homedir as v}from"os";import{existsSync as be,mkdirSync as Y}from"fs";import{fileURLToPath as V}from"url";function K(){return typeof __dirname<"u"?__dirname:G(V(import.meta.url))}var Oe=K(),l=process.env.CLAUDE_MEM_DATA_DIR||m(v(),".claude-mem"),I=process.env.CLAUDE_CONFIG_DIR||m(v(),".claude"),he=m(l,"archives"),Ne=m(l,"logs"),fe=m(l,"trash"),Ie=m(l,"backups"),Ae=m(l,"settings.json"),y=m(l,"claude-mem.db"),Le=m(l,"vector-db"),Ce=m(I,"settings.json"),De=m(I,"commands"),ve=m(I,"CLAUDE.md");function k(a){Y(a,{recursive:!0})}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||{}),L=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=A[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} +import _e from"path";import{stdin as j}from"process";import J from"better-sqlite3";import{join as m,dirname as Y,basename as le}from"path";import{homedir as y}from"os";import{existsSync as be,mkdirSync as V}from"fs";import{fileURLToPath as K}from"url";function q(){return typeof __dirname<"u"?__dirname:Y(K(import.meta.url))}var Oe=q(),l=process.env.CLAUDE_MEM_DATA_DIR||m(y(),".claude-mem"),I=process.env.CLAUDE_CONFIG_DIR||m(y(),".claude"),he=m(l,"archives"),fe=m(l,"logs"),Ne=m(l,"trash"),Ie=m(l,"backups"),Ae=m(l,"settings.json"),k=m(l,"claude-mem.db"),Le=m(l,"vector-db"),Ce=m(I,"settings.json"),De=m(I,"commands"),ve=m(I,"CLAUDE.md");function x(a){V(a,{recursive:!0})}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||{}),L=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=A[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,o){if(e0&&(_=` {${Object.entries(c).map(([j,$])=>`${j}=${$}`).join(", ")}}`)}let T=`[${n}] [${i}] [${p}] ${d}${t}${_}${E}`;e===3?console.error(T):console.log(T)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},M=new L;var R=class{db;constructor(){k(l),this.db=new q(y),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable(),this.ensureDiscoveryTokensColumn()}initializeSchema(){try{this.db.exec(` +`+JSON.stringify(o,null,2):E=" "+this.formatData(o));let _="";if(r){let{sessionId:S,sdkSessionId:R,correlationId:u,...c}=r;Object.keys(c).length>0&&(_=` {${Object.entries(c).map(([$,G])=>`${$}=${G}`).join(", ")}}`)}let T=`[${n}] [${i}] [${p}] ${d}${t}${_}${E}`;e===3?console.error(T):console.log(T)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},M=new L;var h=class{db;constructor(){x(l),this.db=new J(k),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable(),this.ensureDiscoveryTokensColumn()}initializeSchema(){try{this.db.exec(` CREATE TABLE IF NOT EXISTS schema_versions ( id INTEGER PRIMARY KEY, version INTEGER UNIQUE NOT NULL, @@ -383,25 +383,25 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje WHERE id <= ? ${n} ORDER BY id DESC LIMIT ? - `,b=` + `,R=` SELECT id, created_at_epoch FROM observations WHERE id >= ? ${n} ORDER BY id ASC LIMIT ? - `;try{let u=this.db.prepare(S).all(e,...i,t+1),c=this.db.prepare(b).all(e,...i,r+1);if(u.length===0&&c.length===0)return{observations:[],sessions:[],prompts:[]};p=u.length>0?u[u.length-1].created_at_epoch:s,d=c.length>0?c[c.length-1].created_at_epoch:s}catch(u){return console.error("[SessionStore] Error getting boundary observations:",u.message),{observations:[],sessions:[],prompts:[]}}}else{let S=` + `;try{let u=this.db.prepare(S).all(e,...i,t+1),c=this.db.prepare(R).all(e,...i,r+1);if(u.length===0&&c.length===0)return{observations:[],sessions:[],prompts:[]};p=u.length>0?u[u.length-1].created_at_epoch:s,d=c.length>0?c[c.length-1].created_at_epoch:s}catch(u){return console.error("[SessionStore] Error getting boundary observations:",u.message),{observations:[],sessions:[],prompts:[]}}}else{let S=` SELECT created_at_epoch FROM observations WHERE created_at_epoch <= ? ${n} ORDER BY created_at_epoch DESC LIMIT ? - `,b=` + `,R=` SELECT created_at_epoch FROM observations WHERE created_at_epoch >= ? ${n} ORDER BY created_at_epoch ASC LIMIT ? - `;try{let u=this.db.prepare(S).all(s,...i,t),c=this.db.prepare(b).all(s,...i,r+1);if(u.length===0&&c.length===0)return{observations:[],sessions:[],prompts:[]};p=u.length>0?u[u.length-1].created_at_epoch:s,d=c.length>0?c[c.length-1].created_at_epoch:s}catch(u){return console.error("[SessionStore] Error getting boundary timestamps:",u.message),{observations:[],sessions:[],prompts:[]}}}let E=` + `;try{let u=this.db.prepare(S).all(s,...i,t),c=this.db.prepare(R).all(s,...i,r+1);if(u.length===0&&c.length===0)return{observations:[],sessions:[],prompts:[]};p=u.length>0?u[u.length-1].created_at_epoch:s,d=c.length>0?c[c.length-1].created_at_epoch:s}catch(u){return console.error("[SessionStore] Error getting boundary timestamps:",u.message),{observations:[],sessions:[],prompts:[]}}}let E=` SELECT * FROM observations WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${n} @@ -417,12 +417,12 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${n.replace("project","s.project")} ORDER BY up.created_at_epoch ASC - `;try{let S=this.db.prepare(E).all(p,d,...i),b=this.db.prepare(_).all(p,d,...i),u=this.db.prepare(T).all(p,d,...i);return{observations:S,sessions:b.map(c=>({id:c.id,sdk_session_id:c.sdk_session_id,project:c.project,request:c.request,completed:c.completed,next_steps:c.next_steps,created_at:c.created_at,created_at_epoch:c.created_at_epoch})),prompts:u.map(c=>({id:c.id,claude_session_id:c.claude_session_id,project:c.project,prompt:c.prompt_text,created_at:c.created_at,created_at_epoch:c.created_at_epoch}))}}catch(S){return console.error("[SessionStore] Error querying timeline records:",S.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function J(a,e,s){return a==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:a==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:a==="UserPromptSubmit"||a==="PostToolUse"?{continue:!0,suppressOutput:!0}:a==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function C(a,e,s={}){let t=J(a,e,s);return JSON.stringify(t)}import N from"path";import{existsSync as w}from"fs";import{homedir as F}from"os";import{spawnSync as se}from"child_process";import{readFileSync as Z,existsSync as ee}from"fs";var Q=["bugfix","feature","refactor","discovery","decision","change"],z=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var U=Q.join(","),x=z.join(",");var O=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",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:x,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(e){return process.env[e]||this.DEFAULTS[e]}static getInt(e){let s=this.get(e);return parseInt(s,10)}static getBool(e){return this.get(e)==="true"}static loadFromFile(e){if(!ee(e))return this.getAllDefaults();let s=Z(e,"utf-8"),r=JSON.parse(s).env||{},o={...this.DEFAULTS};for(let n of Object.keys(this.DEFAULTS))r[n]!==void 0&&(o[n]=r[n]);return o}};var h=N.join(F(),".claude","plugins","marketplaces","thedotmack"),te=100,re=500,oe=10;function f(){let a=N.join(F(),".claude-mem","settings.json"),e=O.loadFromFile(a);return parseInt(e.CLAUDE_MEM_WORKER_PORT,10)}async function X(){try{let a=f();return(await fetch(`http://127.0.0.1:${a}/health`,{signal:AbortSignal.timeout(te)})).ok}catch{return!1}}async function ne(){try{let a=N.join(h,"ecosystem.config.cjs");if(!w(a))throw new Error(`Ecosystem config not found at ${a}`);let e=N.join(h,"node_modules",".bin","pm2"),s=process.platform==="win32"?e+".cmd":e,t=w(s)?s:"pm2",r=se(t,["start",a],{cwd:h,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(r.status!==0)throw new Error(r.stderr||"PM2 start failed");for(let o=0;osetTimeout(n,re)),await X())return!0;return!1}catch{return!1}}async function P(){if(await X())return;if(!await ne()){let e=f();throw new Error(`Worker service failed to start on port ${e}. + `;try{let S=this.db.prepare(E).all(p,d,...i),R=this.db.prepare(_).all(p,d,...i),u=this.db.prepare(T).all(p,d,...i);return{observations:S,sessions:R.map(c=>({id:c.id,sdk_session_id:c.sdk_session_id,project:c.project,request:c.request,completed:c.completed,next_steps:c.next_steps,created_at:c.created_at,created_at_epoch:c.created_at_epoch})),prompts:u.map(c=>({id:c.id,claude_session_id:c.claude_session_id,project:c.project,prompt:c.prompt_text,created_at:c.created_at,created_at_epoch:c.created_at_epoch}))}}catch(S){return console.error("[SessionStore] Error querying timeline records:",S.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function Q(a,e,s){return a==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:a==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:a==="UserPromptSubmit"||a==="PostToolUse"?{continue:!0,suppressOutput:!0}:a==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function C(a,e,s={}){let t=Q(a,e,s);return JSON.stringify(t)}import O from"path";import{existsSync as D}from"fs";import{homedir as P}from"os";import{spawnSync as F}from"child_process";import{readFileSync as ee,existsSync as se}from"fs";var z=["bugfix","feature","refactor","discovery","decision","change"],Z=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var U=z.join(","),w=Z.join(",");var f=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",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(e){return process.env[e]||this.DEFAULTS[e]}static getInt(e){let s=this.get(e);return parseInt(s,10)}static getBool(e){return this.get(e)==="true"}static loadFromFile(e){if(!se(e))return this.getAllDefaults();let s=ee(e,"utf-8"),r=JSON.parse(s).env||{},o={...this.DEFAULTS};for(let n of Object.keys(this.DEFAULTS))r[n]!==void 0&&(o[n]=r[n]);return o}};var g=O.join(P(),".claude","plugins","marketplaces","thedotmack"),te=500,re=1e3,oe=15;function N(){let a=O.join(P(),".claude-mem","settings.json"),e=f.loadFromFile(a);return parseInt(e.CLAUDE_MEM_WORKER_PORT,10)}async function X(){try{let a=N();return(await fetch(`http://127.0.0.1:${a}/health`,{signal:AbortSignal.timeout(te)})).ok}catch{return!1}}async function ne(){try{let a=O.join(g,"plugin","scripts","worker-service.cjs");if(!D(a))throw new Error(`Worker script not found at ${a}`);if(process.platform==="win32"){let e=F("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${a}' -WorkingDirectory '${g}' -WindowStyle Hidden`],{cwd:g,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(e.status!==0)throw new Error(e.stderr||"PowerShell Start-Process failed")}else{let e=O.join(g,"ecosystem.config.cjs");if(!D(e))throw new Error(`Ecosystem config not found at ${e}`);let s=O.join(g,"node_modules",".bin","pm2"),t=D(s)?s:"pm2",r=F(t,["start",e],{cwd:g,stdio:"pipe",encoding:"utf-8"});if(r.status!==0)throw new Error(r.stderr||"PM2 start failed")}for(let e=0;esetTimeout(s,re)),await X())return!0;return!1}catch{return!1}}async function H(){if(await X())return;if(!await ne()){let e=N();throw new Error(`Worker service failed to start on port ${e}. To start manually, run: - cd ${h} + cd ${g} npx pm2 start ecosystem.config.cjs -If already running, try: npx pm2 restart claude-mem-worker`)}}import{appendFileSync as ie}from"fs";import{homedir as ae}from"os";import{join as pe}from"path";var ce=pe(ae(),".claude-mem","silent.log");function g(a,e,s=""){let t=new Date().toISOString(),i=((new Error().stack||"").split(` +If already running, try: npx pm2 restart claude-mem-worker`)}}import{appendFileSync as ie}from"fs";import{homedir as ae}from"os";import{join as pe}from"path";var ce=pe(ae(),".claude-mem","silent.log");function b(a,e,s=""){let t=new Date().toISOString(),i=((new Error().stack||"").split(` `)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),p=i?`${i[1].split("/").pop()}:${i[2]}`:"unknown",d=`[${t}] [${p}] ${a}`;if(e!==void 0)try{d+=` ${JSON.stringify(e)}`}catch(E){d+=` [stringify error: ${E}]`}d+=` -`;try{ie(ce,d)}catch(E){console.error("[silent-debug] Failed to write to log:",E)}return s}var H=100;function de(a){let e=(a.match(//g)||[]).length,s=(a.match(//g)||[]).length;return e+s}function B(a){if(typeof a!="string")return g("[tag-stripping] received non-string for prompt context:",{type:typeof a}),"";let e=de(a);return e>H&&g("[tag-stripping] tag count exceeds limit, truncating:",{tagCount:e,maxAllowed:H,contentLength:a.length}),a.replace(/[\s\S]*?<\/claude-mem-context>/g,"").replace(/[\s\S]*?<\/private>/g,"").trim()}async function ue(a){if(await P(),!a)throw new Error("newHook requires input");let{session_id:e,cwd:s,prompt:t}=a;g("[new-hook] Input received",{session_id:e,cwd:s,cwd_type:typeof s,cwd_length:s?.length,has_cwd:!!s,prompt_length:t?.length});let r=_e.basename(s);g("[new-hook] Project extracted",{project:r,project_type:typeof r,project_length:r?.length,is_empty:r==="",cwd_was:s});let o=new R,n=o.createSDKSession(e,r,t),i=o.incrementPromptCounter(n),p=B(t);if(!p||p.trim()===""){g("[new-hook] Prompt entirely private, skipping memory operations",{session_id:e,promptNumber:i,originalLength:t.length}),o.close(),console.error(`[new-hook] Session ${n}, prompt #${i} (fully private - skipped)`),console.log(C("UserPromptSubmit",!0));return}o.saveUserPrompt(e,i,p),console.error(`[new-hook] Session ${n}, prompt #${i}`),o.close();let d=f(),E=t.startsWith("/")?t.substring(1):t;try{let _=await fetch(`http://127.0.0.1:${d}/sessions/${n}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:r,userPrompt:E,promptNumber:i}),signal:AbortSignal.timeout(5e3)});if(!_.ok){let T=await _.text();throw new Error(`Failed to initialize session: ${_.status} ${T}`)}}catch(_){throw _.cause?.code==="ECONNREFUSED"||_.name==="TimeoutError"||_.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):_}console.log(C("UserPromptSubmit",!0))}var D="";W.on("data",a=>D+=a);W.on("end",async()=>{let a=D?JSON.parse(D):void 0;await ue(a)}); +`;try{ie(ce,d)}catch(E){console.error("[silent-debug] Failed to write to log:",E)}return s}var B=100;function de(a){let e=(a.match(//g)||[]).length,s=(a.match(//g)||[]).length;return e+s}function W(a){if(typeof a!="string")return b("[tag-stripping] received non-string for prompt context:",{type:typeof a}),"";let e=de(a);return e>B&&b("[tag-stripping] tag count exceeds limit, truncating:",{tagCount:e,maxAllowed:B,contentLength:a.length}),a.replace(/[\s\S]*?<\/claude-mem-context>/g,"").replace(/[\s\S]*?<\/private>/g,"").trim()}async function ue(a){if(await H(),!a)throw new Error("newHook requires input");let{session_id:e,cwd:s,prompt:t}=a;b("[new-hook] Input received",{session_id:e,cwd:s,cwd_type:typeof s,cwd_length:s?.length,has_cwd:!!s,prompt_length:t?.length});let r=_e.basename(s);b("[new-hook] Project extracted",{project:r,project_type:typeof r,project_length:r?.length,is_empty:r==="",cwd_was:s});let o=new h,n=o.createSDKSession(e,r,t),i=o.incrementPromptCounter(n),p=W(t);if(!p||p.trim()===""){b("[new-hook] Prompt entirely private, skipping memory operations",{session_id:e,promptNumber:i,originalLength:t.length}),o.close(),console.error(`[new-hook] Session ${n}, prompt #${i} (fully private - skipped)`),console.log(C("UserPromptSubmit",!0));return}o.saveUserPrompt(e,i,p),console.error(`[new-hook] Session ${n}, prompt #${i}`),o.close();let d=N(),E=t.startsWith("/")?t.substring(1):t;try{let _=await fetch(`http://127.0.0.1:${d}/sessions/${n}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:r,userPrompt:E,promptNumber:i}),signal:AbortSignal.timeout(5e3)});if(!_.ok){let T=await _.text();throw new Error(`Failed to initialize session: ${_.status} ${T}`)}}catch(_){throw _.cause?.code==="ECONNREFUSED"||_.name==="TimeoutError"||_.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):_}console.log(C("UserPromptSubmit",!0))}var v="";j.on("data",a=>v+=a);j.on("end",async()=>{let a=v?JSON.parse(v):void 0;await ue(a)}); diff --git a/plugin/scripts/save-hook.js b/plugin/scripts/save-hook.js index 708c90b1..0a4006c8 100755 --- a/plugin/scripts/save-hook.js +++ b/plugin/scripts/save-hook.js @@ -1,10 +1,10 @@ #!/usr/bin/env node -import{stdin as U}from"process";function P(r,t,e){return r==="PreCompact"?t?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:e.reason||"Pre-compact operation failed",suppressOutput:!0}:r==="SessionStart"?t&&e.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:e.context}}:{continue:!0,suppressOutput:!0}:r==="UserPromptSubmit"||r==="PostToolUse"?{continue:!0,suppressOutput:!0}:r==="Stop"?{continue:!0,suppressOutput:!0}:{continue:t,suppressOutput:!0,...e.reason&&!t?{stopReason:e.reason}:{}}}function S(r,t,e={}){let o=P(r,t,e);return JSON.stringify(o)}var T=(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))(T||{}),g=class{level;useColor;constructor(){let t=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=T[t]??1,this.useColor=process.stdout.isTTY??!1}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.level===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 o=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&o.command){let n=o.command.length>50?o.command.substring(0,50)+"...":o.command;return`${t}(${n})`}if(t==="Read"&&o.file_path){let n=o.file_path.split("/").pop()||o.file_path;return`${t}(${n})`}if(t==="Edit"&&o.file_path){let n=o.file_path.split("/").pop()||o.file_path;return`${t}(${n})`}if(t==="Write"&&o.file_path){let n=o.file_path.split("/").pop()||o.file_path;return`${t}(${n})`}return t}catch{return t}}log(t,e,o,n,s){if(t0&&(C=` {${Object.entries(A).map(([D,I])=>`${D}=${I}`).join(", ")}}`)}let d=`[${a}] [${f}] [${i}] ${u}${o}${C}${O}`;t===3?console.error(d):console.log(d)}debug(t,e,o,n){this.log(0,t,e,o,n)}info(t,e,o,n){this.log(1,t,e,o,n)}warn(t,e,o,n){this.log(2,t,e,o,n)}error(t,e,o,n){this.log(3,t,e,o,n)}dataIn(t,e,o,n){this.info(t,`\u2192 ${e}`,o,n)}dataOut(t,e,o,n){this.info(t,`\u2190 ${e}`,o,n)}success(t,e,o,n){this.info(t,`\u2713 ${e}`,o,n)}failure(t,e,o,n){this.error(t,`\u2717 ${e}`,o,n)}timing(t,e,o,n){this.info(t,`\u23F1 ${e}`,n,{duration:`${o}ms`})}},c=new g;import _ from"path";import{existsSync as h}from"fs";import{homedir as R}from"os";import{spawnSync as x}from"child_process";import{readFileSync as v,existsSync as w}from"fs";var b=["bugfix","feature","refactor","discovery","decision","change"],k=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var y=b.join(","),M=k.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_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:M,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 process.env[t]||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(!w(t))return this.getAllDefaults();let e=v(t,"utf-8"),n=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let a of Object.keys(this.DEFAULTS))n[a]!==void 0&&(s[a]=n[a]);return s}};var E=_.join(R(),".claude","plugins","marketplaces","thedotmack"),$=100,H=500,W=10;function l(){let r=_.join(R(),".claude-mem","settings.json"),t=p.loadFromFile(r);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function L(){try{let r=l();return(await fetch(`http://127.0.0.1:${r}/health`,{signal:AbortSignal.timeout($)})).ok}catch{return!1}}async function F(){try{let r=_.join(E,"ecosystem.config.cjs");if(!h(r))throw new Error(`Ecosystem config not found at ${r}`);let t=_.join(E,"node_modules",".bin","pm2"),e=process.platform==="win32"?t+".cmd":t,o=h(e)?e:"pm2",n=x(o,["start",r],{cwd:E,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(n.status!==0)throw new Error(n.stderr||"PM2 start failed");for(let s=0;ssetTimeout(a,H)),await L())return!0;return!1}catch{return!1}}async function N(){if(await L())return;if(!await F()){let t=l();throw new Error(`Worker service failed to start on port ${t}. +import{stdin as D}from"process";function w(n,t,e){return n==="PreCompact"?t?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:e.reason||"Pre-compact operation failed",suppressOutput:!0}: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 S(n,t,e={}){let o=w(n,t,e);return JSON.stringify(o)}var T=(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))(T||{}),g=class{level;useColor;constructor(){let t=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=T[t]??1,this.useColor=process.stdout.isTTY??!1}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.level===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 o=typeof e=="string"?JSON.parse(e):e;if(t==="Bash"&&o.command){let r=o.command.length>50?o.command.substring(0,50)+"...":o.command;return`${t}(${r})`}if(t==="Read"&&o.file_path){let r=o.file_path.split("/").pop()||o.file_path;return`${t}(${r})`}if(t==="Edit"&&o.file_path){let r=o.file_path.split("/").pop()||o.file_path;return`${t}(${r})`}if(t==="Write"&&o.file_path){let r=o.file_path.split("/").pop()||o.file_path;return`${t}(${r})`}return t}catch{return t}}log(t,e,o,r,s){if(t0&&(d=` {${Object.entries(h).map(([I,P])=>`${I}=${P}`).join(", ")}}`)}let A=`[${a}] [${f}] [${i}] ${c}${o}${d}${O}`;t===3?console.error(A):console.log(A)}debug(t,e,o,r){this.log(0,t,e,o,r)}info(t,e,o,r){this.log(1,t,e,o,r)}warn(t,e,o,r){this.log(2,t,e,o,r)}error(t,e,o,r){this.log(3,t,e,o,r)}dataIn(t,e,o,r){this.info(t,`\u2192 ${e}`,o,r)}dataOut(t,e,o,r){this.info(t,`\u2190 ${e}`,o,r)}success(t,e,o,r){this.info(t,`\u2713 ${e}`,o,r)}failure(t,e,o,r){this.error(t,`\u2717 ${e}`,o,r)}timing(t,e,o,r){this.info(t,`\u23F1 ${e}`,r,{duration:`${o}ms`})}},p=new g;import E from"path";import{existsSync as m}from"fs";import{homedir as N}from"os";import{spawnSync as R}from"child_process";import{readFileSync as v,existsSync as $}from"fs";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(","),M=b.join(",");var _=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",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:M,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 process.env[t]||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(!$(t))return this.getAllDefaults();let e=v(t,"utf-8"),r=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let a of Object.keys(this.DEFAULTS))r[a]!==void 0&&(s[a]=r[a]);return s}};var u=E.join(N(),".claude","plugins","marketplaces","thedotmack"),x=500,H=1e3,W=15;function l(){let n=E.join(N(),".claude-mem","settings.json"),t=_.loadFromFile(n);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function L(){try{let n=l();return(await fetch(`http://127.0.0.1:${n}/health`,{signal:AbortSignal.timeout(x)})).ok}catch{return!1}}async function F(){try{let n=E.join(u,"plugin","scripts","worker-service.cjs");if(!m(n))throw new Error(`Worker script not found at ${n}`);if(process.platform==="win32"){let t=R("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${n}' -WorkingDirectory '${u}' -WindowStyle Hidden`],{cwd:u,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=E.join(u,"ecosystem.config.cjs");if(!m(t))throw new Error(`Ecosystem config not found at ${t}`);let e=E.join(u,"node_modules",".bin","pm2"),o=m(e)?e:"pm2",r=R(o,["start",t],{cwd:u,stdio:"pipe",encoding:"utf-8"});if(r.status!==0)throw new Error(r.stderr||"PM2 start failed")}for(let t=0;tsetTimeout(e,H)),await L())return!0;return!1}catch{return!1}}async function U(){if(await L())return;if(!await F()){let t=l();throw new Error(`Worker service failed to start on port ${t}. To start manually, run: - cd ${E} + cd ${u} npx pm2 start ecosystem.config.cjs -If already running, try: npx pm2 restart claude-mem-worker`)}}var X=new Set(["ListMcpResourcesTool","SlashCommand","Skill","TodoWrite","AskUserQuestion"]);async function B(r){if(await N(),!r)throw new Error("saveHook requires input");let{session_id:t,cwd:e,tool_name:o,tool_input:n,tool_response:s}=r;if(X.has(o)){console.log(S("PostToolUse",!0));return}let a=l(),f=c.formatTool(o,n);c.dataIn("HOOK",`PostToolUse: ${f}`,{workerPort:a});try{let i=await fetch(`http://127.0.0.1:${a}/api/sessions/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,tool_name:o,tool_input:n,tool_response:s,cwd:e||""}),signal:AbortSignal.timeout(2e3)});if(!i.ok){let u=await i.text();throw c.failure("HOOK","Failed to send observation",{status:i.status},u),new Error(`Failed to send observation to worker: ${i.status} ${u}`)}c.debug("HOOK","Observation sent successfully",{toolName:o})}catch(i){throw i.cause?.code==="ECONNREFUSED"||i.name==="TimeoutError"||i.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):i}console.log(S("PostToolUse",!0))}var m="";U.on("data",r=>m+=r);U.on("end",async()=>{let r=m?JSON.parse(m):void 0;await B(r)}); +If already running, try: npx pm2 restart claude-mem-worker`)}}var X=new Set(["ListMcpResourcesTool","SlashCommand","Skill","TodoWrite","AskUserQuestion"]);async function B(n){if(await U(),!n)throw new Error("saveHook requires input");let{session_id:t,cwd:e,tool_name:o,tool_input:r,tool_response:s}=n;if(X.has(o)){console.log(S("PostToolUse",!0));return}let a=l(),f=p.formatTool(o,r);p.dataIn("HOOK",`PostToolUse: ${f}`,{workerPort:a});try{let i=await fetch(`http://127.0.0.1:${a}/api/sessions/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({claudeSessionId:t,tool_name:o,tool_input:r,tool_response:s,cwd:e||""}),signal:AbortSignal.timeout(2e3)});if(!i.ok){let c=await i.text();throw p.failure("HOOK","Failed to send observation",{status:i.status},c),new Error(`Failed to send observation to worker: ${i.status} ${c}`)}p.debug("HOOK","Observation sent successfully",{toolName:o})}catch(i){throw i.cause?.code==="ECONNREFUSED"||i.name==="TimeoutError"||i.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):i}console.log(S("PostToolUse",!0))}var C="";D.on("data",n=>C+=n);D.on("end",async()=>{let n=C?JSON.parse(C):void 0;await B(n)}); diff --git a/plugin/scripts/smart-install.js b/plugin/scripts/smart-install.js new file mode 100644 index 00000000..ee79f332 --- /dev/null +++ b/plugin/scripts/smart-install.js @@ -0,0 +1,389 @@ +#!/usr/bin/env node + +/** + * Smart Install Script for claude-mem + * + * Features: + * - Only runs npm install when necessary (version change or missing deps) + * - Caches installation state with version marker + * - Provides helpful Windows-specific error messages + * - Cross-platform compatible (pure Node.js) + * - Fast when already installed (just version check) + */ + +import { existsSync, readFileSync, writeFileSync } from 'fs'; +import { execSync, spawnSync, spawn } from 'child_process'; +import { join } from 'path'; +import { homedir } from 'os'; +import { createRequire } from 'module'; + +// CRITICAL: Always use marketplace directory for ALL operations +// This script may run from the cache directory (plugin/scripts/) but must +// operate on the marketplace directory where package.json and node_modules live. +// This ensures cross-platform compatibility and avoids cache directory confusion. +const MARKETPLACE_ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack'); + +// Use MARKETPLACE_ROOT for all paths - this script can be deployed anywhere +// but always operates on the marketplace directory +const PLUGIN_ROOT = MARKETPLACE_ROOT; +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 = { + reset: '\x1b[0m', + bright: '\x1b[1m', + green: '\x1b[32m', + yellow: '\x1b[33m', + red: '\x1b[31m', + cyan: '\x1b[36m', + dim: '\x1b[2m', +}; + +function log(message, color = colors.reset) { + console.error(`${color}${message}${colors.reset}`); +} + +function getPackageVersion() { + try { + const packageJson = JSON.parse(readFileSync(PACKAGE_JSON_PATH, 'utf-8')); + return packageJson.version; + } catch (error) { + log(`âš ī¸ Failed to read package.json: ${error.message}`, colors.yellow); + return null; + } +} + +function getNodeVersion() { + return process.version; // e.g., "v22.21.1" +} + +function getInstalledVersion() { + try { + if (existsSync(VERSION_MARKER_PATH)) { + const content = readFileSync(VERSION_MARKER_PATH, 'utf-8').trim(); + + // Try parsing as JSON (new format) + try { + const marker = JSON.parse(content); + return { + packageVersion: marker.packageVersion, + nodeVersion: marker.nodeVersion, + installedAt: marker.installedAt + }; + } catch { + // Fallback: old format (plain text version string) + return { + packageVersion: content, + nodeVersion: null, // Unknown + installedAt: null + }; + } + } + } catch (error) { + // Marker doesn't exist or can't be read + } + return null; +} + +function setInstalledVersion(packageVersion, nodeVersion) { + try { + const marker = { + packageVersion, + nodeVersion, + installedAt: new Date().toISOString() + }; + writeFileSync(VERSION_MARKER_PATH, JSON.stringify(marker, null, 2), 'utf-8'); + } catch (error) { + log(`âš ī¸ Failed to write version marker: ${error.message}`, colors.yellow); + } +} + +function needsInstall() { + // Check if node_modules exists + if (!existsSync(NODE_MODULES_PATH)) { + log('đŸ“Ļ Dependencies not found - first time setup', colors.cyan); + 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(); + const installed = getInstalledVersion(); + + if (!installed) { + log('đŸ“Ļ No version marker found - installing', colors.cyan); + return true; + } + + // Check package version + if (currentPackageVersion !== installed.packageVersion) { + log(`đŸ“Ļ Version changed (${installed.packageVersion} → ${currentPackageVersion}) - updating`, colors.cyan); + return true; + } + + // Check Node.js version + if (installed.nodeVersion && currentNodeVersion !== installed.nodeVersion) { + log(`đŸ“Ļ Node.js version changed (${installed.nodeVersion} → ${currentNodeVersion}) - rebuilding native modules`, colors.cyan); + return true; + } + + // If old format (no nodeVersion), assume needs install + if (!installed.nodeVersion) { + log('đŸ“Ļ Old version marker format - updating', colors.cyan); + return true; + } + + // All good - no install needed + log(`✓ Dependencies already installed (v${currentPackageVersion})`, colors.dim); + 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); + + // CRITICAL: Use createRequire() to resolve from MARKETPLACE_ROOT + // This script may run from cache but must load modules from marketplace's node_modules + const require = createRequire(join(MARKETPLACE_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'; + + log('', colors.cyan); + log('🔨 Installing dependencies...', colors.bright); + log('', colors.reset); + + // Try normal install first, then retry with force if it fails + const strategies = [ + { command: 'npm install', label: 'normal' }, + { command: 'npm install --force', label: 'with force flag' }, + ]; + + let lastError = null; + + for (const { command, label } of strategies) { + try { + log(`Attempting install ${label}...`, colors.dim); + + // Run npm install silently + execSync(command, { + cwd: PLUGIN_ROOT, + stdio: 'pipe', // Silent output unless error + encoding: 'utf-8', + }); + + // Verify better-sqlite3 was installed + if (!existsSync(BETTER_SQLITE3_PATH)) { + throw new Error('better-sqlite3 installation verification failed'); + } + + // NEW: Verify native modules actually work + const nativeModulesWork = await verifyNativeModules(); + if (!nativeModulesWork) { + throw new Error('Native modules failed to load after install'); + } + + const packageVersion = getPackageVersion(); + const nodeVersion = getNodeVersion(); + setInstalledVersion(packageVersion, nodeVersion); + + log('', colors.green); + log('✅ Dependencies installed successfully!', colors.bright); + log(` Package version: ${packageVersion}`, colors.dim); + log(` Node.js version: ${nodeVersion}`, colors.dim); + log('', colors.reset); + + return true; + + } catch (error) { + lastError = error; + // Continue to next strategy + } + } + + // All strategies failed - show error + log('', colors.red); + 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) { + log('Error output:', colors.dim); + log(lastError.stderr.toString(), colors.red); + } else if (lastError.message) { + log(lastError.message, colors.red); + } + + log('', colors.yellow); + log('📋 Troubleshooting Steps:', colors.bright); + log('', colors.reset); + log('1. Check your internet connection', colors.yellow); + log('2. Try running: npm cache clean --force', colors.yellow); + log('3. Try running: npm install (in plugin directory)', colors.yellow); + log('4. Check npm version: npm --version (requires npm 7+)', colors.yellow); + log('5. Try updating npm: npm install -g npm@latest', colors.yellow); + log('', colors.reset); + } + + return false; +} + +/** + * Check if we should fail when worker startup fails + * Returns true if worker failed AND dependencies are missing + */ +function shouldFailOnWorkerStartup(workerStarted) { + return !workerStarted && !existsSync(NODE_MODULES_PATH); +} + +async function main() { + try { + // Check if we need to install dependencies + const installNeeded = needsInstall(); + + if (installNeeded) { + // Run installation (now async) + const installSuccess = await runNpmInstall(); + + if (!installSuccess) { + log('', colors.red); + log('âš ī¸ Installation failed', colors.yellow); + log('', colors.reset); + process.exit(1); + } + } else { + // NEW: 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 + // The context-hook.js calls ensureWorkerRunning() which handles worker startup + // This avoids potential process management conflicts during plugin initialization + log('✅ Installation complete', colors.green); + + // Success - dependencies installed (if needed) + process.exit(0); + + } catch (error) { + log(`❌ Unexpected error: ${error.message}`, colors.red); + log('', colors.reset); + process.exit(1); + } +} + +main(); diff --git a/plugin/scripts/summary-hook.js b/plugin/scripts/summary-hook.js index f0742e2f..c87815c0 100755 --- a/plugin/scripts/summary-hook.js +++ b/plugin/scripts/summary-hook.js @@ -1,16 +1,16 @@ #!/usr/bin/env node -import{stdin as D}from"process";import{readFileSync as U,existsSync as I}from"fs";function k(o,t,e){return o==="PreCompact"?t?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:e.reason||"Pre-compact operation failed",suppressOutput:!0}: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 d(o,t,e={}){let n=k(o,t,e);return JSON.stringify(n)}var O=(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))(O||{}),S=class{level;useColor;constructor(){let t=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=O[t]??1,this.useColor=process.stdout.isTTY??!1}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.level===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&&(T=` {${Object.entries(C).map(([x,b])=>`${x}=${b}`).join(", ")}}`)}let y=`[${i}] [${a}] [${u}] ${l}${n}${T}${g}`;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`})}},c=new S;import f from"path";import{existsSync as M}from"fs";import{homedir as R}from"os";import{spawnSync as H}from"child_process";import{readFileSync as w,existsSync as $}from"fs";var P=["bugfix","feature","refactor","discovery","decision","change"],v=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var A=P.join(","),h=v.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_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:A,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:h,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 process.env[t]||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(!$(t))return this.getAllDefaults();let e=w(t,"utf-8"),r=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))r[i]!==void 0&&(s[i]=r[i]);return s}};var E=f.join(R(),".claude","plugins","marketplaces","thedotmack"),W=100,F=500,X=10;function _(){let o=f.join(R(),".claude-mem","settings.json"),t=p.loadFromFile(o);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function L(){try{let o=_();return(await fetch(`http://127.0.0.1:${o}/health`,{signal:AbortSignal.timeout(W)})).ok}catch{return!1}}async function j(){try{let o=f.join(E,"ecosystem.config.cjs");if(!M(o))throw new Error(`Ecosystem config not found at ${o}`);let t=f.join(E,"node_modules",".bin","pm2"),e=process.platform==="win32"?t+".cmd":t,n=M(e)?e:"pm2",r=H(n,["start",o],{cwd:E,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(r.status!==0)throw new Error(r.stderr||"PM2 start failed");for(let s=0;ssetTimeout(i,F)),await L())return!0;return!1}catch{return!1}}async function N(){if(await L())return;if(!await j()){let t=_();throw new Error(`Worker service failed to start on port ${t}. +import{stdin as U}from"process";import{readFileSync as I,existsSync as x}from"fs";function P(o,t,e){return o==="PreCompact"?t?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:e.reason||"Pre-compact operation failed",suppressOutput:!0}: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 A(o,t,e={}){let n=P(o,t,e);return JSON.stringify(n)}var O=(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))(O||{}),S=class{level;useColor;constructor(){let t=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=O[t]??1,this.useColor=process.stdout.isTTY??!1}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.level===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&&(d=` {${Object.entries(C).map(([w,k])=>`${w}=${k}`).join(", ")}}`)}let y=`[${i}] [${c}] [${p}] ${_}${n}${d}${g}`;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`})}},u=new S;import E from"path";import{existsSync as m}from"fs";import{homedir as L}from"os";import{spawnSync as N}from"child_process";import{readFileSync as v,existsSync as H}from"fs";var b=["bugfix","feature","refactor","discovery","decision","change"],$=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var h=b.join(","),M=$.join(",");var f=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",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:h,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:M,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 process.env[t]||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(!H(t))return this.getAllDefaults();let e=v(t,"utf-8"),r=JSON.parse(e).env||{},s={...this.DEFAULTS};for(let i of Object.keys(this.DEFAULTS))r[i]!==void 0&&(s[i]=r[i]);return s}};var a=E.join(L(),".claude","plugins","marketplaces","thedotmack"),W=500,F=1e3,j=15;function l(){let o=E.join(L(),".claude-mem","settings.json"),t=f.loadFromFile(o);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function R(){try{let o=l();return(await fetch(`http://127.0.0.1:${o}/health`,{signal:AbortSignal.timeout(W)})).ok}catch{return!1}}async function X(){try{let o=E.join(a,"plugin","scripts","worker-service.cjs");if(!m(o))throw new Error(`Worker script not found at ${o}`);if(process.platform==="win32"){let t=N("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${o}' -WorkingDirectory '${a}' -WindowStyle Hidden`],{cwd:a,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=E.join(a,"ecosystem.config.cjs");if(!m(t))throw new Error(`Ecosystem config not found at ${t}`);let e=E.join(a,"node_modules",".bin","pm2"),n=m(e)?e:"pm2",r=N(n,["start",t],{cwd:a,stdio:"pipe",encoding:"utf-8"});if(r.status!==0)throw new Error(r.stderr||"PM2 start failed")}for(let t=0;tsetTimeout(e,F)),await R())return!0;return!1}catch{return!1}}async function D(){if(await R())return;if(!await X()){let t=l();throw new Error(`Worker service failed to start on port ${t}. To start manually, run: - cd ${E} + cd ${a} npx pm2 start ecosystem.config.cjs -If already running, try: npx pm2 restart claude-mem-worker`)}}function B(o){if(!o||!I(o))return"";try{let t=U(o,"utf-8").trim();if(!t)return"";let e=t.split(` -`);for(let n=e.length-1;n>=0;n--)try{let r=JSON.parse(e[n]);if(r.type==="user"&&r.message?.content){let s=r.message.content;if(typeof s=="string")return s;if(Array.isArray(s))return s.filter(a=>a.type==="text").map(a=>a.text).join(` -`)}}catch{continue}}catch(t){c.error("HOOK","Failed to read transcript",{transcriptPath:o},t)}return""}function K(o){if(!o||!I(o))return"";try{let t=U(o,"utf-8").trim();if(!t)return"";let e=t.split(` -`);for(let n=e.length-1;n>=0;n--)try{let r=JSON.parse(e[n]);if(r.type==="assistant"&&r.message?.content){let s="",i=r.message.content;return typeof i=="string"?s=i:Array.isArray(i)&&(s=i.filter(u=>u.type==="text").map(u=>u.text).join(` +If already running, try: npx pm2 restart claude-mem-worker`)}}function B(o){if(!o||!x(o))return"";try{let t=I(o,"utf-8").trim();if(!t)return"";let e=t.split(` +`);for(let n=e.length-1;n>=0;n--)try{let r=JSON.parse(e[n]);if(r.type==="user"&&r.message?.content){let s=r.message.content;if(typeof s=="string")return s;if(Array.isArray(s))return s.filter(c=>c.type==="text").map(c=>c.text).join(` +`)}}catch{continue}}catch(t){u.error("HOOK","Failed to read transcript",{transcriptPath:o},t)}return""}function K(o){if(!o||!x(o))return"";try{let t=I(o,"utf-8").trim();if(!t)return"";let e=t.split(` +`);for(let n=e.length-1;n>=0;n--)try{let r=JSON.parse(e[n]);if(r.type==="assistant"&&r.message?.content){let s="",i=r.message.content;return typeof i=="string"?s=i:Array.isArray(i)&&(s=i.filter(p=>p.type==="text").map(p=>p.text).join(` `)),s=s.replace(/[\s\S]*?<\/system-reminder>/g,""),s=s.replace(/\n{3,}/g,` -`).trim(),s}}catch{continue}}catch(t){c.error("HOOK","Failed to read transcript",{transcriptPath:o},t)}return""}async function V(o){if(await N(),!o)throw new Error("summaryHook requires input");let{session_id:t}=o,e=_(),n=B(o.transcript_path||""),r=K(o.transcript_path||"");c.dataIn("HOOK","Stop: Requesting summary",{workerPort:e,hasLastUserMessage:!!n,hasLastAssistantMessage:!!r});try{let s=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:r}),signal:AbortSignal.timeout(2e3)});if(!s.ok){let i=await s.text();throw c.failure("HOOK","Failed to generate summary",{status:s.status},i),new Error(`Failed to request summary from worker: ${s.status} ${i}`)}c.debug("HOOK","Summary request sent successfully")}catch(s){throw s.cause?.code==="ECONNREFUSED"||s.name==="TimeoutError"||s.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):s}finally{fetch(`http://127.0.0.1:${e}/api/processing`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({isProcessing:!1})}).catch(()=>{})}console.log(d("Stop",!0))}var m="";D.on("data",o=>m+=o);D.on("end",async()=>{let o=m?JSON.parse(m):void 0;await V(o)}); +`).trim(),s}}catch{continue}}catch(t){u.error("HOOK","Failed to read transcript",{transcriptPath:o},t)}return""}async function V(o){if(await D(),!o)throw new Error("summaryHook requires input");let{session_id:t}=o,e=l(),n=B(o.transcript_path||""),r=K(o.transcript_path||"");u.dataIn("HOOK","Stop: Requesting summary",{workerPort:e,hasLastUserMessage:!!n,hasLastAssistantMessage:!!r});try{let s=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:r}),signal:AbortSignal.timeout(2e3)});if(!s.ok){let i=await s.text();throw u.failure("HOOK","Failed to generate summary",{status:s.status},i),new Error(`Failed to request summary from worker: ${s.status} ${i}`)}u.debug("HOOK","Summary request sent successfully")}catch(s){throw s.cause?.code==="ECONNREFUSED"||s.name==="TimeoutError"||s.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):s}finally{fetch(`http://127.0.0.1:${e}/api/processing`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({isProcessing:!1})}).catch(()=>{})}console.log(A("Stop",!0))}var T="";U.on("data",o=>T+=o);U.on("end",async()=>{let o=T?JSON.parse(T):void 0;await V(o)}); diff --git a/plugin/scripts/user-message-hook.js b/plugin/scripts/user-message-hook.js index 12000110..fca44957 100755 --- a/plugin/scripts/user-message-hook.js +++ b/plugin/scripts/user-message-hook.js @@ -1,11 +1,11 @@ #!/usr/bin/env node -import{join as M,basename as k}from"path";import{homedir as v}from"os";import{existsSync as x}from"fs";import c from"path";import{existsSync as m}from"fs";import{homedir as C}from"os";import{spawnSync as R}from"child_process";import{readFileSync as L,existsSync as y}from"fs";var h=["bugfix","feature","refactor","discovery","decision","change"],U=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var p=h.join(","),S=U.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_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:S,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 process.env[t]||this.DEFAULTS[t]}static getInt(t){let n=this.get(t);return parseInt(n,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!y(t))return this.getAllDefaults();let n=L(t,"utf-8"),o=JSON.parse(n).env||{},r={...this.DEFAULTS};for(let s of Object.keys(this.DEFAULTS))o[s]!==void 0&&(r[s]=o[s]);return r}};var E=c.join(C(),".claude","plugins","marketplaces","thedotmack"),w=100,I=500,P=10;function _(){let e=c.join(C(),".claude-mem","settings.json"),t=a.loadFromFile(e);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function f(){try{let e=_();return(await fetch(`http://127.0.0.1:${e}/health`,{signal:AbortSignal.timeout(w)})).ok}catch{return!1}}async function W(){try{let e=c.join(E,"ecosystem.config.cjs");if(!m(e))throw new Error(`Ecosystem config not found at ${e}`);let t=c.join(E,"node_modules",".bin","pm2"),n=process.platform==="win32"?t+".cmd":t,i=m(n)?n:"pm2",o=R(i,["start",e],{cwd:E,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(o.status!==0)throw new Error(o.stderr||"PM2 start failed");for(let r=0;rsetTimeout(s,I)),await f())return!0;return!1}catch{return!1}}async function A(){if(await f())return;if(!await W()){let t=_();throw new Error(`Worker service failed to start on port ${t}. +import{join as M,basename as W}from"path";import{homedir as v}from"os";import{existsSync as x}from"fs";import i from"path";import{existsSync as u}from"fs";import{homedir as f}from"os";import{spawnSync as d}from"child_process";import{readFileSync as y,existsSync as w}from"fs";var L=["bugfix","feature","refactor","discovery","decision","change"],U=["how-it-works","why-it-exists","what-changed","problem-solution","gotcha","pattern","trade-off"];var S=L.join(","),m=U.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_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:S,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:m,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 process.env[t]||this.DEFAULTS[t]}static getInt(t){let o=this.get(t);return parseInt(o,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){if(!w(t))return this.getAllDefaults();let o=y(t,"utf-8"),r=JSON.parse(o).env||{},c={...this.DEFAULTS};for(let s of Object.keys(this.DEFAULTS))r[s]!==void 0&&(c[s]=r[s]);return c}};var n=i.join(f(),".claude","plugins","marketplaces","thedotmack"),R=500,P=1e3,I=15;function _(){let e=i.join(f(),".claude-mem","settings.json"),t=E.loadFromFile(e);return parseInt(t.CLAUDE_MEM_WORKER_PORT,10)}async function C(){try{let e=_();return(await fetch(`http://127.0.0.1:${e}/health`,{signal:AbortSignal.timeout(R)})).ok}catch{return!1}}async function k(){try{let e=i.join(n,"plugin","scripts","worker-service.cjs");if(!u(e))throw new Error(`Worker script not found at ${e}`);if(process.platform==="win32"){let t=d("powershell.exe",["-NoProfile","-NonInteractive","-Command",`Start-Process -FilePath 'node' -ArgumentList '${e}' -WorkingDirectory '${n}' -WindowStyle Hidden`],{cwd:n,stdio:"pipe",encoding:"utf-8",windowsHide:!0});if(t.status!==0)throw new Error(t.stderr||"PowerShell Start-Process failed")}else{let t=i.join(n,"ecosystem.config.cjs");if(!u(t))throw new Error(`Ecosystem config not found at ${t}`);let o=i.join(n,"node_modules",".bin","pm2"),a=u(o)?o:"pm2",r=d(a,["start",t],{cwd:n,stdio:"pipe",encoding:"utf-8"});if(r.status!==0)throw new Error(r.stderr||"PM2 start failed")}for(let t=0;tsetTimeout(o,P)),await C())return!0;return!1}catch{return!1}}async function A(){if(await C())return;if(!await k()){let t=_();throw new Error(`Worker service failed to start on port ${t}. To start manually, run: - cd ${E} + cd ${n} npx pm2 start ecosystem.config.cjs -If already running, try: npx pm2 restart claude-mem-worker`)}}var b=M(v(),".claude","plugins","marketplaces","thedotmack"),X=M(b,"node_modules");x(X)||(console.error(` +If already running, try: npx pm2 restart claude-mem-worker`)}}var b=M(v(),".claude","plugins","marketplaces","thedotmack"),F=M(b,"node_modules");x(F)||(console.error(` --- \u{1F389} Note: This appears under Plugin Hook Error, but it's not an error. That's the only option for user messages in Claude Code UI until a better method is provided. @@ -23,7 +23,7 @@ Dependencies have been installed 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(3));try{await A();let e=_(),t=k(process.cwd()),n=await fetch(`http://127.0.0.1:${e}/api/context/inject?project=${encodeURIComponent(t)}&colors=true`,{method:"GET",signal:AbortSignal.timeout(5e3)});if(!n.ok)throw new Error(`Worker error ${n.status}`);let i=await n.text(),o=new Date,r=new Date("2025-12-06T00:00:00Z"),s=new Date("2025-12-05T05:00:00Z"),u="";o=1&&O<=5,D=l>=17&&l<19;N&&D?T=` +`);let T="";if(r=1&&O<=5,D=p>=17&&p<19;N&&D?T=` \u{1F534} LIVE NOW: AMA w/ Dev (@thedotmack) until 7pm EST `:T=` \u2013 LIVE AMA w/ Dev (@thedotmack) Dec 1st\u20135th, 5pm to 7pm EST @@ -42,10 +42,10 @@ This message was not added to your startup context, so you can continue working \u{1F4DD} Claude-Mem Context Loaded \u2139\uFE0F Note: This appears as stderr but is informational only -`+i+` +`+a+` \u{1F4A1} New! Wrap all or part of any message with ... to prevent storing sensitive information in your observation history. -\u{1F4AC} Community https://discord.gg/J4wttp9vDu`+u+T+` +\u{1F4AC} Community https://discord.gg/J4wttp9vDu`+l+T+` \u{1F4FA} Watch live in browser http://localhost:${e}/ `)}catch(e){console.error(`\u274C Failed to load context display: ${e}`)}process.exit(3); diff --git a/plugin/scripts/worker-service.cjs b/plugin/scripts/worker-service.cjs index 7ac60a5a..5b046b06 100755 --- a/plugin/scripts/worker-service.cjs +++ b/plugin/scripts/worker-service.cjs @@ -1037,7 +1037,7 @@ Other tips: WHERE project IS NOT NULL GROUP BY project ORDER BY MAX(created_at_epoch) DESC - `).all().map(o=>o.project);t.json({projects:n})});handleGetProcessingStatus=this.wrapHandler((r,t)=>{let s=this.sessionManager.isAnySessionProcessing(),i=this.sessionManager.getTotalActiveWork();t.json({isProcessing:s,queueDepth:i})});handleSetProcessing=this.wrapHandler((r,t)=>{this.workerService.broadcastProcessingStatus();let s=this.sessionManager.isAnySessionProcessing(),i=this.sessionManager.getTotalQueueDepth(),n=this.sessionManager.getActiveSessionCount();t.json({status:"ok",isProcessing:s})});parsePaginationParams(r){let t=parseInt(r.query.offset,10)||0,s=Math.min(parseInt(r.query.limit,10)||20,100),i=r.query.project;return{offset:t,limit:s,project:i}}};var Al=class extends gr{constructor(r){super();this.searchManager=r}setupRoutes(r){r.get("/api/search",this.handleUnifiedSearch.bind(this)),r.get("/api/timeline",this.handleUnifiedTimeline.bind(this)),r.get("/api/decisions",this.handleDecisions.bind(this)),r.get("/api/changes",this.handleChanges.bind(this)),r.get("/api/how-it-works",this.handleHowItWorks.bind(this)),r.get("/api/search/observations",this.handleSearchObservations.bind(this)),r.get("/api/search/sessions",this.handleSearchSessions.bind(this)),r.get("/api/search/prompts",this.handleSearchPrompts.bind(this)),r.get("/api/search/by-concept",this.handleSearchByConcept.bind(this)),r.get("/api/search/by-file",this.handleSearchByFile.bind(this)),r.get("/api/search/by-type",this.handleSearchByType.bind(this)),r.get("/api/context/recent",this.handleGetRecentContext.bind(this)),r.get("/api/context/timeline",this.handleGetContextTimeline.bind(this)),r.get("/api/context/preview",this.handleContextPreview.bind(this)),r.get("/api/context/inject",this.handleContextInject.bind(this)),r.get("/api/timeline/by-query",this.handleGetTimelineByQuery.bind(this)),r.get("/api/search/help",this.handleSearchHelp.bind(this))}handleUnifiedSearch=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.search(r.query);t.json(s.content)});handleUnifiedTimeline=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.timeline(r.query);t.json(s.content)});handleDecisions=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.decisions(r.query);t.json(s.content)});handleChanges=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.changes(r.query);t.json(s.content)});handleHowItWorks=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.howItWorks(r.query);t.json(s.content)});handleSearchObservations=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.searchObservations(r.query);t.json(s.content)});handleSearchSessions=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.searchSessions(r.query);t.json(s.content)});handleSearchPrompts=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.searchUserPrompts(r.query);t.json(s.content)});handleSearchByConcept=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.findByConcept(r.query);t.json(s.content)});handleSearchByFile=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.findByFile(r.query);t.json(s.content)});handleSearchByType=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.findByType(r.query);t.json(s.content)});handleGetRecentContext=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.getRecentContext(r.query);t.json(s.content)});handleGetContextTimeline=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.getContextTimeline(r.query);t.json(s.content)});handleContextPreview=this.wrapHandler(async(r,t)=>{let s=r.query.project;if(!s){this.badRequest(t,"Project parameter is required");return}let{generateContext:i}=await Promise.resolve().then(()=>(wf(),Ef)),n=`/preview/${s}`,o=await i({session_id:"preview-"+Date.now(),cwd:n},!0);t.setHeader("Content-Type","text/plain; charset=utf-8"),t.send(o)});handleContextInject=this.wrapHandler(async(r,t)=>{let s=r.query.project,i=r.query.colors==="true";if(!s){this.badRequest(t,"Project parameter is required");return}let{generateContext:n}=await Promise.resolve().then(()=>(wf(),Ef)),o=`/context/${s}`,l=await n({session_id:"context-inject-"+Date.now(),cwd:o},i);t.setHeader("Content-Type","text/plain; charset=utf-8"),t.send(l)});handleGetTimelineByQuery=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.getTimelineByQuery(r.query);t.json(s.content)});handleSearchHelp=this.wrapHandler((r,t)=>{t.json({title:"Claude-Mem Search API",description:"HTTP API for searching persistent memory",endpoints:[{path:"/api/search/observations",method:"GET",description:"Search observations using full-text search",parameters:{query:"Search query (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 20)",project:"Filter by project name (optional)"}},{path:"/api/search/sessions",method:"GET",description:"Search session summaries using full-text search",parameters:{query:"Search query (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 20)"}},{path:"/api/search/prompts",method:"GET",description:"Search user prompts using full-text search",parameters:{query:"Search query (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 20)",project:"Filter by project name (optional)"}},{path:"/api/search/by-concept",method:"GET",description:"Find observations by concept tag",parameters:{concept:"Concept tag (required): discovery, decision, bugfix, feature, refactor",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/by-file",method:"GET",description:"Find observations and sessions by file path",parameters:{filePath:"File path or partial path (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results per type (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/by-type",method:"GET",description:"Find observations by type",parameters:{type:"Observation type (required): discovery, decision, bugfix, feature, refactor",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/context/recent",method:"GET",description:"Get recent session context including summaries and observations",parameters:{project:"Project name (default: current directory)",limit:"Number of recent sessions (default: 3)"}},{path:"/api/context/timeline",method:"GET",description:"Get unified timeline around a specific point in time",parameters:{anchor:'Anchor point: observation ID, session ID (e.g., "S123"), or ISO timestamp (required)',depth_before:"Number of records before anchor (default: 10)",depth_after:"Number of records after anchor (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/timeline/by-query",method:"GET",description:"Search for best match, then get timeline around it",parameters:{query:"Search query (required)",mode:'Search mode: "auto", "observations", or "sessions" (default: "auto")',depth_before:"Number of records before match (default: 10)",depth_after:"Number of records after match (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/help",method:"GET",description:"Get this help documentation"}],examples:['curl "http://localhost:37777/api/search/observations?query=authentication&format=index&limit=5"','curl "http://localhost:37777/api/search/by-type?type=bugfix&limit=10"','curl "http://localhost:37777/api/context/recent?project=claude-mem&limit=3"','curl "http://localhost:37777/api/context/timeline?anchor=123&depth_before=5&depth_after=5"']})})};var Wn=gt(require("path"),1),br=require("fs"),Rf=require("os");xs();wt();var Tf=require("child_process"),Fs=require("fs"),Fw=require("os"),Ji=require("path");wt();var Yi=(0,Ji.join)((0,Fw.homedir)(),".claude","plugins","marketplaces","thedotmack");function yr(a){return(0,Tf.execSync)(`git ${a}`,{cwd:Yi,encoding:"utf-8",timeout:3e4}).trim()}function Uw(a,e=6e4){return(0,Tf.execSync)(a,{cwd:Yi,encoding:"utf-8",timeout:e}).trim()}function Nl(){let a=(0,Ji.join)(Yi,".git");if(!(0,Fs.existsSync)(a))return{branch:null,isBeta:!1,isGitRepo:!1,isDirty:!1,canSwitch:!1,error:"Installed plugin is not a git repository"};try{let e=yr("rev-parse --abbrev-ref HEAD"),t=yr("status --porcelain").length>0,s=e.startsWith("beta");return{branch:e,isBeta:s,isGitRepo:!0,isDirty:t,canSwitch:!0}}catch(e){return W.error("BRANCH","Failed to get branch info",{},e),{branch:null,isBeta:!1,isGitRepo:!0,isDirty:!1,canSwitch:!1,error:e.message}}}async function zw(a){let e=Nl();if(!e.isGitRepo)return{success:!1,error:"Installed plugin is not a git repository. Please reinstall."};if(e.branch===a)return{success:!0,branch:a,message:`Already on branch ${a}`};try{W.info("BRANCH","Starting branch switch",{from:e.branch,to:a}),W.debug("BRANCH","Discarding local changes"),yr("checkout -- ."),yr("clean -fd"),W.debug("BRANCH","Fetching from origin"),yr("fetch origin"),W.debug("BRANCH","Checking out branch",{branch:a});try{yr(`checkout ${a}`)}catch{yr(`checkout -b ${a} origin/${a}`)}W.debug("BRANCH","Pulling latest"),yr(`pull origin ${a}`);let r=(0,Ji.join)(Yi,".install-version");return(0,Fs.existsSync)(r)&&(0,Fs.unlinkSync)(r),W.debug("BRANCH","Running npm install"),Uw("npm install",12e4),W.success("BRANCH","Branch switch complete",{branch:a}),{success:!0,branch:a,message:`Switched to ${a}. Worker will restart automatically.`}}catch(r){W.error("BRANCH","Branch switch failed",{targetBranch:a},r);try{e.branch&&yr(`checkout ${e.branch}`)}catch{}return{success:!1,error:`Branch switch failed: ${r.message}`}}}async function Bw(){let a=Nl();if(!a.isGitRepo||!a.branch)return{success:!1,error:"Cannot pull updates: not a git repository"};try{W.info("BRANCH","Pulling updates",{branch:a.branch}),yr("checkout -- ."),yr("fetch origin"),yr(`pull origin ${a.branch}`);let e=(0,Ji.join)(Yi,".install-version");return(0,Fs.existsSync)(e)&&(0,Fs.unlinkSync)(e),Uw("npm install",12e4),W.success("BRANCH","Updates pulled",{branch:a.branch}),{success:!0,branch:a.branch,message:`Updated ${a.branch}. Worker will restart automatically.`}}catch(e){return W.error("BRANCH","Pull failed",{},e),{success:!1,error:`Pull failed: ${e.message}`}}}Zc();zi();var Dl=class extends gr{constructor(r){super();this.settingsManager=r}setupRoutes(r){r.get("/api/settings",this.handleGetSettings.bind(this)),r.post("/api/settings",this.handleUpdateSettings.bind(this)),r.get("/api/mcp/status",this.handleGetMcpStatus.bind(this)),r.post("/api/mcp/toggle",this.handleToggleMcp.bind(this)),r.get("/api/branch/status",this.handleGetBranchStatus.bind(this)),r.post("/api/branch/switch",this.handleSwitchBranch.bind(this)),r.post("/api/branch/update",this.handleUpdateBranch.bind(this))}handleGetSettings=this.wrapHandler((r,t)=>{let s=Wn.default.join((0,Rf.homedir)(),".claude-mem","settings.json"),i=Br.loadFromFile(s);t.json(i)});handleUpdateSettings=this.wrapHandler((r,t)=>{if(r.body.CLAUDE_MEM_CONTEXT_OBSERVATIONS){let l=parseInt(r.body.CLAUDE_MEM_CONTEXT_OBSERVATIONS,10);if(isNaN(l)||l<1||l>200){t.status(400).json({success:!1,error:"CLAUDE_MEM_CONTEXT_OBSERVATIONS must be between 1 and 200"});return}}if(r.body.CLAUDE_MEM_WORKER_PORT){let l=parseInt(r.body.CLAUDE_MEM_WORKER_PORT,10);if(isNaN(l)||l<1024||l>65535){t.status(400).json({success:!1,error:"CLAUDE_MEM_WORKER_PORT must be between 1024 and 65535"});return}}let s=this.validateContextSettings(r.body);if(!s.valid){t.status(400).json({success:!1,error:s.error});return}let i=Wn.default.join((0,Rf.homedir)(),".claude-mem","settings.json"),n={env:{}};if((0,br.existsSync)(i)){let l=(0,br.readFileSync)(i,"utf-8");n=JSON.parse(l),n.env||(n.env={})}let o=["CLAUDE_MEM_MODEL","CLAUDE_MEM_CONTEXT_OBSERVATIONS","CLAUDE_MEM_WORKER_PORT","CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT","CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES","CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS","CLAUDE_MEM_CONTEXT_FULL_COUNT","CLAUDE_MEM_CONTEXT_FULL_FIELD","CLAUDE_MEM_CONTEXT_SESSION_COUNT","CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY","CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE"];for(let l of o)r.body[l]!==void 0&&(n.env[l]=r.body[l]);(0,br.writeFileSync)(i,JSON.stringify(n,null,2),"utf-8"),W.info("WORKER","Settings updated"),t.json({success:!0,message:"Settings updated successfully"})});handleGetMcpStatus=this.wrapHandler((r,t)=>{let s=this.isMcpEnabled();t.json({enabled:s})});handleToggleMcp=this.wrapHandler((r,t)=>{let{enabled:s}=r.body;if(typeof s!="boolean"){this.badRequest(t,"enabled must be a boolean");return}this.toggleMcp(s),t.json({success:!0,enabled:this.isMcpEnabled()})});handleGetBranchStatus=this.wrapHandler((r,t)=>{let s=Nl();t.json(s)});handleSwitchBranch=this.wrapHandler(async(r,t)=>{let{branch:s}=r.body;if(!s){t.status(400).json({success:!1,error:"Missing branch parameter"});return}let i=["main","beta/7.0"];if(!i.includes(s)){t.status(400).json({success:!1,error:`Invalid branch. Allowed: ${i.join(", ")}`});return}W.info("WORKER","Branch switch requested",{branch:s});let n=await zw(s);n.success&&setTimeout(()=>{W.info("WORKER","Restarting worker after branch switch"),process.exit(0)},1e3),t.json(n)});handleUpdateBranch=this.wrapHandler(async(r,t)=>{W.info("WORKER","Branch update requested");let s=await Bw();s.success&&setTimeout(()=>{W.info("WORKER","Restarting worker after branch update"),process.exit(0)},1e3),t.json(s)});validateContextSettings(r){let t=["CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT","CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY","CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE"];for(let s of t)if(r[s]&&!["true","false"].includes(r[s]))return{valid:!1,error:`${s} must be "true" or "false"`};if(r.CLAUDE_MEM_CONTEXT_FULL_COUNT){let s=parseInt(r.CLAUDE_MEM_CONTEXT_FULL_COUNT,10);if(isNaN(s)||s<0||s>20)return{valid:!1,error:"CLAUDE_MEM_CONTEXT_FULL_COUNT must be between 0 and 20"}}if(r.CLAUDE_MEM_CONTEXT_SESSION_COUNT){let s=parseInt(r.CLAUDE_MEM_CONTEXT_SESSION_COUNT,10);if(isNaN(s)||s<1||s>50)return{valid:!1,error:"CLAUDE_MEM_CONTEXT_SESSION_COUNT must be between 1 and 50"}}if(r.CLAUDE_MEM_CONTEXT_FULL_FIELD&&!["narrative","facts"].includes(r.CLAUDE_MEM_CONTEXT_FULL_FIELD))return{valid:!1,error:'CLAUDE_MEM_CONTEXT_FULL_FIELD must be "narrative" or "facts"'};if(r.CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES){let s=r.CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES.split(",").map(i=>i.trim());for(let i of s)if(i&&!Cn.includes(i))return{valid:!1,error:`Invalid observation type: ${i}. Valid types: ${Cn.join(", ")}`}}if(r.CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS){let s=r.CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS.split(",").map(i=>i.trim());for(let i of s)if(i&&!In.includes(i))return{valid:!1,error:`Invalid observation concept: ${i}. Valid concepts: ${In.join(", ")}`}}return{valid:!0}}isMcpEnabled(){let r=sa(),t=Wn.default.join(r,"plugin",".mcp.json");return(0,br.existsSync)(t)}toggleMcp(r){try{let t=sa(),s=Wn.default.join(t,"plugin",".mcp.json"),i=Wn.default.join(t,"plugin",".mcp.json.disabled");r&&(0,br.existsSync)(i)?((0,br.renameSync)(i,s),W.info("WORKER","MCP search server enabled")):!r&&(0,br.existsSync)(s)?((0,br.renameSync)(s,i),W.info("WORKER","MCP search server disabled")):W.debug("WORKER","MCP toggle no-op (already in desired state)",{enabled:r})}catch(t){throw W.failure("WORKER","Failed to toggle MCP",{enabled:r},t),t}}};var $l=class{app;server=null;startTime=Date.now();mcpClient;dbManager;sessionManager;sseBroadcaster;sdkAgent;paginationHelper;settingsManager;sessionEventBroadcaster;viewerRoutes;sessionRoutes;dataRoutes;searchRoutes;settingsRoutes;constructor(){this.app=(0,Hw.default)(),this.dbManager=new tl,this.sessionManager=new rl(this.dbManager),this.sseBroadcaster=new al,this.sdkAgent=new bl(this.dbManager,this.sessionManager),this.paginationHelper=new xl(this.dbManager),this.settingsManager=new _l(this.dbManager),this.sessionEventBroadcaster=new Tl(this.sseBroadcaster,this),this.sessionManager.setOnSessionDeleted(()=>{this.broadcastProcessingStatus()}),this.mcpClient=new Rn({name:"worker-search-proxy",version:"1.0.0"},{capabilities:{}}),this.viewerRoutes=new Rl(this.sseBroadcaster,this.dbManager,this.sessionManager),this.sessionRoutes=new Ol(this.sessionManager,this.dbManager,this.sdkAgent,this.sessionEventBroadcaster,this),this.dataRoutes=new kl(this.paginationHelper,this.dbManager,this.sessionManager,this.sseBroadcaster,this,this.startTime),this.searchRoutes=null,this.settingsRoutes=new Dl(this.settingsManager),this.setupMiddleware(),this.setupRoutes()}setupMiddleware(){Aw(this.summarizeRequestBody.bind(this)).forEach(r=>this.app.use(r))}setupRoutes(){this.app.get("/api/health",(e,r)=>{r.status(200).json({status:"ok"})}),this.viewerRoutes.setupRoutes(this.app),this.sessionRoutes.setupRoutes(this.app),this.dataRoutes.setupRoutes(this.app),this.settingsRoutes.setupRoutes(this.app)}async start(){let e=An();this.server=await new Promise((r,t)=>{let s=this.app.listen(e,()=>r(s));s.on("error",t)}),W.info("SYSTEM","Worker started",{port:e,pid:process.pid}),this.initializeBackground().catch(r=>{W.error("SYSTEM","Background initialization failed",{},r)})}async initializeBackground(){await this.dbManager.initialize();let e=new El,r=new wl,t=new Sl(this.dbManager.getSessionSearch(),this.dbManager.getSessionStore(),this.dbManager.getChromaSync(),e,r);this.searchRoutes=new Al(t),this.searchRoutes.setupRoutes(this.app),W.info("WORKER","SearchManager initialized and search routes registered");let s=Vw.default.join(__dirname,"..","..","plugin","scripts","mcp-server.cjs"),i=new kn({command:"node",args:[s],env:process.env});await this.mcpClient.connect(i),W.success("WORKER","Connected to MCP server")}async shutdown(){if(await this.sessionManager.shutdownAll(),this.mcpClient)try{await this.mcpClient.close(),W.info("SYSTEM","MCP client closed")}catch(e){W.error("SYSTEM","Failed to close MCP client",{},e)}this.server&&await new Promise((e,r)=>{this.server.close(t=>t?r(t):e())}),await this.dbManager.close(),W.info("SYSTEM","Worker shutdown complete")}summarizeRequestBody(e,r,t){return Nw(e,r,t)}broadcastProcessingStatus(){let e=this.sessionManager.isAnySessionProcessing(),r=this.sessionManager.getTotalActiveWork(),t=this.sessionManager.getActiveSessionCount();W.info("WORKER","Broadcasting processing status",{isProcessing:e,queueDepth:r,activeSessions:t}),this.sseBroadcaster.broadcast({type:"processing_status",isProcessing:e,queueDepth:r})}};if(require.main===module||!module.parent){let a=new $l;process.on("SIGTERM",async()=>{W.info("SYSTEM","Received SIGTERM, shutting down gracefully"),await a.shutdown(),process.exit(0)}),process.on("SIGINT",async()=>{W.info("SYSTEM","Received SIGINT, shutting down gracefully"),await a.shutdown(),process.exit(0)}),a.start().catch(e=>{W.failure("SYSTEM","Worker failed to start",{},e),process.exit(1)})}0&&(module.exports={WorkerService}); + `).all().map(o=>o.project);t.json({projects:n})});handleGetProcessingStatus=this.wrapHandler((r,t)=>{let s=this.sessionManager.isAnySessionProcessing(),i=this.sessionManager.getTotalActiveWork();t.json({isProcessing:s,queueDepth:i})});handleSetProcessing=this.wrapHandler((r,t)=>{this.workerService.broadcastProcessingStatus();let s=this.sessionManager.isAnySessionProcessing(),i=this.sessionManager.getTotalQueueDepth(),n=this.sessionManager.getActiveSessionCount();t.json({status:"ok",isProcessing:s})});parsePaginationParams(r){let t=parseInt(r.query.offset,10)||0,s=Math.min(parseInt(r.query.limit,10)||20,100),i=r.query.project;return{offset:t,limit:s,project:i}}};var Al=class extends gr{constructor(r){super();this.searchManager=r}setupRoutes(r){r.get("/api/search",this.handleUnifiedSearch.bind(this)),r.get("/api/timeline",this.handleUnifiedTimeline.bind(this)),r.get("/api/decisions",this.handleDecisions.bind(this)),r.get("/api/changes",this.handleChanges.bind(this)),r.get("/api/how-it-works",this.handleHowItWorks.bind(this)),r.get("/api/search/observations",this.handleSearchObservations.bind(this)),r.get("/api/search/sessions",this.handleSearchSessions.bind(this)),r.get("/api/search/prompts",this.handleSearchPrompts.bind(this)),r.get("/api/search/by-concept",this.handleSearchByConcept.bind(this)),r.get("/api/search/by-file",this.handleSearchByFile.bind(this)),r.get("/api/search/by-type",this.handleSearchByType.bind(this)),r.get("/api/context/recent",this.handleGetRecentContext.bind(this)),r.get("/api/context/timeline",this.handleGetContextTimeline.bind(this)),r.get("/api/context/preview",this.handleContextPreview.bind(this)),r.get("/api/context/inject",this.handleContextInject.bind(this)),r.get("/api/timeline/by-query",this.handleGetTimelineByQuery.bind(this)),r.get("/api/search/help",this.handleSearchHelp.bind(this))}handleUnifiedSearch=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.search(r.query);t.json(s.content)});handleUnifiedTimeline=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.timeline(r.query);t.json(s.content)});handleDecisions=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.decisions(r.query);t.json(s.content)});handleChanges=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.changes(r.query);t.json(s.content)});handleHowItWorks=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.howItWorks(r.query);t.json(s.content)});handleSearchObservations=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.searchObservations(r.query);t.json(s.content)});handleSearchSessions=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.searchSessions(r.query);t.json(s.content)});handleSearchPrompts=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.searchUserPrompts(r.query);t.json(s.content)});handleSearchByConcept=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.findByConcept(r.query);t.json(s.content)});handleSearchByFile=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.findByFile(r.query);t.json(s.content)});handleSearchByType=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.findByType(r.query);t.json(s.content)});handleGetRecentContext=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.getRecentContext(r.query);t.json(s.content)});handleGetContextTimeline=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.getContextTimeline(r.query);t.json(s.content)});handleContextPreview=this.wrapHandler(async(r,t)=>{let s=r.query.project;if(!s){this.badRequest(t,"Project parameter is required");return}let{generateContext:i}=await Promise.resolve().then(()=>(wf(),Ef)),n=`/preview/${s}`,o=await i({session_id:"preview-"+Date.now(),cwd:n},!0);t.setHeader("Content-Type","text/plain; charset=utf-8"),t.send(o)});handleContextInject=this.wrapHandler(async(r,t)=>{let s=r.query.project,i=r.query.colors==="true";if(!s){this.badRequest(t,"Project parameter is required");return}let{generateContext:n}=await Promise.resolve().then(()=>(wf(),Ef)),o=`/context/${s}`,l=await n({session_id:"context-inject-"+Date.now(),cwd:o},i);t.setHeader("Content-Type","text/plain; charset=utf-8"),t.send(l)});handleGetTimelineByQuery=this.wrapHandler(async(r,t)=>{let s=await this.searchManager.getTimelineByQuery(r.query);t.json(s.content)});handleSearchHelp=this.wrapHandler((r,t)=>{t.json({title:"Claude-Mem Search API",description:"HTTP API for searching persistent memory",endpoints:[{path:"/api/search/observations",method:"GET",description:"Search observations using full-text search",parameters:{query:"Search query (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 20)",project:"Filter by project name (optional)"}},{path:"/api/search/sessions",method:"GET",description:"Search session summaries using full-text search",parameters:{query:"Search query (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 20)"}},{path:"/api/search/prompts",method:"GET",description:"Search user prompts using full-text search",parameters:{query:"Search query (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 20)",project:"Filter by project name (optional)"}},{path:"/api/search/by-concept",method:"GET",description:"Find observations by concept tag",parameters:{concept:"Concept tag (required): discovery, decision, bugfix, feature, refactor",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/by-file",method:"GET",description:"Find observations and sessions by file path",parameters:{filePath:"File path or partial path (required)",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results per type (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/by-type",method:"GET",description:"Find observations by type",parameters:{type:"Observation type (required): discovery, decision, bugfix, feature, refactor",format:'Response format: "index" or "full" (default: "full")',limit:"Number of results (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/context/recent",method:"GET",description:"Get recent session context including summaries and observations",parameters:{project:"Project name (default: current directory)",limit:"Number of recent sessions (default: 3)"}},{path:"/api/context/timeline",method:"GET",description:"Get unified timeline around a specific point in time",parameters:{anchor:'Anchor point: observation ID, session ID (e.g., "S123"), or ISO timestamp (required)',depth_before:"Number of records before anchor (default: 10)",depth_after:"Number of records after anchor (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/timeline/by-query",method:"GET",description:"Search for best match, then get timeline around it",parameters:{query:"Search query (required)",mode:'Search mode: "auto", "observations", or "sessions" (default: "auto")',depth_before:"Number of records before match (default: 10)",depth_after:"Number of records after match (default: 10)",project:"Filter by project name (optional)"}},{path:"/api/search/help",method:"GET",description:"Get this help documentation"}],examples:['curl "http://localhost:37777/api/search/observations?query=authentication&format=index&limit=5"','curl "http://localhost:37777/api/search/by-type?type=bugfix&limit=10"','curl "http://localhost:37777/api/context/recent?project=claude-mem&limit=3"','curl "http://localhost:37777/api/context/timeline?anchor=123&depth_before=5&depth_after=5"']})})};var Wn=gt(require("path"),1),br=require("fs"),Rf=require("os");xs();wt();var Tf=require("child_process"),Fs=require("fs"),Fw=require("os"),Ji=require("path");wt();var Yi=(0,Ji.join)((0,Fw.homedir)(),".claude","plugins","marketplaces","thedotmack");function yr(a){return(0,Tf.execSync)(`git ${a}`,{cwd:Yi,encoding:"utf-8",timeout:3e4}).trim()}function Uw(a,e=6e4){return(0,Tf.execSync)(a,{cwd:Yi,encoding:"utf-8",timeout:e}).trim()}function Nl(){let a=(0,Ji.join)(Yi,".git");if(!(0,Fs.existsSync)(a))return{branch:null,isBeta:!1,isGitRepo:!1,isDirty:!1,canSwitch:!1,error:"Installed plugin is not a git repository"};try{let e=yr("rev-parse --abbrev-ref HEAD"),t=yr("status --porcelain").length>0,s=e.startsWith("beta");return{branch:e,isBeta:s,isGitRepo:!0,isDirty:t,canSwitch:!0}}catch(e){return W.error("BRANCH","Failed to get branch info",{},e),{branch:null,isBeta:!1,isGitRepo:!0,isDirty:!1,canSwitch:!1,error:e.message}}}async function zw(a){let e=Nl();if(!e.isGitRepo)return{success:!1,error:"Installed plugin is not a git repository. Please reinstall."};if(e.branch===a)return{success:!0,branch:a,message:`Already on branch ${a}`};try{W.info("BRANCH","Starting branch switch",{from:e.branch,to:a}),W.debug("BRANCH","Discarding local changes"),yr("checkout -- ."),yr("clean -fd"),W.debug("BRANCH","Fetching from origin"),yr("fetch origin"),W.debug("BRANCH","Checking out branch",{branch:a});try{yr(`checkout ${a}`)}catch{yr(`checkout -b ${a} origin/${a}`)}W.debug("BRANCH","Pulling latest"),yr(`pull origin ${a}`);let r=(0,Ji.join)(Yi,".install-version");return(0,Fs.existsSync)(r)&&(0,Fs.unlinkSync)(r),W.debug("BRANCH","Running npm install"),Uw("npm install",12e4),W.success("BRANCH","Branch switch complete",{branch:a}),{success:!0,branch:a,message:`Switched to ${a}. Worker will restart automatically.`}}catch(r){W.error("BRANCH","Branch switch failed",{targetBranch:a},r);try{e.branch&&yr(`checkout ${e.branch}`)}catch{}return{success:!1,error:`Branch switch failed: ${r.message}`}}}async function Bw(){let a=Nl();if(!a.isGitRepo||!a.branch)return{success:!1,error:"Cannot pull updates: not a git repository"};try{W.info("BRANCH","Pulling updates",{branch:a.branch}),yr("checkout -- ."),yr("fetch origin"),yr(`pull origin ${a.branch}`);let e=(0,Ji.join)(Yi,".install-version");return(0,Fs.existsSync)(e)&&(0,Fs.unlinkSync)(e),Uw("npm install",12e4),W.success("BRANCH","Updates pulled",{branch:a.branch}),{success:!0,branch:a.branch,message:`Updated ${a.branch}. Worker will restart automatically.`}}catch(e){return W.error("BRANCH","Pull failed",{},e),{success:!1,error:`Pull failed: ${e.message}`}}}Zc();zi();var Dl=class extends gr{constructor(r){super();this.settingsManager=r}setupRoutes(r){r.get("/api/settings",this.handleGetSettings.bind(this)),r.post("/api/settings",this.handleUpdateSettings.bind(this)),r.get("/api/mcp/status",this.handleGetMcpStatus.bind(this)),r.post("/api/mcp/toggle",this.handleToggleMcp.bind(this)),r.get("/api/branch/status",this.handleGetBranchStatus.bind(this)),r.post("/api/branch/switch",this.handleSwitchBranch.bind(this)),r.post("/api/branch/update",this.handleUpdateBranch.bind(this))}handleGetSettings=this.wrapHandler((r,t)=>{let s=Wn.default.join((0,Rf.homedir)(),".claude-mem","settings.json"),i=Br.loadFromFile(s);t.json(i)});handleUpdateSettings=this.wrapHandler((r,t)=>{if(r.body.CLAUDE_MEM_CONTEXT_OBSERVATIONS){let l=parseInt(r.body.CLAUDE_MEM_CONTEXT_OBSERVATIONS,10);if(isNaN(l)||l<1||l>200){t.status(400).json({success:!1,error:"CLAUDE_MEM_CONTEXT_OBSERVATIONS must be between 1 and 200"});return}}if(r.body.CLAUDE_MEM_WORKER_PORT){let l=parseInt(r.body.CLAUDE_MEM_WORKER_PORT,10);if(isNaN(l)||l<1024||l>65535){t.status(400).json({success:!1,error:"CLAUDE_MEM_WORKER_PORT must be between 1024 and 65535"});return}}let s=this.validateContextSettings(r.body);if(!s.valid){t.status(400).json({success:!1,error:s.error});return}let i=Wn.default.join((0,Rf.homedir)(),".claude-mem","settings.json"),n={env:{}};if((0,br.existsSync)(i)){let l=(0,br.readFileSync)(i,"utf-8");n=JSON.parse(l),n.env||(n.env={})}let o=["CLAUDE_MEM_MODEL","CLAUDE_MEM_CONTEXT_OBSERVATIONS","CLAUDE_MEM_WORKER_PORT","CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT","CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES","CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS","CLAUDE_MEM_CONTEXT_FULL_COUNT","CLAUDE_MEM_CONTEXT_FULL_FIELD","CLAUDE_MEM_CONTEXT_SESSION_COUNT","CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY","CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE"];for(let l of o)r.body[l]!==void 0&&(n.env[l]=r.body[l]);(0,br.writeFileSync)(i,JSON.stringify(n,null,2),"utf-8"),W.info("WORKER","Settings updated"),t.json({success:!0,message:"Settings updated successfully"})});handleGetMcpStatus=this.wrapHandler((r,t)=>{let s=this.isMcpEnabled();t.json({enabled:s})});handleToggleMcp=this.wrapHandler((r,t)=>{let{enabled:s}=r.body;if(typeof s!="boolean"){this.badRequest(t,"enabled must be a boolean");return}this.toggleMcp(s),t.json({success:!0,enabled:this.isMcpEnabled()})});handleGetBranchStatus=this.wrapHandler((r,t)=>{let s=Nl();t.json(s)});handleSwitchBranch=this.wrapHandler(async(r,t)=>{let{branch:s}=r.body;if(!s){t.status(400).json({success:!1,error:"Missing branch parameter"});return}let i=["main","beta/7.0"];if(!i.includes(s)){t.status(400).json({success:!1,error:`Invalid branch. Allowed: ${i.join(", ")}`});return}W.info("WORKER","Branch switch requested",{branch:s});let n=await zw(s);n.success&&setTimeout(()=>{W.info("WORKER","Restarting worker after branch switch"),process.exit(0)},1e3),t.json(n)});handleUpdateBranch=this.wrapHandler(async(r,t)=>{W.info("WORKER","Branch update requested");let s=await Bw();s.success&&setTimeout(()=>{W.info("WORKER","Restarting worker after branch update"),process.exit(0)},1e3),t.json(s)});validateContextSettings(r){let t=["CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT","CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT","CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY","CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE"];for(let s of t)if(r[s]&&!["true","false"].includes(r[s]))return{valid:!1,error:`${s} must be "true" or "false"`};if(r.CLAUDE_MEM_CONTEXT_FULL_COUNT){let s=parseInt(r.CLAUDE_MEM_CONTEXT_FULL_COUNT,10);if(isNaN(s)||s<0||s>20)return{valid:!1,error:"CLAUDE_MEM_CONTEXT_FULL_COUNT must be between 0 and 20"}}if(r.CLAUDE_MEM_CONTEXT_SESSION_COUNT){let s=parseInt(r.CLAUDE_MEM_CONTEXT_SESSION_COUNT,10);if(isNaN(s)||s<1||s>50)return{valid:!1,error:"CLAUDE_MEM_CONTEXT_SESSION_COUNT must be between 1 and 50"}}if(r.CLAUDE_MEM_CONTEXT_FULL_FIELD&&!["narrative","facts"].includes(r.CLAUDE_MEM_CONTEXT_FULL_FIELD))return{valid:!1,error:'CLAUDE_MEM_CONTEXT_FULL_FIELD must be "narrative" or "facts"'};if(r.CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES){let s=r.CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES.split(",").map(i=>i.trim());for(let i of s)if(i&&!Cn.includes(i))return{valid:!1,error:`Invalid observation type: ${i}. Valid types: ${Cn.join(", ")}`}}if(r.CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS){let s=r.CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS.split(",").map(i=>i.trim());for(let i of s)if(i&&!In.includes(i))return{valid:!1,error:`Invalid observation concept: ${i}. Valid concepts: ${In.join(", ")}`}}return{valid:!0}}isMcpEnabled(){let r=sa(),t=Wn.default.join(r,"plugin",".mcp.json");return(0,br.existsSync)(t)}toggleMcp(r){try{let t=sa(),s=Wn.default.join(t,"plugin",".mcp.json"),i=Wn.default.join(t,"plugin",".mcp.json.disabled");r&&(0,br.existsSync)(i)?((0,br.renameSync)(i,s),W.info("WORKER","MCP search server enabled")):!r&&(0,br.existsSync)(s)?((0,br.renameSync)(s,i),W.info("WORKER","MCP search server disabled")):W.debug("WORKER","MCP toggle no-op (already in desired state)",{enabled:r})}catch(t){throw W.failure("WORKER","Failed to toggle MCP",{enabled:r},t),t}}};var $l=class{app;server=null;startTime=Date.now();mcpClient;dbManager;sessionManager;sseBroadcaster;sdkAgent;paginationHelper;settingsManager;sessionEventBroadcaster;viewerRoutes;sessionRoutes;dataRoutes;searchRoutes;settingsRoutes;constructor(){this.app=(0,Hw.default)(),this.dbManager=new tl,this.sessionManager=new rl(this.dbManager),this.sseBroadcaster=new al,this.sdkAgent=new bl(this.dbManager,this.sessionManager),this.paginationHelper=new xl(this.dbManager),this.settingsManager=new _l(this.dbManager),this.sessionEventBroadcaster=new Tl(this.sseBroadcaster,this),this.sessionManager.setOnSessionDeleted(()=>{this.broadcastProcessingStatus()}),this.mcpClient=new Rn({name:"worker-search-proxy",version:"1.0.0"},{capabilities:{}}),this.viewerRoutes=new Rl(this.sseBroadcaster,this.dbManager,this.sessionManager),this.sessionRoutes=new Ol(this.sessionManager,this.dbManager,this.sdkAgent,this.sessionEventBroadcaster,this),this.dataRoutes=new kl(this.paginationHelper,this.dbManager,this.sessionManager,this.sseBroadcaster,this,this.startTime),this.searchRoutes=null,this.settingsRoutes=new Dl(this.settingsManager),this.setupMiddleware(),this.setupRoutes()}setupMiddleware(){Aw(this.summarizeRequestBody.bind(this)).forEach(r=>this.app.use(r))}setupRoutes(){this.app.get("/api/health",(e,r)=>{r.status(200).json({status:"ok"})}),this.viewerRoutes.setupRoutes(this.app),this.sessionRoutes.setupRoutes(this.app),this.dataRoutes.setupRoutes(this.app),this.settingsRoutes.setupRoutes(this.app)}async start(){let e=An();this.server=await new Promise((r,t)=>{let s=this.app.listen(e,()=>r(s));s.on("error",t)}),W.info("SYSTEM","Worker started",{port:e,pid:process.pid}),this.initializeBackground().catch(r=>{W.error("SYSTEM","Background initialization failed",{},r)})}async initializeBackground(){await this.dbManager.initialize();let e=new El,r=new wl,t=new Sl(this.dbManager.getSessionSearch(),this.dbManager.getSessionStore(),this.dbManager.getChromaSync(),e,r);this.searchRoutes=new Al(t),this.searchRoutes.setupRoutes(this.app),W.info("WORKER","SearchManager initialized and search routes registered");let s=Vw.default.join(__dirname,"mcp-server.cjs"),i=new kn({command:"node",args:[s],env:process.env});await this.mcpClient.connect(i),W.success("WORKER","Connected to MCP server")}async shutdown(){if(await this.sessionManager.shutdownAll(),this.mcpClient)try{await this.mcpClient.close(),W.info("SYSTEM","MCP client closed")}catch(e){W.error("SYSTEM","Failed to close MCP client",{},e)}this.server&&await new Promise((e,r)=>{this.server.close(t=>t?r(t):e())}),await this.dbManager.close(),W.info("SYSTEM","Worker shutdown complete")}summarizeRequestBody(e,r,t){return Nw(e,r,t)}broadcastProcessingStatus(){let e=this.sessionManager.isAnySessionProcessing(),r=this.sessionManager.getTotalActiveWork(),t=this.sessionManager.getActiveSessionCount();W.info("WORKER","Broadcasting processing status",{isProcessing:e,queueDepth:r,activeSessions:t}),this.sseBroadcaster.broadcast({type:"processing_status",isProcessing:e,queueDepth:r})}};if(require.main===module||!module.parent){let a=new $l;process.on("SIGTERM",async()=>{W.info("SYSTEM","Received SIGTERM, shutting down gracefully"),await a.shutdown(),process.exit(0)}),process.on("SIGINT",async()=>{W.info("SYSTEM","Received SIGINT, shutting down gracefully"),await a.shutdown(),process.exit(0)}),a.start().catch(e=>{W.failure("SYSTEM","Worker failed to start",{},e),process.exit(1)})}0&&(module.exports={WorkerService}); /*! Bundled license information: depd/index.js: diff --git a/scripts/smart-install.js b/scripts/smart-install.js index 104f9ea1..ee79f332 100644 --- a/scripts/smart-install.js +++ b/scripts/smart-install.js @@ -12,25 +12,25 @@ */ import { existsSync, readFileSync, writeFileSync } from 'fs'; -import { execSync, spawnSync } from 'child_process'; -import { join, dirname } from 'path'; -import { fileURLToPath } from 'url'; +import { execSync, spawnSync, spawn } from 'child_process'; +import { join } from 'path'; import { homedir } from 'os'; +import { createRequire } from 'module'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); +// CRITICAL: Always use marketplace directory for ALL operations +// This script may run from the cache directory (plugin/scripts/) but must +// operate on the marketplace directory where package.json and node_modules live. +// This ensures cross-platform compatibility and avoids cache directory confusion. +const MARKETPLACE_ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack'); -// Plugin root is parent directory of scripts/ -const PLUGIN_ROOT = join(__dirname, '..'); +// Use MARKETPLACE_ROOT for all paths - this script can be deployed anywhere +// but always operates on the marketplace directory +const PLUGIN_ROOT = MARKETPLACE_ROOT; 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'); -// CRITICAL: Always use marketplace directory for PM2/ecosystem -// This ensures cross-platform compatibility and avoids cache directory confusion -const MARKETPLACE_ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack'); - // Colors for output const colors = { reset: '\x1b[0m', @@ -155,8 +155,10 @@ async function verifyNativeModules() { try { log('🔍 Verifying native modules...', colors.dim); - // Try to actually load better-sqlite3 - const { default: Database } = await import('better-sqlite3'); + // CRITICAL: Use createRequire() to resolve from MARKETPLACE_ROOT + // This script may run from cache but must load modules from marketplace's node_modules + const require = createRequire(join(MARKETPLACE_ROOT, 'package.json')); + const Database = require('better-sqlite3'); // Try to create a test in-memory database const db = new Database(':memory:'); @@ -369,32 +371,10 @@ async function main() { } } - // Try to start the PM2 worker after fresh install - try { - log('🚀 Starting worker service...', colors.cyan); - // CRITICAL: Always use marketplace directory for PM2/ecosystem - // This ensures PM2 starts from the correct location regardless of where this script runs from - const localPm2Base = join(MARKETPLACE_ROOT, 'node_modules', '.bin', 'pm2'); - const localPm2Cmd = process.platform === 'win32' ? localPm2Base + '.cmd' : localPm2Base; - const pm2Command = existsSync(localPm2Cmd) ? localPm2Cmd : 'pm2'; - const ecosystemPath = join(MARKETPLACE_ROOT, 'ecosystem.config.cjs'); - - // Using spawnSync with array args to avoid command injection risks - const result = spawnSync(pm2Command, ['start', ecosystemPath], { - cwd: MARKETPLACE_ROOT, - stdio: 'pipe', - encoding: 'utf-8' - }); - if (result.status !== 0) { - throw new Error(result.stderr || 'PM2 start failed'); - } - - log('✅ Worker service started', colors.green); - } catch (error) { - // Worker might already be running or PM2 not available - that's okay - // The ensureWorkerRunning() function will handle auto-start when needed - log('â„šī¸ Worker will start automatically when needed', colors.dim); - } + // NOTE: Worker auto-start disabled in smart-install.js + // The context-hook.js calls ensureWorkerRunning() which handles worker startup + // This avoids potential process management conflicts during plugin initialization + log('✅ Installation complete', colors.green); // Success - dependencies installed (if needed) process.exit(0); diff --git a/src/services/worker-service.ts b/src/services/worker-service.ts index 8804744f..ec9588b8 100644 --- a/src/services/worker-service.ts +++ b/src/services/worker-service.ts @@ -157,7 +157,7 @@ export class WorkerService { logger.info('WORKER', 'SearchManager initialized and search routes registered'); // Connect to MCP server - const mcpServerPath = path.join(__dirname, '..', '..', 'plugin', 'scripts', 'mcp-server.cjs'); + const mcpServerPath = path.join(__dirname, 'mcp-server.cjs'); const transport = new StdioClientTransport({ command: 'node', args: [mcpServerPath], diff --git a/src/shared/worker-utils.ts b/src/shared/worker-utils.ts index 4dc84ef9..90a71701 100644 --- a/src/shared/worker-utils.ts +++ b/src/shared/worker-utils.ts @@ -9,9 +9,10 @@ import { SettingsDefaultsManager } from "../services/worker/settings/SettingsDef const MARKETPLACE_ROOT = path.join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack'); // Named constants for health checks -const HEALTH_CHECK_TIMEOUT_MS = 100; -const WORKER_STARTUP_WAIT_MS = 500; -const WORKER_STARTUP_RETRIES = 10; +// Windows needs longer timeouts due to startup overhead +const HEALTH_CHECK_TIMEOUT_MS = 500; +const WORKER_STARTUP_WAIT_MS = 1000; +const WORKER_STARTUP_RETRIES = 15; /** * Get the worker port number @@ -39,35 +40,56 @@ async function isWorkerHealthy(): Promise { } /** - * Start the worker using PM2 + * Start the worker service + * On Windows: Uses PowerShell Start-Process with hidden window to avoid console flash + * On Unix: Uses PM2 for process management */ async function startWorker(): Promise { try { - // CRITICAL: Always use marketplace directory for ecosystem.config.cjs - // This ensures PM2 starts from the correct location regardless of where hooks run from - const ecosystemPath = path.join(MARKETPLACE_ROOT, 'ecosystem.config.cjs'); + const workerScript = path.join(MARKETPLACE_ROOT, 'plugin', 'scripts', 'worker-service.cjs'); - if (!existsSync(ecosystemPath)) { - throw new Error(`Ecosystem config not found at ${ecosystemPath}`); + if (!existsSync(workerScript)) { + throw new Error(`Worker script not found at ${workerScript}`); } - // Try to use local PM2 from node_modules first, fall back to global PM2 - // On Windows, PM2 executable is pm2.cmd, not pm2 - const localPm2Base = path.join(MARKETPLACE_ROOT, 'node_modules', '.bin', 'pm2'); - const localPm2Cmd = process.platform === 'win32' ? localPm2Base + '.cmd' : localPm2Base; - const pm2Command = existsSync(localPm2Cmd) ? localPm2Cmd : 'pm2'; + if (process.platform === 'win32') { + // On Windows, use PowerShell Start-Process with -WindowStyle Hidden + // This avoids visible console windows that PM2 creates on Windows + const result = spawnSync('powershell.exe', [ + '-NoProfile', + '-NonInteractive', + '-Command', + `Start-Process -FilePath 'node' -ArgumentList '${workerScript}' -WorkingDirectory '${MARKETPLACE_ROOT}' -WindowStyle Hidden` + ], { + cwd: MARKETPLACE_ROOT, + stdio: 'pipe', + encoding: 'utf-8', + windowsHide: true + }); - // Start using PM2 with the ecosystem config - // CRITICAL: Must set cwd to MARKETPLACE_ROOT so PM2 starts from marketplace directory - // Using spawnSync with array args to avoid command injection risks - const result = spawnSync(pm2Command, ['start', ecosystemPath], { - cwd: MARKETPLACE_ROOT, - stdio: 'pipe', - encoding: 'utf-8', - windowsHide: true - }); - if (result.status !== 0) { - throw new Error(result.stderr || 'PM2 start failed'); + if (result.status !== 0) { + throw new Error(result.stderr || 'PowerShell Start-Process failed'); + } + } else { + // On Unix, use PM2 for process management + const ecosystemPath = path.join(MARKETPLACE_ROOT, 'ecosystem.config.cjs'); + + if (!existsSync(ecosystemPath)) { + throw new Error(`Ecosystem config not found at ${ecosystemPath}`); + } + + const localPm2Base = path.join(MARKETPLACE_ROOT, 'node_modules', '.bin', 'pm2'); + const pm2Command = existsSync(localPm2Base) ? localPm2Base : 'pm2'; + + const result = spawnSync(pm2Command, ['start', ecosystemPath], { + cwd: MARKETPLACE_ROOT, + stdio: 'pipe', + encoding: 'utf-8' + }); + + if (result.status !== 0) { + throw new Error(result.stderr || 'PM2 start failed'); + } } // Wait for worker to become healthy