@cachly-dev/mcp-server 0.10.227 → 0.10.229
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/src/ambient-hooks.js +1 -1
- package/dist/src/aussetzer.js +1 -1
- package/dist/src/bauabdruck.js +1 -1
- package/dist/src/brain-watch-hook.js +2 -2
- package/dist/src/cls-hook.js +7 -7
- package/dist/src/doctor.js +2 -2
- package/dist/src/handlers/advanced.js +24 -24
- package/dist/src/handlers/brain.js +65 -65
- package/dist/src/handlers/cache.js +31 -30
- package/dist/src/handlers/context.js +12 -12
- package/dist/src/handlers/roadmap.js +9 -9
- package/dist/src/handlers/team.js +44 -44
- package/dist/src/index.js +161 -160
- package/dist/src/kanten-vorrat.js +1 -0
- package/dist/src/pflichtfelder.js +1 -1
- package/dist/src/speicherlage.js +1 -1
- package/dist/src/tools.js +1 -1
- package/dist/src/uninstall.js +5 -5
- package/glama.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -1,54 +1,55 @@
|
|
|
1
|
-
import{randomUUID as
|
|
1
|
+
import{randomUUID as M}from"node:crypto";import{McpError as F,ErrorCode as W}from"@modelcontextprotocol/sdk/types.js";import{readdir as Z,stat as ee,readFile as te}from"node:fs/promises";import{join as se,relative as ae,extname as ne}from"node:path";import{computeEmbedding as I,hasEmbedProvider as D,embedProviderHint as B}from"../embeddings.js";import{einbettTakt as ie}from"../einbett-takt.js";import{detectNamespace as q}from"../namespace.js";import{simpleHash as ce}from"../confidence.js";import{scanKeys as re}from"../utils.js";import{cachlyUrl as z}from"../cachly-url.js";const we=new Set(["cache_get","cache_set","cache_delete","cache_exists","cache_ttl","cache_keys","cache_stats","semantic_search","detect_namespace","cache_warmup","index_project","cache_mset","cache_mget","cache_lock_acquire","cache_lock_release","cache_stream_set","cache_stream_get","cache_org_stats","set_cost_per_call"]);async function ge(G,h,_,O){switch(G){case"cache_get":{const{instance_id:n,key:e,org_id:a}=h,t=await _(n);let s=await t.get(e),i="direct";if(s===null&&a){const o=`org:${a}:sem:${e}`;s=await t.get(o),s!==null&&(i=`org:${a}:sem`)}if(s===null)return`Key \`${e}\` \u2192 **not found** (null)`;let r=s;try{r=JSON.stringify(JSON.parse(s),null,2)}catch{}const c=i!=="direct"?` _(from shared org namespace \`${i}\`)_`:"";return`Key \`${e}\`:${c}
|
|
2
2
|
\`\`\`
|
|
3
|
-
${
|
|
4
|
-
\`\`\``}case"cache_set":{const{instance_id:n,key:e,value:a,ttl:t,org_id:s}=
|
|
3
|
+
${r}
|
|
4
|
+
\`\`\``}case"cache_set":{const{instance_id:n,key:e,value:a,ttl:t,org_id:s}=h,i=await _(n);return t&&t>0?(await i.set(e,a,"EX",t),s&&await i.set(`org:${s}:sem:${e}`,a,"EX",t),`\u2705 Set \`${e}\` (TTL: ${t}s)${s?` + shared to \`org:${s}:sem\``:""}`):(await i.set(e,a),s&&await i.set(`org:${s}:sem:${e}`,a),`\u2705 Set \`${e}\` (no expiry)${s?` + shared to \`org:${s}:sem\``:""}`)}case"cache_delete":{const{instance_id:n,keys:e}=h;return`\u2705 Deleted **${await(await _(n)).del(...e)}** of ${e.length} key(s): ${e.map(s=>`\`${s}\``).join(", ")}`}case"cache_exists":{const{instance_id:n,keys:e}=h;return`**${await(await _(n)).exists(...e)}** of ${e.length} key(s) exist in cache.`}case"cache_ttl":{const{instance_id:n,key:e}=h,t=await(await _(n)).ttl(e);if(t===-2)return`Key \`${e}\` \u2192 **does not exist**`;if(t===-1)return`Key \`${e}\` \u2192 **no expiry** (persists forever)`;const s=Math.floor(t/60),i=t%60;return`Key \`${e}\` \u2192 TTL: **${t}s** (${s}m ${i}s remaining)`}case"cache_keys":{const{instance_id:n,pattern:e="*",count:a=50}=h,t=Math.min(a,500),s=await _(n),i=[],r=s.scanStream({match:e,count:100});await new Promise((o,p)=>{r.on("data",m=>{i.push(...m),i.length>=t&&(r.destroy(),o())}),r.on("end",o),r.on("error",p)});const c=i.slice(0,t);return c.length===0?`No keys found matching pattern \`${e}\`.`:[`Found **${c.length}** key(s) matching \`${e}\`:`,...c.map(o=>` \u2022 \`${o}\``),c.length===t?`
|
|
5
5
|
_(showing first ${t} \u2014 narrow pattern to see more)_`:""].filter(Boolean).join(`
|
|
6
|
-
`)}case"cache_stats":{const{instance_id:n}=
|
|
7
|
-
]+)`))?.[1]?.trim()??"n/a",
|
|
8
|
-
`).filter(
|
|
9
|
-
${
|
|
10
|
-
`)}`:" \u{1F5C2}\uFE0F Keyspace: (empty)"];try{const
|
|
11
|
-
`)}case"semantic_search":{const{instance_id:n,query:e,threshold:a=.82,namespace:t="cachly:sem",top_k:s=5,use_hybrid:i=!1,auto_namespace:
|
|
6
|
+
`)}case"cache_stats":{const{instance_id:n}=h,e=await _(n),[a,t,s]=await Promise.all([e.info("memory"),e.info("stats"),e.info("keyspace")]),i=(y,C)=>y.match(new RegExp(`${C}:([^\r
|
|
7
|
+
]+)`))?.[1]?.trim()??"n/a",r=i(a,"used_memory_human"),c=i(a,"used_memory_peak_human"),o=i(t,"keyspace_hits"),p=i(t,"keyspace_misses"),m=i(t,"instantaneous_ops_per_sec"),d=(await e.info("clients")).match(/connected_clients:(\d+)/)?.[1]??"n/a",u=parseInt(o)||0,w=parseInt(p)||0,k=u+w,S=k>0?(u/k*100).toFixed(1):"n/a",$=s.split(`
|
|
8
|
+
`).filter(y=>y.startsWith("db")).map(y=>` ${y.trim()}`),l=[`\u{1F4CA} **Cache Stats for instance \`${n}\`:**`,"",` \u{1F4BE} Memory used: ${r} (peak: ${c})`,` \u26A1 Ops/sec: ${m}`,` \u{1F3AF} Hit rate: ${S}% (${o} hits / ${p} misses)`,` \u{1F517} Clients: ${d}`,"",$.length>0?` \u{1F5C2}\uFE0F Keyspace:
|
|
9
|
+
${$.join(`
|
|
10
|
+
`)}`:" \u{1F5C2}\uFE0F Keyspace: (empty)"];try{const y=await O(`/api/v1/instances/${n}`);if(y.vector_token){const C=process.env.CACHLY_VECTOR_URL??"https://api.cachly.dev",j=await fetch(`${C}/v1/sem/${y.vector_token}/stats`,{signal:AbortSignal.timeout(5e3)});if(j.ok){const g=await j.json(),L=v=>v!==void 0?v.toLocaleString("en-US"):"n/a",H=g.total_hits??0,T=g.total_entries??0;if(H===0&&T===0)l.push("","**Cache usage:**"," \u{1F4ED} No cache hits yet \u2014 you're just getting started.",""," \u{1F680} Next: route LLM calls through cachly to start accumulating hits.",` Docs: ${z("/docs/semantic","cache-stats")}`);else{const v=g.savings?.week_over_week_pct,A=v!==void 0?` ${v>=0?"\u{1F4C8}":"\u{1F4C9}"} 7d trend: ${v>=0?"+":""}${v.toFixed(1)}% vs prev week`:"";l.push("","**Cache usage:**",` \u{1F4E6} Semantic entries: ${L(g.total_entries)}`,` \u{1F3AF} Total cache hits: ${L(g.total_hits)}`,` \u23F1\uFE0F Hits last 24h: ${L(g.savings?.hits_last_24h)}`,` \u{1F4C5} Hits last 7d: ${L(g.savings?.hits_last_7d)}`,...A?[A]:[])}g.top_hits&&g.top_hits.length>0&&(l.push("","\u{1F51D} Top cached prompts:"),g.top_hits.slice(0,3).forEach((A,f)=>{const x=A.prompt.length>50?`${A.prompt.slice(0,50)}\u2026`:A.prompt;l.push(` ${f+1}. "${x}" (hit ${A.hit_count}\xD7)`)}))}}}catch{}return l.join(`
|
|
11
|
+
`)}case"semantic_search":{const{instance_id:n,query:e,threshold:a=.82,namespace:t="cachly:sem",top_k:s=5,use_hybrid:i=!1,auto_namespace:r=!1}=h,c=r?q(e):t;if(!D())return`\u274C semantic_search requires an embedding provider.
|
|
12
12
|
|
|
13
|
-
Current: ${
|
|
13
|
+
Current: ${B()}
|
|
14
14
|
|
|
15
15
|
Easiest: sign in (CACHLY_JWT). Embeddings then run on the cachly servers in the EU, no extra key.
|
|
16
16
|
Or set OLLAMA_BASE_URL to use a local model (no key needed).
|
|
17
|
-
Your own key with a third-party provider works only if you name the provider yourself: CACHLY_EMBED_PROVIDER=<openai|gemini|mistral|cohere> plus its API key. The text is then sent to that provider.`;const
|
|
17
|
+
Your own key with a third-party provider works only if you name the provider yourself: CACHLY_EMBED_PROVIDER=<openai|gemini|mistral|cohere> plus its API key. The text is then sent to that provider.`;const o=await O(`/api/v1/instances/${n}`);if(!o.vector_token)return`\u274C Instance "${o.name}" needs a vector token for semantic search.
|
|
18
18
|
|
|
19
|
-
Every tier includes one. Open ${
|
|
19
|
+
Every tier includes one. Open ${z(`/instances/${n}`,"health")} and rotate the vector token.`;const p=await I(e),m=process.env.CACHLY_VECTOR_URL??`https://api.cachly.dev/v1/sem/${o.vector_token}`,d={embedding:p,namespace:c,threshold:a,top_k:s};i&&(d.hybrid=!0,d.prompt=e);const u=await fetch(`${m}/search`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d),signal:AbortSignal.timeout(8e3)});if(!u.ok){const $=(await u.text().catch(()=>"")).replace(/\s+/g," ").trim().slice(0,200);throw new F(W.InternalError,`Vector search failed (HTTP ${u.status}${u.statusText?` ${u.statusText}`:""})${$?`: ${$}`:""}`)}const w=await u.json();if(!w.length||w.length===1&&!w[0].found)return`\u{1F50D} No semantically similar entries found for:
|
|
20
20
|
_"${e}"_
|
|
21
21
|
|
|
22
|
-
Try lowering the threshold (current: ${a}) or using different keywords
|
|
23
|
-
|
|
24
|
-
`)}case"
|
|
22
|
+
Try lowering the threshold (current: ${a}) or using different keywords.`+(c==="cachly:sem"?`
|
|
23
|
+
Files indexed with index_project are stored in namespace "cachly:sem:code". Search there with namespace: "cachly:sem:code".`:"");const k=await _(n),S=[`\u{1F50D} **Semantic search results** for: _"${e}"_`,` Threshold: ${a} \xB7 Namespace: \`${c}\``,""];for(const $ of w){if(!$.found||!$.id)continue;const l=await k.get(`${c}:val:${$.id}`);S.push(`**Match** (similarity: ${(($.similarity??0)*100).toFixed(1)}%)`,` Prompt: _"${$.prompt??"(unknown)"}"_`,l?` Value: \`${l.slice(0,200)}${l.length>200?"\u2026":""}\``:" Value: _(evicted from cache)_","")}return S.join(`
|
|
24
|
+
`)}case"detect_namespace":{const{prompt:n}=h,e=q(n),a=e.split(":").pop(),t={code:"\u{1F4BB} Code \u2014 contains programming constructs or syntax",translation:"\u{1F310} Translation \u2014 asks to translate between languages",summary:"\u{1F4DD} Summary \u2014 requests a summary or key points (TL;DR)",qa:"\u2753 Q&A \u2014 a direct question or query",creative:"\u{1F3A8} Creative \u2014 general, creative, or conversational prompt"};return[`**Detected namespace:** \`${e}\``,`**Type:** ${t[a]??a}`,"",`_Prompt: "${n.slice(0,120)}${n.length>120?"\u2026":""}"_`,"","\u{1F4A1} Use this namespace in `semantic_search` or `cache_warmup` for better hit rates."," Set `auto_namespace: true` to apply this detection automatically."].join(`
|
|
25
|
+
`)}case"cache_warmup":{const{instance_id:n,entries:e,namespace:a="cachly:sem",ttl:t,auto_namespace:s=!1}=h;if(!D())return`\u274C cache_warmup requires an embedding provider.
|
|
25
26
|
|
|
26
|
-
Current: ${
|
|
27
|
+
Current: ${B()}
|
|
27
28
|
|
|
28
29
|
Supported: openai (default) \xB7 mistral \xB7 cohere \xB7 ollama (local) \xB7 gemini
|
|
29
|
-
Set CACHLY_EMBED_PROVIDER and the matching API key env var.`;const i=await
|
|
30
|
-
`)}case"index_project":{const{instance_id:n,dir:e,extensions:a,max_files:t=100,ttl:s=86400,summary_chars:i=1200,namespace:
|
|
31
|
-
Extensions checked: ${[...
|
|
32
|
-
${
|
|
33
|
-
${
|
|
34
|
-
`)}case"cache_mset":{const{instance_id:n,items:e}=
|
|
30
|
+
Set CACHLY_EMBED_PROVIDER and the matching API key env var.`;const i=await O(`/api/v1/instances/${n}`),r=process.env.CACHLY_VECTOR_URL??(i.vector_token?`https://api.cachly.dev/v1/sem/${i.vector_token}`:null),c=await _(n);let o=0,p=0;const m=[];for(const d of e){const u=d.namespace??(s?q(d.prompt):a),w=await I(d.prompt);let k=!1;if(r){const l=await fetch(`${r}/search`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({embedding:w,namespace:u,threshold:.98}),signal:AbortSignal.timeout(8e3)}).catch(()=>null);l?.ok&&(k=(await l.json())[0]?.found??!1)}if(k){p++,m.push(` \u23ED\uFE0F _"${d.prompt.slice(0,60)}${d.prompt.length>60?"\u2026":""}"_ \u2192 already cached`);continue}const S=M(),$=`${u}:val:${S}`;if(t&&t>0?await c.set($,d.value,"EX",t):await c.set($,d.value),r){const l={id:S,prompt:d.prompt,namespace:u,embedding:w};t&&t>0&&(l.expires_at=new Date(Date.now()+t*1e3).toISOString()),await fetch(`${r}/entries`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(l),signal:AbortSignal.timeout(8e3)}).catch(()=>{})}else{const l=`${u}:emb:${S}`,y=JSON.stringify({embedding:w,prompt:d.prompt});t&&t>0?await c.set(l,y,"EX",t):await c.set(l,y)}o++,m.push(` \u2705 _"${d.prompt.slice(0,60)}${d.prompt.length>60?"\u2026":""}"_ \u2192 \`${u}\``)}return["\u{1F525} **Cache Warmup Complete**","",` \u2705 Warmed: **${o}** new entries`,` \u23ED\uFE0F Skipped: **${p}** (already cached at \u2265 0.98 similarity)`,` \u{1F4E6} Total: ${e.length}`,s?" \u{1F3F7}\uFE0F Namespacing: auto-detected per prompt":` \u{1F3F7}\uFE0F Namespace: \`${a}\``,r?" \u{1F50D} Mode: pgvector HNSW":" \u{1F50D} Mode: Valkey SCAN (set CACHLY_VECTOR_URL for pgvector search)","",...m].join(`
|
|
31
|
+
`)}case"index_project":{const{instance_id:n,dir:e,extensions:a,max_files:t=100,ttl:s=86400,summary_chars:i=1200,namespace:r="cachly:sem:code",hintergrund:c=!1}=h;let o=e;const p=new Set((a??["ts","js","tsx","jsx","go","py","java","rs","md","kt","swift"]).map(f=>f.startsWith(".")?f:`.${f}`)),m=[];async function d(f){if(m.length>=t)return;const x=await Z(f,{withFileTypes:!0}).catch(()=>null);if(x)for(const b of x){if(m.length>=t)break;const N=se(f,b.name);if(b.isDirectory()){if([".git","node_modules","dist","build",".next","__pycache__","vendor"].includes(b.name))continue;await d(N)}else b.isFile()&&p.has(ne(b.name).toLowerCase())&&m.push(N)}}if(typeof o!="string"||!o.trim()){const f=h.path;if(typeof f=="string"&&f.trim())o=f;else return"\u274C index_project needs `dir`: the absolute path of the folder to index."}if(await d(o),m.length===0)return`\u274C No matching files found in \`${o}\`.
|
|
32
|
+
Extensions checked: ${[...p].join(", ")}`;const u=await O(`/api/v1/instances/${n}`),w=process.env.CACHLY_VECTOR_URL??(u.vector_token?`https://api.cachly.dev/v1/sem/${u.vector_token}`:null),k=w&&D();let S=0,$=0,l=0,y=0,C=0,j,g=0;const L=[],H=new Date(Date.now()+s*1e3).toISOString(),T=await _(n),v=ie({hintergrund:c});for(const f of m){const x=ae(o,f);let b,N;try{const E=await ee(f);if(E.size>2e5){$++;continue}N=E.size,b=await te(f,"utf-8")}catch{l++;continue}const P=`cachly:idx:hash:${x}`,K=`${N}:${b.length}:${ce(b)}`;if(await T.get(P)===K){const E=`cachly:idx:${x}`;s>0&&await T.expire(E,s),s>0&&await T.expire(P,s),g++;continue}const V=b.slice(0,i).replace(/\s+/g," ").trim(),Y=`cachly:idx:${x}`,J=`File: ${x}
|
|
33
|
+
${V}`;if(s>0?await T.set(Y,J,"EX",s):await T.set(Y,J),s>0?await T.set(P,K,"EX",s):await T.set(P,K),S++,L.push(` \u2705 ${x}`),k&&v.darf())try{const E=`File: ${x}
|
|
34
|
+
${V}`;await v.warte();const Q=await I(E).catch(R=>{throw v.fehler(R),R}),X=M(),U=await fetch(`${w}/entries`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:X,prompt:E,namespace:r,embedding:Q,expires_at:H}),signal:AbortSignal.timeout(8e3)});if(!U.ok){const R=(await U.text().catch(()=>"")).replace(/\s+/g," ").trim().slice(0,200);throw new Error(`HTTP ${U.status}${R?`: ${R}`:""}`)}await T.set(`${r}:val:${X}`,x,"EX",s),y++}catch(E){C++,j??=E instanceof Error?E.message:String(E),await T.del(P).catch(()=>{})}}return[`\u{1F4C2} **index_project Complete** \u2014 ${k?"\u{1F50D} Keyword + \u{1F3AF} Semantic":"\u{1F50D} Keyword only (no embedding provider)"}`,"",` \u{1F4C1} Dir: ${o}`,` \u2705 Indexed: **${S}** files (new/changed)`,` \u267B\uFE0F Unchanged: ${g} files (hash match \u2014 skipped)`,...k?[` \u{1F3AF} Semantic: **${y}** files (vector-searchable)`]:[],...C>0?[` \u26A0\uFE0F Semantic failed for ${C} file(s): ${j}. The keyword index still works (brain_search).`]:[],` \u23ED\uFE0F Skipped: ${$} (too large or filtered)`,` \u274C Errors: ${l}`,` \u23F1\uFE0F TTL: ${s}s (${Math.round(s/3600)}h)`,"","\u{1F4A1} **Next steps:**",' 1. Use `smart_recall("how does auth work")` to find relevant files.'," 2. Re-run index_project after major refactors.",...k?[]:[" 3. Sign in (CACHLY_JWT) or set OLLAMA_BASE_URL to also enable semantic search."],"",...L.length<=20?L:[...L.slice(0,20),` \u2026 and ${L.length-20} more`]].join(`
|
|
35
|
+
`)}case"cache_mset":{const{instance_id:n,items:e}=h;if(!Array.isArray(e)||e.length===0)return"\u26A0\uFE0F No items provided.";const t=(await _(n)).pipeline();for(const s of e){const i=typeof s.value=="string"?s.value:JSON.stringify(s.value);s.ttl&&s.ttl>0?t.set(s.key,i,"EX",s.ttl):t.set(s.key,i)}return await t.exec(),`\u2705 **cache_mset** \u2013 ${e.length} key(s) written in one pipeline round-trip.
|
|
35
36
|
`+e.map(s=>` \u2022 \`${s.key}\`${s.ttl?` (TTL ${s.ttl}s)`:""}`).join(`
|
|
36
|
-
`)}case"cache_mget":{const{instance_id:n,keys:e}=
|
|
37
|
+
`)}case"cache_mget":{const{instance_id:n,keys:e}=h;if(!Array.isArray(e)||e.length===0)return"\u26A0\uFE0F No keys provided.";const t=await(await _(n)).mget(...e),s=e.map((i,r)=>{const c=t[r];return c===null?` \u2022 \`${i}\`: _null (miss)_`:` \u2022 \`${i}\`: ${c}`});return`\u2705 **cache_mget** \u2013 ${e.length} key(s) fetched in one round-trip.
|
|
37
38
|
`+s.join(`
|
|
38
|
-
`)}case"cache_lock_acquire":{const{instance_id:n,key:e,ttl_ms:a=5e3,retries:t=3,retry_delay_ms:s=50}=
|
|
39
|
+
`)}case"cache_lock_acquire":{const{instance_id:n,key:e,ttl_ms:a=5e3,retries:t=3,retry_delay_ms:s=50}=h,i=await _(n),r=`cachly:lock:${e}`,c=M();for(let o=0;o<=t;o++){if(await i.set(r,c,"PX",a,"NX")==="OK")return`\u{1F512} **cache_lock_acquire** \u2013 Lock acquired!
|
|
39
40
|
|
|
40
41
|
Key: \`${e}\`
|
|
41
42
|
Token: \`${c}\`
|
|
42
43
|
TTL: ${a} ms
|
|
43
44
|
|
|
44
|
-
\u{1F4A1} Use **cache_lock_release** with this token to release early.`;
|
|
45
|
+
\u{1F4A1} Use **cache_lock_release** with this token to release early.`;o<t&&await new Promise(m=>setTimeout(m,s))}return`\u274C **cache_lock_acquire** \u2013 Could not acquire lock for \`${e}\` after ${t+1} attempts.`}case"cache_lock_release":{const{instance_id:n,key:e,token:a}=h;return await(await _(n)).eval('if redis.call("get",KEYS[1])==ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end',1,`cachly:lock:${e}`,a)===1?`\u{1F513} **cache_lock_release** \u2013 Lock \`${e}\` released.`:`\u26A0\uFE0F **cache_lock_release** \u2013 Lock \`${e}\` expired or token mismatch.`}case"cache_stream_set":{const{instance_id:n,key:e,chunks:a,ttl:t}=h;if(!Array.isArray(a)||a.length===0)return"\u26A0\uFE0F No chunks provided.";const s=await _(n),i=`cachly:stream:${e}`;await s.del(i);const r=s.pipeline();for(const c of a)r.rpush(i,c);return t&&t>0&&r.expire(i,t),await r.exec(),`\u2705 **cache_stream_set** \u2013 ${a.length} chunk(s) stored.
|
|
45
46
|
Key: \`${e}\`
|
|
46
47
|
${t?` TTL: ${t}s
|
|
47
|
-
`:""} Total size: ${a.reduce((c,
|
|
48
|
+
`:""} Total size: ${a.reduce((c,o)=>c+o.length,0)} chars`}case"cache_stream_get":{const{instance_id:n,key:e}=h,a=await _(n),t=`cachly:stream:${e}`,s=await a.llen(t);if(s===0)return`\u26A0\uFE0F **cache_stream_get** \u2013 Cache miss for key \`${e}\`.`;const r=(await a.lrange(t,0,-1)).join(""),c=r.slice(0,500);return`\u2705 **cache_stream_get** \u2013 ${s} chunk(s) retrieved for \`${e}\`.
|
|
48
49
|
|
|
49
50
|
**Preview** (first 500 chars):
|
|
50
51
|
\`\`\`
|
|
51
|
-
${c}${c.length<
|
|
52
|
-
\`\`\``}case"cache_org_stats":{const{instance_id:n,org_id:e}=
|
|
53
|
-
`)}case"set_cost_per_call":{const{instance_id:n,cost_per_call_usd:e}=
|
|
54
|
-
`)}default:return null}}export{
|
|
52
|
+
${c}${c.length<r.length?"\u2026":""}
|
|
53
|
+
\`\`\``}case"cache_org_stats":{const{instance_id:n,org_id:e}=h,a=await _(n),t=`org:${e}:sem`,i=(await re(a,`${t}:*`,{max:1e4,timeoutMs:5e3})).length,r=[`\u{1F3E2} Org Cache Stats for org \`${e}\`:`,` \u{1F4E6} Shared entries: ${i}`,` \u{1F511} Namespace: ${t}`," \u{1F465} Benefit: All instances sharing this org_id serve from the same cache","",`\u{1F4A1} To enable sharing: set org_id="${e}" in cache_set calls`];try{const c=await O(`/api/v1/orgs/${e}/savings`);r.push("","Org-wide cache usage:",` \u{1F5A5}\uFE0F Instances: ${c.instance_count}`,` \u{1F3AF} Total cache hits: ${c.total_hits.toLocaleString("en-US")}`,` \u23F1\uFE0F Hits last 24h: ${c.hits_last_24h.toLocaleString("en-US")}`);const o=[...c.per_instance??[]].sort((p,m)=>m.total_hits-p.total_hits).slice(0,5);if(o.length>0){r.push(""," Top instances by hits:");for(const p of o)r.push(` \u2022 ${p.name||p.instance_id}: ${p.total_hits.toLocaleString("en-US")} hits`)}}catch{}return r.join(`
|
|
54
|
+
`)}case"set_cost_per_call":{const{instance_id:n,cost_per_call_usd:e}=h;if(!e||e<=0||e>100)throw new F(W.InvalidParams,"cost_per_call_usd must be a positive number \u2264 100");await O(`/api/v1/instances/${n}/cost-per-call`,{method:"PUT",body:JSON.stringify({cost_per_call_usd:e})});const a={"0.02":"claude-opus-4.8","0.015":"gpt-5.5","0.009":"claude-sonnet-4.6","0.002":"gpt-5.5-mini","0.001":"claude-haiku-4.5"},t=a[e.toString()]?` (${a[e.toString()]})`:"";return[`\u2705 **Cost per call updated** \u2014 $${e.toFixed(4)}${t}`,"",`ROI savings in \`cache_stats\` will now use $${e.toFixed(4)} per avoided LLM call.`,"Run `cache_stats` to see your updated savings estimate."].join(`
|
|
55
|
+
`)}default:return null}}export{we as CACHE_TOOL_NAMES,ge as handleCacheTool};
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import{computeEmbedding as _}from"../embeddings.js";import{schwaerzeFelder as k}from"../geheimnis-filter.js";import{pruefeFelder as S,verdachtsWarnung as b}from"../tuersteher.js";import{sofortAntwortNeuesBrain as
|
|
2
|
-
`)}catch(
|
|
3
|
-
`)}return["\u{1F9E0} **Context Saved**","",` Key: \`${
|
|
4
|
-
`)}case"recall_context":{const{instance_id:d,key:
|
|
5
|
-
`];for(const s of e.slice(0,10)){const r=await
|
|
1
|
+
import{computeEmbedding as _}from"../embeddings.js";import{schwaerzeFelder as k}from"../geheimnis-filter.js";import{pruefeFelder as S,verdachtsWarnung as b}from"../tuersteher.js";import{sofortAntwortNeuesBrain as g}from"../leeres-brain.js";const j=new Set(["remember_context","recall_context","list_remembered","forget_context"]);async function z(p,m,y,x){switch(p){case"remember_context":{const d=k(m).felder,c=S(d),{instance_id:h,key:o,content:a,category:e="custom",ttl:t=86400}=d,n=await y(h),s=`cachly:ctx:${e}:${o}`,r=JSON.stringify({key:o,category:e,size:a.length,created:new Date().toISOString(),...c.length?{verdacht:c}:{}});t&&t>0?(await n.set(s,a,"EX",t),await n.set(`${s}:meta`,r,"EX",t)):(await n.set(s,a),await n.set(`${s}:meta`,r));const i=await x(`/api/v1/instances/${h}`);if(i.vector_token)try{const l=await _(`${o}: ${a.slice(0,500)}`),f=`https://api.cachly.dev/v1/sem/${i.vector_token}`,$={id:`ctx:${e}:${o}`,prompt:o,namespace:"cachly:ctx",embedding:l};t&&t>0&&($.expires_at=new Date(Date.now()+t*1e3).toISOString());const u=await fetch(`${f}/entries`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify($),signal:AbortSignal.timeout(8e3)});u.ok||process.stderr.write(`[cachly] semantic index skipped (HTTP ${u.status}) \u2014 smart_recall on this context will be keyword-only
|
|
2
|
+
`)}catch(l){process.stderr.write(`[cachly] semantic index failed: ${l instanceof Error?l.message:String(l)} \u2014 smart_recall on this context will be keyword-only
|
|
3
|
+
`)}return["\u{1F9E0} **Context Saved**","",` Key: \`${o}\``,` Category: ${e}`,` Size: ${a.length} chars`,` TTL: ${t>0?`${t}s (${Math.round(t/3600)}h)`:"no expiry"}`,"",`\u{1F4A1} Use \`recall_context("${o}")\` to retrieve this later.`,` Or \`smart_recall("${o.split("_").join(" ")}")\` for semantic search.`,...c.length?[b(c)]:[]].join(`
|
|
4
|
+
`)}case"recall_context":{const{instance_id:d,key:c}=m,h=await g(d,x,`\u26A0\uFE0F No cached context found ${c.includes("*")?"matching pattern":"for key"} \`${c}\`.`);if(h)return h;const o=await y(d);if(c.includes("*")){const e=[],t=o.scanStream({match:`cachly:ctx:*:${c}`,count:100});if(await new Promise((s,r)=>{t.on("data",i=>{e.push(...i.filter(l=>!l.endsWith(":meta"))),e.length>=20&&(t.destroy(),s())}),t.on("end",s),t.on("error",r)}),e.length===0)return`\u26A0\uFE0F No cached context found matching pattern \`${c}\`.`;const n=[`\u{1F9E0} **Recalled ${e.length} context entries matching \`${c}\`:**
|
|
5
|
+
`];for(const s of e.slice(0,10)){const r=await o.get(s),i=s.replace("cachly:ctx:","");n.push(`### ${i}
|
|
6
6
|
\`\`\`
|
|
7
7
|
${r?.slice(0,500)}${(r?.length??0)>500?"\u2026":""}
|
|
8
8
|
\`\`\`
|
|
9
|
-
`)}return e.length>10&&
|
|
10
|
-
`)}const a=["overview","architecture","file_summary","dependency","thinking","custom"];for(const e of a){const t=await
|
|
11
|
-
`)}}return`\u26A0\uFE0F No cached context found for key \`${
|
|
9
|
+
`)}return e.length>10&&n.push(`_(+${e.length-10} more matches)_`),n.join(`
|
|
10
|
+
`)}const a=["overview","architecture","file_summary","dependency","thinking","custom"];for(const e of a){const t=await o.get(`cachly:ctx:${e}:${c}`);if(t){const n=await o.ttl(`cachly:ctx:${e}:${c}`);return[`\u{1F9E0} **Recalled Context: \`${c}\`**`,"",` Category: ${e}`,` Size: ${t.length} chars`,` TTL: ${n===-1?"no expiry":n===-2?"expired":`${n}s remaining`}`,"","---","",t].join(`
|
|
11
|
+
`)}}return`\u26A0\uFE0F No cached context found for key \`${c}\`.
|
|
12
12
|
|
|
13
|
-
Use \`list_remembered\` to see available cached context.`}case"list_remembered":{const{instance_id:d,category:
|
|
14
|
-
`];for(const r of t.slice(0,
|
|
15
|
-
`)}case"forget_context":{const{instance_id:d,keys:
|
|
13
|
+
Use \`list_remembered\` to see available cached context.`}case"list_remembered":{const{instance_id:d,category:c="all",limit:h=50}=m,o=await g(d,x,"\u{1F4ED} No cached context found.");if(o)return o;const a=await y(d),e=c==="all"?"cachly:ctx:*":`cachly:ctx:${c}:*`,t=[],n=a.scanStream({match:e,count:100});if(await new Promise((r,i)=>{n.on("data",l=>{t.push(...l.filter(f=>!f.endsWith(":meta"))),t.length>=h&&(n.destroy(),r())}),n.on("end",r),n.on("error",i)}),t.length===0)return"\u{1F4ED} No cached context found.\n\nUse `remember_context` to cache context for faster future access.";const s=[`\u{1F9E0} **Cached Context** (${t.length} entries):
|
|
14
|
+
`];for(const r of t.slice(0,h)){const i=await a.ttl(r),l=await a.get(r),f=r.replace("cachly:ctx:","").split(":"),$=f[0],u=f.slice(1).join(":"),w=l?.slice(0,80).replace(/\n/g," ")??"";s.push(` \u2022 **${u}** (${$})`,` Size: ${l?.length??0} chars \xB7 TTL: ${i===-1?"\u221E":`${Math.round(i/60)}m`}`,` _"${w}${(l?.length??0)>80?"\u2026":""}"_`,"")}return s.join(`
|
|
15
|
+
`)}case"forget_context":{const{instance_id:d}=m,c=m.keys??m.key,h=(Array.isArray(c)?c:[c]).filter(e=>typeof e=="string"&&e.trim()!=="");if(h.length===0)return'\u274C forget_context needs `keys`: a list of context keys to delete, e.g. keys: ["wip:billing"]. Use list_remembered to see the keys.';const o=await y(d);let a=0;for(const e of h)if(e.includes("*")){const t=[],n=o.scanStream({match:`cachly:ctx:*:${e}*`,count:100});await new Promise((s,r)=>{n.on("data",i=>t.push(...i)),n.on("end",s),n.on("error",r)}),t.length>0&&(a+=await o.del(...t))}else{const t=["overview","architecture","file_summary","dependency","thinking","custom"];for(const n of t)a+=await o.del(`cachly:ctx:${n}:${e}`,`cachly:ctx:${n}:${e}:meta`)}return`\u{1F5D1}\uFE0F **Forgot ${a} context entries.**
|
|
16
16
|
|
|
17
|
-
Keys: ${
|
|
17
|
+
Keys: ${h.map(e=>`\`${e}\``).join(", ")}`}default:return null}}export{j as CONTEXT_TOOL_NAMES,z as handleContextTool};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import{safeJsonParse as
|
|
2
|
-
`)}case"roadmap_update":{const{instance_id:
|
|
3
|
-
[${new Date().toISOString().slice(0,10)}] ${t}`:`[${new Date().toISOString().slice(0,10)}] ${t}`),o.updated=new Date().toISOString(),r.verdacht.length&&(o.verdacht=[...new Set([...Array.isArray(o.verdacht)?o.verdacht:[],...r.verdacht])]),await $.hset(`cachly:roadmap:${
|
|
4
|
-
`)}case"roadmap_list":{const{instance_id:
|
|
5
|
-
`).pop()?.slice(0,100)}`)}
|
|
6
|
-
`)}case"roadmap_next":{const{instance_id:
|
|
7
|
-
Tags: ${
|
|
1
|
+
import{safeJsonParse as O}from"../utils.js";import{sofortAntwortNeuesBrain as I}from"../leeres-brain.js";import{fuerEigenenSpeicher as T,hinweise as j}from"../schreibtor.js";const A=["planned","in-progress","done","blocked","cancelled"],S=["critical","high","medium","low"],M=new Set(["roadmap_add","roadmap_update","roadmap_list","roadmap_next"]);async function v(D,w,y,R){switch(D){case"roadmap_add":{const{instance_id:l,title:a,description:n="",priority:d="medium",tags:f=[],milestone:h=""}=w,g=T({title:a,description:n,tags:f,milestone:h}),{title:r,description:t,tags:c,milestone:m}=g.felder;if(!S.includes(d))return`\u26A0\uFE0F **roadmap_add** \u2013 Unknown priority \`${d}\`. Use one of: ${S.join(", ")}.`;const $=await y(l),s=`rm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,6)}`,o={id:s,title:r,description:t,priority:d,tags:c,milestone:m,status:"planned",created:new Date().toISOString(),updated:new Date().toISOString(),notes:"",...g.verdacht.length?{verdacht:g.verdacht}:{}};await $.hset(`cachly:roadmap:${l}`,s,JSON.stringify(o));const i={critical:"\u{1F534}",high:"\u{1F7E0}",medium:"\u{1F7E1}",low:"\u{1F535}"};return["\u{1F4CB} **Roadmap item added**","",` ID: \`${s}\``,` Title: ${r}`,` Priority: ${i[d]??"\u26AA"} ${d}`," Status: planned",m?` Milestone: ${m}`:"",c.length?` Tags: ${c.join(", ")}`:"","",`\u{1F4A1} Use \`roadmap_update(id: "${s}", status: "in-progress")\` when you start working on it.`,...j(g,"notiz")].filter(Boolean).join(`
|
|
2
|
+
`)}case"roadmap_update":{const{instance_id:l,id:a,status:n,priority:d,notes:f,title:h,description:g}=w,r=T({notes:f,title:h,description:g}),{notes:t,title:c,description:m}=r.felder;if(n&&!A.includes(n))return`\u26A0\uFE0F **roadmap_update** \u2013 Unknown status \`${n}\`. Use one of: ${A.join(", ")}.`;if(d&&!S.includes(d))return`\u26A0\uFE0F **roadmap_update** \u2013 Unknown priority \`${d}\`. Use one of: ${S.join(", ")}.`;const $=await y(l),s=await $.hget(`cachly:roadmap:${l}`,a);if(!s)return`\u26A0\uFE0F **roadmap_update** \u2013 Item \`${a}\` not found. Use \`roadmap_list\` to see all items.`;const o=O(s,null);if(!o)return`\u26A0\uFE0F **roadmap_update** \u2013 Item \`${a}\` data is corrupted. Re-add with \`roadmap_add\`.`;const i=o.status;return n&&(o.status=n),d&&(o.priority=d),c&&(o.title=c),m&&(o.description=m),t&&(o.notes=o.notes?`${o.notes}
|
|
3
|
+
[${new Date().toISOString().slice(0,10)}] ${t}`:`[${new Date().toISOString().slice(0,10)}] ${t}`),o.updated=new Date().toISOString(),r.verdacht.length&&(o.verdacht=[...new Set([...Array.isArray(o.verdacht)?o.verdacht:[],...r.verdacht])]),await $.hset(`cachly:roadmap:${l}`,a,JSON.stringify(o)),[`${{planned:"\u{1F4CB}","in-progress":"\u26A1",done:"\u2705",blocked:"\u{1F6AB}",cancelled:"\u{1F5D1}\uFE0F"}[n??i]??"\u{1F4CB}"} **Roadmap updated** \`${a}\``,"",` Title: ${o.title}`,n&&i!==n?` Status: ${i} \u2192 ${n}`:` Status: ${o.status}`,t?` Notes: ${t}`:"",...j(r,"notiz")].filter(Boolean).join(`
|
|
4
|
+
`)}case"roadmap_list":{const{instance_id:l,status:a="open",tag:n,milestone:d,priority:f}=w,h=await I(l,R,"\u{1F4CB} **Roadmap is empty.**");if(h)return h;const r=await(await y(l)).hgetall(`cachly:roadmap:${l}`);if(!r||Object.keys(r).length===0)return"\u{1F4CB} **Roadmap is empty.**\n\nUse `roadmap_add` to create your first item.";const t={critical:0,high:1,medium:2,low:3},c={critical:"\u{1F534}",high:"\u{1F7E0}",medium:"\u{1F7E1}",low:"\u{1F535}"},m={planned:"\u{1F4CB}","in-progress":"\u26A1",done:"\u2705",blocked:"\u{1F6AB}",cancelled:"\u{1F5D1}\uFE0F"},$=new Set(["planned","in-progress","blocked"]);let s=Object.values(r).flatMap(e=>{const u=O(e,null);return u?[u]:[]});if(a==="open"?s=s.filter(e=>$.has(e.status)):a&&(s=s.filter(e=>e.status===a)),n&&(s=s.filter(e=>e.tags.includes(n))),d&&(s=s.filter(e=>e.milestone===d)),f&&(s=s.filter(e=>(t[e.priority]??99)<=(t[f]??99))),s.sort((e,u)=>{const p=t[e.priority]??99,_=t[u.priority]??99;return p!==_?p-_:e.created.localeCompare(u.created)}),s.length===0)return`\u{1F4CB} **No roadmap items** match the current filter (status: ${a}).`;const o={};for(const e of s){const u=e.status;o[u]||(o[u]=[]),o[u].push(e)}const i=[`\u{1F4CB} **Roadmap** (${s.length} item${s.length!==1?"s":""})`,""];for(const[e,u]of Object.entries(o)){i.push(`**${m[e]??"\u2022"} ${e.toUpperCase()}** (${u.length})`);for(const p of u){const _=p.tags.length?` [${p.tags.join(", ")}]`:"",N=p.milestone?` \xB7 ${p.milestone}`:"";i.push(` ${c[p.priority]??"\u26AA"} \`${p.id}\` **${p.title}**${_}${N}`),p.description&&i.push(` ${p.description.slice(0,120)}`),p.notes&&i.push(` \u{1F4DD} ${p.notes.split(`
|
|
5
|
+
`).pop()?.slice(0,100)}`)}i.push("")}return i.push('\u{1F4A1} `roadmap_update(id, status: "in-progress")` to start \xB7 `roadmap_next` for top priority item'),i.join(`
|
|
6
|
+
`)}case"roadmap_next":{const{instance_id:l,tag:a}=w,n=await I(l,R,"\u{1F4CB} **Roadmap is empty.**");if(n)return n;const f=await(await y(l)).hgetall(`cachly:roadmap:${l}`);if(!f||Object.keys(f).length===0)return"\u{1F4CB} **Roadmap is empty.** Use `roadmap_add` to plan your first task.";const h={critical:0,high:1,medium:2,low:3},g={critical:"\u{1F534}",high:"\u{1F7E0}",medium:"\u{1F7E1}",low:"\u{1F535}"},r=Object.values(f).flatMap(i=>{const e=O(i,null);return e?[e]:[]}).filter(i=>i.status==="in-progress"||i.status==="planned").filter(i=>!a||(Array.isArray(i.tags)?i.tags:[]).includes(a));if(r.length===0)return"\u{1F389} **No open roadmap items!** All tasks are done (or use `roadmap_list` to check).";r.sort((i,e)=>i.status==="in-progress"&&e.status!=="in-progress"?-1:e.status==="in-progress"&&i.status!=="in-progress"?1:(h[i.priority]??99)-(h[e.priority]??99));const t=r[0],c=r.length-1,m=Array.isArray(t.tags)?t.tags:[],$=m.length?`
|
|
7
|
+
Tags: ${m.join(", ")}`:"",s=t.milestone?`
|
|
8
8
|
Milestone: ${t.milestone}`:"",o=t.notes?`
|
|
9
9
|
Notes: ${t.notes.split(`
|
|
10
10
|
`).pop()?.slice(0,120)}`:"";return[`${t.status==="in-progress"?"\u26A1":"\u{1F4CB}"} **Next up: ${t.title}**`,"",`ID: \`${t.id}\``,`Priority: ${g[t.priority]??"\u26AA"} ${t.priority}`,`Status: ${t.status}`,t.description?`
|
|
11
11
|
What to do:
|
|
12
|
-
${t.description}`:"",$,
|
|
13
|
-
`)}default:return null}}export{
|
|
12
|
+
${t.description}`:"",$,s,o,"",c>0?`(+${c} more open item${c!==1?"s":""} in roadmap)`:"(last open item)","",t.status==="planned"?`\u{1F4A1} Start with: \`roadmap_update(id: "${t.id}", status: "in-progress")\``:`\u{1F4A1} Finish with: \`roadmap_update(id: "${t.id}", status: "done", notes: "...")\``].filter(i=>i!==void 0).join(`
|
|
13
|
+
`)}default:return null}}export{S as ROADMAP_PRIORITY,A as ROADMAP_STATUS,M as ROADMAP_TOOL_NAMES,v as handleRoadmapTool};
|