fix(windows): Comprehensive fixes for Windows plugin installation

This PR addresses issue #193 affecting Windows installations of claude-mem.

## Bug 1: Missing ecosystem.config.cjs in packaged plugin

**Problem**: The ecosystem.config.cjs file was not included in the plugin
package, causing PM2 to fail when trying to start the worker from cache.

**Fix**: Added `plugin/ecosystem.config.cjs` with correct path for packaged
structure (`./scripts/worker-service.cjs` instead of `./plugin/scripts/`).

## Bug 2: Incorrect MCP Server Path (src/services/worker-service.ts)

**Problem**: Path `__dirname, '..', '..', 'plugin', 'scripts', 'mcp-server.cjs'`
only worked in dev structure, failed in packaged plugin.

**Error produced**:
```
Error: Cannot find module 'C:\Users\...\claude-mem\plugin\scripts\mcp-server.cjs'
[ERROR] [SYSTEM] Background initialization failed MCP error -32000: Connection closed
```

**Fix**: Changed to `path.join(__dirname, 'mcp-server.cjs')` since mcp-server.cjs
is in the same directory as worker-service.cjs after bundling.

## Bug 3: Missing smart-install.js in plugin package

**Problem**: smart-install.js was referenced in hooks.json but not included
in the plugin/ directory for cache deployment.

**Fix**: Added `plugin/scripts/smart-install.js` that uses `createRequire()`
to resolve modules from MARKETPLACE_ROOT.

## Bug 4: hooks.json incorrect path

**Problem**: Referenced `/../scripts/smart-install.js` but CLAUDE_PLUGIN_ROOT
points to the plugin/ directory.

**Fix**: Changed to `/scripts/smart-install.js`.

## Bug 5: Windows Worker Startup - Visible Console Windows

**Problem**: PM2 ignores windowsHide option on Windows, opening visible
console windows when starting the worker service.

**Fix**: Use PowerShell `Start-Process -WindowStyle Hidden` on Windows while
keeping PM2 for Unix systems (src/shared/worker-utils.ts).

## Additional Improvements

- Increased worker startup timeouts for Windows (500ms health check, 1000ms
  wait between retries, 15 retries = 15s total vs previous 5s)
- Added `windowsHide: true` to root ecosystem.config.cjs for PM2

## Note on Assertion Failure

