diff --git a/plugin/hooks/hooks.json b/plugin/hooks/hooks.json index ff17b567..3850a2ac 100644 --- a/plugin/hooks/hooks.json +++ b/plugin/hooks/hooks.json @@ -4,6 +4,11 @@ "SessionStart": [ { "hooks": [ + { + "type": "command", + "command": "[ ! -d \"${CLAUDE_PLUGIN_ROOT}/scripts/node_modules\" ] && cd \"${CLAUDE_PLUGIN_ROOT}/scripts\" && npm install || true", + "timeout": 60000 + }, { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js", @@ -15,6 +20,11 @@ "UserPromptSubmit": [ { "hooks": [ + { + "type": "command", + "command": "[ ! -d \"${CLAUDE_PLUGIN_ROOT}/scripts/node_modules\" ] && cd \"${CLAUDE_PLUGIN_ROOT}/scripts\" && npm install || true", + "timeout": 60000 + }, { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/new-hook.js", @@ -27,6 +37,11 @@ { "matcher": "*", "hooks": [ + { + "type": "command", + "command": "[ ! -d \"${CLAUDE_PLUGIN_ROOT}/scripts/node_modules\" ] && cd \"${CLAUDE_PLUGIN_ROOT}/scripts\" && npm install || true", + "timeout": 60000 + }, { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/save-hook.js", @@ -38,6 +53,11 @@ "Stop": [ { "hooks": [ + { + "type": "command", + "command": "[ ! -d \"${CLAUDE_PLUGIN_ROOT}/scripts/node_modules\" ] && cd \"${CLAUDE_PLUGIN_ROOT}/scripts\" && npm install || true", + "timeout": 60000 + }, { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/summary-hook.js", @@ -49,6 +69,11 @@ "SessionEnd": [ { "hooks": [ + { + "type": "command", + "command": "[ ! -d \"${CLAUDE_PLUGIN_ROOT}/scripts/node_modules\" ] && cd \"${CLAUDE_PLUGIN_ROOT}/scripts\" && npm install || true", + "timeout": 60000 + }, { "type": "command", "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/cleanup-hook.js", diff --git a/plugin/scripts/cleanup-hook.js b/plugin/scripts/cleanup-hook.js index ed9babf4..1a8ed656 100755 --- a/plugin/scripts/cleanup-hook.js +++ b/plugin/scripts/cleanup-hook.js @@ -1,13 +1,13 @@ #!/usr/bin/env node -var j=Object.defineProperty;var l=(o,e)=>()=>(o&&(e=o(o=0)),e);var G=(o,e)=>{for(var s in e)j(o,s,{get:e[s],enumerable:!0})};import{join as a,dirname as K,basename as ue}from"path";import{homedir as v}from"os";import{existsSync as ge,mkdirSync as Y}from"fs";import{fileURLToPath as q}from"url";function V(){return typeof __dirname<"u"?__dirname:K(q(import.meta.url))}function k(o){Y(o,{recursive:!0})}function C(){return a(J,"..","..")}var J,d,_,be,he,Ne,fe,Re,A,Ie,Oe,Le,T=l(()=>{"use strict";J=V(),d=process.env.CLAUDE_MEM_DATA_DIR||a(v(),".claude-mem"),_=process.env.CLAUDE_CONFIG_DIR||a(v(),".claude"),be=a(d,"archives"),he=a(d,"logs"),Ne=a(d,"trash"),fe=a(d,"backups"),Re=a(d,"settings.json"),A=a(d,"claude-mem.db"),Ie=a(_,"settings.json"),Oe=a(_,"commands"),Le=a(_,"CLAUDE.md")});var g,S,D,y=l(()=>{"use strict";g=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(g||{}),S=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=g[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} -${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e0&&(f=` {${Object.entries(I).map(([H,W])=>`${H}=${W}`).join(", ")}}`)}let R=`[${i}] [${p}] [${c}] ${u}${t}${f}${E}`;e===3?console.error(R):console.log(R)}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`})}},D=new S});import Q from"better-sqlite3";var m,x=l(()=>{"use strict";T();y();m=class{db;constructor(){k(d),this.db=new Q(A),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(` +import F from"better-sqlite3";import{join as a,dirname as U,basename as q}from"path";import{homedir as R}from"os";import{existsSync as z,mkdirSync as w}from"fs";import{fileURLToPath as X}from"url";function M(){return typeof __dirname<"u"?__dirname:U(X(import.meta.url))}var P=M(),d=process.env.CLAUDE_MEM_DATA_DIR||a(R(),".claude-mem"),u=process.env.CLAUDE_CONFIG_DIR||a(R(),".claude"),ee=a(d,"archives"),se=a(d,"logs"),te=a(d,"trash"),re=a(d,"backups"),ne=a(d,"settings.json"),I=a(d,"claude-mem.db"),oe=a(u,"settings.json"),ie=a(u,"commands"),ae=a(u,"CLAUDE.md");function O(o){w(o,{recursive:!0})}function L(){return a(P,"..","..")}var _=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(_||{}),T=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=_[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,t){return`obs-${e}-${t}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} +${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let t=Object.keys(e);return t.length===0?"{}":t.length<=3?JSON.stringify(e):`{${t.length} keys: ${t.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,t){if(!t)return e;try{let s=typeof t=="string"?JSON.parse(t):t;if(e==="Bash"&&s.command){let r=s.command.length>50?s.command.substring(0,50)+"...":s.command;return`${e}(${r})`}if(e==="Read"&&s.file_path){let r=s.file_path.split("/").pop()||s.file_path;return`${e}(${r})`}if(e==="Edit"&&s.file_path){let r=s.file_path.split("/").pop()||s.file_path;return`${e}(${r})`}if(e==="Write"&&s.file_path){let r=s.file_path.split("/").pop()||s.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,t,s,r,n){if(e0&&(h=` {${Object.entries(f).map(([y,x])=>`${y}=${x}`).join(", ")}}`)}let N=`[${i}] [${p}] [${c}] ${m}${s}${h}${E}`;e===3?console.error(N):console.log(N)}debug(e,t,s,r){this.log(0,e,t,s,r)}info(e,t,s,r){this.log(1,e,t,s,r)}warn(e,t,s,r){this.log(2,e,t,s,r)}error(e,t,s,r){this.log(3,e,t,s,r)}dataIn(e,t,s,r){this.info(e,`\u2192 ${t}`,s,r)}dataOut(e,t,s,r){this.info(e,`\u2190 ${t}`,s,r)}success(e,t,s,r){this.info(e,`\u2713 ${t}`,s,r)}failure(e,t,s,r){this.error(e,`\u2717 ${t}`,s,r)}timing(e,t,s,r){this.info(e,`\u23F1 ${t}`,r,{duration:`${s}ms`})}},v=new T;var l=class{db;constructor(){O(d),this.db=new F(I),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(` CREATE TABLE IF NOT EXISTS schema_versions ( id INTEGER PRIMARY KEY, version INTEGER UNIQUE NOT NULL, applied_at TEXT NOT NULL ) - `);let e=this.db.prepare("SELECT version FROM schema_versions ORDER BY version").all();(e.length>0?Math.max(...e.map(t=>t.version)):0)===0&&(console.error("[SessionStore] Initializing fresh database with migration004..."),this.db.exec(` + `);let e=this.db.prepare("SELECT version FROM schema_versions ORDER BY version").all();(e.length>0?Math.max(...e.map(s=>s.version)):0)===0&&(console.error("[SessionStore] Initializing fresh database with migration004..."),this.db.exec(` CREATE TABLE IF NOT EXISTS sdk_sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, claude_session_id TEXT UNIQUE NOT NULL, @@ -99,7 +99,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje ALTER TABLE observations ADD COLUMN concepts TEXT; ALTER TABLE observations ADD COLUMN files_read TEXT; ALTER TABLE observations ADD COLUMN files_modified TEXT; - `),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(8,new Date().toISOString()),console.error("[SessionStore] Successfully added hierarchical fields to observations table")}catch(e){console.error("[SessionStore] Migration error (add hierarchical fields):",e.message)}}makeObservationsTextNullable(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(9))return;let t=this.db.pragma("table_info(observations)").find(r=>r.name==="text");if(!t||t.notnull===0){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(9,new Date().toISOString());return}console.error("[SessionStore] Making observations.text nullable..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(` + `),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(8,new Date().toISOString()),console.error("[SessionStore] Successfully added hierarchical fields to observations table")}catch(e){console.error("[SessionStore] Migration error (add hierarchical fields):",e.message)}}makeObservationsTextNullable(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(9))return;let s=this.db.pragma("table_info(observations)").find(r=>r.name==="text");if(!s||s.notnull===0){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(9,new Date().toISOString());return}console.error("[SessionStore] Making observations.text nullable..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(` CREATE TABLE observations_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, sdk_session_id TEXT NOT NULL, @@ -129,7 +129,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje CREATE INDEX idx_observations_project ON observations(project); CREATE INDEX idx_observations_type ON observations(type); CREATE INDEX idx_observations_created ON observations(created_at_epoch DESC); - `),this.db.exec("COMMIT"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(9,new Date().toISOString()),console.error("[SessionStore] Successfully made observations.text nullable")}catch(r){throw this.db.exec("ROLLBACK"),r}}catch(e){console.error("[SessionStore] Migration error (make text nullable):",e.message)}}getRecentSummaries(e,s=10){return this.db.prepare(` + `),this.db.exec("COMMIT"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(9,new Date().toISOString()),console.error("[SessionStore] Successfully made observations.text nullable")}catch(r){throw this.db.exec("ROLLBACK"),r}}catch(e){console.error("[SessionStore] Migration error (make text nullable):",e.message)}}getRecentSummaries(e,t=10){return this.db.prepare(` SELECT request, investigated, learned, completed, next_steps, files_read, files_edited, notes, prompt_number, created_at @@ -137,13 +137,13 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ? - `).all(e,s)}getRecentObservations(e,s=20){return this.db.prepare(` + `).all(e,t)}getRecentObservations(e,t=20){return this.db.prepare(` SELECT type, text, prompt_number, created_at FROM observations WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ? - `).all(e,s)}getRecentSessionsWithStatus(e,s=3){return this.db.prepare(` + `).all(e,t)}getRecentSessionsWithStatus(e,t=3){return this.db.prepare(` SELECT * FROM ( SELECT s.sdk_session_id, @@ -160,7 +160,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje LIMIT ? ) ORDER BY started_at_epoch ASC - `).all(e,s)}getObservationsForSession(e){return this.db.prepare(` + `).all(e,t)}getObservationsForSession(e){return this.db.prepare(` SELECT title, subtitle, type, prompt_number FROM observations WHERE sdk_session_id = ? @@ -188,11 +188,11 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje FROM sdk_sessions WHERE claude_session_id = ? LIMIT 1 - `).get(e)||null}reactivateSession(e,s){this.db.prepare(` + `).get(e)||null}reactivateSession(e,t){this.db.prepare(` UPDATE sdk_sessions SET status = 'active', user_prompt = ?, worker_port = NULL WHERE id = ? - `).run(s,e)}incrementPromptCounter(e){return this.db.prepare(` + `).run(t,e)}incrementPromptCounter(e){return this.db.prepare(` UPDATE sdk_sessions SET prompt_counter = COALESCE(prompt_counter, 0) + 1 WHERE id = ? @@ -200,44 +200,44 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje SELECT prompt_counter FROM sdk_sessions WHERE id = ? `).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(` SELECT prompt_counter FROM sdk_sessions WHERE id = ? - `).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime();return this.db.prepare(` + `).get(e)?.prompt_counter||0}createSDKSession(e,t,s){let r=new Date,n=r.getTime();return this.db.prepare(` INSERT INTO sdk_sessions (claude_session_id, project, user_prompt, started_at, started_at_epoch, status) VALUES (?, ?, ?, ?, ?, 'active') - `).run(e,s,t,r.toISOString(),n).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(` + `).run(e,t,s,r.toISOString(),n).lastInsertRowid}updateSDKSessionId(e,t){return this.db.prepare(` UPDATE sdk_sessions SET sdk_session_id = ? WHERE id = ? AND sdk_session_id IS NULL - `).run(s,e).changes===0?(D.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(` + `).run(t,e).changes===0?(v.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:t}),!1):!0}setWorkerPort(e,t){this.db.prepare(` UPDATE sdk_sessions SET worker_port = ? WHERE id = ? - `).run(s,e)}getWorkerPort(e){return this.db.prepare(` + `).run(t,e)}getWorkerPort(e){return this.db.prepare(` SELECT worker_port FROM sdk_sessions WHERE id = ? LIMIT 1 - `).get(e)?.worker_port||null}storeObservation(e,s,t,r){let n=new Date,i=n.getTime();this.db.prepare(` + `).get(e)?.worker_port||null}storeObservation(e,t,s,r){let n=new Date,i=n.getTime();this.db.prepare(` INSERT INTO observations (sdk_session_id, project, type, title, subtitle, facts, narrative, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,n.toISOString(),i)}storeSummary(e,s,t,r){let n=new Date,i=n.getTime();this.db.prepare(` + `).run(e,t,s.type,s.title,s.subtitle,JSON.stringify(s.facts),s.narrative,JSON.stringify(s.concepts),JSON.stringify(s.files_read),JSON.stringify(s.files_modified),r||null,n.toISOString(),i)}storeSummary(e,t,s,r){let n=new Date,i=n.getTime();this.db.prepare(` INSERT INTO session_summaries (sdk_session_id, project, request, investigated, learned, completed, next_steps, notes, prompt_number, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,n.toISOString(),i)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(` + `).run(e,t,s.request,s.investigated,s.learned,s.completed,s.next_steps,s.notes,r||null,n.toISOString(),i)}markSessionCompleted(e){let t=new Date,s=t.getTime();this.db.prepare(` UPDATE sdk_sessions SET status = 'completed', completed_at = ?, completed_at_epoch = ? WHERE id = ? - `).run(s.toISOString(),t,e)}markSessionFailed(e){let s=new Date,t=s.getTime();this.db.prepare(` + `).run(t.toISOString(),s,e)}markSessionFailed(e){let t=new Date,s=t.getTime();this.db.prepare(` UPDATE sdk_sessions SET status = 'failed', completed_at = ?, completed_at_epoch = ? WHERE id = ? - `).run(s.toISOString(),t,e)}cleanupOrphanedSessions(){let e=new Date,s=e.getTime();return this.db.prepare(` + `).run(t.toISOString(),s,e)}cleanupOrphanedSessions(){let e=new Date,t=e.getTime();return this.db.prepare(` UPDATE sdk_sessions SET status = 'failed', completed_at = ?, completed_at_epoch = ? WHERE status = 'active' - `).run(e.toISOString(),s).changes}close(){this.db.close()}}});import b from"path";import{existsSync as h}from"fs";import{spawn as z}from"child_process";async function U(){try{return(await fetch(ee,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function w(){try{if(await U())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=C(),e=b.join(o,"plugin","scripts","worker-service.cjs");if(!h(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=b.join(o,"ecosystem.config.cjs"),t=b.join(o,"node_modules",".bin","pm2");if(!h(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!h(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=z(t,["start",s],{detached:!0,stdio:"ignore",cwd:o});r.on("error",n=>{throw new Error(`Failed to spawn PM2: ${n.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let n=0;n<3;n++)if(await new Promise(i=>setTimeout(i,500)),await U())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(o){return console.error(`[claude-mem] Failed to start worker: ${o.message}`),!1}}var Z,ee,X=l(()=>{"use strict";T();Z=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),ee=`http://127.0.0.1:${Z}/health`});var M={};G(M,{cleanupHook:()=>se});async function se(o){try{console.error("[claude-mem cleanup] Hook fired",{input:o?{session_id:o.session_id,cwd:o.cwd,reason:o.reason}:null}),o||(console.log("No input provided - this script is designed to run as a Claude Code SessionEnd hook"),console.log(` -Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string",transcript_path:"string",hook_event_name:"SessionEnd",reason:"exit"},null,2)),process.exit(0));let{session_id:e,reason:s}=o;console.error("[claude-mem cleanup] Searching for active SDK session",{session_id:e,reason:s});let t=await w();t||console.error("[claude-mem cleanup] Worker not available - skipping HTTP cleanup");let r=new m,n=r.findActiveSDKSession(e);if(n||(console.error("[claude-mem cleanup] No active SDK session found",{session_id:e}),r.close(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)),console.error("[claude-mem cleanup] Active SDK session found",{session_id:n.id,sdk_session_id:n.sdk_session_id,project:n.project,worker_port:n.worker_port}),n.worker_port&&t)try{let i=await fetch(`http://127.0.0.1:${n.worker_port}/sessions/${n.id}`,{method:"DELETE",signal:AbortSignal.timeout(5e3)});i.ok?console.error("[claude-mem cleanup] Session deleted successfully via HTTP"):console.error("[claude-mem cleanup] Failed to delete session:",await i.text())}catch(i){console.error("[claude-mem cleanup] HTTP DELETE error:",i.message)}else console.error("[claude-mem cleanup] No worker available or no worker port, skipping HTTP cleanup");try{r.markSessionFailed(n.id),console.error("[claude-mem cleanup] Session marked as failed in database")}catch(i){console.error("[claude-mem cleanup] Failed to mark session as failed:",i)}r.close(),console.error("[claude-mem cleanup] Cleanup completed successfully"),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}catch(e){console.error("[claude-mem cleanup] Unexpected error in hook",{error:e.message,stack:e.stack,name:e.name}),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}}var P=l(()=>{"use strict";x();X()});import{execSync as $}from"child_process";import{existsSync as B}from"fs";import{join as O}from"path";function L(){try{let o=process.env.CLAUDE_PLUGIN_ROOT;if(!o){console.error("[bootstrap] CLAUDE_PLUGIN_ROOT not set, skipping dependency check");return}let e=O(o,"scripts"),s=O(e,"node_modules");if(B(s))return;console.error("[bootstrap] Installing dependencies in plugin/scripts..."),$("npm install",{cwd:e,stdio:"inherit",timeout:6e4}),console.error("[bootstrap] Dependencies installed successfully")}catch(o){console.error("[bootstrap] Failed to install dependencies:",o instanceof Error?o.message:o)}}import{stdin as F}from"process";L();var{cleanupHook:te}=await Promise.resolve().then(()=>(P(),M)),N="";F.on("data",o=>N+=o);F.on("end",async()=>{try{let o=N.trim()?JSON.parse(N):void 0;await te(o)}catch(o){console.error(`[claude-mem cleanup-hook error: ${o.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}}); + `).run(e.toISOString(),t).changes}close(){this.db.close()}};import g from"path";import{existsSync as S}from"fs";import{spawn as H}from"child_process";var W=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),j=`http://127.0.0.1:${W}/health`;async function A(){try{return(await fetch(j,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function k(){try{if(await A())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=L(),e=g.join(o,"plugin","scripts","worker-service.cjs");if(!S(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let t=g.join(o,"ecosystem.config.cjs"),s=g.join(o,"node_modules",".bin","pm2");if(!S(s))throw new Error(`PM2 binary not found at ${s}. This is a bundled dependency - try running: npm install`);if(!S(t))throw new Error(`PM2 ecosystem config not found at ${t}. Plugin installation may be corrupted.`);let r=H(s,["start",t],{detached:!0,stdio:"ignore",cwd:o});r.on("error",n=>{throw new Error(`Failed to spawn PM2: ${n.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let n=0;n<3;n++)if(await new Promise(i=>setTimeout(i,500)),await A())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(o){return console.error(`[claude-mem] Failed to start worker: ${o.message}`),!1}}async function C(o){try{console.error("[claude-mem cleanup] Hook fired",{input:o?{session_id:o.session_id,cwd:o.cwd,reason:o.reason}:null}),o||(console.log("No input provided - this script is designed to run as a Claude Code SessionEnd hook"),console.log(` +Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string",transcript_path:"string",hook_event_name:"SessionEnd",reason:"exit"},null,2)),process.exit(0));let{session_id:e,reason:t}=o;console.error("[claude-mem cleanup] Searching for active SDK session",{session_id:e,reason:t});let s=await k();s||console.error("[claude-mem cleanup] Worker not available - skipping HTTP cleanup");let r=new l,n=r.findActiveSDKSession(e);if(n||(console.error("[claude-mem cleanup] No active SDK session found",{session_id:e}),r.close(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)),console.error("[claude-mem cleanup] Active SDK session found",{session_id:n.id,sdk_session_id:n.sdk_session_id,project:n.project,worker_port:n.worker_port}),n.worker_port&&s)try{let i=await fetch(`http://127.0.0.1:${n.worker_port}/sessions/${n.id}`,{method:"DELETE",signal:AbortSignal.timeout(5e3)});i.ok?console.error("[claude-mem cleanup] Session deleted successfully via HTTP"):console.error("[claude-mem cleanup] Failed to delete session:",await i.text())}catch(i){console.error("[claude-mem cleanup] HTTP DELETE error:",i.message)}else console.error("[claude-mem cleanup] No worker available or no worker port, skipping HTTP cleanup");try{r.markSessionFailed(n.id),console.error("[claude-mem cleanup] Session marked as failed in database")}catch(i){console.error("[claude-mem cleanup] Failed to mark session as failed:",i)}r.close(),console.error("[claude-mem cleanup] Cleanup completed successfully"),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}catch(e){console.error("[claude-mem cleanup] Unexpected error in hook",{error:e.message,stack:e.stack,name:e.name}),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}}import{stdin as D}from"process";var b="";D.on("data",o=>b+=o);D.on("end",async()=>{try{let o=b.trim()?JSON.parse(b):void 0;await C(o)}catch(o){console.error(`[claude-mem cleanup-hook error: ${o.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}}); diff --git a/plugin/scripts/context-hook.js b/plugin/scripts/context-hook.js index e759fa50..42a6cc97 100755 --- a/plugin/scripts/context-hook.js +++ b/plugin/scripts/context-hook.js @@ -1,13 +1,13 @@ #!/usr/bin/env node -var W=Object.defineProperty;var E=(i,e)=>()=>(i&&(e=i(i=0)),e);var H=(i,e)=>{for(var s in e)W(i,s,{get:e[s],enumerable:!0})};import{join as c,dirname as Y,basename as Ee}from"path";import{homedir as v}from"os";import{existsSync as ge,mkdirSync as K}from"fs";import{fileURLToPath as q}from"url";function V(){return typeof __dirname<"u"?__dirname:Y(q(import.meta.url))}function C(i){K(i,{recursive:!0})}function D(){return c(J,"..","..")}var J,u,_,Se,fe,be,Ne,Re,A,Oe,Ie,Le,T=E(()=>{"use strict";J=V(),u=process.env.CLAUDE_MEM_DATA_DIR||c(v(),".claude-mem"),_=process.env.CLAUDE_CONFIG_DIR||c(v(),".claude"),Se=c(u,"archives"),fe=c(u,"logs"),be=c(u,"trash"),Ne=c(u,"backups"),Re=c(u,"settings.json"),A=c(u,"claude-mem.db"),Oe=c(_,"settings.json"),Ie=c(_,"commands"),Le=c(_,"CLAUDE.md")});var g,h,k,y=E(()=>{"use strict";g=(r=>(r[r.DEBUG=0]="DEBUG",r[r.INFO=1]="INFO",r[r.WARN=2]="WARN",r[r.ERROR=3]="ERROR",r[r.SILENT=4]="SILENT",r))(g||{}),h=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=g[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} -${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let n=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${n})`}if(e==="Read"&&t.file_path){let n=t.file_path.split("/").pop()||t.file_path;return`${e}(${n})`}if(e==="Edit"&&t.file_path){let n=t.file_path.split("/").pop()||t.file_path;return`${e}(${n})`}if(e==="Write"&&t.file_path){let n=t.file_path.split("/").pop()||t.file_path;return`${e}(${n})`}return e}catch{return e}}log(e,s,t,n,r){if(e0&&(N=` {${Object.entries(O).map(([$,j])=>`${$}=${j}`).join(", ")}}`)}let R=`[${a}] [${o}] [${d}] ${m}${t}${N}${p}`;e===3?console.error(R):console.log(R)}debug(e,s,t,n){this.log(0,e,s,t,n)}info(e,s,t,n){this.log(1,e,s,t,n)}warn(e,s,t,n){this.log(2,e,s,t,n)}error(e,s,t,n){this.log(3,e,s,t,n)}dataIn(e,s,t,n){this.info(e,`\u2192 ${s}`,t,n)}dataOut(e,s,t,n){this.info(e,`\u2190 ${s}`,t,n)}success(e,s,t,n){this.info(e,`\u2713 ${s}`,t,n)}failure(e,s,t,n){this.error(e,`\u2717 ${s}`,t,n)}timing(e,s,t,n){this.info(e,`\u23F1 ${s}`,n,{duration:`${t}ms`})}},k=new h});import Q from"better-sqlite3";var l,x=E(()=>{"use strict";T();y();l=class{db;constructor(){C(u),this.db=new Q(A),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(` +import W from"path";import F from"better-sqlite3";import{join as p,dirname as x,basename as q}from"path";import{homedir as I}from"os";import{existsSync as z,mkdirSync as U}from"fs";import{fileURLToPath as X}from"url";function w(){return typeof __dirname<"u"?__dirname:x(X(import.meta.url))}var M=w(),u=process.env.CLAUDE_MEM_DATA_DIR||p(I(),".claude-mem"),_=process.env.CLAUDE_CONFIG_DIR||p(I(),".claude"),ee=p(u,"archives"),se=p(u,"logs"),te=p(u,"trash"),re=p(u,"backups"),ne=p(u,"settings.json"),O=p(u,"claude-mem.db"),oe=p(_,"settings.json"),ie=p(_,"commands"),ae=p(_,"CLAUDE.md");function L(a){U(a,{recursive:!0})}function v(){return p(M,"..","..")}var l=(r=>(r[r.DEBUG=0]="DEBUG",r[r.INFO=1]="INFO",r[r.WARN=2]="WARN",r[r.ERROR=3]="ERROR",r[r.SILENT=4]="SILENT",r))(l||{}),T=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=l[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,t){return`obs-${e}-${t}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} +${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let t=Object.keys(e);return t.length===0?"{}":t.length<=3?JSON.stringify(e):`{${t.length} keys: ${t.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,t){if(!t)return e;try{let s=typeof t=="string"?JSON.parse(t):t;if(e==="Bash"&&s.command){let n=s.command.length>50?s.command.substring(0,50)+"...":s.command;return`${e}(${n})`}if(e==="Read"&&s.file_path){let n=s.file_path.split("/").pop()||s.file_path;return`${e}(${n})`}if(e==="Edit"&&s.file_path){let n=s.file_path.split("/").pop()||s.file_path;return`${e}(${n})`}if(e==="Write"&&s.file_path){let n=s.file_path.split("/").pop()||s.file_path;return`${e}(${n})`}return e}catch{return e}}log(e,t,s,n,r){if(e0&&(b=` {${Object.entries(R).map(([D,y])=>`${D}=${y}`).join(", ")}}`)}let N=`[${i}] [${o}] [${d}] ${m}${s}${b}${c}`;e===3?console.error(N):console.log(N)}debug(e,t,s,n){this.log(0,e,t,s,n)}info(e,t,s,n){this.log(1,e,t,s,n)}warn(e,t,s,n){this.log(2,e,t,s,n)}error(e,t,s,n){this.log(3,e,t,s,n)}dataIn(e,t,s,n){this.info(e,`\u2192 ${t}`,s,n)}dataOut(e,t,s,n){this.info(e,`\u2190 ${t}`,s,n)}success(e,t,s,n){this.info(e,`\u2713 ${t}`,s,n)}failure(e,t,s,n){this.error(e,`\u2717 ${t}`,s,n)}timing(e,t,s,n){this.info(e,`\u23F1 ${t}`,n,{duration:`${s}ms`})}},A=new T;var E=class{db;constructor(){L(u),this.db=new F(O),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(` CREATE TABLE IF NOT EXISTS schema_versions ( id INTEGER PRIMARY KEY, version INTEGER UNIQUE NOT NULL, applied_at TEXT NOT NULL ) - `);let e=this.db.prepare("SELECT version FROM schema_versions ORDER BY version").all();(e.length>0?Math.max(...e.map(t=>t.version)):0)===0&&(console.error("[SessionStore] Initializing fresh database with migration004..."),this.db.exec(` + `);let e=this.db.prepare("SELECT version FROM schema_versions ORDER BY version").all();(e.length>0?Math.max(...e.map(s=>s.version)):0)===0&&(console.error("[SessionStore] Initializing fresh database with migration004..."),this.db.exec(` CREATE TABLE IF NOT EXISTS sdk_sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, claude_session_id TEXT UNIQUE NOT NULL, @@ -99,7 +99,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje ALTER TABLE observations ADD COLUMN concepts TEXT; ALTER TABLE observations ADD COLUMN files_read TEXT; ALTER TABLE observations ADD COLUMN files_modified TEXT; - `),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(8,new Date().toISOString()),console.error("[SessionStore] Successfully added hierarchical fields to observations table")}catch(e){console.error("[SessionStore] Migration error (add hierarchical fields):",e.message)}}makeObservationsTextNullable(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(9))return;let t=this.db.pragma("table_info(observations)").find(n=>n.name==="text");if(!t||t.notnull===0){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(9,new Date().toISOString());return}console.error("[SessionStore] Making observations.text nullable..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(` + `),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(8,new Date().toISOString()),console.error("[SessionStore] Successfully added hierarchical fields to observations table")}catch(e){console.error("[SessionStore] Migration error (add hierarchical fields):",e.message)}}makeObservationsTextNullable(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(9))return;let s=this.db.pragma("table_info(observations)").find(n=>n.name==="text");if(!s||s.notnull===0){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(9,new Date().toISOString());return}console.error("[SessionStore] Making observations.text nullable..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(` CREATE TABLE observations_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, sdk_session_id TEXT NOT NULL, @@ -129,7 +129,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje CREATE INDEX idx_observations_project ON observations(project); CREATE INDEX idx_observations_type ON observations(type); CREATE INDEX idx_observations_created ON observations(created_at_epoch DESC); - `),this.db.exec("COMMIT"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(9,new Date().toISOString()),console.error("[SessionStore] Successfully made observations.text nullable")}catch(n){throw this.db.exec("ROLLBACK"),n}}catch(e){console.error("[SessionStore] Migration error (make text nullable):",e.message)}}getRecentSummaries(e,s=10){return this.db.prepare(` + `),this.db.exec("COMMIT"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(9,new Date().toISOString()),console.error("[SessionStore] Successfully made observations.text nullable")}catch(n){throw this.db.exec("ROLLBACK"),n}}catch(e){console.error("[SessionStore] Migration error (make text nullable):",e.message)}}getRecentSummaries(e,t=10){return this.db.prepare(` SELECT request, investigated, learned, completed, next_steps, files_read, files_edited, notes, prompt_number, created_at @@ -137,13 +137,13 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ? - `).all(e,s)}getRecentObservations(e,s=20){return this.db.prepare(` + `).all(e,t)}getRecentObservations(e,t=20){return this.db.prepare(` SELECT type, text, prompt_number, created_at FROM observations WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ? - `).all(e,s)}getRecentSessionsWithStatus(e,s=3){return this.db.prepare(` + `).all(e,t)}getRecentSessionsWithStatus(e,t=3){return this.db.prepare(` SELECT * FROM ( SELECT s.sdk_session_id, @@ -160,7 +160,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje LIMIT ? ) ORDER BY started_at_epoch ASC - `).all(e,s)}getObservationsForSession(e){return this.db.prepare(` + `).all(e,t)}getObservationsForSession(e){return this.db.prepare(` SELECT title, subtitle, type, prompt_number FROM observations WHERE sdk_session_id = ? @@ -188,11 +188,11 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje FROM sdk_sessions WHERE claude_session_id = ? LIMIT 1 - `).get(e)||null}reactivateSession(e,s){this.db.prepare(` + `).get(e)||null}reactivateSession(e,t){this.db.prepare(` UPDATE sdk_sessions SET status = 'active', user_prompt = ?, worker_port = NULL WHERE id = ? - `).run(s,e)}incrementPromptCounter(e){return this.db.prepare(` + `).run(t,e)}incrementPromptCounter(e){return this.db.prepare(` UPDATE sdk_sessions SET prompt_counter = COALESCE(prompt_counter, 0) + 1 WHERE id = ? @@ -200,46 +200,46 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje SELECT prompt_counter FROM sdk_sessions WHERE id = ? `).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(` SELECT prompt_counter FROM sdk_sessions WHERE id = ? - `).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let n=new Date,r=n.getTime();return this.db.prepare(` + `).get(e)?.prompt_counter||0}createSDKSession(e,t,s){let n=new Date,r=n.getTime();return this.db.prepare(` INSERT INTO sdk_sessions (claude_session_id, project, user_prompt, started_at, started_at_epoch, status) VALUES (?, ?, ?, ?, ?, 'active') - `).run(e,s,t,n.toISOString(),r).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(` + `).run(e,t,s,n.toISOString(),r).lastInsertRowid}updateSDKSessionId(e,t){return this.db.prepare(` UPDATE sdk_sessions SET sdk_session_id = ? WHERE id = ? AND sdk_session_id IS NULL - `).run(s,e).changes===0?(k.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(` + `).run(t,e).changes===0?(A.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:t}),!1):!0}setWorkerPort(e,t){this.db.prepare(` UPDATE sdk_sessions SET worker_port = ? WHERE id = ? - `).run(s,e)}getWorkerPort(e){return this.db.prepare(` + `).run(t,e)}getWorkerPort(e){return this.db.prepare(` SELECT worker_port FROM sdk_sessions WHERE id = ? LIMIT 1 - `).get(e)?.worker_port||null}storeObservation(e,s,t,n){let r=new Date,a=r.getTime();this.db.prepare(` + `).get(e)?.worker_port||null}storeObservation(e,t,s,n){let r=new Date,i=r.getTime();this.db.prepare(` INSERT INTO observations (sdk_session_id, project, type, title, subtitle, facts, narrative, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),n||null,r.toISOString(),a)}storeSummary(e,s,t,n){let r=new Date,a=r.getTime();this.db.prepare(` + `).run(e,t,s.type,s.title,s.subtitle,JSON.stringify(s.facts),s.narrative,JSON.stringify(s.concepts),JSON.stringify(s.files_read),JSON.stringify(s.files_modified),n||null,r.toISOString(),i)}storeSummary(e,t,s,n){let r=new Date,i=r.getTime();this.db.prepare(` INSERT INTO session_summaries (sdk_session_id, project, request, investigated, learned, completed, next_steps, notes, prompt_number, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,n||null,r.toISOString(),a)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(` + `).run(e,t,s.request,s.investigated,s.learned,s.completed,s.next_steps,s.notes,n||null,r.toISOString(),i)}markSessionCompleted(e){let t=new Date,s=t.getTime();this.db.prepare(` UPDATE sdk_sessions SET status = 'completed', completed_at = ?, completed_at_epoch = ? WHERE id = ? - `).run(s.toISOString(),t,e)}markSessionFailed(e){let s=new Date,t=s.getTime();this.db.prepare(` + `).run(t.toISOString(),s,e)}markSessionFailed(e){let t=new Date,s=t.getTime();this.db.prepare(` UPDATE sdk_sessions SET status = 'failed', completed_at = ?, completed_at_epoch = ? WHERE id = ? - `).run(s.toISOString(),t,e)}cleanupOrphanedSessions(){let e=new Date,s=e.getTime();return this.db.prepare(` + `).run(t.toISOString(),s,e)}cleanupOrphanedSessions(){let e=new Date,t=e.getTime();return this.db.prepare(` UPDATE sdk_sessions SET status = 'failed', completed_at = ?, completed_at_epoch = ? WHERE status = 'active' - `).run(e.toISOString(),s).changes}close(){this.db.close()}}});import S from"path";import{existsSync as f}from"fs";import{spawn as z}from"child_process";async function U(){try{return(await fetch(ee,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function w(){try{if(await U())return!0;console.error("[claude-mem] Worker not responding, starting...");let i=D(),e=S.join(i,"plugin","scripts","worker-service.cjs");if(!f(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=S.join(i,"ecosystem.config.cjs"),t=S.join(i,"node_modules",".bin","pm2");if(!f(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!f(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let n=z(t,["start",s],{detached:!0,stdio:"ignore",cwd:i});n.on("error",r=>{throw new Error(`Failed to spawn PM2: ${r.message}`)}),n.unref(),console.error("[claude-mem] Worker started with PM2");for(let r=0;r<3;r++)if(await new Promise(a=>setTimeout(a,500)),await U())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(i){return console.error(`[claude-mem] Failed to start worker: ${i.message}`),!1}}var Z,ee,X=E(()=>{"use strict";T();Z=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),ee=`http://127.0.0.1:${Z}/health`});var M={};H(M,{contextHook:()=>te});import se from"path";function te(i){w();let e=i?.cwd??process.cwd(),s=e?se.basename(e):"unknown-project",t=new l;try{let n=t.getRecentSessionsWithStatus(s,3);if(n.length===0)return`# Recent Session Context + `).run(e.toISOString(),t).changes}close(){this.db.close()}};import g from"path";import{existsSync as h}from"fs";import{spawn as P}from"child_process";var $=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),j=`http://127.0.0.1:${$}/health`;async function C(){try{return(await fetch(j,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function k(){try{if(await C())return!0;console.error("[claude-mem] Worker not responding, starting...");let a=v(),e=g.join(a,"plugin","scripts","worker-service.cjs");if(!h(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let t=g.join(a,"ecosystem.config.cjs"),s=g.join(a,"node_modules",".bin","pm2");if(!h(s))throw new Error(`PM2 binary not found at ${s}. This is a bundled dependency - try running: npm install`);if(!h(t))throw new Error(`PM2 ecosystem config not found at ${t}. Plugin installation may be corrupted.`);let n=P(s,["start",t],{detached:!0,stdio:"ignore",cwd:a});n.on("error",r=>{throw new Error(`Failed to spawn PM2: ${r.message}`)}),n.unref(),console.error("[claude-mem] Worker started with PM2");for(let r=0;r<3;r++)if(await new Promise(i=>setTimeout(i,500)),await C())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(a){return console.error(`[claude-mem] Failed to start worker: ${a.message}`),!1}}function S(a){k();let e=a?.cwd??process.cwd(),t=e?W.basename(e):"unknown-project",s=new E;try{let n=s.getRecentSessionsWithStatus(t,3);if(n.length===0)return`# Recent Session Context -No previous sessions found for this project yet.`;let r=[];r.push("# Recent Session Context"),r.push(""),r.push(`Showing last ${n.length} session(s) for **${s}**:`),r.push("");for(let a of n)if(a.sdk_session_id){if(r.push("---"),r.push(""),a.has_summary){let o=t.getSummaryForSession(a.sdk_session_id);if(o){let d=o.prompt_number?` (Prompt #${o.prompt_number})`:"";if(r.push(`**Summary${d}**`),r.push(""),o.request&&r.push(`**Request:** ${o.request}`),o.completed&&r.push(`**Completed:** ${o.completed}`),o.learned&&r.push(`**Learned:** ${o.learned}`),o.next_steps&&r.push(`**Next Steps:** ${o.next_steps}`),o.files_read)try{let p=JSON.parse(o.files_read);Array.isArray(p)&&p.length>0&&r.push(`**Files Read:** ${p.join(", ")}`)}catch{o.files_read.trim()&&r.push(`**Files Read:** ${o.files_read}`)}if(o.files_edited)try{let p=JSON.parse(o.files_edited);Array.isArray(p)&&p.length>0&&r.push(`**Files Edited:** ${p.join(", ")}`)}catch{o.files_edited.trim()&&r.push(`**Files Edited:** ${o.files_edited}`)}let m=new Date(o.created_at).toLocaleString();r.push(`**Date:** ${m}`)}}else if(a.status==="active"){r.push("**In Progress**"),r.push(""),a.user_prompt&&r.push(`**Request:** ${a.user_prompt}`);let o=t.getObservationsForSession(a.sdk_session_id);if(o.length>0){r.push(""),r.push(`**Observations (${o.length}):**`);for(let m of o)r.push(`- ${m.title}`)}else r.push(""),r.push("*No observations yet*");r.push(""),r.push("**Status:** Active - summary pending");let d=new Date(a.started_at).toLocaleString();r.push(`**Date:** ${d}`)}else{let o=a.status==="failed"?"stopped":a.status;r.push(`**${o.charAt(0).toUpperCase()+o.slice(1)}**`),r.push(""),a.user_prompt&&r.push(`**Request:** ${a.user_prompt}`),r.push(""),r.push(`**Status:** ${o} - no summary available`);let d=new Date(a.started_at).toLocaleString();r.push(`**Date:** ${d}`)}r.push("")}return r.join(` -`)}finally{t.close()}}var F=E(()=>{"use strict";x();X()});import{execSync as G}from"child_process";import{existsSync as B}from"fs";import{join as I}from"path";function L(){try{let i=process.env.CLAUDE_PLUGIN_ROOT;if(!i){console.error("[bootstrap] CLAUDE_PLUGIN_ROOT not set, skipping dependency check");return}let e=I(i,"scripts"),s=I(e,"node_modules");if(B(s))return;console.error("[bootstrap] Installing dependencies in plugin/scripts..."),G("npm install",{cwd:e,stdio:"inherit",timeout:6e4}),console.error("[bootstrap] Dependencies installed successfully")}catch(i){console.error("[bootstrap] Failed to install dependencies:",i instanceof Error?i.message:i)}}import{stdin as b}from"process";L();var{contextHook:P}=await Promise.resolve().then(()=>(F(),M));try{if(b.isTTY){let e={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:P()}};console.log(JSON.stringify(e)),process.exit(0)}else{let i="";b.on("data",e=>i+=e),b.on("end",()=>{let e=i.trim()?JSON.parse(i):void 0,t={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:P(e)}};console.log(JSON.stringify(t)),process.exit(0)})}}catch(i){console.error(`[claude-mem context-hook error: ${i.message}]`),process.exit(0)} +No previous sessions found for this project yet.`;let r=[];r.push("# Recent Session Context"),r.push(""),r.push(`Showing last ${n.length} session(s) for **${t}**:`),r.push("");for(let i of n)if(i.sdk_session_id){if(r.push("---"),r.push(""),i.has_summary){let o=s.getSummaryForSession(i.sdk_session_id);if(o){let d=o.prompt_number?` (Prompt #${o.prompt_number})`:"";if(r.push(`**Summary${d}**`),r.push(""),o.request&&r.push(`**Request:** ${o.request}`),o.completed&&r.push(`**Completed:** ${o.completed}`),o.learned&&r.push(`**Learned:** ${o.learned}`),o.next_steps&&r.push(`**Next Steps:** ${o.next_steps}`),o.files_read)try{let c=JSON.parse(o.files_read);Array.isArray(c)&&c.length>0&&r.push(`**Files Read:** ${c.join(", ")}`)}catch{o.files_read.trim()&&r.push(`**Files Read:** ${o.files_read}`)}if(o.files_edited)try{let c=JSON.parse(o.files_edited);Array.isArray(c)&&c.length>0&&r.push(`**Files Edited:** ${c.join(", ")}`)}catch{o.files_edited.trim()&&r.push(`**Files Edited:** ${o.files_edited}`)}let m=new Date(o.created_at).toLocaleString();r.push(`**Date:** ${m}`)}}else if(i.status==="active"){r.push("**In Progress**"),r.push(""),i.user_prompt&&r.push(`**Request:** ${i.user_prompt}`);let o=s.getObservationsForSession(i.sdk_session_id);if(o.length>0){r.push(""),r.push(`**Observations (${o.length}):**`);for(let m of o)r.push(`- ${m.title}`)}else r.push(""),r.push("*No observations yet*");r.push(""),r.push("**Status:** Active - summary pending");let d=new Date(i.started_at).toLocaleString();r.push(`**Date:** ${d}`)}else{let o=i.status==="failed"?"stopped":i.status;r.push(`**${o.charAt(0).toUpperCase()+o.slice(1)}**`),r.push(""),i.user_prompt&&r.push(`**Request:** ${i.user_prompt}`),r.push(""),r.push(`**Status:** ${o} - no summary available`);let d=new Date(i.started_at).toLocaleString();r.push(`**Date:** ${d}`)}r.push("")}return r.join(` +`)}finally{s.close()}}import{stdin as f}from"process";try{if(f.isTTY){let e={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:S()}};console.log(JSON.stringify(e)),process.exit(0)}else{let a="";f.on("data",e=>a+=e),f.on("end",()=>{let e=a.trim()?JSON.parse(a):void 0,s={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:S(e)}};console.log(JSON.stringify(s)),process.exit(0)})}}catch(a){console.error(`[claude-mem context-hook error: ${a.message}]`),process.exit(0)} diff --git a/plugin/scripts/new-hook.js b/plugin/scripts/new-hook.js index d1a594de..ecbf2b97 100755 --- a/plugin/scripts/new-hook.js +++ b/plugin/scripts/new-hook.js @@ -1,7 +1,7 @@ #!/usr/bin/env node -var K=Object.defineProperty;var l=(n,e)=>()=>(n&&(e=n(n=0)),e);var Y=(n,e)=>{for(var s in e)K(n,s,{get:e[s],enumerable:!0})};import{join as d,dirname as J,basename as Se}from"path";import{homedir as v}from"os";import{existsSync as he,mkdirSync as Q}from"fs";import{fileURLToPath as z}from"url";function Z(){return typeof __dirname<"u"?__dirname:J(z(import.meta.url))}function k(n){Q(n,{recursive:!0})}function C(){return d(ee,"..","..")}var ee,m,g,Oe,Ie,Le,ve,Ae,A,ke,Ce,De,S=l(()=>{"use strict";ee=Z(),m=process.env.CLAUDE_MEM_DATA_DIR||d(v(),".claude-mem"),g=process.env.CLAUDE_CONFIG_DIR||d(v(),".claude"),Oe=d(m,"archives"),Ie=d(m,"logs"),Le=d(m,"trash"),ve=d(m,"backups"),Ae=d(m,"settings.json"),A=d(m,"claude-mem.db"),ke=d(g,"settings.json"),Ce=d(g,"commands"),De=d(g,"CLAUDE.md")});var b,f,D,y=l(()=>{"use strict";b=(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))(b||{}),f=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=b[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} -${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,o){if(e0&&(p=` {${Object.entries(O).map(([G,B])=>`${G}=${B}`).join(", ")}}`)}let u=`[${a}] [${c}] [${i}] ${E}${t}${p}${_}`;e===3?console.error(u):console.log(u)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},D=new f});import se from"better-sqlite3";var T,x=l(()=>{"use strict";S();y();T=class{db;constructor(){k(m),this.db=new se(A),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(` +import K from"path";import W from"better-sqlite3";import{join as d,dirname as P,basename as z}from"path";import{homedir as R}from"os";import{existsSync as te,mkdirSync as M}from"fs";import{fileURLToPath as F}from"url";function H(){return typeof __dirname<"u"?__dirname:P(F(import.meta.url))}var $=H(),m=process.env.CLAUDE_MEM_DATA_DIR||d(R(),".claude-mem"),T=process.env.CLAUDE_CONFIG_DIR||d(R(),".claude"),ne=d(m,"archives"),oe=d(m,"logs"),ie=d(m,"trash"),ae=d(m,"backups"),de=d(m,"settings.json"),O=d(m,"claude-mem.db"),pe=d(T,"settings.json"),ce=d(T,"commands"),ue=d(T,"CLAUDE.md");function I(o){M(o,{recursive:!0})}function L(){return d($,"..","..")}var g=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(g||{}),S=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=g[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} +${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e0&&(p=` {${Object.entries(h).map(([w,X])=>`${w}=${X}`).join(", ")}}`)}let u=`[${a}] [${c}] [${i}] ${E}${t}${p}${l}`;e===3?console.error(u):console.log(u)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},v=new S;var _=class{db;constructor(){I(m),this.db=new W(O),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(` CREATE TABLE IF NOT EXISTS schema_versions ( id INTEGER PRIMARY KEY, version INTEGER UNIQUE NOT NULL, @@ -200,15 +200,15 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje SELECT prompt_counter FROM sdk_sessions WHERE id = ? `).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(` SELECT prompt_counter FROM sdk_sessions WHERE id = ? - `).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,o=r.getTime();return this.db.prepare(` + `).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime();return this.db.prepare(` INSERT INTO sdk_sessions (claude_session_id, project, user_prompt, started_at, started_at_epoch, status) VALUES (?, ?, ?, ?, ?, 'active') - `).run(e,s,t,r.toISOString(),o).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(` + `).run(e,s,t,r.toISOString(),n).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(` UPDATE sdk_sessions SET sdk_session_id = ? WHERE id = ? AND sdk_session_id IS NULL - `).run(s,e).changes===0?(D.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(` + `).run(s,e).changes===0?(v.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:s}),!1):!0}setWorkerPort(e,s){this.db.prepare(` UPDATE sdk_sessions SET worker_port = ? WHERE id = ? @@ -217,17 +217,17 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje FROM sdk_sessions WHERE id = ? LIMIT 1 - `).get(e)?.worker_port||null}storeObservation(e,s,t,r){let o=new Date,a=o.getTime();this.db.prepare(` + `).get(e)?.worker_port||null}storeObservation(e,s,t,r){let n=new Date,a=n.getTime();this.db.prepare(` INSERT INTO observations (sdk_session_id, project, type, title, subtitle, facts, narrative, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,o.toISOString(),a)}storeSummary(e,s,t,r){let o=new Date,a=o.getTime();this.db.prepare(` + `).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,n.toISOString(),a)}storeSummary(e,s,t,r){let n=new Date,a=n.getTime();this.db.prepare(` INSERT INTO session_summaries (sdk_session_id, project, request, investigated, learned, completed, next_steps, notes, prompt_number, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,o.toISOString(),a)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(` + `).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,n.toISOString(),a)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(` UPDATE sdk_sessions SET status = 'completed', completed_at = ?, completed_at_epoch = ? WHERE id = ? @@ -239,4 +239,4 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje UPDATE sdk_sessions SET status = 'failed', completed_at = ?, completed_at_epoch = ? WHERE status = 'active' - `).run(e.toISOString(),s).changes}close(){this.db.close()}}});function te(n,e,s){return n==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:n==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:n==="UserPromptSubmit"||n==="PostToolUse"?{continue:!0,suppressOutput:!0}:n==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function U(n,e,s={}){let t=te(n,e,s);return JSON.stringify(t)}var w=l(()=>{"use strict"});import N from"path";import{existsSync as h}from"fs";import{spawn as re}from"child_process";async function X(){try{return(await fetch(ne,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function M(){try{if(await X())return!0;console.error("[claude-mem] Worker not responding, starting...");let n=C(),e=N.join(n,"plugin","scripts","worker-service.cjs");if(!h(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=N.join(n,"ecosystem.config.cjs"),t=N.join(n,"node_modules",".bin","pm2");if(!h(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!h(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=re(t,["start",s],{detached:!0,stdio:"ignore",cwd:n});r.on("error",o=>{throw new Error(`Failed to spawn PM2: ${o.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let o=0;o<3;o++)if(await new Promise(a=>setTimeout(a,500)),await X())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(n){return console.error(`[claude-mem] Failed to start worker: ${n.message}`),!1}}function F(){return P}var P,ne,H=l(()=>{"use strict";S();P=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),ne=`http://127.0.0.1:${P}/health`});var $={};Y($,{newHook:()=>ie});import oe from"path";async function ie(n){if(!n)throw new Error("newHook requires input");let{session_id:e,cwd:s,prompt:t}=n,r=oe.basename(s);if(!await M())throw new Error("Worker service failed to start or become healthy");let a=new T;try{let c=a.findActiveSDKSession(e),i,E=!1;if(c){i=c.id;let p=a.incrementPromptCounter(i);console.error(`[new-hook] Continuing session ${i}, prompt #${p}`)}else{let p=a.findAnySDKSession(e);if(p){i=p.id,a.reactivateSession(i,t);let u=a.incrementPromptCounter(i);E=!0,console.error(`[new-hook] Reactivated session ${i}, prompt #${u}`)}else{i=a.createSDKSession(e,r,t);let u=a.incrementPromptCounter(i);E=!0,console.error(`[new-hook] Created new session ${i}, prompt #${u}`)}}let _=F();if(E){let p=await fetch(`http://127.0.0.1:${_}/sessions/${i}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:r,userPrompt:t}),signal:AbortSignal.timeout(5e3)});if(!p.ok){let u=await p.text();throw new Error(`Failed to initialize session: ${p.status} ${u}`)}}console.log(U("UserPromptSubmit",!0))}finally{a.close()}}var W=l(()=>{"use strict";x();w();H()});import{execSync as q}from"child_process";import{existsSync as V}from"fs";import{join as I}from"path";function L(){try{let n=process.env.CLAUDE_PLUGIN_ROOT;if(!n){console.error("[bootstrap] CLAUDE_PLUGIN_ROOT not set, skipping dependency check");return}let e=I(n,"scripts"),s=I(e,"node_modules");if(V(s))return;console.error("[bootstrap] Installing dependencies in plugin/scripts..."),q("npm install",{cwd:e,stdio:"inherit",timeout:6e4}),console.error("[bootstrap] Dependencies installed successfully")}catch(n){console.error("[bootstrap] Failed to install dependencies:",n instanceof Error?n.message:n)}}import{stdin as j}from"process";L();var{newHook:ae}=await Promise.resolve().then(()=>(W(),$)),R="";j.on("data",n=>R+=n);j.on("end",async()=>{let n=R.trim()?JSON.parse(R):void 0;await ae(n),process.exit(0)}); + `).run(e.toISOString(),s).changes}close(){this.db.close()}};function j(o,e,s){return o==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:o==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function A(o,e,s={}){let t=j(o,e,s);return JSON.stringify(t)}import b from"path";import{existsSync as N}from"fs";import{spawn as B}from"child_process";var C=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),G=`http://127.0.0.1:${C}/health`;async function k(){try{return(await fetch(G,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function D(){try{if(await k())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=L(),e=b.join(o,"plugin","scripts","worker-service.cjs");if(!N(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=b.join(o,"ecosystem.config.cjs"),t=b.join(o,"node_modules",".bin","pm2");if(!N(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!N(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=B(t,["start",s],{detached:!0,stdio:"ignore",cwd:o});r.on("error",n=>{throw new Error(`Failed to spawn PM2: ${n.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let n=0;n<3;n++)if(await new Promise(a=>setTimeout(a,500)),await k())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(o){return console.error(`[claude-mem] Failed to start worker: ${o.message}`),!1}}function y(){return C}async function x(o){if(!o)throw new Error("newHook requires input");let{session_id:e,cwd:s,prompt:t}=o,r=K.basename(s);if(!await D())throw new Error("Worker service failed to start or become healthy");let a=new _;try{let c=a.findActiveSDKSession(e),i,E=!1;if(c){i=c.id;let p=a.incrementPromptCounter(i);console.error(`[new-hook] Continuing session ${i}, prompt #${p}`)}else{let p=a.findAnySDKSession(e);if(p){i=p.id,a.reactivateSession(i,t);let u=a.incrementPromptCounter(i);E=!0,console.error(`[new-hook] Reactivated session ${i}, prompt #${u}`)}else{i=a.createSDKSession(e,r,t);let u=a.incrementPromptCounter(i);E=!0,console.error(`[new-hook] Created new session ${i}, prompt #${u}`)}}let l=y();if(E){let p=await fetch(`http://127.0.0.1:${l}/sessions/${i}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:r,userPrompt:t}),signal:AbortSignal.timeout(5e3)});if(!p.ok){let u=await p.text();throw new Error(`Failed to initialize session: ${p.status} ${u}`)}}console.log(A("UserPromptSubmit",!0))}finally{a.close()}}import{stdin as U}from"process";var f="";U.on("data",o=>f+=o);U.on("end",async()=>{let o=f.trim()?JSON.parse(f):void 0;await x(o),process.exit(0)}); diff --git a/plugin/scripts/save-hook.js b/plugin/scripts/save-hook.js index 13ec489c..3119e347 100755 --- a/plugin/scripts/save-hook.js +++ b/plugin/scripts/save-hook.js @@ -1,7 +1,7 @@ #!/usr/bin/env node -var G=Object.defineProperty;var m=(o,e)=>()=>(o&&(e=o(o=0)),e);var B=(o,e)=>{for(var s in e)G(o,s,{get:e[s],enumerable:!0})};import{join as d,dirname as q,basename as ge}from"path";import{homedir as C}from"os";import{existsSync as Ne,mkdirSync as V}from"fs";import{fileURLToPath as J}from"url";function Q(){return typeof __dirname<"u"?__dirname:q(J(import.meta.url))}function y(o){V(o,{recursive:!0})}function x(){return d(z,"..","..")}var z,u,S,Oe,Re,Ie,Le,ve,D,Ae,ke,Ce,b=m(()=>{"use strict";z=Q(),u=process.env.CLAUDE_MEM_DATA_DIR||d(C(),".claude-mem"),S=process.env.CLAUDE_CONFIG_DIR||d(C(),".claude"),Oe=d(u,"archives"),Re=d(u,"logs"),Ie=d(u,"trash"),Le=d(u,"backups"),ve=d(u,"settings.json"),D=d(u,"claude-mem.db"),Ae=d(S,"settings.json"),ke=d(S,"commands"),Ce=d(S,"CLAUDE.md")});var f,N,E,h=m(()=>{"use strict";f=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(f||{}),N=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=f[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} -${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e0&&(_=` {${Object.entries(v).map(([$,j])=>`${$}=${j}`).join(", ")}}`)}let L=`[${a}] [${i}] [${p}] ${l}${t}${_}${c}`;e===3?console.error(L):console.log(L)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},E=new N});import Z from"better-sqlite3";var T,U=m(()=>{"use strict";b();h();T=class{db;constructor(){y(u),this.db=new Z(D),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(` +import H from"better-sqlite3";import{join as d,dirname as w,basename as Q}from"path";import{homedir as I}from"os";import{existsSync as se,mkdirSync as X}from"fs";import{fileURLToPath as P}from"url";function M(){return typeof __dirname<"u"?__dirname:w(P(import.meta.url))}var F=M(),u=process.env.CLAUDE_MEM_DATA_DIR||d(I(),".claude-mem"),g=process.env.CLAUDE_CONFIG_DIR||d(I(),".claude"),re=d(u,"archives"),oe=d(u,"logs"),ne=d(u,"trash"),ie=d(u,"backups"),ae=d(u,"settings.json"),L=d(u,"claude-mem.db"),de=d(g,"settings.json"),pe=d(g,"commands"),ce=d(g,"CLAUDE.md");function v(n){X(n,{recursive:!0})}function A(){return d(F,"..","..")}var S=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(S||{}),b=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=S[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} +${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,o){if(e0&&(l=` {${Object.entries(O).map(([x,U])=>`${x}=${U}`).join(", ")}}`)}let h=`[${a}] [${i}] [${p}] ${m}${t}${l}${c}`;e===3?console.error(h):console.log(h)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},E=new b;var _=class{db;constructor(){v(u),this.db=new H(L),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(` CREATE TABLE IF NOT EXISTS schema_versions ( id INTEGER PRIMARY KEY, version INTEGER UNIQUE NOT NULL, @@ -200,11 +200,11 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje SELECT prompt_counter FROM sdk_sessions WHERE id = ? `).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(` SELECT prompt_counter FROM sdk_sessions WHERE id = ? - `).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime();return this.db.prepare(` + `).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,o=r.getTime();return this.db.prepare(` INSERT INTO sdk_sessions (claude_session_id, project, user_prompt, started_at, started_at_epoch, status) VALUES (?, ?, ?, ?, ?, 'active') - `).run(e,s,t,r.toISOString(),n).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(` + `).run(e,s,t,r.toISOString(),o).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(` UPDATE sdk_sessions SET sdk_session_id = ? WHERE id = ? AND sdk_session_id IS NULL @@ -217,17 +217,17 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje FROM sdk_sessions WHERE id = ? LIMIT 1 - `).get(e)?.worker_port||null}storeObservation(e,s,t,r){let n=new Date,a=n.getTime();this.db.prepare(` + `).get(e)?.worker_port||null}storeObservation(e,s,t,r){let o=new Date,a=o.getTime();this.db.prepare(` INSERT INTO observations (sdk_session_id, project, type, title, subtitle, facts, narrative, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,n.toISOString(),a)}storeSummary(e,s,t,r){let n=new Date,a=n.getTime();this.db.prepare(` + `).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,o.toISOString(),a)}storeSummary(e,s,t,r){let o=new Date,a=o.getTime();this.db.prepare(` INSERT INTO session_summaries (sdk_session_id, project, request, investigated, learned, completed, next_steps, notes, prompt_number, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,n.toISOString(),a)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(` + `).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,o.toISOString(),a)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(` UPDATE sdk_sessions SET status = 'completed', completed_at = ?, completed_at_epoch = ? WHERE id = ? @@ -239,4 +239,4 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje UPDATE sdk_sessions SET status = 'failed', completed_at = ?, completed_at_epoch = ? WHERE status = 'active' - `).run(e.toISOString(),s).changes}close(){this.db.close()}}});function ee(o,e,s){return o==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:o==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function g(o,e,s={}){let t=ee(o,e,s);return JSON.stringify(t)}var w=m(()=>{"use strict"});import O from"path";import{existsSync as R}from"fs";import{spawn as se}from"child_process";async function X(){try{return(await fetch(re,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function P(){try{if(await X())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=x(),e=O.join(o,"plugin","scripts","worker-service.cjs");if(!R(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=O.join(o,"ecosystem.config.cjs"),t=O.join(o,"node_modules",".bin","pm2");if(!R(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!R(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=se(t,["start",s],{detached:!0,stdio:"ignore",cwd:o});r.on("error",n=>{throw new Error(`Failed to spawn PM2: ${n.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let n=0;n<3;n++)if(await new Promise(a=>setTimeout(a,500)),await X())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(o){return console.error(`[claude-mem] Failed to start worker: ${o.message}`),!1}}var te,re,M=m(()=>{"use strict";b();te=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),re=`http://127.0.0.1:${te}/health`});var F={};B(F,{saveHook:()=>ne});async function ne(o){if(!o)throw new Error("saveHook requires input");let{session_id:e,tool_name:s,tool_input:t,tool_output:r}=o;if(oe.has(s)){console.log(g("PostToolUse",!0));return}if(!await P())throw new Error("Worker service failed to start or become healthy");let a=new T,i=a.findActiveSDKSession(e);if(!i){a.close(),console.log(g("PostToolUse",!0));return}if(!i.worker_port)throw a.close(),E.error("HOOK","No worker port for session",{sessionId:i.id}),new Error("No worker port for session - session may not be properly initialized");let p=a.getPromptCounter(i.id);a.close();let l=E.formatTool(s,t);E.dataIn("HOOK",`PostToolUse: ${l}`,{sessionId:i.id,workerPort:i.worker_port});let c=await fetch(`http://127.0.0.1:${i.worker_port}/sessions/${i.id}/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tool_name:s,tool_input:t!==void 0?JSON.stringify(t):"{}",tool_output:r!==void 0?JSON.stringify(r):"{}",prompt_number:p}),signal:AbortSignal.timeout(2e3)});if(!c.ok){let _=await c.text();throw E.failure("HOOK","Failed to send observation",{sessionId:i.id,status:c.status},_),new Error(`Failed to send observation to worker: ${c.status} ${_}`)}E.debug("HOOK","Observation sent successfully",{sessionId:i.id,toolName:s}),console.log(g("PostToolUse",!0))}var oe,H=m(()=>{"use strict";U();w();h();M();oe=new Set(["ListMcpResourcesTool"])});import{execSync as K}from"child_process";import{existsSync as Y}from"fs";import{join as A}from"path";function k(){try{let o=process.env.CLAUDE_PLUGIN_ROOT;if(!o){console.error("[bootstrap] CLAUDE_PLUGIN_ROOT not set, skipping dependency check");return}let e=A(o,"scripts"),s=A(e,"node_modules");if(Y(s))return;console.error("[bootstrap] Installing dependencies in plugin/scripts..."),K("npm install",{cwd:e,stdio:"inherit",timeout:6e4}),console.error("[bootstrap] Dependencies installed successfully")}catch(o){console.error("[bootstrap] Failed to install dependencies:",o instanceof Error?o.message:o)}}import{stdin as W}from"process";k();var{saveHook:ie}=await Promise.resolve().then(()=>(H(),F)),I="";W.on("data",o=>I+=o);W.on("end",async()=>{let o=I.trim()?JSON.parse(I):void 0;await ie(o),process.exit(0)}); + `).run(e.toISOString(),s).changes}close(){this.db.close()}};function W(n,e,s){return n==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:n==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:n==="UserPromptSubmit"||n==="PostToolUse"?{continue:!0,suppressOutput:!0}:n==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function T(n,e,s={}){let t=W(n,e,s);return JSON.stringify(t)}import f from"path";import{existsSync as N}from"fs";import{spawn as $}from"child_process";var j=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),B=`http://127.0.0.1:${j}/health`;async function k(){try{return(await fetch(B,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function C(){try{if(await k())return!0;console.error("[claude-mem] Worker not responding, starting...");let n=A(),e=f.join(n,"plugin","scripts","worker-service.cjs");if(!N(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=f.join(n,"ecosystem.config.cjs"),t=f.join(n,"node_modules",".bin","pm2");if(!N(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!N(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=$(t,["start",s],{detached:!0,stdio:"ignore",cwd:n});r.on("error",o=>{throw new Error(`Failed to spawn PM2: ${o.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let o=0;o<3;o++)if(await new Promise(a=>setTimeout(a,500)),await k())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(n){return console.error(`[claude-mem] Failed to start worker: ${n.message}`),!1}}var G=new Set(["ListMcpResourcesTool"]);async function D(n){if(!n)throw new Error("saveHook requires input");let{session_id:e,tool_name:s,tool_input:t,tool_output:r}=n;if(G.has(s)){console.log(T("PostToolUse",!0));return}if(!await C())throw new Error("Worker service failed to start or become healthy");let a=new _,i=a.findActiveSDKSession(e);if(!i){a.close(),console.log(T("PostToolUse",!0));return}if(!i.worker_port)throw a.close(),E.error("HOOK","No worker port for session",{sessionId:i.id}),new Error("No worker port for session - session may not be properly initialized");let p=a.getPromptCounter(i.id);a.close();let m=E.formatTool(s,t);E.dataIn("HOOK",`PostToolUse: ${m}`,{sessionId:i.id,workerPort:i.worker_port});let c=await fetch(`http://127.0.0.1:${i.worker_port}/sessions/${i.id}/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tool_name:s,tool_input:t!==void 0?JSON.stringify(t):"{}",tool_output:r!==void 0?JSON.stringify(r):"{}",prompt_number:p}),signal:AbortSignal.timeout(2e3)});if(!c.ok){let l=await c.text();throw E.failure("HOOK","Failed to send observation",{sessionId:i.id,status:c.status},l),new Error(`Failed to send observation to worker: ${c.status} ${l}`)}E.debug("HOOK","Observation sent successfully",{sessionId:i.id,toolName:s}),console.log(T("PostToolUse",!0))}import{stdin as y}from"process";var R="";y.on("data",n=>R+=n);y.on("end",async()=>{let n=R.trim()?JSON.parse(R):void 0;await D(n),process.exit(0)}); diff --git a/plugin/scripts/summary-hook.js b/plugin/scripts/summary-hook.js index 0b01075c..c2168896 100755 --- a/plugin/scripts/summary-hook.js +++ b/plugin/scripts/summary-hook.js @@ -1,7 +1,7 @@ #!/usr/bin/env node -var G=Object.defineProperty;var m=(o,e)=>()=>(o&&(e=o(o=0)),e);var B=(o,e)=>{for(var s in e)G(o,s,{get:e[s],enumerable:!0})};import{join as a,dirname as q,basename as Te}from"path";import{homedir as C}from"os";import{existsSync as fe,mkdirSync as V}from"fs";import{fileURLToPath as J}from"url";function Q(){return typeof __dirname<"u"?__dirname:q(J(import.meta.url))}function y(o){V(o,{recursive:!0})}function x(){return a(z,"..","..")}var z,p,T,Re,he,Oe,Ie,Le,D,ve,Ae,ke,g=m(()=>{"use strict";z=Q(),p=process.env.CLAUDE_MEM_DATA_DIR||a(C(),".claude-mem"),T=process.env.CLAUDE_CONFIG_DIR||a(C(),".claude"),Re=a(p,"archives"),he=a(p,"logs"),Oe=a(p,"trash"),Ie=a(p,"backups"),Le=a(p,"settings.json"),D=a(p,"claude-mem.db"),ve=a(T,"settings.json"),Ae=a(T,"commands"),ke=a(T,"CLAUDE.md")});var S,b,u,f=m(()=>{"use strict";S=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(S||{}),b=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=S[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} -${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e0&&(I=` {${Object.entries(v).map(([j,$])=>`${j}=${$}`).join(", ")}}`)}let L=`[${i}] [${d}] [${c}] ${l}${t}${I}${_}`;e===3?console.error(L):console.log(L)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},u=new b});import Z from"better-sqlite3";var E,U=m(()=>{"use strict";g();f();E=class{db;constructor(){y(p),this.db=new Z(D),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(` +import H from"better-sqlite3";import{join as a,dirname as w,basename as J}from"path";import{homedir as I}from"os";import{existsSync as ee,mkdirSync as X}from"fs";import{fileURLToPath as M}from"url";function P(){return typeof __dirname<"u"?__dirname:w(M(import.meta.url))}var F=P(),p=process.env.CLAUDE_MEM_DATA_DIR||a(I(),".claude-mem"),l=process.env.CLAUDE_CONFIG_DIR||a(I(),".claude"),te=a(p,"archives"),re=a(p,"logs"),oe=a(p,"trash"),ne=a(p,"backups"),ie=a(p,"settings.json"),L=a(p,"claude-mem.db"),ae=a(l,"settings.json"),de=a(l,"commands"),pe=a(l,"CLAUDE.md");function v(n){X(n,{recursive:!0})}function A(){return a(F,"..","..")}var T=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(T||{}),g=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=T[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message} +${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,o){if(e0&&(R=` {${Object.entries(O).map(([x,U])=>`${x}=${U}`).join(", ")}}`)}let h=`[${i}] [${d}] [${c}] ${E}${t}${R}${_}`;e===3?console.error(h):console.log(h)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},u=new g;var m=class{db;constructor(){v(p),this.db=new H(L),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(` CREATE TABLE IF NOT EXISTS schema_versions ( id INTEGER PRIMARY KEY, version INTEGER UNIQUE NOT NULL, @@ -200,11 +200,11 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje SELECT prompt_counter FROM sdk_sessions WHERE id = ? `).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(` SELECT prompt_counter FROM sdk_sessions WHERE id = ? - `).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime();return this.db.prepare(` + `).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,o=r.getTime();return this.db.prepare(` INSERT INTO sdk_sessions (claude_session_id, project, user_prompt, started_at, started_at_epoch, status) VALUES (?, ?, ?, ?, ?, 'active') - `).run(e,s,t,r.toISOString(),n).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(` + `).run(e,s,t,r.toISOString(),o).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(` UPDATE sdk_sessions SET sdk_session_id = ? WHERE id = ? AND sdk_session_id IS NULL @@ -217,17 +217,17 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje FROM sdk_sessions WHERE id = ? LIMIT 1 - `).get(e)?.worker_port||null}storeObservation(e,s,t,r){let n=new Date,i=n.getTime();this.db.prepare(` + `).get(e)?.worker_port||null}storeObservation(e,s,t,r){let o=new Date,i=o.getTime();this.db.prepare(` INSERT INTO observations (sdk_session_id, project, type, title, subtitle, facts, narrative, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,n.toISOString(),i)}storeSummary(e,s,t,r){let n=new Date,i=n.getTime();this.db.prepare(` + `).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,o.toISOString(),i)}storeSummary(e,s,t,r){let o=new Date,i=o.getTime();this.db.prepare(` INSERT INTO session_summaries (sdk_session_id, project, request, investigated, learned, completed, next_steps, notes, prompt_number, created_at, created_at_epoch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,n.toISOString(),i)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(` + `).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,o.toISOString(),i)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(` UPDATE sdk_sessions SET status = 'completed', completed_at = ?, completed_at_epoch = ? WHERE id = ? @@ -239,4 +239,4 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje UPDATE sdk_sessions SET status = 'failed', completed_at = ?, completed_at_epoch = ? WHERE status = 'active' - `).run(e.toISOString(),s).changes}close(){this.db.close()}}});function ee(o,e,s){return o==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:o==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function N(o,e,s={}){let t=ee(o,e,s);return JSON.stringify(t)}var w=m(()=>{"use strict"});import R from"path";import{existsSync as h}from"fs";import{spawn as se}from"child_process";async function X(){try{return(await fetch(re,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function M(){try{if(await X())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=x(),e=R.join(o,"plugin","scripts","worker-service.cjs");if(!h(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=R.join(o,"ecosystem.config.cjs"),t=R.join(o,"node_modules",".bin","pm2");if(!h(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!h(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=se(t,["start",s],{detached:!0,stdio:"ignore",cwd:o});r.on("error",n=>{throw new Error(`Failed to spawn PM2: ${n.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let n=0;n<3;n++)if(await new Promise(i=>setTimeout(i,500)),await X())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(o){return console.error(`[claude-mem] Failed to start worker: ${o.message}`),!1}}var te,re,P=m(()=>{"use strict";g();te=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),re=`http://127.0.0.1:${te}/health`});var F={};B(F,{summaryHook:()=>oe});async function oe(o){if(!o)throw new Error("summaryHook requires input");let{session_id:e}=o;if(!await M())throw new Error("Worker service failed to start or become healthy");let t=new E,r=t.findActiveSDKSession(e);if(!r){t.close(),console.log(N("Stop",!0));return}if(!r.worker_port)throw t.close(),u.error("HOOK","No worker port for session",{sessionId:r.id}),new Error("No worker port for session - session may not be properly initialized");let n=t.getPromptCounter(r.id);t.close(),u.dataIn("HOOK","Stop: Requesting summary",{sessionId:r.id,workerPort:r.worker_port,promptNumber:n});let i=await fetch(`http://127.0.0.1:${r.worker_port}/sessions/${r.id}/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({prompt_number:n}),signal:AbortSignal.timeout(2e3)});if(!i.ok){let d=await i.text();throw u.failure("HOOK","Failed to generate summary",{sessionId:r.id,status:i.status},d),new Error(`Failed to request summary from worker: ${i.status} ${d}`)}u.debug("HOOK","Summary request sent successfully",{sessionId:r.id}),console.log(N("Stop",!0))}var H=m(()=>{"use strict";U();w();f();P()});import{execSync as K}from"child_process";import{existsSync as Y}from"fs";import{join as A}from"path";function k(){try{let o=process.env.CLAUDE_PLUGIN_ROOT;if(!o){console.error("[bootstrap] CLAUDE_PLUGIN_ROOT not set, skipping dependency check");return}let e=A(o,"scripts"),s=A(e,"node_modules");if(Y(s))return;console.error("[bootstrap] Installing dependencies in plugin/scripts..."),K("npm install",{cwd:e,stdio:"inherit",timeout:6e4}),console.error("[bootstrap] Dependencies installed successfully")}catch(o){console.error("[bootstrap] Failed to install dependencies:",o instanceof Error?o.message:o)}}import{stdin as W}from"process";k();var{summaryHook:ne}=await Promise.resolve().then(()=>(H(),F)),O="";W.on("data",o=>O+=o);W.on("end",async()=>{let o=O.trim()?JSON.parse(O):void 0;await ne(o),process.exit(0)}); + `).run(e.toISOString(),s).changes}close(){this.db.close()}};function W(n,e,s){return n==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:n==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:n==="UserPromptSubmit"||n==="PostToolUse"?{continue:!0,suppressOutput:!0}:n==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function S(n,e,s={}){let t=W(n,e,s);return JSON.stringify(t)}import b from"path";import{existsSync as N}from"fs";import{spawn as $}from"child_process";var j=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),B=`http://127.0.0.1:${j}/health`;async function k(){try{return(await fetch(B,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function C(){try{if(await k())return!0;console.error("[claude-mem] Worker not responding, starting...");let n=A(),e=b.join(n,"plugin","scripts","worker-service.cjs");if(!N(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=b.join(n,"ecosystem.config.cjs"),t=b.join(n,"node_modules",".bin","pm2");if(!N(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!N(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=$(t,["start",s],{detached:!0,stdio:"ignore",cwd:n});r.on("error",o=>{throw new Error(`Failed to spawn PM2: ${o.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let o=0;o<3;o++)if(await new Promise(i=>setTimeout(i,500)),await k())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(n){return console.error(`[claude-mem] Failed to start worker: ${n.message}`),!1}}async function D(n){if(!n)throw new Error("summaryHook requires input");let{session_id:e}=n;if(!await C())throw new Error("Worker service failed to start or become healthy");let t=new m,r=t.findActiveSDKSession(e);if(!r){t.close(),console.log(S("Stop",!0));return}if(!r.worker_port)throw t.close(),u.error("HOOK","No worker port for session",{sessionId:r.id}),new Error("No worker port for session - session may not be properly initialized");let o=t.getPromptCounter(r.id);t.close(),u.dataIn("HOOK","Stop: Requesting summary",{sessionId:r.id,workerPort:r.worker_port,promptNumber:o});let i=await fetch(`http://127.0.0.1:${r.worker_port}/sessions/${r.id}/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({prompt_number:o}),signal:AbortSignal.timeout(2e3)});if(!i.ok){let d=await i.text();throw u.failure("HOOK","Failed to generate summary",{sessionId:r.id,status:i.status},d),new Error(`Failed to request summary from worker: ${i.status} ${d}`)}u.debug("HOOK","Summary request sent successfully",{sessionId:r.id}),console.log(S("Stop",!0))}import{stdin as y}from"process";var f="";y.on("data",n=>f+=n);y.on("end",async()=>{let n=f.trim()?JSON.parse(f):void 0;await D(n),process.exit(0)}); diff --git a/src/bin/hooks/cleanup-hook.ts b/src/bin/hooks/cleanup-hook.ts index 33c28959..dab3fc80 100644 --- a/src/bin/hooks/cleanup-hook.ts +++ b/src/bin/hooks/cleanup-hook.ts @@ -4,16 +4,9 @@ * Standalone executable for plugin hooks */ -// Bootstrap: Ensure dependencies are installed before importing modules -import { ensureDependencies } from '../../shared/bootstrap.js'; +import { cleanupHook } from '../../hooks/cleanup.js'; import { stdin } from 'process'; -// Run bootstrap synchronously BEFORE any dynamic imports -ensureDependencies(); - -// Dynamic import AFTER bootstrap ensures dependencies are installed -const { cleanupHook } = await import('../../hooks/cleanup.js'); - // Read input from stdin let input = ''; stdin.on('data', (chunk) => input += chunk); diff --git a/src/bin/hooks/context-hook.ts b/src/bin/hooks/context-hook.ts index ee527c4a..783c9ebb 100644 --- a/src/bin/hooks/context-hook.ts +++ b/src/bin/hooks/context-hook.ts @@ -4,16 +4,9 @@ * Standalone executable for plugin hooks */ -// Bootstrap: Ensure dependencies are installed before importing modules -import { ensureDependencies } from '../../shared/bootstrap.js'; +import { contextHook } from '../../hooks/context.js'; import { stdin } from 'process'; -// Run bootstrap synchronously BEFORE any dynamic imports -ensureDependencies(); - -// Dynamic import AFTER bootstrap ensures dependencies are installed -const { contextHook } = await import('../../hooks/context.js'); - try { if (stdin.isTTY) { const contextOutput = contextHook(); diff --git a/src/bin/hooks/new-hook.ts b/src/bin/hooks/new-hook.ts index e7292c91..897bc632 100644 --- a/src/bin/hooks/new-hook.ts +++ b/src/bin/hooks/new-hook.ts @@ -4,16 +4,9 @@ * Standalone executable for plugin hooks */ -// Bootstrap: Ensure dependencies are installed before importing modules -import { ensureDependencies } from '../../shared/bootstrap.js'; +import { newHook } from '../../hooks/new.js'; import { stdin } from 'process'; -// Run bootstrap synchronously BEFORE any dynamic imports -ensureDependencies(); - -// Dynamic import AFTER bootstrap ensures dependencies are installed -const { newHook } = await import('../../hooks/new.js'); - // Read input from stdin let input = ''; stdin.on('data', (chunk) => input += chunk); diff --git a/src/bin/hooks/save-hook.ts b/src/bin/hooks/save-hook.ts index 1c603495..8a1a8bb3 100644 --- a/src/bin/hooks/save-hook.ts +++ b/src/bin/hooks/save-hook.ts @@ -4,16 +4,9 @@ * Standalone executable for plugin hooks */ -// Bootstrap: Ensure dependencies are installed before importing modules -import { ensureDependencies } from '../../shared/bootstrap.js'; +import { saveHook } from '../../hooks/save.js'; import { stdin } from 'process'; -// Run bootstrap synchronously BEFORE any dynamic imports -ensureDependencies(); - -// Dynamic import AFTER bootstrap ensures dependencies are installed -const { saveHook } = await import('../../hooks/save.js'); - // Read input from stdin let input = ''; stdin.on('data', (chunk) => input += chunk); diff --git a/src/bin/hooks/summary-hook.ts b/src/bin/hooks/summary-hook.ts index 4df11b02..ffddafaf 100644 --- a/src/bin/hooks/summary-hook.ts +++ b/src/bin/hooks/summary-hook.ts @@ -4,16 +4,9 @@ * Standalone executable for plugin hooks */ -// Bootstrap: Ensure dependencies are installed before importing modules -import { ensureDependencies } from '../../shared/bootstrap.js'; +import { summaryHook } from '../../hooks/summary.js'; import { stdin } from 'process'; -// Run bootstrap synchronously BEFORE any dynamic imports -ensureDependencies(); - -// Dynamic import AFTER bootstrap ensures dependencies are installed -const { summaryHook } = await import('../../hooks/summary.js'); - // Read input from stdin let input = ''; stdin.on('data', (chunk) => input += chunk); diff --git a/src/shared/bootstrap.ts b/src/shared/bootstrap.ts deleted file mode 100644 index b8aa1d37..00000000 --- a/src/shared/bootstrap.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { execSync } from 'child_process'; -import { existsSync } from 'fs'; -import { join } from 'path'; - -/** - * Bootstrap function to ensure dependencies are installed - * This runs on first hook execution after plugin installation from GitHub - * - * When installed via GitHub Marketplace, files are downloaded but npm install - * doesn't run automatically. This function checks for node_modules and installs - * dependencies if needed. - */ -export function ensureDependencies(): void { - try { - // Get plugin root from environment variable - const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT; - if (!pluginRoot) { - console.error('[bootstrap] CLAUDE_PLUGIN_ROOT not set, skipping dependency check'); - return; - } - - const scriptsDir = join(pluginRoot, 'scripts'); - const nodeModulesPath = join(scriptsDir, 'node_modules'); - - // Check if dependencies are already installed - if (existsSync(nodeModulesPath)) { - return; // Already installed - } - - console.error('[bootstrap] Installing dependencies in plugin/scripts...'); - - // Install dependencies using npm - execSync('npm install', { - cwd: scriptsDir, - stdio: 'inherit', // Show install progress - timeout: 60000 // 60 second timeout - }); - - console.error('[bootstrap] Dependencies installed successfully'); - } catch (error) { - console.error('[bootstrap] Failed to install dependencies:', error instanceof Error ? error.message : error); - // Don't throw - allow hook to continue, it will fail on import but with clearer error - } -}