fix: Use dynamic imports in hooks to fix better-sqlite3 loading
Changed all hook entry points to use dynamic imports after bootstrap runs. This ensures that better-sqlite3 is installed before Node.js attempts to resolve the import.
Changes:
- Modified src/bin/hooks/*.ts to call ensureDependencies() before dynamic import
- Moved from static `import { hook } from '...'` to `const { hook } = await import('...')`
- This delays module resolution until after npm install completes
- Bumped version to 4.0.6
The previous approach failed because static imports are resolved at module link time, before any runtime code (including ensureDependencies) executes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
import{execSync as P}from"child_process";import{existsSync as M}from"fs";import{join as I}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=I(o,"scripts"),s=I(e,"node_modules");if(M(s))return;console.error("[bootstrap] Installing dependencies in plugin/scripts..."),P("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 G from"better-sqlite3";import{join as d,dirname as F,basename as ne}from"path";import{homedir as v}from"os";import{existsSync as pe,mkdirSync as H}from"fs";import{fileURLToPath as W}from"url";function $(){return typeof __dirname<"u"?__dirname:F(W(import.meta.url))}var j=$(),u=process.env.CLAUDE_MEM_DATA_DIR||d(v(),".claude-mem"),g=process.env.CLAUDE_CONFIG_DIR||d(v(),".claude"),ue=d(u,"archives"),Ee=d(u,"logs"),me=d(u,"trash"),le=d(u,"backups"),_e=d(u,"settings.json"),A=d(u,"claude-mem.db"),Te=d(g,"settings.json"),ge=d(g,"commands"),Se=d(g,"CLAUDE.md");function k(o){H(o,{recursive:!0})}function C(){return d(j,"..","..")}var 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(e<this.level)return;let a=new Date().toISOString().replace("T"," ").substring(0,23),i=S[e].padEnd(5),p=s.padEnd(6),m="";r?.correlationId?m=`[${r.correlationId}] `:r?.sessionId&&(m=`[session-${r.sessionId}] `);let c="";n!=null&&(this.level===0&&typeof n=="object"?c=`
|
||||
`+JSON.stringify(n,null,2):c=" "+this.formatData(n));let l="";if(r){let{sessionId:J,sdkSessionId:Q,correlationId:z,...R}=r;Object.keys(R).length>0&&(l=` {${Object.entries(R).map(([w,X])=>`${w}=${X}`).join(", ")}}`)}let O=`[${a}] [${i}] [${p}] ${m}${t}${l}${c}`;e===3?console.error(O):console.log(O)}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(){k(u),this.db=new G(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(`
|
||||
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(e<this.level)return;let a=new Date().toISOString().replace("T"," ").substring(0,23),i=f[e].padEnd(5),p=s.padEnd(6),l="";r?.correlationId?l=`[${r.correlationId}] `:r?.sessionId&&(l=`[session-${r.sessionId}] `);let c="";n!=null&&(this.level===0&&typeof n=="object"?c=`
|
||||
`+JSON.stringify(n,null,2):c=" "+this.formatData(n));let _="";if(r){let{sessionId:ae,sdkSessionId:de,correlationId:pe,...v}=r;Object.keys(v).length>0&&(_=` {${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(`
|
||||
CREATE TABLE IF NOT EXISTS schema_versions (
|
||||
id INTEGER PRIMARY KEY,
|
||||
version INTEGER UNIQUE NOT NULL,
|
||||
@@ -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 B(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 T(o,e,s={}){let t=B(o,e,s);return JSON.stringify(t)}import f from"path";import{existsSync as N}from"fs";import{spawn as K}from"child_process";var Y=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),q=`http://127.0.0.1:${Y}/health`;async function D(){try{return(await fetch(q,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function y(){try{if(await D())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=C(),e=f.join(o,"plugin","scripts","worker-service.cjs");if(!N(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=f.join(o,"ecosystem.config.cjs"),t=f.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=K(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 D())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 V=new Set(["ListMcpResourcesTool"]);async function x(o){if(!o)throw new Error("saveHook requires input");let{session_id:e,tool_name:s,tool_input:t,tool_output:r}=o;if(V.has(s)){console.log(T("PostToolUse",!0));return}if(!await y())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 U}from"process";L();var h="";U.on("data",o=>h+=o);U.on("end",async()=>{let o=h.trim()?JSON.parse(h):void 0;await x(o),process.exit(0)});
|
||||
`).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)});
|
||||
|
||||
Reference in New Issue
Block a user