@cachly-dev/mcp-server 0.10.219 → 0.10.221

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.
@@ -1,14 +1,14 @@
1
- import{randomUUID as R}from"node:crypto";import{McpError as V,ErrorCode as Y}from"@modelcontextprotocol/sdk/types.js";import{readdir as B,stat as z,readFile as G}from"node:fs/promises";import{join as Q,relative as Z,extname as ee}from"node:path";import{computeEmbedding as P,hasEmbedProvider as K,embedProviderHint as J}from"../embeddings.js";import{einbettTakt as te}from"../einbett-takt.js";import{detectNamespace as U}from"../namespace.js";import{simpleHash as se}from"../confidence.js";import{scanKeys as ae}from"../utils.js";import{cachlyUrl as X}from"../cachly-url.js";const ue=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 $e(F,_,f,b){switch(F){case"cache_get":{const{instance_id:n,key:e,org_id:a}=_,t=await f(n);let s=await t.get(e),i="direct";if(s===null&&a){const r=`org:${a}:sem:${e}`;s=await t.get(r),s!==null&&(i=`org:${a}:sem`)}if(s===null)return`Key \`${e}\` \u2192 **not found** (null)`;let c=s;try{c=JSON.stringify(JSON.parse(s),null,2)}catch{}const o=i!=="direct"?` _(from shared org namespace \`${i}\`)_`:"";return`Key \`${e}\`:${o}
1
+ import{randomUUID as j}from"node:crypto";import{McpError as V,ErrorCode as Y}from"@modelcontextprotocol/sdk/types.js";import{readdir as F,stat as z,readFile as G}from"node:fs/promises";import{join as Q,relative as Z,extname as ee}from"node:path";import{computeEmbedding as R,hasEmbedProvider as K,embedProviderHint as J}from"../embeddings.js";import{einbettTakt as te}from"../einbett-takt.js";import{detectNamespace as P}from"../namespace.js";import{simpleHash as se}from"../confidence.js";import{scanKeys as ae}from"../utils.js";import{cachlyUrl as X}from"../cachly-url.js";const _e=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 $e(W,p,$,b){switch(W){case"cache_get":{const{instance_id:n,key:e,org_id:a}=p,t=await $(n);let s=await t.get(e),i="direct";if(s===null&&a){const r=`org:${a}:sem:${e}`;s=await t.get(r),s!==null&&(i=`org:${a}:sem`)}if(s===null)return`Key \`${e}\` \u2192 **not found** (null)`;let o=s;try{o=JSON.stringify(JSON.parse(s),null,2)}catch{}const c=i!=="direct"?` _(from shared org namespace \`${i}\`)_`:"";return`Key \`${e}\`:${c}
2
2
  \`\`\`
3
- ${c}
4
- \`\`\``}case"cache_set":{const{instance_id:n,key:e,value:a,ttl:t,org_id:s}=_,i=await f(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}=_;return`\u2705 Deleted **${await(await f(n)).del(...e)}** of ${e.length} key(s): ${e.map(s=>`\`${s}\``).join(", ")}`}case"cache_exists":{const{instance_id:n,keys:e}=_;return`**${await(await f(n)).exists(...e)}** of ${e.length} key(s) exist in cache.`}case"cache_ttl":{const{instance_id:n,key:e}=_,t=await(await f(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}=_,t=Math.min(a,500),s=await f(n),i=[],c=s.scanStream({match:e,count:100});await new Promise((r,u)=>{c.on("data",d=>{i.push(...d),i.length>=t&&(c.destroy(),r())}),c.on("end",r),c.on("error",u)});const o=i.slice(0,t);return o.length===0?`No keys found matching pattern \`${e}\`.`:[`Found **${o.length}** key(s) matching \`${e}\`:`,...o.map(r=>` \u2022 \`${r}\``),o.length===t?`
3
+ ${o}
4
+ \`\`\``}case"cache_set":{const{instance_id:n,key:e,value:a,ttl:t,org_id:s}=p,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}=p;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}=p;return`**${await(await $(n)).exists(...e)}** of ${e.length} key(s) exist in cache.`}case"cache_ttl":{const{instance_id:n,key:e}=p,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}=p,t=Math.min(a,500),s=await $(n),i=[],o=s.scanStream({match:e,count:100});await new Promise((r,l)=>{o.on("data",f=>{i.push(...f),i.length>=t&&(o.destroy(),r())}),o.on("end",r),o.on("error",l)});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(r=>` \u2022 \`${r}\``),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}=_,e=await f(n),[a,t,s]=await Promise.all([e.info("memory"),e.info("stats"),e.info("keyspace")]),i=(w,L)=>w.match(new RegExp(`${L}:([^\r
7
- ]+)`))?.[1]?.trim()??"n/a",c=i(a,"used_memory_human"),o=i(a,"used_memory_peak_human"),r=i(t,"keyspace_hits"),u=i(t,"keyspace_misses"),d=i(t,"instantaneous_ops_per_sec"),h=(await e.info("clients")).match(/connected_clients:(\d+)/)?.[1]??"n/a",y=parseInt(r)||0,g=parseInt(u)||0,T=y+g,S=T>0?(y/T*100).toFixed(1):"n/a",v=s.split(`
8
- `).filter(w=>w.startsWith("db")).map(w=>` ${w.trim()}`),p=[`\u{1F4CA} **Cache Stats for instance \`${n}\`:**`,"",` \u{1F4BE} Memory used: ${c} (peak: ${o})`,` \u26A1 Ops/sec: ${d}`,` \u{1F3AF} Hit rate: ${S}% (${r} hits / ${u} misses)`,` \u{1F517} Clients: ${h}`,"",v.length>0?` \u{1F5C2}\uFE0F Keyspace:
9
- ${v.join(`
10
- `)}`:" \u{1F5C2}\uFE0F Keyspace: (empty)"];try{const w=await b(`/api/v1/instances/${n}`);if(w.vector_token){const L=process.env.CACHLY_VECTOR_URL??"https://api.cachly.dev",N=await fetch(`${L}/v1/sem/${w.vector_token}/stats`,{signal:AbortSignal.timeout(5e3)});if(N.ok){const m=await N.json(),A=(l,$=2)=>l!==void 0?l.toLocaleString("en-US",{minimumFractionDigits:$,maximumFractionDigits:$}):"n/a",j=l=>l!==void 0?l.toLocaleString("en-US"):"n/a",k=m.total_hits??0,x=m.total_entries??0;if(k===0&&x===0){const l=$=>($*50*.25*.002*30).toFixed(2);p.push("","\u{1F4B0} **Tokenmaxxing ROI \u2014 Projection:**"," \u{1F4ED} No cache hits yet \u2014 you're just getting started.",""," \u{1F4CA} Typical savings for your team size:",` 5 devs \xD7 50 LLM calls/day \xD7 25% hit-rate \u2192 ~$${l(5)}/month`,` 10 devs \xD7 50 LLM calls/day \xD7 25% hit-rate \u2192 ~$${l(10)}/month`,` 20 devs \xD7 50 LLM calls/day \xD7 25% hit-rate \u2192 ~$${l(20)}/month`," (Assumes avg $0.002/call saved per cache hit)",""," \u{1F680} Next: route LLM calls through cachly to start accumulating hits.",` Docs: ${X("/docs/semantic","cache-stats")}`)}else{const l=m.savings?.week_over_week_pct,$=l!==void 0?` ${l>=0?"\u{1F4C8}":"\u{1F4C9}"} 7d trend: ${l>=0?"+":""}${l.toFixed(1)}% vs prev week`:"";p.push("","\u{1F4B0} **Tokenmaxxing ROI:**",` \u{1F4E6} Semantic entries: ${j(m.total_entries)}`,` \u{1F3AF} Total cache hits: ${j(m.total_hits)}`,` \u23F1\uFE0F Hits last 24h: ${j(m.savings?.hits_last_24h)}`,` \u{1F4C5} Hits last 7d: ${j(m.savings?.hits_last_7d)}`,...$?[$]:[],` \u{1F4B5} Total saved: $${A(m.savings?.estimated_total_saved_usd)}`,` \u{1F4C8} Projected/month: $${A(m.savings?.estimated_monthly_saved_usd)}`,` \u{1F4CA} Avg per hit: $${A(m.savings?.avg_cost_per_call_usd,4)}`)}m.top_hits&&m.top_hits.length>0&&(p.push("","\u{1F51D} Top cached prompts:"),m.top_hits.slice(0,3).forEach(($,O)=>{const C=$.prompt.length>50?`${$.prompt.slice(0,50)}\u2026`:$.prompt;p.push(` ${O+1}. "${C}" (hit ${$.hit_count}\xD7)`)}))}}}catch{}return p.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:c=!1}=_,o=c?U(e):t;if(!K())return`\u274C semantic_search requires an embedding provider.
6
+ `)}case"cache_stats":{const{instance_id:n}=p,e=await $(n),[a,t,s]=await Promise.all([e.info("memory"),e.info("stats"),e.info("keyspace")]),i=(g,T)=>g.match(new RegExp(`${T}:([^\r
7
+ ]+)`))?.[1]?.trim()??"n/a",o=i(a,"used_memory_human"),c=i(a,"used_memory_peak_human"),r=i(t,"keyspace_hits"),l=i(t,"keyspace_misses"),f=i(t,"instantaneous_ops_per_sec"),d=(await e.info("clients")).match(/connected_clients:(\d+)/)?.[1]??"n/a",y=parseInt(r)||0,w=parseInt(l)||0,x=y+w,S=x>0?(y/x*100).toFixed(1):"n/a",k=s.split(`
8
+ `).filter(g=>g.startsWith("db")).map(g=>` ${g.trim()}`),h=[`\u{1F4CA} **Cache Stats for instance \`${n}\`:**`,"",` \u{1F4BE} Memory used: ${o} (peak: ${c})`,` \u26A1 Ops/sec: ${f}`,` \u{1F3AF} Hit rate: ${S}% (${r} hits / ${l} misses)`,` \u{1F517} Clients: ${d}`,"",k.length>0?` \u{1F5C2}\uFE0F Keyspace:
9
+ ${k.join(`
10
+ `)}`:" \u{1F5C2}\uFE0F Keyspace: (empty)"];try{const g=await b(`/api/v1/instances/${n}`);if(g.vector_token){const T=process.env.CACHLY_VECTOR_URL??"https://api.cachly.dev",O=await fetch(`${T}/v1/sem/${g.vector_token}/stats`,{signal:AbortSignal.timeout(5e3)});if(O.ok){const m=await O.json(),L=u=>u!==void 0?u.toLocaleString("en-US"):"n/a",U=m.total_hits??0,v=m.total_entries??0;if(U===0&&v===0)h.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: ${X("/docs/semantic","cache-stats")}`);else{const u=m.savings?.week_over_week_pct,_=u!==void 0?` ${u>=0?"\u{1F4C8}":"\u{1F4C9}"} 7d trend: ${u>=0?"+":""}${u.toFixed(1)}% vs prev week`:"";h.push("","**Cache usage:**",` \u{1F4E6} Semantic entries: ${L(m.total_entries)}`,` \u{1F3AF} Total cache hits: ${L(m.total_hits)}`,` \u23F1\uFE0F Hits last 24h: ${L(m.savings?.hits_last_24h)}`,` \u{1F4C5} Hits last 7d: ${L(m.savings?.hits_last_7d)}`,..._?[_]:[])}m.top_hits&&m.top_hits.length>0&&(h.push("","\u{1F51D} Top cached prompts:"),m.top_hits.slice(0,3).forEach((_,E)=>{const C=_.prompt.length>50?`${_.prompt.slice(0,50)}\u2026`:_.prompt;h.push(` ${E+1}. "${C}" (hit ${_.hit_count}\xD7)`)}))}}}catch{}return h.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:o=!1}=p,c=o?P(e):t;if(!K())return`\u274C semantic_search requires an embedding provider.
12
12
 
13
13
  Current: ${J()}
14
14
 
@@ -16,39 +16,39 @@ Easiest: sign in (CACHLY_JWT). Embeddings then run on the cachly servers in the
16
16
  Or set OLLAMA_BASE_URL to use a local model (no key needed).
17
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 r=await b(`/api/v1/instances/${n}`);if(!r.vector_token)return`\u274C Instance "${r.name}" needs a vector token for semantic search.
18
18
 
19
- Every tier includes one. Open ${X(`/instances/${n}`,"health")} and rotate the vector token.`;const u=await P(e),d=process.env.CACHLY_VECTOR_URL??`https://api.cachly.dev/v1/sem/${r.vector_token}`,h={embedding:u,namespace:o,threshold:a,top_k:s};i&&(h.hybrid=!0,h.prompt=e);const y=await fetch(`${d}/search`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(h),signal:AbortSignal.timeout(8e3)});if(!y.ok)throw new V(Y.InternalError,`Vector search failed: ${y.statusText}`);const g=await y.json();if(!g.length||g.length===1&&!g[0].found)return`\u{1F50D} No semantically similar entries found for:
19
+ Every tier includes one. Open ${X(`/instances/${n}`,"health")} and rotate the vector token.`;const l=await R(e),f=process.env.CACHLY_VECTOR_URL??`https://api.cachly.dev/v1/sem/${r.vector_token}`,d={embedding:l,namespace:c,threshold:a,top_k:s};i&&(d.hybrid=!0,d.prompt=e);const y=await fetch(`${f}/search`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d),signal:AbortSignal.timeout(8e3)});if(!y.ok)throw new V(Y.InternalError,`Vector search failed: ${y.statusText}`);const w=await y.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.`;const T=await f(n),S=[`\u{1F50D} **Semantic search results** for: _"${e}"_`,` Threshold: ${a} \xB7 Namespace: \`${o}\``,""];for(const v of g){if(!v.found||!v.id)continue;const p=await T.get(`${o}:val:${v.id}`);S.push(`**Match** (similarity: ${((v.similarity??0)*100).toFixed(1)}%)`,` Prompt: _"${v.prompt??"(unknown)"}"_`,p?` Value: \`${p.slice(0,200)}${p.length>200?"\u2026":""}\``:" Value: _(evicted from cache)_","")}return S.join(`
23
- `)}case"detect_namespace":{const{prompt:n}=_,e=U(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(`
24
- `)}case"cache_warmup":{const{instance_id:n,entries:e,namespace:a="cachly:sem",ttl:t,auto_namespace:s=!1}=_;if(!K())return`\u274C cache_warmup requires an embedding provider.
22
+ Try lowering the threshold (current: ${a}) or using different keywords.`;const x=await $(n),S=[`\u{1F50D} **Semantic search results** for: _"${e}"_`,` Threshold: ${a} \xB7 Namespace: \`${c}\``,""];for(const k of w){if(!k.found||!k.id)continue;const h=await x.get(`${c}:val:${k.id}`);S.push(`**Match** (similarity: ${((k.similarity??0)*100).toFixed(1)}%)`,` Prompt: _"${k.prompt??"(unknown)"}"_`,h?` Value: \`${h.slice(0,200)}${h.length>200?"\u2026":""}\``:" Value: _(evicted from cache)_","")}return S.join(`
23
+ `)}case"detect_namespace":{const{prompt:n}=p,e=P(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(`
24
+ `)}case"cache_warmup":{const{instance_id:n,entries:e,namespace:a="cachly:sem",ttl:t,auto_namespace:s=!1}=p;if(!K())return`\u274C cache_warmup requires an embedding provider.
25
25
 
26
26
  Current: ${J()}
27
27
 
28
28
  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 b(`/api/v1/instances/${n}`),c=process.env.CACHLY_VECTOR_URL??(i.vector_token?`https://api.cachly.dev/v1/sem/${i.vector_token}`:null),o=await f(n);let r=0,u=0;const d=[];for(const h of e){const y=h.namespace??(s?U(h.prompt):a),g=await P(h.prompt);let T=!1;if(c){const p=await fetch(`${c}/search`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({embedding:g,namespace:y,threshold:.98}),signal:AbortSignal.timeout(8e3)}).catch(()=>null);p?.ok&&(T=(await p.json())[0]?.found??!1)}if(T){u++,d.push(` \u23ED\uFE0F _"${h.prompt.slice(0,60)}${h.prompt.length>60?"\u2026":""}"_ \u2192 already cached`);continue}const S=R(),v=`${y}:val:${S}`;if(t&&t>0?await o.set(v,h.value,"EX",t):await o.set(v,h.value),c){const p={id:S,prompt:h.prompt,namespace:y,embedding:g};t&&t>0&&(p.expires_at=new Date(Date.now()+t*1e3).toISOString()),await fetch(`${c}/entries`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p),signal:AbortSignal.timeout(8e3)}).catch(()=>{})}else{const p=`${y}:emb:${S}`,w=JSON.stringify({embedding:g,prompt:h.prompt});t&&t>0?await o.set(p,w,"EX",t):await o.set(p,w)}r++,d.push(` \u2705 _"${h.prompt.slice(0,60)}${h.prompt.length>60?"\u2026":""}"_ \u2192 \`${y}\``)}return["\u{1F525} **Cache Warmup Complete**","",` \u2705 Warmed: **${r}** new entries`,` \u23ED\uFE0F Skipped: **${u}** (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}\``,c?" \u{1F50D} Mode: pgvector HNSW":" \u{1F50D} Mode: Valkey SCAN (set CACHLY_VECTOR_URL for pgvector search)","",...d].join(`
30
- `)}case"index_project":{const{instance_id:n,dir:e,extensions:a,max_files:t=100,ttl:s=86400,summary_chars:i=1200,namespace:c="cachly:sem:code",hintergrund:o=!1}=_,r=new Set((a??["ts","js","tsx","jsx","go","py","java","rs","md","kt","swift"]).map(k=>k.startsWith(".")?k:`.${k}`)),u=[];async function d(k){if(u.length>=t)return;const x=await B(k,{withFileTypes:!0}).catch(()=>null);if(x)for(const l of x){if(u.length>=t)break;const $=Q(k,l.name);if(l.isDirectory()){if([".git","node_modules","dist","build",".next","__pycache__","vendor"].includes(l.name))continue;await d($)}else l.isFile()&&r.has(ee(l.name).toLowerCase())&&u.push($)}}if(await d(e),u.length===0)return`\u274C No matching files found in \`${e}\`.
31
- Extensions checked: ${[...r].join(", ")}`;const h=await b(`/api/v1/instances/${n}`),y=process.env.CACHLY_VECTOR_URL??(h.vector_token?`https://api.cachly.dev/v1/sem/${h.vector_token}`:null),g=y&&K();let T=0,S=0,v=0,p=0,w=0;const L=[],N=new Date(Date.now()+s*1e3).toISOString(),m=await f(n),A=te({hintergrund:o});for(const k of u){const x=Z(e,k);let l,$;try{const E=await z(k);if(E.size>2e5){S++;continue}$=E.size,l=await G(k,"utf-8")}catch{v++;continue}const O=`cachly:idx:hash:${x}`,C=`${$}:${l.length}:${se(l)}`;if(await m.get(O)===C){const E=`cachly:idx:${x}`;s>0&&await m.expire(E,s),s>0&&await m.expire(O,s),w++;continue}const H=l.slice(0,i).replace(/\s+/g," ").trim(),M=`cachly:idx:${x}`,I=`File: ${x}
32
- ${H}`;if(s>0?await m.set(M,I,"EX",s):await m.set(M,I),s>0?await m.set(O,C,"EX",s):await m.set(O,C),T++,L.push(` \u2705 ${x}`),g&&A.darf())try{const E=`File: ${x}
33
- ${H}`;await A.warte();const W=await P(E).catch(q=>{throw A.fehler(q),q}),D=R();await fetch(`${y}/entries`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:D,prompt:E,namespace:c,embedding:W,expires_at:N}),signal:AbortSignal.timeout(8e3)}),await m.set(`${c}:val:${D}`,x,"EX",s),p++}catch{}}return[`\u{1F4C2} **index_project Complete** \u2014 ${g?"\u{1F50D} Keyword + \u{1F3AF} Semantic":"\u{1F50D} Keyword only (no embedding provider)"}`,"",` \u{1F4C1} Dir: ${e}`,` \u2705 Indexed: **${T}** files (new/changed)`,` \u267B\uFE0F Unchanged: ${w} files (hash match \u2014 skipped)`,...g?[` \u{1F3AF} Semantic: **${p}** files (vector-searchable)`]:[],` \u23ED\uFE0F Skipped: ${S} (too large or filtered)`,` \u274C Errors: ${v}`,` \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.",...g?[]:[" 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(`
34
- `)}case"cache_mset":{const{instance_id:n,items:e}=_;if(!Array.isArray(e)||e.length===0)return"\u26A0\uFE0F No items provided.";const t=(await f(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.
29
+ Set CACHLY_EMBED_PROVIDER and the matching API key env var.`;const i=await b(`/api/v1/instances/${n}`),o=process.env.CACHLY_VECTOR_URL??(i.vector_token?`https://api.cachly.dev/v1/sem/${i.vector_token}`:null),c=await $(n);let r=0,l=0;const f=[];for(const d of e){const y=d.namespace??(s?P(d.prompt):a),w=await R(d.prompt);let x=!1;if(o){const h=await fetch(`${o}/search`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({embedding:w,namespace:y,threshold:.98}),signal:AbortSignal.timeout(8e3)}).catch(()=>null);h?.ok&&(x=(await h.json())[0]?.found??!1)}if(x){l++,f.push(` \u23ED\uFE0F _"${d.prompt.slice(0,60)}${d.prompt.length>60?"\u2026":""}"_ \u2192 already cached`);continue}const S=j(),k=`${y}:val:${S}`;if(t&&t>0?await c.set(k,d.value,"EX",t):await c.set(k,d.value),o){const h={id:S,prompt:d.prompt,namespace:y,embedding:w};t&&t>0&&(h.expires_at=new Date(Date.now()+t*1e3).toISOString()),await fetch(`${o}/entries`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(h),signal:AbortSignal.timeout(8e3)}).catch(()=>{})}else{const h=`${y}:emb:${S}`,g=JSON.stringify({embedding:w,prompt:d.prompt});t&&t>0?await c.set(h,g,"EX",t):await c.set(h,g)}r++,f.push(` \u2705 _"${d.prompt.slice(0,60)}${d.prompt.length>60?"\u2026":""}"_ \u2192 \`${y}\``)}return["\u{1F525} **Cache Warmup Complete**","",` \u2705 Warmed: **${r}** new entries`,` \u23ED\uFE0F Skipped: **${l}** (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}\``,o?" \u{1F50D} Mode: pgvector HNSW":" \u{1F50D} Mode: Valkey SCAN (set CACHLY_VECTOR_URL for pgvector search)","",...f].join(`
30
+ `)}case"index_project":{const{instance_id:n,dir:e,extensions:a,max_files:t=100,ttl:s=86400,summary_chars:i=1200,namespace:o="cachly:sem:code",hintergrund:c=!1}=p,r=new Set((a??["ts","js","tsx","jsx","go","py","java","rs","md","kt","swift"]).map(v=>v.startsWith(".")?v:`.${v}`)),l=[];async function f(v){if(l.length>=t)return;const u=await F(v,{withFileTypes:!0}).catch(()=>null);if(u)for(const _ of u){if(l.length>=t)break;const E=Q(v,_.name);if(_.isDirectory()){if([".git","node_modules","dist","build",".next","__pycache__","vendor"].includes(_.name))continue;await f(E)}else _.isFile()&&r.has(ee(_.name).toLowerCase())&&l.push(E)}}if(await f(e),l.length===0)return`\u274C No matching files found in \`${e}\`.
31
+ Extensions checked: ${[...r].join(", ")}`;const d=await b(`/api/v1/instances/${n}`),y=process.env.CACHLY_VECTOR_URL??(d.vector_token?`https://api.cachly.dev/v1/sem/${d.vector_token}`:null),w=y&&K();let x=0,S=0,k=0,h=0,g=0;const T=[],O=new Date(Date.now()+s*1e3).toISOString(),m=await $(n),L=te({hintergrund:c});for(const v of l){const u=Z(e,v);let _,E;try{const A=await z(v);if(A.size>2e5){S++;continue}E=A.size,_=await G(v,"utf-8")}catch{k++;continue}const C=`cachly:idx:hash:${u}`,N=`${E}:${_.length}:${se(_)}`;if(await m.get(C)===N){const A=`cachly:idx:${u}`;s>0&&await m.expire(A,s),s>0&&await m.expire(C,s),g++;continue}const H=_.slice(0,i).replace(/\s+/g," ").trim(),M=`cachly:idx:${u}`,I=`File: ${u}
32
+ ${H}`;if(s>0?await m.set(M,I,"EX",s):await m.set(M,I),s>0?await m.set(C,N,"EX",s):await m.set(C,N),x++,T.push(` \u2705 ${u}`),w&&L.darf())try{const A=`File: ${u}
33
+ ${H}`;await L.warte();const B=await R(A).catch(q=>{throw L.fehler(q),q}),D=j();await fetch(`${y}/entries`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:D,prompt:A,namespace:o,embedding:B,expires_at:O}),signal:AbortSignal.timeout(8e3)}),await m.set(`${o}:val:${D}`,u,"EX",s),h++}catch{}}return[`\u{1F4C2} **index_project Complete** \u2014 ${w?"\u{1F50D} Keyword + \u{1F3AF} Semantic":"\u{1F50D} Keyword only (no embedding provider)"}`,"",` \u{1F4C1} Dir: ${e}`,` \u2705 Indexed: **${x}** files (new/changed)`,` \u267B\uFE0F Unchanged: ${g} files (hash match \u2014 skipped)`,...w?[` \u{1F3AF} Semantic: **${h}** files (vector-searchable)`]:[],` \u23ED\uFE0F Skipped: ${S} (too large or filtered)`,` \u274C Errors: ${k}`,` \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.",...w?[]:[" 3. Sign in (CACHLY_JWT) or set OLLAMA_BASE_URL to also enable semantic search."],"",...T.length<=20?T:[...T.slice(0,20),` \u2026 and ${T.length-20} more`]].join(`
34
+ `)}case"cache_mset":{const{instance_id:n,items:e}=p;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
35
  `+e.map(s=>` \u2022 \`${s.key}\`${s.ttl?` (TTL ${s.ttl}s)`:""}`).join(`
36
- `)}case"cache_mget":{const{instance_id:n,keys:e}=_;if(!Array.isArray(e)||e.length===0)return"\u26A0\uFE0F No keys provided.";const t=await(await f(n)).mget(...e),s=e.map((i,c)=>{const o=t[c];return o===null?` \u2022 \`${i}\`: _null (miss)_`:` \u2022 \`${i}\`: ${o}`});return`\u2705 **cache_mget** \u2013 ${e.length} key(s) fetched in one round-trip.
36
+ `)}case"cache_mget":{const{instance_id:n,keys:e}=p;if(!Array.isArray(e)||e.length===0)return"\u26A0\uFE0F No keys provided.";const t=await(await $(n)).mget(...e),s=e.map((i,o)=>{const c=t[o];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
37
  `+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}=_,i=await f(n),c=`cachly:lock:${e}`,o=R();for(let r=0;r<=t;r++){if(await i.set(c,o,"PX",a,"NX")==="OK")return`\u{1F512} **cache_lock_acquire** \u2013 Lock acquired!
38
+ `)}case"cache_lock_acquire":{const{instance_id:n,key:e,ttl_ms:a=5e3,retries:t=3,retry_delay_ms:s=50}=p,i=await $(n),o=`cachly:lock:${e}`,c=j();for(let r=0;r<=t;r++){if(await i.set(o,c,"PX",a,"NX")==="OK")return`\u{1F512} **cache_lock_acquire** \u2013 Lock acquired!
39
39
 
40
40
  Key: \`${e}\`
41
- Token: \`${o}\`
41
+ Token: \`${c}\`
42
42
  TTL: ${a} ms
43
43
 
44
- \u{1F4A1} Use **cache_lock_release** with this token to release early.`;r<t&&await new Promise(d=>setTimeout(d,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}=_;return await(await f(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}=_;if(!Array.isArray(a)||a.length===0)return"\u26A0\uFE0F No chunks provided.";const s=await f(n),i=`cachly:stream:${e}`;await s.del(i);const c=s.pipeline();for(const o of a)c.rpush(i,o);return t&&t>0&&c.expire(i,t),await c.exec(),`\u2705 **cache_stream_set** \u2013 ${a.length} chunk(s) stored.
44
+ \u{1F4A1} Use **cache_lock_release** with this token to release early.`;r<t&&await new Promise(f=>setTimeout(f,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}=p;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}=p;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 o=s.pipeline();for(const c of a)o.rpush(i,c);return t&&t>0&&o.expire(i,t),await o.exec(),`\u2705 **cache_stream_set** \u2013 ${a.length} chunk(s) stored.
45
45
  Key: \`${e}\`
46
46
  ${t?` TTL: ${t}s
47
- `:""} Total size: ${a.reduce((o,r)=>o+r.length,0)} chars`}case"cache_stream_get":{const{instance_id:n,key:e}=_,a=await f(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 c=(await a.lrange(t,0,-1)).join(""),o=c.slice(0,500);return`\u2705 **cache_stream_get** \u2013 ${s} chunk(s) retrieved for \`${e}\`.
47
+ `:""} Total size: ${a.reduce((c,r)=>c+r.length,0)} chars`}case"cache_stream_get":{const{instance_id:n,key:e}=p,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 o=(await a.lrange(t,0,-1)).join(""),c=o.slice(0,500);return`\u2705 **cache_stream_get** \u2013 ${s} chunk(s) retrieved for \`${e}\`.
48
48
 
49
49
  **Preview** (first 500 chars):
50
50
  \`\`\`
51
- ${o}${o.length<c.length?"\u2026":""}
52
- \`\`\``}case"cache_org_stats":{const{instance_id:n,org_id:e}=_,a=await f(n),t=`org:${e}:sem`,i=(await ae(a,`${t}:*`,{max:1e4,timeoutMs:5e3})).length,c=[`\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 o=await b(`/api/v1/orgs/${e}/savings`),r=d=>`$${(d??0).toFixed(2)}`;c.push("","\u{1F4B0} Org-wide Tokenmaxxing ROI:",` \u{1F5A5}\uFE0F Instances: ${o.instance_count}`,` \u{1F3AF} Total cache hits: ${o.total_hits.toLocaleString("en-US")}`,` \u23F1\uFE0F Hits last 24h: ${o.hits_last_24h.toLocaleString("en-US")}`,` \u{1F4B5} Total saved: ${r(o.estimated_total_saved_usd)}`,` \u{1F4C8} Projected/month: ${r(o.estimated_monthly_saved_usd)}`);const u=[...o.per_instance??[]].sort((d,h)=>h.estimated_total_saved_usd-d.estimated_total_saved_usd).slice(0,5);if(u.length>0){c.push(""," Top instances by savings:");for(const d of u)c.push(` \u2022 ${d.name||d.instance_id}: ${r(d.estimated_total_saved_usd)} (${d.total_hits.toLocaleString("en-US")} hits)`)}}catch{}return c.join(`
53
- `)}case"set_cost_per_call":{const{instance_id:n,cost_per_call_usd:e}=_;if(!e||e<=0||e>100)throw new V(Y.InvalidParams,"cost_per_call_usd must be a positive number \u2264 100");await b(`/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(`
54
- `)}default:return null}}export{ue as CACHE_TOOL_NAMES,$e as handleCacheTool};
51
+ ${c}${c.length<o.length?"\u2026":""}
52
+ \`\`\``}case"cache_org_stats":{const{instance_id:n,org_id:e}=p,a=await $(n),t=`org:${e}:sem`,i=(await ae(a,`${t}:*`,{max:1e4,timeoutMs:5e3})).length,o=[`\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 b(`/api/v1/orgs/${e}/savings`);o.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 r=[...c.per_instance??[]].sort((l,f)=>f.total_hits-l.total_hits).slice(0,5);if(r.length>0){o.push(""," Top instances by hits:");for(const l of r)o.push(` \u2022 ${l.name||l.instance_id}: ${l.total_hits.toLocaleString("en-US")} hits`)}}catch{}return o.join(`
53
+ `)}case"set_cost_per_call":{const{instance_id:n,cost_per_call_usd:e}=p;if(!e||e<=0||e>100)throw new V(Y.InvalidParams,"cost_per_call_usd must be a positive number \u2264 100");await b(`/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(`
54
+ `)}default:return null}}export{_e as CACHE_TOOL_NAMES,$e as handleCacheTool};
@@ -1,17 +1,16 @@
1
- const g=new Set(["list_instances","create_instance","get_instance","get_connection_string","delete_instance","list_orgs","create_org","invite_member","get_org_plan"]);function m(a){const i=a.status==="running"?"\u{1F7E2}":a.status==="provisioning"?"\u{1F7E1}":"\u{1F534}",c=a.memory_mb>=1024?`${a.memory_mb/1024}GB`:`${a.memory_mb}MB`;return`${i} **${a.name}** (\`${a.id}\`) \xB7 ${a.tier} \xB7 ${c} \xB7 ${a.region}`}function l(a){if(!a.host)return"\u274C Instance not ready yet.";const i=a.tls_enabled?"rediss":"redis",c=a.password?`:${a.password}@`:"";return`${i}://${c}${a.host}:${a.port??6379}`}async function u(a,i,c,r){switch(a){case"list_instances":{const e=(await r("/api/v1/instances")).data??[];return e.length===0?"You have no cache instances yet. Use `create_instance` to create one.":[`Found ${e.length} instance(s):
2
- `,...e.map(m)].join(`
3
- `)}case"create_instance":{const{name:n,tier:e}=i,t=await r("/api/v1/instances",{method:"POST",body:JSON.stringify({name:n,tier:e,created_via:"api"})});return t.checkout_url?[`\u2705 Instance **${n}** (${e}) created! ID: \`${t.instance_id}\``,"","\u{1F4B3} This is a paid tier. Complete payment to activate:",` ${t.checkout_url}`,"","After payment, provisioning starts automatically (~30 seconds)."].join(`
4
- `):[`\u2705 Instance **${n}** (${e}) created and provisioning started!`,` ID: \`${t.instance_id}\``,` Status: ${t.status}`,"","Use `get_instance` or `get_connection_string` to get your connection details."].join(`
5
- `)}case"get_instance":{const n=await r(`/api/v1/instances/${i.instance_id}`);return m(n)}case"get_connection_string":{const n=await r(`/api/v1/instances/${i.instance_id}`);if(n.status!=="running")return`Instance is not running yet (status: ${n.status}). Provisioning takes ~30 seconds after payment.`;let e={},t=!1;try{e=await r(`/api/v1/instances/${i.instance_id}/connection`)}catch{t=!0}const s=e.connection_string||l(n);return[...!s.includes("@")?t?["\u26A0\uFE0F Could not read the credentials endpoint \u2014 this string has NO password and will fail with NOAUTH if the instance requires one.",""]:e.password?["\u26A0\uFE0F The server returned a password but this string carries none \u2014 please report this.",""]:[]:[],`**Connection string for ${n.name}:**`,"```",s,"```","","**Environment variable:**","```bash",`REDIS_URL="${s}"`,`CACHLY_URL="${s}"`,"```","","**Quick test:**","```bash",`redis-cli -u "${s}" PING`,"```"].join(`
6
- `)}case"delete_instance":{const{instance_id:n,confirm:e}=i;if(!e)return"Deletion cancelled. Set `confirm: true` to proceed.";try{c._pool?.delete?.(n)}catch{}return await r(`/api/v1/instances/${n}`,{method:"DELETE"}),`\u2705 Instance \`${n}\` has been deleted and all data removed.`}case"list_orgs":{const e=(await r("/api/v1/orgs")).orgs??[];return e.length===0?`\u{1F4ED} No organizations yet.
1
+ const _=new Set(["list_instances","create_instance","get_instance","get_connection_string","delete_instance","list_orgs","create_org","invite_member","get_org_plan"]),l="Team \u20AC49/mo for up to 5 people \xB7 Business \u20AC199/mo for up to 20 people with SSO \xB7 Enterprise on request.";function m(n){return{team:"Free",free:"Free",business:"Business",enterprise:"Enterprise"}[n]??(n?n.charAt(0).toUpperCase()+n.slice(1):"Free")}function d(n){const s=n.status==="running"?"\u{1F7E2}":n.status==="provisioning"?"\u{1F7E1}":"\u{1F534}",c=n.memory_mb>=1024?`${n.memory_mb/1024}GB`:`${n.memory_mb}MB`;return`${s} **${n.name}** (\`${n.id}\`) \xB7 ${n.tier} \xB7 ${c} \xB7 ${n.region}`}function g(n){if(!n.host)return"\u274C Instance not ready yet.";const s=n.tls_enabled?"rediss":"redis",c=n.password?`:${n.password}@`:"";return`${s}://${c}${n.host}:${n.port??6379}`}async function $(n,s,c,i){switch(n){case"list_instances":{const e=(await i("/api/v1/instances")).data??[];return e.length===0?"You have no cache instances yet. Use `create_instance` to create one.":[`Found ${e.length} instance(s):
2
+ `,...e.map(d)].join(`
3
+ `)}case"create_instance":{const{name:t,tier:e}=s,a=await i("/api/v1/instances",{method:"POST",body:JSON.stringify({name:t,tier:e,created_via:"api"})});return a.checkout_url?[`\u2705 Instance **${t}** (${e}) created! ID: \`${a.instance_id}\``,"","\u{1F4B3} This is a paid tier. Complete payment to activate:",` ${a.checkout_url}`,"","After payment, provisioning starts automatically (~30 seconds)."].join(`
4
+ `):[`\u2705 Instance **${t}** (${e}) created and provisioning started!`,` ID: \`${a.instance_id}\``,` Status: ${a.status}`,"","Use `get_instance` or `get_connection_string` to get your connection details."].join(`
5
+ `)}case"get_instance":{const t=await i(`/api/v1/instances/${s.instance_id}`);return d(t)}case"get_connection_string":{const t=await i(`/api/v1/instances/${s.instance_id}`);if(t.status!=="running")return`Instance is not running yet (status: ${t.status}). Provisioning takes ~30 seconds after payment.`;let e={},a=!1;try{e=await i(`/api/v1/instances/${s.instance_id}/connection`)}catch{a=!0}const r=e.connection_string||g(t);return[...!r.includes("@")?a?["\u26A0\uFE0F Could not read the credentials endpoint \u2014 this string has NO password and will fail with NOAUTH if the instance requires one.",""]:e.password?["\u26A0\uFE0F The server returned a password but this string carries none \u2014 please report this.",""]:[]:[],`**Connection string for ${t.name}:**`,"```",r,"```","","**Environment variable:**","```bash",`REDIS_URL="${r}"`,`CACHLY_URL="${r}"`,"```","","**Quick test:**","```bash",`redis-cli -u "${r}" PING`,"```"].join(`
6
+ `)}case"delete_instance":{const{instance_id:t,confirm:e}=s;if(!e)return"Deletion cancelled. Set `confirm: true` to proceed.";try{c._pool?.delete?.(t)}catch{}return await i(`/api/v1/instances/${t}`,{method:"DELETE"}),`\u2705 Instance \`${t}\` has been deleted and all data removed.`}case"list_orgs":{const e=(await i("/api/v1/orgs")).orgs??[];return e.length===0?`\u{1F4ED} No organizations yet.
7
7
 
8
- Create one with \`create_org(name="My Team")\`.
9
- Organizations are free. Up to 5 people share the Brain tier Team (\u20AC49/mo, everything included).
10
- Org plans: Business \u20AC199/mo (20 seats, SSO), Enterprise custom.`:[`\u{1F3E2} **Your organizations (${e.length})**
11
- `,...e.map(t=>`\u2022 **${t.name}** (\`${t.slug}\`) \u2014 plan: ${t.plan} \xB7 seats: ${t.member_count??"?"}/${t.max_members}
12
- ID: \`${t.id}\``),"\n_Manage: `get_org_plan`, `invite_member`, dashboard \u2192 /team_"].join(`
13
- `)}case"create_org":{const{name:n,slug:e}=i,t={name:n};e&&(t.slug=e);const s=await r("/api/v1/orgs",{method:"POST",body:JSON.stringify(t)});return[`\u2705 **Organization created:** ${s.name}`,` ID: \`${s.id}\` \xB7 Slug: \`${s.slug}\` \xB7 Plan: ${s.plan}`,"","**Next steps:**",`1. Invite team members: \`invite_member(org_id="${s.id}", email="dev@example.com")\``,"2. Up to 5 people: Brain tier Team, \u20AC49/mo, everything included (dashboard \u2192 /billing)"," Org plan Business: \u20AC199/mo (20 seats, SSO)"].join(`
14
- `)}case"invite_member":{const{org_id:n,email:e,role:t="member"}=i;return await r(`/api/v1/orgs/${n}/members`,{method:"POST",body:JSON.stringify({email:e,role:t})}),`\u2705 Invite sent to **${e}** as \`${t}\` in org \`${n}\`.
8
+ Create one with \`create_org(name="My Team")\` \u2014 organizations are free.
9
+ ${l}`:[`\u{1F3E2} **Your organizations (${e.length})**
10
+ `,...e.map(a=>`\u2022 **${a.name}** (\`${a.slug}\`) \u2014 plan: ${m(a.plan)} \xB7 seats: ${a.member_count??"?"}/${a.max_members}
11
+ ID: \`${a.id}\``),"\n_Manage: `get_org_plan`, `invite_member`, dashboard \u2192 /team_"].join(`
12
+ `)}case"create_org":{const{name:t,slug:e}=s,a={name:t};e&&(a.slug=e);const r=await i("/api/v1/orgs",{method:"POST",body:JSON.stringify(a)});return[`\u2705 **Organization created:** ${r.name}`,` ID: \`${r.id}\` \xB7 Slug: \`${r.slug}\` \xB7 Plan: ${m(r.plan)}`,"","**Next steps:**",`1. Invite team members: \`invite_member(org_id="${r.id}", email="dev@example.com")\``,"2. Pick the Brain tier for your team (dashboard \u2192 /billing):",` ${l}`].join(`
13
+ `)}case"invite_member":{const{org_id:t,email:e,role:a="member"}=s;return await i(`/api/v1/orgs/${t}/members`,{method:"POST",body:JSON.stringify({email:e,role:a})}),`\u2705 Invite sent to **${e}** as \`${a}\` in org \`${t}\`.
15
14
 
16
- They will receive an email to join the organization.`}case"get_org_plan":{const{org_id:n}=i,e=await r(`/api/v1/orgs/${n}`),t=(e.members??[]).filter(o=>o.accepted_at).length,s=(e.members??[]).filter(o=>!o.accepted_at).length,d={free:"\u20AC0",team:"legacy",business:"\u20AC199/mo",enterprise:"custom"};return[`\u{1F3E2} **${e.name}** \u2014 Plan: **${e.plan}** (${d[e.plan]??e.plan})`,` Seats: ${t} active + ${s} pending / ${e.max_members} max`,"","**Members:**",...(e.members??[]).map(o=>` \u2022 ${o.invite_email} (${o.role})${o.accepted_at?"":" \u2014 pending"}`),"",e.stripe_customer_id?"\u{1F4B3} Billing: managed via Stripe. Upgrade/cancel: dashboard \u2192 /billing":"\u{1F4B3} Add a payment method for Business: dashboard \u2192 /billing \u2192 Teams"].join(`
17
- `)}default:return null}}export{g as INSTANCE_TOOL_NAMES,l as buildConnectionString,m as formatInstance,u as handleInstanceTool};
15
+ They will receive an email to join the organization.`}case"get_org_plan":{const{org_id:t}=s,e=await i(`/api/v1/orgs/${t}`),a=(e.members??[]).filter(o=>o.accepted_at).length,r=(e.members??[]).filter(o=>!o.accepted_at).length;return[`\u{1F3E2} **${e.name}** \u2014 Plan: **${m(e.plan)}**`,` Seats: ${a} active + ${r} pending / ${e.max_members} max`,"","**Members:**",...(e.members??[]).map(o=>` \u2022 ${o.invite_email} (${o.role})${o.accepted_at?"":" \u2014 pending"}`),"",e.stripe_customer_id?"\u{1F4B3} Billing: managed via Stripe. Upgrade/cancel: dashboard \u2192 /billing":"\u{1F4B3} Organizations are free. SSO comes with the Business tier: dashboard \u2192 /billing"].join(`
16
+ `)}default:return null}}export{_ as INSTANCE_TOOL_NAMES,g as buildConnectionString,d as formatInstance,$ as handleInstanceTool,m as orgPlanName};