refactor: Replace TypeScript bootstrap with bash dependency checks
Simplified dependency installation by moving from TypeScript runtime bootstrap to bash-based checks in plugin manifest. This reduces complexity and code size while maintaining the same functionality.
Changes:
- Added bash conditional dependency checks to all 5 hooks in hooks.json
- Check runs before each hook: [ ! -d "${CLAUDE_PLUGIN_ROOT}/scripts/node_modules" ] && cd "${CLAUDE_PLUGIN_ROOT}/scripts" && npm install || true
- Reverted all hook TypeScript files to use simple static imports (removed dynamic imports)
- Removed src/shared/bootstrap.ts (44 lines)
- Removed ensureDependencies() calls from all hook entry points
Benefits:
- Simpler architecture using native bash instead of TypeScript
- Net reduction of 157 lines of code
- No runtime overhead when dependencies already installed
- Uses plugin manifest's command hook feature as intended
- Faster and more efficient
🤖 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
|
||||
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(e<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),d=S[e].padEnd(5),c=s.padEnd(6),l="";r?.correlationId?l=`[${r.correlationId}] `:r?.sessionId&&(l=`[session-${r.sessionId}] `);let _="";n!=null&&(this.level===0&&typeof n=="object"?_=`
|
||||
`+JSON.stringify(n,null,2):_=" "+this.formatData(n));let I="";if(r){let{sessionId:ie,sdkSessionId:ae,correlationId:de,...v}=r;Object.keys(v).length>0&&(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(e<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),d=T[e].padEnd(5),c=s.padEnd(6),E="";r?.correlationId?E=`[${r.correlationId}] `:r?.sessionId&&(E=`[session-${r.sessionId}] `);let _="";o!=null&&(this.level===0&&typeof o=="object"?_=`
|
||||
`+JSON.stringify(o,null,2):_=" "+this.formatData(o));let R="";if(r){let{sessionId:G,sdkSessionId:K,correlationId:Y,...O}=r;Object.keys(O).length>0&&(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)});
|
||||
|
||||
Reference in New Issue
Block a user