claude-mem 10.7.2 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/npx-cli/index.js +212 -140
- package/openclaw/dist/index.js +14 -14
- package/openclaw/install.sh +58 -9
- package/openclaw/openclaw.plugin.json +5 -0
- package/openclaw/src/index.ts +142 -43
- package/openclaw/test-install.sh +1 -1
- package/package.json +17 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/hooks/hooks.json +21 -9
- package/plugin/modes/code.json +3 -3
- package/plugin/package.json +17 -2
- package/plugin/scripts/bun-runner.js +35 -8
- package/plugin/scripts/context-generator.cjs +220 -114
- package/plugin/scripts/mcp-server.cjs +2042 -53
- package/plugin/scripts/smart-install.js +2 -2
- package/plugin/scripts/worker-service.cjs +410 -266
- package/plugin/skills/mem-search/SKILL.md +48 -0
- package/plugin/ui/viewer-bundle.js +9 -9
- package/plugin/ui/viewer.html +124 -1
package/openclaw/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
var
|
|
2
|
-
**${
|
|
3
|
-
${e.subtitle}`),
|
|
1
|
+
var G="127.0.0.1",W=["\u{1F527}","\u{1F4D0}","\u{1F50D}","\u{1F4BB}","\u{1F9EA}","\u{1F41B}","\u{1F6E1}\uFE0F","\u2601\uFE0F","\u{1F4E6}","\u{1F3AF}","\u{1F52E}","\u26A1","\u{1F30A}","\u{1F3A8}","\u{1F4CA}","\u{1F680}","\u{1F52C}","\u{1F3D7}\uFE0F","\u{1F4DD}","\u{1F3AD}"];function z(e){let s=0;for(let i=0;i<e.length;i++)s=(s<<5)-s+e.charCodeAt(i)|0;return W[Math.abs(s)%W.length]}var X="\u{1F99E}",Z="\u2328\uFE0F",Y="Claude Code Session",Q="\u{1F980}";function V(e){let s=e?.primary??X,i=e?.claudeCode??Z,g=e?.claudeCodeLabel??Y,c=e?.default??Q,d=e?.agents??{};return function(f){if(!f)return c;if(f.startsWith("openclaw-")){let b=f.slice(9);return b?`${d[b]||z(b)} ${b}`:`${s} openclaw`}if(f==="openclaw")return`${s} openclaw`;let v=g.trim();return v?`${i} ${v} (${f})`:`${i} ${f}`}}var U=G;function M(e){return`http://${U}:${e}`}async function q(e,s,i,g){try{let c=await fetch(`${M(e)}${s}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});return c.ok?await c.json():(g.warn(`[claude-mem] Worker POST ${s} returned ${c.status}`),null)}catch(c){let d=c instanceof Error?c.message:String(c);return g.warn(`[claude-mem] Worker POST ${s} failed: ${d}`),null}}function J(e,s,i,g){fetch(`${M(e)}${s}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)}).catch(c=>{let d=c instanceof Error?c.message:String(c);g.warn(`[claude-mem] Worker POST ${s} failed: ${d}`)})}async function K(e,s,i){try{let g=await fetch(`${M(e)}${s}`);return g.ok?await g.text():(i.warn(`[claude-mem] Worker GET ${s} returned ${g.status}`),null)}catch(g){let c=g instanceof Error?g.message:String(g);return i.warn(`[claude-mem] Worker GET ${s} failed: ${c}`),null}}async function B(e,s,i){let g=await K(e,s,i);if(!g)return null;try{return JSON.parse(g)}catch{return i.warn(`[claude-mem] Worker GET ${s} returned non-JSON response`),null}}function ee(e,s){let i=e.title||"Untitled",c=`${s(e.project)}
|
|
2
|
+
**${i}**`;return e.subtitle&&(c+=`
|
|
3
|
+
${e.subtitle}`),c}var ne={telegram:{namespace:"telegram",functionName:"sendMessageTelegram"},whatsapp:{namespace:"whatsapp",functionName:"sendMessageWhatsApp"},discord:{namespace:"discord",functionName:"sendMessageDiscord"},slack:{namespace:"slack",functionName:"sendMessageSlack"},signal:{namespace:"signal",functionName:"sendMessageSignal"},imessage:{namespace:"imessage",functionName:"sendMessageIMessage"},line:{namespace:"line",functionName:"sendMessageLine"}};async function te(e,s,i,g){try{let c=await fetch(`https://api.telegram.org/bot${e}/sendMessage`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({chat_id:s,text:i,parse_mode:"Markdown"})});if(!c.ok){let d=await c.text();g.warn(`[claude-mem] Direct Telegram send failed (${c.status}): ${d}`)}}catch(c){let d=c instanceof Error?c.message:String(c);g.warn(`[claude-mem] Direct Telegram send error: ${d}`)}}function re(e,s,i,g,c){if(c&&s==="telegram")return te(c,i,g,e.logger);let d=ne[s];if(!d)return e.logger.warn(`[claude-mem] Unsupported channel type: ${s}`),Promise.resolve();let w=e.runtime.channel[d.namespace];if(!w)return e.logger.warn(`[claude-mem] Channel "${s}" not available in runtime`),Promise.resolve();let f=w[d.functionName];return f?f(...s==="whatsapp"?[i,g,{verbose:!1}]:[i,g]).catch(b=>{let u=b instanceof Error?b.message:String(b);e.logger.error(`[claude-mem] Failed to send to ${s}: ${u}`)}):(e.logger.warn(`[claude-mem] Channel "${s}" has no ${d.functionName} function`),Promise.resolve())}async function se(e,s,i,g,c,d,w,f){let v=1e3,b=3e4;for(;!c.signal.aborted;){try{d("reconnecting"),e.logger.info(`[claude-mem] Connecting to SSE stream at ${M(s)}/stream`);let u=await fetch(`${M(s)}/stream`,{signal:c.signal,headers:{Accept:"text/event-stream"}});if(!u.ok)throw new Error(`SSE stream returned HTTP ${u.status}`);if(!u.body)throw new Error("SSE stream response has no body");d("connected"),v=1e3,e.logger.info("[claude-mem] Connected to SSE stream");let _=u.body.getReader(),j=new TextDecoder,k="";for(;;){let{done:L,value:I}=await _.read();if(L)break;k+=j.decode(I,{stream:!0}),k.length>1048576&&(e.logger.warn("[claude-mem] SSE buffer overflow, clearing buffer"),k="");let P=k.split(`
|
|
4
4
|
|
|
5
|
-
`);
|
|
6
|
-
`).filter(
|
|
7
|
-
`);if(
|
|
8
|
-
`));let
|
|
9
|
-
`));break}}await
|
|
10
|
-
`)}function
|
|
11
|
-
`)}}}),e.registerCommand({name:"claude-mem-search",description:"Search Claude-Mem observations by query",acceptsArgs:!0,handler:async r=>{let n=r.args?.trim()||"";if(!n)return"Usage: /claude-mem-search <query> [limit]";let t=n.split(/\s+/),
|
|
12
|
-
`)}}),e.registerCommand({name:"claude-mem-recent",description:"Show recent Claude-Mem context for a project",acceptsArgs:!0,handler:async r=>{let n=r.args?.trim()||"",t=n?n.split(/\s+/):[],
|
|
13
|
-
`)}}),e.registerCommand({name:"claude-mem-timeline",description:"Find best memory match and show nearby timeline events",acceptsArgs:!0,handler:async r=>{let n=r.args?.trim()||"";if(!n)return"Usage: /claude-mem-timeline <query> [depthBefore] [depthAfter]";let t=n.split(/\s+/),
|
|
14
|
-
`)}}),e.registerCommand({name:"claude_mem_status",description:"Check Claude-Mem worker health and session status",handler:async()=>{let r=await
|
|
15
|
-
`)}}catch{return{text:"Claude-Mem worker responded but returned unexpected data"}}}}),e.logger.info(`[claude-mem] OpenClaw plugin loaded \u2014 v1.0.0 (worker:
|
|
5
|
+
`);k=P.pop()||"";for(let C of P){let x=C.split(`
|
|
6
|
+
`).filter(y=>y.startsWith("data:")).map(y=>y.slice(5).trim());if(x.length===0)continue;let T=x.join(`
|
|
7
|
+
`);if(T)try{let y=JSON.parse(T);if(y.type==="new_observation"&&y.observation){let A=ee(y.observation,w);await re(e,i,g,A,f)}}catch(y){let R=y instanceof Error?y.message:String(y);e.logger.warn(`[claude-mem] Failed to parse SSE frame: ${R}`)}}}}catch(u){if(c.signal.aborted)break;d("reconnecting");let _=u instanceof Error?u.message:String(u);e.logger.warn(`[claude-mem] SSE stream error: ${_}. Reconnecting in ${v/1e3}s`)}if(c.signal.aborted)break;await new Promise(u=>setTimeout(u,v)),v=Math.min(v*2,b)}d("disconnected")}function oe(e){let s=e.pluginConfig||{},i=s.workerPort||37777;U=s.workerHost||G;let g=s.project||"openclaw",c=V(s.observationFeed?.emojis);function d(r){return r.agentId?`openclaw-${r.agentId}`:g}let w=new Map,f=new Map,v=new Map,b=new Map,u=new Map,_=(()=>{let r=Number(s.completionDelayMs);return Number.isFinite(r)?Math.max(0,r):5e3})(),j=s.syncMemoryFile!==!1,k=new Set(s.syncMemoryFileExclude||[]);function L(r){let n=r||"default";return w.has(n)||w.set(n,`openclaw-${n}-${Date.now()}`),w.get(n)}function I(r){if(!j)return!1;let n=r?.agentId;return!(n&&k.has(n))}function P(r){let n=new Set;for(let t of[r.sessionKey,r.conversationId,r.channelId]){let o=typeof t=="string"?t.trim():"";o&&n.add(o)}return n.size===0&&n.add("default"),Array.from(n)}function C(r){let n=P(r),t=n.find(l=>f.has(l));t=t?f.get(t):n[0];let o=v.get(t);o||(o=new Set([t]),v.set(t,o));for(let l of n)o.add(l),f.set(l,t);let a=L(t);for(let l of o)w.set(l,a);return{canonicalKey:t,contentSessionId:a}}function x(r,n,t){let o=Date.now();for(let[m,p]of u)o-p>2e3&&u.delete(m);let a=`${r}::${n}::${t}`,l=u.get(a);return u.set(a,o),typeof l=="number"&&o-l<=2e3}function T(r){let n=P(r),t=n.map(a=>f.get(a)).find(Boolean)||n[0],o=v.get(t)||new Set([t,...n]);for(let a of o)f.delete(a),w.delete(a);v.delete(t),w.delete(t)}function y(r){let n=b.get(r);n&&clearTimeout(n);let t=setTimeout(()=>{b.delete(r),J(i,"/api/sessions/complete",{contentSessionId:r},e.logger)},_);b.set(r,t)}let R=6e4,A=new Map;async function H(r){let n=[g],t=r?d(r):null;t&&t!==g&&n.push(t);let o=n.join(","),a=A.get(o);if(a&&Date.now()-a.fetchedAt<R)return a.text;let l=await K(i,`/api/context/inject?projects=${encodeURIComponent(o)}`,e.logger);if(l&&l.trim().length>0){let m=l.trim();return A.set(o,{text:m,fetchedAt:Date.now()}),m}return null}e.on("session_start",async(r,n)=>{let{contentSessionId:t}=C(n);e.logger.info(`[claude-mem] Session tracking initialized: ${t}`)}),e.on("message_received",async(r,n)=>{let{canonicalKey:t,contentSessionId:o}=C(n);e.logger.info(`[claude-mem] Message received \u2014 prompt capture deferred to before_agent_start: session=${t} contentSessionId=${o} hasContent=${!!r.content}`)}),e.on("after_compaction",async(r,n)=>{let{contentSessionId:t}=C(n);e.logger.info(`[claude-mem] Session preserved after compaction: ${t}`)}),e.on("before_agent_start",async(r,n)=>{let{contentSessionId:t}=C(n),o=d(n),a=r.prompt||"agent run";if(x(t,o,a)){e.logger.info(`[claude-mem] Skipping duplicate prompt init: contentSessionId=${t} project=${o}`);return}await q(i,"/api/sessions/init",{contentSessionId:t,project:o,prompt:a},e.logger),e.logger.info(`[claude-mem] Session initialized via before_agent_start: contentSessionId=${t} project=${o}`)}),e.on("before_prompt_build",async(r,n)=>{if(!I(n))return;let t=await H(n);if(t)return e.logger.info(`[claude-mem] Context injected via system prompt for agent=${n.agentId??"unknown"}`),{appendSystemContext:t}}),e.on("tool_result_persist",(r,n)=>{e.logger.info(`[claude-mem] tool_result_persist fired: tool=${r.toolName??"unknown"} agent=${n.agentId??"none"} session=${n.sessionKey??"none"}`);let t=r.toolName;if(!t||t.startsWith("memory_"))return;let{canonicalKey:o,contentSessionId:a}=C(n),l="",m=r.message?.content;Array.isArray(m)&&(l=m.filter(h=>(h.type==="tool_result"||h.type==="text")&&"text"in h).map(h=>String(h.text)).join(`
|
|
8
|
+
`));let p=1e3;l.length>p&&(l=l.slice(0,p));let S=n.workspaceDir;if(!S){e.logger.warn(`[claude-mem] Skipping observation persist because workspaceDir is unavailable: session=${o} tool=${t}`);return}J(i,"/api/sessions/observations",{contentSessionId:a,tool_name:t,tool_input:r.params||{},tool_response:l,cwd:S},e.logger)}),e.on("agent_end",async(r,n)=>{let{contentSessionId:t}=C(n),o="";if(Array.isArray(r.messages))for(let a=r.messages.length-1;a>=0;a--){let l=r.messages[a];if(l?.role==="assistant"){typeof l.content=="string"?o=l.content:Array.isArray(l.content)&&(o=l.content.filter(m=>m.type==="text").map(m=>m.text||"").join(`
|
|
9
|
+
`));break}}await q(i,"/api/sessions/summarize",{contentSessionId:t,last_assistant_message:o},e.logger),e.logger.info(`[claude-mem] Scheduling session complete in ${_}ms: ${t}`),y(t)}),e.on("session_end",async(r,n)=>{T(n),e.logger.info("[claude-mem] Session tracking cleaned up")}),e.on("gateway_start",async()=>{w.clear(),A.clear(),u.clear(),f.clear(),v.clear();for(let r of b.values())clearTimeout(r);b.clear(),e.logger.info("[claude-mem] Gateway started \u2014 session tracking reset")});let E=null,O="disconnected",$=null;e.registerService({id:"claude-mem-observation-feed",start:async r=>{E&&(E.abort(),$&&(await $,$=null));let n=s.observationFeed;if(!n?.enabled){e.logger.info("[claude-mem] Observation feed disabled");return}if(!n.channel||!n.to){e.logger.warn("[claude-mem] Observation feed misconfigured \u2014 channel or target missing");return}e.logger.info(`[claude-mem] Observation feed starting \u2014 channel: ${n.channel}, target: ${n.to}`),E=new AbortController,$=se(e,i,n.channel,n.to,E,t=>{O=t},c,n.botToken)},stop:async r=>{E&&(E.abort(),E=null),$&&(await $,$=null),O="disconnected",e.logger.info("[claude-mem] Observation feed stopped \u2014 SSE connection closed")}});function N(r,n=5){return!Array.isArray(r)||r.length===0?"No results found.":r.slice(0,n).map((t,o)=>{let a=t,l=String(a.title||a.subtitle||a.text||"Untitled"),m=a.project?` [${String(a.project)}]`:"";return`${o+1}. ${l}${m}`}).join(`
|
|
10
|
+
`)}function F(r,n=10){let t=Number(r);return Number.isFinite(t)?Math.max(1,Math.min(50,Math.trunc(t))):n}e.registerCommand({name:"claude_mem_feed",description:"Show or toggle Claude-Mem observation feed status",acceptsArgs:!0,handler:async r=>{let n=s.observationFeed;if(!n)return{text:"Observation feed not configured. Add observationFeed to your plugin config."};let t=r.args?.trim();return t==="on"?(e.logger.info("[claude-mem] Feed enable requested via command"),{text:"Feed enable requested. Update observationFeed.enabled in your plugin config to persist."}):t==="off"?(e.logger.info("[claude-mem] Feed disable requested via command"),{text:"Feed disable requested. Update observationFeed.enabled in your plugin config to persist."}):{text:["Claude-Mem Observation Feed",`Enabled: ${n.enabled?"yes":"no"}`,`Channel: ${n.channel||"not set"}`,`Target: ${n.to||"not set"}`,`Connection: ${O}`].join(`
|
|
11
|
+
`)}}}),e.registerCommand({name:"claude-mem-search",description:"Search Claude-Mem observations by query",acceptsArgs:!0,handler:async r=>{let n=r.args?.trim()||"";if(!n)return"Usage: /claude-mem-search <query> [limit]";let t=n.split(/\s+/),o=t[t.length-1],a=/^\d+$/.test(o),l=a?F(o,10):10,m=a?t.slice(0,-1).join(" "):n,p=await B(i,`/api/search/observations?query=${encodeURIComponent(m)}&limit=${l}`,e.logger);if(!p)return"Claude-Mem search failed (worker unavailable or invalid response).";let S=Array.isArray(p.items)?p.items:[];return[`Claude-Mem Search: "${m}"`,N(S,l)].join(`
|
|
12
|
+
`)}}),e.registerCommand({name:"claude-mem-recent",description:"Show recent Claude-Mem context for a project",acceptsArgs:!0,handler:async r=>{let n=r.args?.trim()||"",t=n?n.split(/\s+/):[],o=t.length>0?t[t.length-1]:"",a=/^\d+$/.test(o),l=a?F(o,3):3,m=a?t.slice(0,-1).join(" "):n,p=new URLSearchParams;p.set("limit",String(l)),m&&p.set("project",m);let S=await B(i,`/api/context/recent?${p.toString()}`,e.logger);if(!S)return"Claude-Mem recent context failed (worker unavailable or invalid response).";let h=Array.isArray(S.session_summaries)?S.session_summaries:[],D=Array.isArray(S.recent_observations)?S.recent_observations:[];return["Claude-Mem Recent Context",`Project: ${m||"(auto)"}`,`Session summaries: ${h.length}`,`Recent observations: ${D.length}`,N(D,Math.min(5,D.length||5))].join(`
|
|
13
|
+
`)}}),e.registerCommand({name:"claude-mem-timeline",description:"Find best memory match and show nearby timeline events",acceptsArgs:!0,handler:async r=>{let n=r.args?.trim()||"";if(!n)return"Usage: /claude-mem-timeline <query> [depthBefore] [depthAfter]";let t=n.split(/\s+/),o=5,a=5;t.length>=2&&/^\d+$/.test(t[t.length-1])&&(o=F(t.pop(),5)),t.length>=2&&/^\d+$/.test(t[t.length-1])&&(a=F(t.pop(),5));let l=t.join(" "),m=new URLSearchParams({query:l,mode:"auto",depth_before:String(a),depth_after:String(o)}),p=await B(i,`/api/timeline/by-query?${m.toString()}`,e.logger);if(!p)return"Claude-Mem timeline lookup failed (worker unavailable or invalid response).";let S=Array.isArray(p.timeline)?p.timeline:[],h=p.anchor?String(p.anchor):"(none)";return[`Claude-Mem Timeline: "${l}"`,`Anchor: ${h}`,N(S,8)].join(`
|
|
14
|
+
`)}}),e.registerCommand({name:"claude_mem_status",description:"Check Claude-Mem worker health and session status",handler:async()=>{let r=await K(i,"/api/health",e.logger);if(!r)return{text:`Claude-Mem worker unreachable at port ${i}`};try{return{text:["Claude-Mem Worker Status",`Status: ${JSON.parse(r).status||"unknown"}`,`Port: ${i}`,`Active sessions: ${w.size}`,`Observation feed: ${O}`].join(`
|
|
15
|
+
`)}}catch{return{text:"Claude-Mem worker responded but returned unexpected data"}}}}),e.logger.info(`[claude-mem] OpenClaw plugin loaded \u2014 v1.0.0 (worker: ${U}:${i})`)}export{oe as default};
|
package/openclaw/install.sh
CHANGED
|
@@ -80,17 +80,18 @@ setup_tty() {
|
|
|
80
80
|
if [[ -t 0 ]]; then
|
|
81
81
|
# stdin IS a terminal — use it directly
|
|
82
82
|
TTY_FD=0
|
|
83
|
-
elif [[
|
|
84
|
-
#
|
|
83
|
+
elif [[ "$NON_INTERACTIVE" == "true" ]]; then
|
|
84
|
+
# In non-interactive mode, do not require /dev/tty
|
|
85
|
+
TTY_FD=0
|
|
86
|
+
elif [[ -r /dev/tty ]]; then
|
|
87
|
+
# stdin is piped (curl | bash) but /dev/tty is available and readable
|
|
85
88
|
exec 3</dev/tty
|
|
86
89
|
TTY_FD=3
|
|
87
90
|
else
|
|
88
91
|
# No terminal available at all
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
exit 1
|
|
93
|
-
fi
|
|
92
|
+
echo "Error: No terminal available for interactive prompts." >&2
|
|
93
|
+
echo "Use --non-interactive or run directly: bash install.sh" >&2
|
|
94
|
+
exit 1
|
|
94
95
|
fi
|
|
95
96
|
}
|
|
96
97
|
|
|
@@ -787,11 +788,16 @@ install_plugin() {
|
|
|
787
788
|
const configPath = process.env.INSTALLER_CONFIG_FILE;
|
|
788
789
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
789
790
|
const entry = config?.plugins?.entries?.['claude-mem'];
|
|
790
|
-
|
|
791
|
+
const allowHasClaudeMem = Array.isArray(config?.plugins?.allow) && config.plugins.allow.includes('claude-mem');
|
|
792
|
+
if (entry || config?.plugins?.slots?.memory === 'claude-mem' || allowHasClaudeMem) {
|
|
791
793
|
// Save the config block so we can restore it after install
|
|
792
794
|
process.stdout.write(JSON.stringify(entry?.config || {}));
|
|
793
795
|
// Remove the stale entry so OpenClaw CLI can run
|
|
794
796
|
if (entry) delete config.plugins.entries['claude-mem'];
|
|
797
|
+
// Also remove stale allowlist reference — this alone can block ALL CLI commands
|
|
798
|
+
if (Array.isArray(config?.plugins?.allow)) {
|
|
799
|
+
config.plugins.allow = config.plugins.allow.filter((x) => x !== 'claude-mem');
|
|
800
|
+
}
|
|
795
801
|
// Also remove the slot reference — if the slot points to a plugin
|
|
796
802
|
// that isn't in entries, OpenClaw's config validator rejects ALL commands
|
|
797
803
|
if (config?.plugins?.slots?.memory === 'claude-mem') {
|
|
@@ -818,6 +824,49 @@ install_plugin() {
|
|
|
818
824
|
exit 1
|
|
819
825
|
fi
|
|
820
826
|
|
|
827
|
+
# Ensure claude-mem is present in plugins.allow after successful install+enable.
|
|
828
|
+
# Some OpenClaw environments require explicit allowlisting for local plugins.
|
|
829
|
+
# This write is guaranteed: if config doesn't exist, configure_memory_slot() will create it.
|
|
830
|
+
if [[ -f "$oc_config" ]]; then
|
|
831
|
+
if ! INSTALLER_CONFIG_FILE="$oc_config" node -e "
|
|
832
|
+
const fs = require('fs');
|
|
833
|
+
const configPath = process.env.INSTALLER_CONFIG_FILE;
|
|
834
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
835
|
+
if (!config.plugins) config.plugins = {};
|
|
836
|
+
if (!Array.isArray(config.plugins.allow)) config.plugins.allow = [];
|
|
837
|
+
if (!config.plugins.allow.includes('claude-mem')) {
|
|
838
|
+
config.plugins.allow.push('claude-mem');
|
|
839
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
840
|
+
console.log('Added claude-mem to plugins.allow');
|
|
841
|
+
} else {
|
|
842
|
+
console.log('claude-mem already in plugins.allow');
|
|
843
|
+
}
|
|
844
|
+
" 2>&1; then
|
|
845
|
+
warn "Failed to write plugins.allow — claude-mem may need manual allowlisting"
|
|
846
|
+
fi
|
|
847
|
+
else
|
|
848
|
+
# Config doesn't exist yet; configure_memory_slot() will create it with plugins.allow
|
|
849
|
+
# We'll add claude-mem to the allowlist in a follow-up step after config is materialized
|
|
850
|
+
info "OpenClaw config not yet materialized; will ensure allowlist in post-install"
|
|
851
|
+
# Force config materialization by running a harmless OpenClaw command
|
|
852
|
+
if run_openclaw status --json >/dev/null 2>&1 && [[ -f "$oc_config" ]]; then
|
|
853
|
+
if ! INSTALLER_CONFIG_FILE="$oc_config" node -e "
|
|
854
|
+
const fs = require('fs');
|
|
855
|
+
const configPath = process.env.INSTALLER_CONFIG_FILE;
|
|
856
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
857
|
+
if (!config.plugins) config.plugins = {};
|
|
858
|
+
if (!Array.isArray(config.plugins.allow)) config.plugins.allow = [];
|
|
859
|
+
if (!config.plugins.allow.includes('claude-mem')) {
|
|
860
|
+
config.plugins.allow.push('claude-mem');
|
|
861
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
862
|
+
console.log('Added claude-mem to plugins.allow (post-materialization)');
|
|
863
|
+
}
|
|
864
|
+
" 2>&1; then
|
|
865
|
+
warn "Failed to write plugins.allow after materialization — configure manually"
|
|
866
|
+
fi
|
|
867
|
+
fi
|
|
868
|
+
fi
|
|
869
|
+
|
|
821
870
|
# Restore saved plugin config (workerPort, syncMemoryFile, observationFeed, etc.)
|
|
822
871
|
# from any pre-existing installation that was temporarily removed above.
|
|
823
872
|
if [[ -n "$saved_plugin_config" && "$saved_plugin_config" != "{}" ]]; then
|
|
@@ -1101,7 +1150,7 @@ write_settings() {
|
|
|
1101
1150
|
|
|
1102
1151
|
// All defaults from SettingsDefaultsManager.ts
|
|
1103
1152
|
const defaults = {
|
|
1104
|
-
CLAUDE_MEM_MODEL: 'claude-sonnet-4-
|
|
1153
|
+
CLAUDE_MEM_MODEL: 'claude-sonnet-4-6',
|
|
1105
1154
|
CLAUDE_MEM_CONTEXT_OBSERVATIONS: '50',
|
|
1106
1155
|
CLAUDE_MEM_WORKER_PORT: '37777',
|
|
1107
1156
|
CLAUDE_MEM_WORKER_HOST: '127.0.0.1',
|
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
"default": 37777,
|
|
28
28
|
"description": "Port for Claude-Mem worker service"
|
|
29
29
|
},
|
|
30
|
+
"workerHost": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"default": "127.0.0.1",
|
|
33
|
+
"description": "Hostname for Claude-Mem worker service. Set to host.docker.internal when the gateway runs in Docker and the worker runs on the host."
|
|
34
|
+
},
|
|
30
35
|
"project": {
|
|
31
36
|
"type": "string",
|
|
32
37
|
"default": "openclaw",
|
package/openclaw/src/index.ts
CHANGED
|
@@ -183,6 +183,7 @@ interface ClaudeMemPluginConfig {
|
|
|
183
183
|
syncMemoryFileExclude?: string[];
|
|
184
184
|
project?: string;
|
|
185
185
|
workerPort?: number;
|
|
186
|
+
workerHost?: string;
|
|
186
187
|
observationFeed?: {
|
|
187
188
|
enabled?: boolean;
|
|
188
189
|
channel?: string;
|
|
@@ -198,6 +199,7 @@ interface ClaudeMemPluginConfig {
|
|
|
198
199
|
|
|
199
200
|
const MAX_SSE_BUFFER_SIZE = 1024 * 1024; // 1MB
|
|
200
201
|
const DEFAULT_WORKER_PORT = 37777;
|
|
202
|
+
const DEFAULT_WORKER_HOST = "127.0.0.1";
|
|
201
203
|
|
|
202
204
|
// Emoji pool for deterministic auto-assignment to unknown agents.
|
|
203
205
|
// Uses a hash of the agentId to pick a consistent emoji — no persistent state needed.
|
|
@@ -256,8 +258,10 @@ function buildGetSourceLabel(
|
|
|
256
258
|
// Worker HTTP Client
|
|
257
259
|
// ============================================================================
|
|
258
260
|
|
|
261
|
+
let _workerHost = DEFAULT_WORKER_HOST;
|
|
262
|
+
|
|
259
263
|
function workerBaseUrl(port: number): string {
|
|
260
|
-
return `http
|
|
264
|
+
return `http://${_workerHost}:${port}`;
|
|
261
265
|
}
|
|
262
266
|
|
|
263
267
|
async function workerPost(
|
|
@@ -533,6 +537,7 @@ async function connectToSSEStream(
|
|
|
533
537
|
export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|
534
538
|
const userConfig = (api.pluginConfig || {}) as ClaudeMemPluginConfig;
|
|
535
539
|
const workerPort = userConfig.workerPort || DEFAULT_WORKER_PORT;
|
|
540
|
+
_workerHost = userConfig.workerHost || DEFAULT_WORKER_HOST;
|
|
536
541
|
const baseProjectName = userConfig.project || "openclaw";
|
|
537
542
|
const getSourceLabel = buildGetSourceLabel(userConfig.observationFeed?.emojis);
|
|
538
543
|
|
|
@@ -547,6 +552,14 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|
|
547
552
|
// Session tracking for observation I/O
|
|
548
553
|
// ------------------------------------------------------------------
|
|
549
554
|
const sessionIds = new Map<string, string>();
|
|
555
|
+
const canonicalSessionKeys = new Map<string, string>();
|
|
556
|
+
const sessionAliasesByCanonicalKey = new Map<string, Set<string>>();
|
|
557
|
+
const pendingCompletionTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
558
|
+
const recentPromptInits = new Map<string, number>();
|
|
559
|
+
const completionDelayMs = (() => {
|
|
560
|
+
const val = Number((userConfig as Record<string, unknown>).completionDelayMs);
|
|
561
|
+
return Number.isFinite(val) ? Math.max(0, val) : 5000;
|
|
562
|
+
})();
|
|
550
563
|
const syncMemoryFile = userConfig.syncMemoryFile !== false; // default true
|
|
551
564
|
const syncMemoryFileExclude = new Set(userConfig.syncMemoryFileExclude || []);
|
|
552
565
|
|
|
@@ -565,6 +578,83 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|
|
565
578
|
return true;
|
|
566
579
|
}
|
|
567
580
|
|
|
581
|
+
type SessionTrackingContext = {
|
|
582
|
+
sessionKey?: string;
|
|
583
|
+
workspaceDir?: string;
|
|
584
|
+
channelId?: string;
|
|
585
|
+
conversationId?: string;
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
function getSessionAliases(ctx: SessionTrackingContext): string[] {
|
|
589
|
+
const aliases = new Set<string>();
|
|
590
|
+
for (const rawKey of [ctx.sessionKey, ctx.conversationId, ctx.channelId]) {
|
|
591
|
+
const key = typeof rawKey === "string" ? rawKey.trim() : "";
|
|
592
|
+
if (key) aliases.add(key);
|
|
593
|
+
}
|
|
594
|
+
if (aliases.size === 0) aliases.add("default");
|
|
595
|
+
return Array.from(aliases);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function rememberSessionContext(ctx: SessionTrackingContext): { canonicalKey: string; contentSessionId: string } {
|
|
599
|
+
const aliases = getSessionAliases(ctx);
|
|
600
|
+
let canonicalKey = aliases.find((alias) => canonicalSessionKeys.has(alias));
|
|
601
|
+
canonicalKey = canonicalKey ? canonicalSessionKeys.get(canonicalKey)! : aliases[0];
|
|
602
|
+
let aliasSet = sessionAliasesByCanonicalKey.get(canonicalKey);
|
|
603
|
+
if (!aliasSet) {
|
|
604
|
+
aliasSet = new Set([canonicalKey]);
|
|
605
|
+
sessionAliasesByCanonicalKey.set(canonicalKey, aliasSet);
|
|
606
|
+
}
|
|
607
|
+
for (const alias of aliases) {
|
|
608
|
+
aliasSet.add(alias);
|
|
609
|
+
canonicalSessionKeys.set(alias, canonicalKey);
|
|
610
|
+
}
|
|
611
|
+
const contentSessionId = getContentSessionId(canonicalKey);
|
|
612
|
+
for (const alias of aliasSet) {
|
|
613
|
+
sessionIds.set(alias, contentSessionId);
|
|
614
|
+
}
|
|
615
|
+
return { canonicalKey, contentSessionId };
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function shouldSkipDuplicatePromptInit(contentSessionId: string, project: string, prompt: string): boolean {
|
|
619
|
+
const now = Date.now();
|
|
620
|
+
for (const [key, timestamp] of recentPromptInits) {
|
|
621
|
+
if (now - timestamp > 2000) recentPromptInits.delete(key);
|
|
622
|
+
}
|
|
623
|
+
const cacheKey = `${contentSessionId}::${project}::${prompt}`;
|
|
624
|
+
const lastSeenAt = recentPromptInits.get(cacheKey);
|
|
625
|
+
// Note: cache is set unconditionally before return. If workerPost fails
|
|
626
|
+
// after this check, a retry within 2s would be incorrectly skipped.
|
|
627
|
+
// Acceptable because before_agent_start is not retried by the runtime.
|
|
628
|
+
recentPromptInits.set(cacheKey, now);
|
|
629
|
+
return typeof lastSeenAt === "number" && now - lastSeenAt <= 2000;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function clearSessionContext(ctx: SessionTrackingContext): void {
|
|
633
|
+
const aliases = getSessionAliases(ctx);
|
|
634
|
+
const canonicalKey = aliases
|
|
635
|
+
.map((alias) => canonicalSessionKeys.get(alias))
|
|
636
|
+
.find(Boolean) || aliases[0];
|
|
637
|
+
const knownAliases = sessionAliasesByCanonicalKey.get(canonicalKey) || new Set([canonicalKey, ...aliases]);
|
|
638
|
+
for (const alias of knownAliases) {
|
|
639
|
+
canonicalSessionKeys.delete(alias);
|
|
640
|
+
sessionIds.delete(alias);
|
|
641
|
+
}
|
|
642
|
+
sessionAliasesByCanonicalKey.delete(canonicalKey);
|
|
643
|
+
sessionIds.delete(canonicalKey);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function scheduleSessionComplete(contentSessionId: string): void {
|
|
647
|
+
const existingTimer = pendingCompletionTimers.get(contentSessionId);
|
|
648
|
+
if (existingTimer) clearTimeout(existingTimer);
|
|
649
|
+
const timer = setTimeout(() => {
|
|
650
|
+
pendingCompletionTimers.delete(contentSessionId);
|
|
651
|
+
workerPostFireAndForget(workerPort, "/api/sessions/complete", {
|
|
652
|
+
contentSessionId,
|
|
653
|
+
}, api.logger);
|
|
654
|
+
}, completionDelayMs);
|
|
655
|
+
pendingCompletionTimers.set(contentSessionId, timer);
|
|
656
|
+
}
|
|
657
|
+
|
|
568
658
|
// TTL cache for context injection to avoid re-fetching on every LLM turn.
|
|
569
659
|
// before_prompt_build fires on every turn; caching for 60s keeps the worker
|
|
570
660
|
// load manageable while still picking up new observations reasonably quickly.
|
|
@@ -600,61 +690,54 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|
|
600
690
|
}
|
|
601
691
|
|
|
602
692
|
// ------------------------------------------------------------------
|
|
603
|
-
// Event: session_start —
|
|
693
|
+
// Event: session_start — track session (fires on /new, /reset)
|
|
694
|
+
// Init is deferred to before_agent_start to avoid duplicate prompt records.
|
|
604
695
|
// ------------------------------------------------------------------
|
|
605
696
|
api.on("session_start", async (_event, ctx) => {
|
|
606
|
-
const contentSessionId =
|
|
607
|
-
|
|
608
|
-
await workerPost(workerPort, "/api/sessions/init", {
|
|
609
|
-
contentSessionId,
|
|
610
|
-
project: getProjectName(ctx),
|
|
611
|
-
prompt: "",
|
|
612
|
-
}, api.logger);
|
|
613
|
-
|
|
614
|
-
api.logger.info(`[claude-mem] Session initialized: ${contentSessionId}`);
|
|
697
|
+
const { contentSessionId } = rememberSessionContext(ctx);
|
|
698
|
+
api.logger.info(`[claude-mem] Session tracking initialized: ${contentSessionId}`);
|
|
615
699
|
});
|
|
616
700
|
|
|
617
701
|
// ------------------------------------------------------------------
|
|
618
|
-
// Event: message_received —
|
|
702
|
+
// Event: message_received — alias tracking only; init deferred to before_agent_start
|
|
619
703
|
// ------------------------------------------------------------------
|
|
620
704
|
api.on("message_received", async (event, ctx) => {
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
await workerPost(workerPort, "/api/sessions/init", {
|
|
625
|
-
contentSessionId,
|
|
626
|
-
project: baseProjectName,
|
|
627
|
-
prompt: event.content || "[media prompt]",
|
|
628
|
-
}, api.logger);
|
|
705
|
+
const { canonicalKey, contentSessionId } = rememberSessionContext(ctx);
|
|
706
|
+
api.logger.info(`[claude-mem] Message received — prompt capture deferred to before_agent_start: session=${canonicalKey} contentSessionId=${contentSessionId} hasContent=${Boolean(event.content)}`);
|
|
629
707
|
});
|
|
630
708
|
|
|
631
709
|
// ------------------------------------------------------------------
|
|
632
|
-
// Event: after_compaction —
|
|
710
|
+
// Event: after_compaction — preserve session tracking after context compaction.
|
|
711
|
+
// Re-init is intentionally NOT called here; the worker retains session state
|
|
712
|
+
// independently and re-initializing would create duplicate prompt records.
|
|
633
713
|
// ------------------------------------------------------------------
|
|
634
714
|
api.on("after_compaction", async (_event, ctx) => {
|
|
635
|
-
const contentSessionId =
|
|
636
|
-
|
|
637
|
-
await workerPost(workerPort, "/api/sessions/init", {
|
|
638
|
-
contentSessionId,
|
|
639
|
-
project: getProjectName(ctx),
|
|
640
|
-
prompt: "",
|
|
641
|
-
}, api.logger);
|
|
642
|
-
|
|
643
|
-
api.logger.info(`[claude-mem] Session re-initialized after compaction: ${contentSessionId}`);
|
|
715
|
+
const { contentSessionId } = rememberSessionContext(ctx);
|
|
716
|
+
api.logger.info(`[claude-mem] Session preserved after compaction: ${contentSessionId}`);
|
|
644
717
|
});
|
|
645
718
|
|
|
646
719
|
// ------------------------------------------------------------------
|
|
647
|
-
// Event: before_agent_start — init
|
|
720
|
+
// Event: before_agent_start — single init point with dedup guard
|
|
648
721
|
// ------------------------------------------------------------------
|
|
649
722
|
api.on("before_agent_start", async (event, ctx) => {
|
|
723
|
+
const { contentSessionId } = rememberSessionContext(ctx);
|
|
724
|
+
const projectName = getProjectName(ctx);
|
|
725
|
+
const promptText = event.prompt || "agent run";
|
|
726
|
+
|
|
727
|
+
if (shouldSkipDuplicatePromptInit(contentSessionId, projectName, promptText)) {
|
|
728
|
+
api.logger.info(`[claude-mem] Skipping duplicate prompt init: contentSessionId=${contentSessionId} project=${projectName}`);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
|
|
650
732
|
// Initialize session in the worker so observations are not skipped
|
|
651
733
|
// (the privacy check requires a stored user prompt to exist)
|
|
652
|
-
const contentSessionId = getContentSessionId(ctx.sessionKey);
|
|
653
734
|
await workerPost(workerPort, "/api/sessions/init", {
|
|
654
735
|
contentSessionId,
|
|
655
|
-
project:
|
|
656
|
-
prompt:
|
|
736
|
+
project: projectName,
|
|
737
|
+
prompt: promptText,
|
|
657
738
|
}, api.logger);
|
|
739
|
+
|
|
740
|
+
api.logger.info(`[claude-mem] Session initialized via before_agent_start: contentSessionId=${contentSessionId} project=${projectName}`);
|
|
658
741
|
});
|
|
659
742
|
|
|
660
743
|
// ------------------------------------------------------------------
|
|
@@ -686,7 +769,7 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|
|
686
769
|
// Skip memory_ tools to prevent recursive observation loops
|
|
687
770
|
if (toolName.startsWith("memory_")) return;
|
|
688
771
|
|
|
689
|
-
const contentSessionId =
|
|
772
|
+
const { canonicalKey, contentSessionId } = rememberSessionContext(ctx);
|
|
690
773
|
|
|
691
774
|
// Extract result text from all content blocks
|
|
692
775
|
let toolResponseText = "";
|
|
@@ -704,13 +787,23 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|
|
704
787
|
toolResponseText = toolResponseText.slice(0, MAX_TOOL_RESPONSE_LENGTH);
|
|
705
788
|
}
|
|
706
789
|
|
|
790
|
+
// Resolve workspaceDir with fallback chain.
|
|
791
|
+
// Empty cwd causes worker-side observation queueing failures,
|
|
792
|
+
// so we drop the observation rather than sending cwd: "".
|
|
793
|
+
const workspaceDir = ctx.workspaceDir;
|
|
794
|
+
|
|
795
|
+
if (!workspaceDir) {
|
|
796
|
+
api.logger.warn(`[claude-mem] Skipping observation persist because workspaceDir is unavailable: session=${canonicalKey} tool=${toolName}`);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
|
|
707
800
|
// Fire-and-forget: send observation to worker
|
|
708
801
|
workerPostFireAndForget(workerPort, "/api/sessions/observations", {
|
|
709
802
|
contentSessionId,
|
|
710
803
|
tool_name: toolName,
|
|
711
804
|
tool_input: event.params || {},
|
|
712
805
|
tool_response: toolResponseText,
|
|
713
|
-
cwd:
|
|
806
|
+
cwd: workspaceDir,
|
|
714
807
|
}, api.logger);
|
|
715
808
|
});
|
|
716
809
|
|
|
@@ -718,7 +811,7 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|
|
718
811
|
// Event: agent_end — summarize and complete session
|
|
719
812
|
// ------------------------------------------------------------------
|
|
720
813
|
api.on("agent_end", async (event, ctx) => {
|
|
721
|
-
const contentSessionId =
|
|
814
|
+
const { contentSessionId } = rememberSessionContext(ctx);
|
|
722
815
|
|
|
723
816
|
// Extract last assistant message for summarization
|
|
724
817
|
let lastAssistantMessage = "";
|
|
@@ -747,17 +840,16 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|
|
747
840
|
last_assistant_message: lastAssistantMessage,
|
|
748
841
|
}, api.logger);
|
|
749
842
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}, api.logger);
|
|
843
|
+
api.logger.info(`[claude-mem] Scheduling session complete in ${completionDelayMs}ms: ${contentSessionId}`);
|
|
844
|
+
scheduleSessionComplete(contentSessionId);
|
|
753
845
|
});
|
|
754
846
|
|
|
755
847
|
// ------------------------------------------------------------------
|
|
756
848
|
// Event: session_end — clean up session tracking to prevent unbounded growth
|
|
757
849
|
// ------------------------------------------------------------------
|
|
758
850
|
api.on("session_end", async (_event, ctx) => {
|
|
759
|
-
|
|
760
|
-
|
|
851
|
+
clearSessionContext(ctx);
|
|
852
|
+
api.logger.info(`[claude-mem] Session tracking cleaned up`);
|
|
761
853
|
});
|
|
762
854
|
|
|
763
855
|
// ------------------------------------------------------------------
|
|
@@ -766,6 +858,13 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|
|
766
858
|
api.on("gateway_start", async () => {
|
|
767
859
|
sessionIds.clear();
|
|
768
860
|
contextCache.clear();
|
|
861
|
+
recentPromptInits.clear();
|
|
862
|
+
canonicalSessionKeys.clear();
|
|
863
|
+
sessionAliasesByCanonicalKey.clear();
|
|
864
|
+
for (const timer of pendingCompletionTimers.values()) {
|
|
865
|
+
clearTimeout(timer);
|
|
866
|
+
}
|
|
867
|
+
pendingCompletionTimers.clear();
|
|
769
868
|
api.logger.info("[claude-mem] Gateway started — session tracking reset");
|
|
770
869
|
});
|
|
771
870
|
|
|
@@ -1047,5 +1146,5 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
|
|
|
1047
1146
|
},
|
|
1048
1147
|
});
|
|
1049
1148
|
|
|
1050
|
-
api.logger.info(`[claude-mem] OpenClaw plugin loaded — v1.0.0 (worker:
|
|
1149
|
+
api.logger.info(`[claude-mem] OpenClaw plugin loaded — v1.0.0 (worker: ${_workerHost}:${workerPort})`);
|
|
1051
1150
|
}
|
package/openclaw/test-install.sh
CHANGED
|
@@ -643,7 +643,7 @@ test_write_settings_new_file() {
|
|
|
643
643
|
|
|
644
644
|
local model
|
|
645
645
|
model="$(node -e "const s = JSON.parse(require('fs').readFileSync('${settings_file}','utf8')); console.log(s.CLAUDE_MEM_MODEL);")"
|
|
646
|
-
assert_eq "claude-sonnet-4-
|
|
646
|
+
assert_eq "claude-sonnet-4-6" "$model" "CLAUDE_MEM_MODEL defaults to claude-sonnet-4-6"
|
|
647
647
|
|
|
648
648
|
HOME="$ORIGINAL_HOME"
|
|
649
649
|
rm -rf "$fake_home"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "12.0.0",
|
|
4
4
|
"description": "Memory compression system for Claude Code - persist context across sessions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"dev": "npm run build-and-sync",
|
|
63
|
-
"build": "node scripts/build-hooks.js",
|
|
63
|
+
"build": "node scripts/sync-plugin-manifests.js && node scripts/build-hooks.js",
|
|
64
64
|
"build-and-sync": "npm run build && npm run sync-marketplace && sleep 1 && cd ~/.claude/plugins/marketplaces/thedotmack && npm run worker:restart",
|
|
65
65
|
"sync-marketplace": "node scripts/sync-marketplace.cjs",
|
|
66
66
|
"sync-marketplace:force": "node scripts/sync-marketplace.cjs --force",
|
|
@@ -124,6 +124,12 @@
|
|
|
124
124
|
"zod-to-json-schema": "^3.24.6"
|
|
125
125
|
},
|
|
126
126
|
"devDependencies": {
|
|
127
|
+
"@derekstride/tree-sitter-sql": "^0.3.11",
|
|
128
|
+
"@tree-sitter-grammars/tree-sitter-lua": "^0.4.1",
|
|
129
|
+
"@tree-sitter-grammars/tree-sitter-markdown": "^0.3.2",
|
|
130
|
+
"@tree-sitter-grammars/tree-sitter-toml": "^0.7.0",
|
|
131
|
+
"@tree-sitter-grammars/tree-sitter-yaml": "^0.7.1",
|
|
132
|
+
"@tree-sitter-grammars/tree-sitter-zig": "^1.1.2",
|
|
127
133
|
"@types/cors": "^2.8.19",
|
|
128
134
|
"@types/dompurify": "^3.0.5",
|
|
129
135
|
"@types/express": "^4.17.21",
|
|
@@ -132,15 +138,24 @@
|
|
|
132
138
|
"@types/react-dom": "^18.3.0",
|
|
133
139
|
"esbuild": "^0.27.2",
|
|
134
140
|
"np": "^11.0.2",
|
|
141
|
+
"tree-sitter-bash": "^0.25.1",
|
|
135
142
|
"tree-sitter-c": "^0.24.1",
|
|
136
143
|
"tree-sitter-cli": "^0.26.5",
|
|
137
144
|
"tree-sitter-cpp": "^0.23.4",
|
|
145
|
+
"tree-sitter-css": "^0.25.0",
|
|
146
|
+
"tree-sitter-elixir": "^0.3.5",
|
|
138
147
|
"tree-sitter-go": "^0.25.0",
|
|
148
|
+
"tree-sitter-haskell": "^0.23.1",
|
|
139
149
|
"tree-sitter-java": "^0.23.5",
|
|
140
150
|
"tree-sitter-javascript": "^0.25.0",
|
|
151
|
+
"tree-sitter-kotlin": "^0.3.8",
|
|
152
|
+
"tree-sitter-php": "^0.24.2",
|
|
141
153
|
"tree-sitter-python": "^0.25.0",
|
|
142
154
|
"tree-sitter-ruby": "^0.23.1",
|
|
143
155
|
"tree-sitter-rust": "^0.24.0",
|
|
156
|
+
"tree-sitter-scala": "^0.24.0",
|
|
157
|
+
"tree-sitter-scss": "^1.0.0",
|
|
158
|
+
"tree-sitter-swift": "^0.7.1",
|
|
144
159
|
"tree-sitter-typescript": "^0.23.2",
|
|
145
160
|
"tsx": "^4.20.6",
|
|
146
161
|
"typescript": "^5.3.0"
|