The Windows libuv assertion failure `!(handle->flags & UV_HANDLE_CLOSING)`
at `src\win\async.c:76` is a known upstream issue in Claude Code (Issue #7579),
triggered by fetch() calls on Windows. This is NOT caused by worker spawning
and cannot be fixed in claude-mem.

Tested on Windows 11 with Node.js v24.

Fixes #193

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kat-bell
2025-12-09 05:00:44 -06:00
parent 679a077f9b
commit 1f2e5f1a9c
14 changed files with 543 additions and 107 deletions
+2
View File
@@ -14,6 +14,8 @@ module.exports = {
{ {
name: 'claude-mem-worker', name: 'claude-mem-worker',
script: './plugin/scripts/worker-service.cjs', script: './plugin/scripts/worker-service.cjs',
// Windows: prevent visible console windows
windowsHide: true,
// INTENTIONAL: Watch mode enables auto-restart on plugin updates // INTENTIONAL: Watch mode enables auto-restart on plugin updates
// //
// Why this is enabled: // Why this is enabled:
+43
View File
@@ -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'
]
}
]
};
+1 -1
View File
@@ -7,7 +7,7 @@
"hooks": [ "hooks": [
{ {
"type": "command", "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 "timeout": 300
}, },
{ {
+6 -6
View File
@@ -1,11 +1,11 @@
#!/usr/bin/env node #!/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;s<I;s++)if(await new Promise(i=>setTimeout(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;t<w;t++)if(await new Promise(o=>setTimeout(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: To start manually, run:
cd ${E} cd ${n}
npx pm2 start ecosystem.config.cjs 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(` 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+)\)?/),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+=` `)[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{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(` `;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: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)})} 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)})}
+3 -3
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env node #!/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;n<L;n++)if(await new Promise(o=>setTimeout(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;t<L;t++)if(await new Promise(r=>setTimeout(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: To start manually, run:
cd ${E} cd ${s}
npx pm2 start ecosystem.config.cjs 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)})}
+10 -10
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node #!/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(e<this.level)return;let n=new Date().toISOString().replace("T"," ").substring(0,23),i=A[e].padEnd(5),p=s.padEnd(6),d="";r?.correlationId?d=`[${r.correlationId}] `:r?.sessionId&&(d=`[session-${r.sessionId}] `);let E="";o!=null&&(this.level===0&&typeof o=="object"?E=` ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,o){if(e<this.level)return;let n=new Date().toISOString().replace("T"," ").substring(0,23),i=A[e].padEnd(5),p=s.padEnd(6),d="";r?.correlationId?d=`[${r.correlationId}] `:r?.sessionId&&(d=`[session-${r.sessionId}] `);let E="";o!=null&&(this.level===0&&typeof o=="object"?E=`
`+JSON.stringify(o,null,2):E=" "+this.formatData(o));let _="";if(r){let{sessionId:S,sdkSessionId:b,correlationId:u,...c}=r;Object.keys(c).length>0&&(_=` {${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 ( CREATE TABLE IF NOT EXISTS schema_versions (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
version INTEGER UNIQUE NOT NULL, 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} WHERE id <= ? ${n}
ORDER BY id DESC ORDER BY id DESC
LIMIT ? LIMIT ?
`,b=` `,R=`
SELECT id, created_at_epoch SELECT id, created_at_epoch
FROM observations FROM observations
WHERE id >= ? ${n} WHERE id >= ? ${n}
ORDER BY id ASC ORDER BY id ASC
LIMIT ? 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 SELECT created_at_epoch
FROM observations FROM observations
WHERE created_at_epoch <= ? ${n} WHERE created_at_epoch <= ? ${n}
ORDER BY created_at_epoch DESC ORDER BY created_at_epoch DESC
LIMIT ? LIMIT ?
`,b=` `,R=`
SELECT created_at_epoch SELECT created_at_epoch
FROM observations FROM observations
WHERE created_at_epoch >= ? ${n} WHERE created_at_epoch >= ? ${n}
ORDER BY created_at_epoch ASC ORDER BY created_at_epoch ASC
LIMIT ? 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 * SELECT *
FROM observations FROM observations
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${n} 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 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")} WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${n.replace("project","s.project")}
ORDER BY up.created_at_epoch ASC 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;o<oe;o++)if(await new Promise(n=>setTimeout(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;e<oe;e++)if(await new Promise(s=>setTimeout(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: To start manually, run:
cd ${h} cd ${g}
npx pm2 start ecosystem.config.cjs 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+=` `)[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(/<private>/g)||[]).length,s=(a.match(/<claude-mem-context>/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(/<claude-mem-context>[\s\S]*?<\/claude-mem-context>/g,"").replace(/<private>[\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(/<private>/g)||[]).length,s=(a.match(/<claude-mem-context>/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(/<claude-mem-context>[\s\S]*?<\/claude-mem-context>/g,"").replace(/<private>[\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)});
+5 -5
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env node #!/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} 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 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(t<this.level)return;let a=new Date().toISOString().replace("T"," ").substring(0,23),f=T[t].padEnd(5),i=e.padEnd(6),u="";n?.correlationId?u=`[${n.correlationId}] `:n?.sessionId&&(u=`[session-${n.sessionId}] `);let O="";s!=null&&(this.level===0&&typeof s=="object"?O=` ${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(t<this.level)return;let a=new Date().toISOString().replace("T"," ").substring(0,23),f=T[t].padEnd(5),i=e.padEnd(6),c="";r?.correlationId?c=`[${r.correlationId}] `:r?.sessionId&&(c=`[session-${r.sessionId}] `);let O="";s!=null&&(this.level===0&&typeof s=="object"?O=`
`+JSON.stringify(s,null,2):O=" "+this.formatData(s));let C="";if(n){let{sessionId:K,sdkSessionId:j,correlationId:V,...A}=n;Object.keys(A).length>0&&(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;s<W;s++)if(await new Promise(a=>setTimeout(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}. `+JSON.stringify(s,null,2):O=" "+this.formatData(s));let d="";if(r){let{sessionId:j,sdkSessionId:K,correlationId:V,...h}=r;Object.keys(h).length>0&&(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;t<W;t++)if(await new Promise(e=>setTimeout(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: To start manually, run:
cd ${E} cd ${u}
npx pm2 start ecosystem.config.cjs 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)});
+389
View File
@@ -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();
+9 -9
View File
@@ -1,16 +1,16 @@
#!/usr/bin/env node #!/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} 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(t<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),a=O[t].padEnd(5),u=e.padEnd(6),l="";r?.correlationId?l=`[${r.correlationId}] `:r?.sessionId&&(l=`[session-${r.sessionId}] `);let g="";s!=null&&(this.level===0&&typeof s=="object"?g=` ${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(t<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),c=O[t].padEnd(5),p=e.padEnd(6),_="";r?.correlationId?_=`[${r.correlationId}] `:r?.sessionId&&(_=`[session-${r.sessionId}] `);let g="";s!=null&&(this.level===0&&typeof s=="object"?g=`
`+JSON.stringify(s,null,2):g=" "+this.formatData(s));let T="";if(r){let{sessionId:G,sdkSessionId:Y,correlationId:J,...C}=r;Object.keys(C).length>0&&(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;s<X;s++)if(await new Promise(i=>setTimeout(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}. `+JSON.stringify(s,null,2):g=" "+this.formatData(s));let d="";if(r){let{sessionId:G,sdkSessionId:Y,correlationId:J,...C}=r;Object.keys(C).length>0&&(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;t<j;t++)if(await new Promise(e=>setTimeout(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: To start manually, run:
cd ${E} cd ${a}
npx pm2 start ecosystem.config.cjs 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(` 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(a=>a.type==="text").map(a=>a.text).join(` `);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){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(` `)}}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(u=>u.type==="text").map(u=>u.text).join(` `);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(/<system-reminder>[\s\S]*?<\/system-reminder>/g,""),s=s.replace(/\n{3,}/g,` `)),s=s.replace(/<system-reminder>[\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)});
+7 -7
View File
@@ -1,11 +1,11 @@
#!/usr/bin/env node #!/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;r<P;r++)if(await new Promise(s=>setTimeout(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;t<I;t++)if(await new Promise(o=>setTimeout(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: To start manually, run:
cd ${E} cd ${n}
npx pm2 start ecosystem.config.cjs 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 \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. 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! Thank you for installing Claude-Mem!
This message was not added to your startup context, so you can continue working as normal. 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<s&&(u=` `),process.exit(3));try{await A();let e=_(),t=W(process.cwd()),o=await fetch(`http://127.0.0.1:${e}/api/context/inject?project=${encodeURIComponent(t)}&colors=true`,{method:"GET",signal:AbortSignal.timeout(5e3)});if(!o.ok)throw new Error(`Worker error ${o.status}`);let a=await o.text(),r=new Date,c=new Date("2025-12-06T00:00:00Z"),s=new Date("2025-12-05T05:00:00Z"),l="";r<s&&(l=`
\u{1F680} \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 \u{1F680} \u{1F680} \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 \u{1F680}
@@ -33,7 +33,7 @@ This message was not added to your startup context, so you can continue working
\u2B50 Your upvote means the world - thank you! \u2B50 Your upvote means the world - thank you!
\u{1F680} \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 \u{1F680} \u{1F680} \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 \u{1F680}
`);let T="";if(o<r){let d=o.getUTCHours()*60+o.getUTCMinutes(),l=Math.floor((d-300+1440)%1440/60),O=o.getUTCDate(),g=o.getUTCMonth(),N=o.getUTCFullYear()===2025&&g===11&&O>=1&&O<=5,D=l>=17&&l<19;N&&D?T=` `);let T="";if(r<c){let g=r.getUTCHours()*60+r.getUTCMinutes(),p=Math.floor((g-300+1440)%1440/60),O=r.getUTCDate(),h=r.getUTCMonth(),N=r.getUTCFullYear()===2025&&h===11&&O>=1&&O<=5,D=p>=17&&p<19;N&&D?T=`
\u{1F534} LIVE NOW: AMA w/ Dev (@thedotmack) until 7pm EST \u{1F534} LIVE NOW: AMA w/ Dev (@thedotmack) until 7pm EST
`:T=` `:T=`
\u2013 LIVE AMA w/ Dev (@thedotmack) Dec 1st\u20135th, 5pm to 7pm EST \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 \u{1F4DD} Claude-Mem Context Loaded
\u2139\uFE0F Note: This appears as stderr but is informational only \u2139\uFE0F Note: This appears as stderr but is informational only
`+i+` `+a+`
\u{1F4A1} New! Wrap all or part of any message with <private> ... </private> to prevent storing sensitive information in your observation history. \u{1F4A1} New! Wrap all or part of any message with <private> ... </private> 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}/ \u{1F4FA} Watch live in browser http://localhost:${e}/
`)}catch(e){console.error(`\u274C Failed to load context display: ${e}`)}process.exit(3); `)}catch(e){console.error(`\u274C Failed to load context display: ${e}`)}process.exit(3);
File diff suppressed because one or more lines are too long
+19 -39
View File
@@ -12,25 +12,25 @@
*/ */
import { existsSync, readFileSync, writeFileSync } from 'fs'; import { existsSync, readFileSync, writeFileSync } from 'fs';
import { execSync, spawnSync } from 'child_process'; import { execSync, spawnSync, spawn } from 'child_process';
import { join, dirname } from 'path'; import { join } from 'path';
import { fileURLToPath } from 'url';
import { homedir } from 'os'; import { homedir } from 'os';
import { createRequire } from 'module';
const __filename = fileURLToPath(import.meta.url); // CRITICAL: Always use marketplace directory for ALL operations
const __dirname = dirname(__filename); // 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/ // Use MARKETPLACE_ROOT for all paths - this script can be deployed anywhere
const PLUGIN_ROOT = join(__dirname, '..'); // but always operates on the marketplace directory
const PLUGIN_ROOT = MARKETPLACE_ROOT;
const PACKAGE_JSON_PATH = join(PLUGIN_ROOT, 'package.json'); const PACKAGE_JSON_PATH = join(PLUGIN_ROOT, 'package.json');
const VERSION_MARKER_PATH = join(PLUGIN_ROOT, '.install-version'); const VERSION_MARKER_PATH = join(PLUGIN_ROOT, '.install-version');
const NODE_MODULES_PATH = join(PLUGIN_ROOT, 'node_modules'); const NODE_MODULES_PATH = join(PLUGIN_ROOT, 'node_modules');
const BETTER_SQLITE3_PATH = join(NODE_MODULES_PATH, 'better-sqlite3'); 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 // Colors for output
const colors = { const colors = {
reset: '\x1b[0m', reset: '\x1b[0m',
@@ -155,8 +155,10 @@ async function verifyNativeModules() {
try { try {
log('🔍 Verifying native modules...', colors.dim); log('🔍 Verifying native modules...', colors.dim);
// Try to actually load better-sqlite3 // CRITICAL: Use createRequire() to resolve from MARKETPLACE_ROOT
const { default: Database } = await import('better-sqlite3'); // 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 // Try to create a test in-memory database
const db = new Database(':memory:'); const db = new Database(':memory:');
@@ -369,32 +371,10 @@ async function main() {
} }
} }
// Try to start the PM2 worker after fresh install // NOTE: Worker auto-start disabled in smart-install.js
try { // The context-hook.js calls ensureWorkerRunning() which handles worker startup
log('🚀 Starting worker service...', colors.cyan); // This avoids potential process management conflicts during plugin initialization
// CRITICAL: Always use marketplace directory for PM2/ecosystem log('✅ Installation complete', colors.green);
// 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);
}
// Success - dependencies installed (if needed) // Success - dependencies installed (if needed)
process.exit(0); process.exit(0);
+1 -1
View File
@@ -157,7 +157,7 @@ export class WorkerService {
logger.info('WORKER', 'SearchManager initialized and search routes registered'); logger.info('WORKER', 'SearchManager initialized and search routes registered');
// Connect to MCP server // 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({ const transport = new StdioClientTransport({
command: 'node', command: 'node',
args: [mcpServerPath], args: [mcpServerPath],
+47 -25
View File
@@ -9,9 +9,10 @@ import { SettingsDefaultsManager } from "../services/worker/settings/SettingsDef
const MARKETPLACE_ROOT = path.join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack'); const MARKETPLACE_ROOT = path.join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
// Named constants for health checks // Named constants for health checks
const HEALTH_CHECK_TIMEOUT_MS = 100; // Windows needs longer timeouts due to startup overhead
const WORKER_STARTUP_WAIT_MS = 500; const HEALTH_CHECK_TIMEOUT_MS = 500;
const WORKER_STARTUP_RETRIES = 10; const WORKER_STARTUP_WAIT_MS = 1000;
const WORKER_STARTUP_RETRIES = 15;
/** /**
* Get the worker port number * Get the worker port number
@@ -39,35 +40,56 @@ async function isWorkerHealthy(): Promise<boolean> {
} }
/** /**
* 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<boolean> { async function startWorker(): Promise<boolean> {
try { try {
// CRITICAL: Always use marketplace directory for ecosystem.config.cjs const workerScript = path.join(MARKETPLACE_ROOT, 'plugin', 'scripts', 'worker-service.cjs');
// This ensures PM2 starts from the correct location regardless of where hooks run from
const ecosystemPath = path.join(MARKETPLACE_ROOT, 'ecosystem.config.cjs');
if (!existsSync(ecosystemPath)) { if (!existsSync(workerScript)) {
throw new Error(`Ecosystem config not found at ${ecosystemPath}`); throw new Error(`Worker script not found at ${workerScript}`);
} }
// Try to use local PM2 from node_modules first, fall back to global PM2 if (process.platform === 'win32') {
// On Windows, PM2 executable is pm2.cmd, not pm2 // On Windows, use PowerShell Start-Process with -WindowStyle Hidden
const localPm2Base = path.join(MARKETPLACE_ROOT, 'node_modules', '.bin', 'pm2'); // This avoids visible console windows that PM2 creates on Windows
const localPm2Cmd = process.platform === 'win32' ? localPm2Base + '.cmd' : localPm2Base; const result = spawnSync('powershell.exe', [
const pm2Command = existsSync(localPm2Cmd) ? localPm2Cmd : 'pm2'; '-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 if (result.status !== 0) {
// CRITICAL: Must set cwd to MARKETPLACE_ROOT so PM2 starts from marketplace directory throw new Error(result.stderr || 'PowerShell Start-Process failed');
// Using spawnSync with array args to avoid command injection risks }
const result = spawnSync(pm2Command, ['start', ecosystemPath], { } else {
cwd: MARKETPLACE_ROOT, // On Unix, use PM2 for process management
stdio: 'pipe', const ecosystemPath = path.join(MARKETPLACE_ROOT, 'ecosystem.config.cjs');
encoding: 'utf-8',
windowsHide: true if (!existsSync(ecosystemPath)) {
}); throw new Error(`Ecosystem config not found at ${ecosystemPath}`);
if (result.status !== 0) { }
throw new Error(result.stderr || 'PM2 start failed');
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 // Wait for worker to become healthy