@cachly-dev/mcp-server 0.10.158 → 0.10.159
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/handlers/brain.js +65 -65
- package/dist/src/handlers/team.js +34 -34
- package/dist/src/herkunft.js +1 -0
- package/dist/src/index.js +50 -50
- package/dist/src/sitzungs-kosten.js +1 -0
- package/glama.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import{readFileSync as le}from"node:fs";import{bauabdruck as ue,formatiereBauabdruck as
|
|
2
|
-
`)}async function re(k,y,_){const
|
|
3
|
-
`)}const
|
|
4
|
-
`)}case"team_learn":{const{instance_id:i,author:e,topic:a,outcome:o,what_worked:r,what_failed:t,severity:c,file_paths:l,commands:
|
|
5
|
-
\u{1F4A1} ${r.slice(0,120)}`}case"team_confirm":{const{instance_id:i,topic:e,reviewer:a,level:o,note:r=""}=y;if(!i)return"\u274C instance_id required";if(!e||!a)return"\u274C Required: topic, reviewer";const t=await _(i),c=await ee(t,i,a,"reviewer","confirm team lessons");if(c)return c;const l=await
|
|
1
|
+
import{readFileSync as le}from"node:fs";import{bauabdruck as ue,formatiereBauabdruck as he}from"../bauabdruck.js";import{beurteileSpeicher as de,zahlAusInfo as se}from"../speicherlage.js";import{beurteileDeckung as pe}from"../eingaenge.js";import{join as E}from"node:path";import{calculateConfidence as G,CONFIDENCE_STALE_VALUE as X,CONFIDENCE_WARN_VALUE as me,CONFIDENCE_WARN_DAYS as ne,CONFIDENCE_STALE_DAYS as oe}from"../confidence.js";import{safeJsonParse as C}from"../utils.js";import{ersparteMinuten as fe,istStarterLektion as ge,fmtStunden as ae}from"../wertbeitrag.js";import{cachlyUrl as ye}from"../cachly-url.js";import{leseVermerke as we}from"../aussetzer.js";const Pe=new Set(["sync_file_changes","team_learn","team_confirm","team_recall","team_synthesize","memory_crystalize","team_crystallize","brain_doctor","brain_hygiene","recall_at","trace_dependency","global_learn","global_recall","publish_lesson","import_public_brain","setup_ai_memory","team_assign_role","team_whoami","team_roster","team_grant_scope","team_scopes","team_audit"]),H={admin:4,reviewer:3,contributor:2,viewer:1},U={admin:"\u{1F451}",reviewer:"\u{1F6E1}\uFE0F",contributor:"\u270F\uFE0F",viewer:"\u{1F441}\uFE0F"},Q={admin:"assign roles \xB7 delete lessons \xB7 all reviewer + contributor actions",reviewer:"senior-review lessons (\u{1F6E1}\uFE0F) \xB7 all contributor actions",contributor:"store lessons \xB7 peer-review lessons (\u2714\uFE0F) \xB7 recall",viewer:"recall only \u2014 cannot store or review lessons"},B=k=>`cachly:team:roles:${k}`;async function W(k,y,_){return await k.hget(B(y),_.toLowerCase()).catch(()=>null)}function q(k,y){return k?(H[k]??0)>=(H[y]??0):!1}function _e(k){return k==="admin"||k==="reviewer"?"senior":"peer"}async function ve(k,y){const _=await k.hgetall(B(y)).catch(()=>({}));return Object.values(_).includes("admin")}async function ee(k,y,_,N,i){if(!await ve(k,y))return null;const e=_?await W(k,y,_):null;if(q(e,N))return null;const a=e?`${U[e]} ${e}`:"no role";return[`\u{1F512} **Permission denied** \u2014 to ${i} you need at least \`${N}\` ${U[N]} (you have: ${a}).`,"","Ask an admin to grant it:",`\`team_assign_role(instance_id="${y}", handle="${_||"<your-handle>"}", role="${N}", assigned_by="<admin>")\``,"_Governance is active on this brain. Run `team_whoami` to check your role._"].join(`
|
|
2
|
+
`)}async function re(k,y,_){const N=`cachly:team:audit:${y}`,i=JSON.stringify({..._,ts:new Date().toISOString()});await k.rpush(N,i).catch(()=>{}),await k.ltrim(N,-500,-1).catch(()=>{}),await k.expire(N,365*2*86400).catch(()=>{})}const $e="cachly:team:review_mode",be="cachly:lesson:proposed:",ke=90*86400;async function Se(k,y){const _=await k.get($e).catch(()=>null);return _==="1"||_==="true"||_==="on"}async function xe(k,y,_){const N={..._,proposed_at:new Date().toISOString(),status:"pending"};await k.set(`${be}${y}`,JSON.stringify(N),"EX",ke).catch(()=>{});const i=typeof _.author=="string"&&_.author?` by **${_.author}**`:"";return[`\u{1F4DD} **Lesson submitted for review** \u2014 \`${y}\`${i} is now a pending proposal.`,"","This team requires review before knowledge enters the Team Brain (org setting).","An org owner/admin can approve or reject it in the dashboard under **Team \u2192 Review**.","Until approved it will NOT surface in `smart_recall` / `team_recall`."].join(`
|
|
3
|
+
`)}const V=k=>`cachly:team:groups:${k}`,Y=(k,y)=>`cachly:team:group:${k}:${y.toLowerCase()}`;async function Ae(k,y,_){const N=await k.smembers(V(y)).catch(()=>[]),i=new Set;for(const e of N)await k.sismember(Y(y,e),_.toLowerCase()).catch(()=>0)&&i.add(e.toLowerCase());return i}function ze(k,y,_){return!k||_?!0:y.has(k.toLowerCase())}async function Be(k,y,_,N){switch(k){case"sync_file_changes":{const{instance_id:i,changed_files:e,git_diff_stat:a,commit_msg:o}=y,r=await _(i),t={ts:new Date().toISOString(),files:e,commit_msg:o,diff_stat:a?.slice(0,500)};await r.lpush("cachly:session:file_changes",JSON.stringify(t)),await r.ltrim("cachly:session:file_changes",0,99),await r.expire("cachly:session:file_changes",30*86400);const c=[],l=r.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((p,g)=>{l.on("data",v=>c.push(...v)),l.on("end",p),l.on("error",g)});const d=[],s=c.length>0?await r.mget(...c):[];for(const p of s){if(!p)continue;const g=C(p,null);if(!g)continue;const v=g.topic.toLowerCase().split(/[:\-_]/);if(e.some(w=>{const b=w.split("/").pop()?.replace(/\.[^.]+$/,"").toLowerCase()??"";return v.some(m=>m.length>3&&b.includes(m))||(g.file_paths??[]).some(m=>w.includes(m)||m.includes(w))})){const w=g.outcome==="success"?"\u2705":"\u26A0\uFE0F";d.push(` ${w} \`${g.topic}\` \u2014 ${g.what_worked.slice(0,80)}`)}}const n=[`\u{1F4C1} **File sync recorded**: ${e.length} files`,o?`\u{1F4DD} Commit: "${o}"`:"","",`**Changed:** ${e.slice(0,8).map(p=>`\`${p}\``).join(", ")}${e.length>8?` +${e.length-8} more`:""}`,""];return d.length>0?n.push(`\u{1F9E0} **Relevant brain lessons (${d.length}):**`,...d):n.push("\u{1F4A1} No existing lessons match these files yet. Add them with `learn_from_attempts`."),n.filter(Boolean).join(`
|
|
4
|
+
`)}case"team_learn":{const{instance_id:i,author:e,topic:a,outcome:o,what_worked:r,what_failed:t,severity:c,file_paths:l,commands:d,tags:s}=y;if(!e||!a||!o||!r)return"\u274C Required: author, topic, outcome, what_worked";const n=i;if(!n)return"\u274C instance_id required";const p=await _(n),g=await ee(p,n,e,"contributor","store team lessons");if(g)return g;const v={topic:a,outcome:o,what_worked:r,what_failed:t??"",severity:c??"minor",author:e,file_paths:l??[],commands:d??[],tags:[...s??[],"team"],timestamp:new Date().toISOString(),recall_count:0,version:2};if(await Se(p,n))return xe(p,a,v);const L=`cachly:lessons:${a}`;return await p.rpush(L,JSON.stringify(v)),o==="success"&&await p.set(`cachly:lesson:best:${a}`,JSON.stringify(v)),`\u2705 Team lesson stored by **${e}**: \`${a}\` (${o})
|
|
5
|
+
\u{1F4A1} ${r.slice(0,120)}`}case"team_confirm":{const{instance_id:i,topic:e,reviewer:a,level:o,note:r=""}=y;if(!i)return"\u274C instance_id required";if(!e||!a)return"\u274C Required: topic, reviewer";const t=await _(i),c=await ee(t,i,a,"reviewer","confirm team lessons");if(c)return c;const l=await W(t,i,a),d=_e(l),s=o==="peer"?"peer":o==="senior"&&q(l,"reviewer")?"senior":d,n=l?` (role: ${U[l]} ${l})`:" (no role assigned \u2014 peer weight applied)",p=`cachly:lesson:best:${e}`,g=await t.get(p),v=C(g,null);if(!v)return`\u{1F4ED} No best-solution lesson found for \`${e}\`.
|
|
6
6
|
|
|
7
|
-
A lesson must exist (store one with \`team_learn\` / \`learn_from_attempts\`) before it can be confirmed.`;const L=`cachly:lesson:endorsers:${e}`,w=await t.sadd(L,a)===1;await t.expire(L,365*86400);const
|
|
7
|
+
A lesson must exist (store one with \`team_learn\` / \`learn_from_attempts\`) before it can be confirmed.`;const L=`cachly:lesson:endorsers:${e}`,w=await t.sadd(L,a)===1;await t.expire(L,365*86400);const b=await t.scard(L),h=(v.review_level??"")==="senior"?"senior":s,u={...v,reviewed_by:a,review_level:h,endorsements:b,reviewed_at:new Date().toISOString()};await t.set(p,JSON.stringify(u));const x={reviewer:a,level:s,note:r.slice(0,280),ts:new Date().toISOString()},j=`cachly:lesson:reviews:${e}`;await t.rpush(j,JSON.stringify(x)),await t.ltrim(j,-50,-1),await t.expire(j,365*86400),await re(t,i,{action:"team_confirm",actor:a,topic:e,level:h});const A=h==="senior"?"\u{1F6E1}\uFE0F senior-reviewed":"\u2714\uFE0F peer-reviewed",S=w?"":` (already endorsed by ${a} \u2014 no change to rank)`;return`${A} \u2014 \`${e}\` confirmed by **${a}**${n} (${b} distinct endorsement${b===1?"":"s"})${S}.
|
|
8
8
|
\u{1F4C8} This lesson now ranks higher in \`smart_recall\` / \`team_recall\`.${r?`
|
|
9
|
-
\u{1F4DD} ${r.slice(0,200)}`:""}`}case"team_recall":{const{instance_id:i,topic:e,author:a,limit:o=10}=y,r=await _(i),t=[],c=r.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((n,p)=>{c.on("data",g=>t.push(...g)),c.on("end",n),c.on("error",p)});let l=[];if(t.length>0){const n=await r.mget(...t);for(const p of n){const g=
|
|
9
|
+
\u{1F4DD} ${r.slice(0,200)}`:""}`}case"team_recall":{const{instance_id:i,topic:e,author:a,limit:o=10}=y,r=await _(i),t=[],c=r.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((n,p)=>{c.on("data",g=>t.push(...g)),c.on("end",n),c.on("error",p)});let l=[];if(t.length>0){const n=await r.mget(...t);for(const p of n){const g=C(p??null,null);g&&l.push(g)}}if(e){const n=e.toLowerCase();l=l.filter(p=>p.topic.toLowerCase().includes(n)||(p.tags??[]).some(g=>g.toLowerCase().includes(n)))}if(a){const n=a.toLowerCase();l=l.filter(p=>p.author?.toLowerCase().includes(n))}const d=n=>n.reviewed_by?n.review_level==="senior"?2:1:0;if(l.sort((n,p)=>d(p)-d(n)||(p.recall_count??0)-(n.recall_count??0)),l=l.slice(0,o),l.length===0)return e?`\u{1F4ED} No team lessons found for \`${e}\`.
|
|
10
10
|
|
|
11
11
|
Shared instance: add lessons with \`learn_from_attempts\` and include an \`author\` field.`:`\u{1F4ED} No lessons in this brain yet.
|
|
12
12
|
|
|
13
|
-
All team members sharing this instance will see lessons here.`;const s=[`\u{1F465} **Team Brain** \u2014 ${l.length} lesson${l.length>1?"s":""}`,""];for(const n of l){const p=n.outcome==="success"?"\u2705":n.outcome==="partial"?"\u26A0\uFE0F":"\u274C",g=n.severity==="critical"?"\u{1F534} ":n.severity==="major"?"\u{1F7E1} ":"",v=n.reviewed_by?n.review_level==="senior"?" \u{1F6E1}\uFE0F":" \u2714\uFE0F":"",L=n.author?` \xB7 _by ${n.author}_`:"",w=n.reviewed_by?` \xB7 reviewed by ${n.reviewed_by}${(n.endorsements??0)>1?` +${(n.endorsements??0)-1}`:""}`:""
|
|
13
|
+
All team members sharing this instance will see lessons here.`;const s=[`\u{1F465} **Team Brain** \u2014 ${l.length} lesson${l.length>1?"s":""}`,""];for(const n of l){const p=n.outcome==="success"?"\u2705":n.outcome==="partial"?"\u26A0\uFE0F":"\u274C",g=n.severity==="critical"?"\u{1F534} ":n.severity==="major"?"\u{1F7E1} ":"",v=n.reviewed_by?n.review_level==="senior"?" \u{1F6E1}\uFE0F":" \u2714\uFE0F":"",L=n.author?` \xB7 _by ${n.author}_`:"",w=n.reviewed_by?` \xB7 reviewed by ${n.reviewed_by}${(n.endorsements??0)>1?` +${(n.endorsements??0)-1}`:""}`:"",b=(n.recall_count??0)>0?` \xB7 recalled ${n.recall_count}\xD7`:"",m=Math.round((Date.now()-new Date(n.ts).getTime())/864e5),h=m===0?"today":m===1?"yesterday":`${m}d ago`;s.push(`${p} ${g}**\`${n.topic}\`**${v}${L}${w}${b} \xB7 ${h}`),s.push(` ${n.what_worked.slice(0,120)}`),s.push("")}return s.join(`
|
|
14
14
|
`)}case"team_synthesize":{const{instance_id:i,topic:e}=y,a=await _(i),o=`cachly:lessons:${e}`,r=await a.lrange(o,0,-1);if(r.length<2)return`\u{1F4ED} Need at least 2 entries for topic \`${e}\` to synthesize.
|
|
15
15
|
|
|
16
16
|
Currently: ${r.length} entr${r.length===1?"y":"ies"}.
|
|
17
17
|
|
|
18
|
-
Have team members store lessons via \`learn_from_attempts(topic="${e}", ...)\`.`;const t=r.map(
|
|
19
|
-
`)}case"memory_crystalize":{const{instance_id:i,label:e=""}=y,a=await _(i),o=new Date,r=`${o.getFullYear()}-W${String(Math.ceil((o.getDate()-o.getDay()+10)/7)).padStart(2,"0")}`,t=e||`${o.toISOString().slice(0,7)} Crystal`,c=await a.lrange("cachly:session:history",0,49),l=[],
|
|
20
|
-
`)}case"team_crystallize":{const{instance_id:i,min_authors:e=2,label:a=""}=y,o=await _(i),r=new Date,t=Math.max(2,Number(e)||2),c=[],l=o.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((m,
|
|
21
|
-
`);const
|
|
22
|
-
`)}case"brain_doctor":{const{instance_id:i,workspace_path:e=""}=y,a=await _(i),o=[],r=[],t=[],c=a.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((f
|
|
23
|
-
`)}case"brain_hygiene":{const{instance_id:i,dry_run:e=!1,provisional_threshold:a=.5,archive_days:o=30}=y,r=await _(i),t=[],c=r.scanStream({match:"cachly:lesson:best:*",count:200});if(await new Promise((u,x)=>{c.on("data",j=>t.push(...j)),c.on("end",u),c.on("error",x)}),t.length===0)return"\u{1F9F9} **Brain Hygiene** \u2014 no lessons found. Nothing to sweep.";const l=await r.mget(...t),
|
|
24
|
-
`)}case"recall_at":{const{instance_id:i,topic:e,date:a}=y,o=await _(i),r=new Date(a).getTime();if(isNaN(r))return`\u274C Invalid date "${a}". Use ISO format: "2026-01-15"`;const t=`cachly:lessons:${e}`,c=await o.lrange(t,0,-1);if(c.length===0)return`\u{1F4ED} No history found for \`${e}\`. Lessons are stored via \`learn_from_attempts\`.`;const l=c.map(s=>{try{return JSON.parse(s)}catch{return null}}).filter(s=>s!==null&&new Date(s.ts).getTime()<=r).sort((s,n)=>new Date(s.ts).getTime()-new Date(n.ts).getTime());if(l.length===0){const s=
|
|
18
|
+
Have team members store lessons via \`learn_from_attempts(topic="${e}", ...)\`.`;const t=r.map(h=>{try{return JSON.parse(h)}catch{return null}}).filter(h=>h!==null),c=t.filter(h=>h.outcome==="success"),l=t.filter(h=>h.outcome==="failure"),d=t.filter(h=>h.outcome==="partial"),s=[...new Set(t.map(h=>h.author).filter(Boolean))],n=s.length>1,p=c.sort((h,u)=>new Date(u.ts).getTime()-new Date(h.ts).getTime())[0],v=c.map(h=>h.what_worked).filter(h=>h&&h.length>10).sort((h,u)=>u.length-h.length)[0]??p?.what_worked??"",L=[...new Set([...l,...d].map(h=>h.what_failed).filter(h=>!!h&&h.length>5))],w=t.map(h=>h.severity).filter(Boolean),b=w.includes("critical")?"critical":w.includes("major")?"major":"minor";return[`\u{1F9EC} **Team Brain Synthesis: \`${e}\`**`,`_${t.length} entries from ${s.length} author${s.length===1?"":"s"}${n?` (${s.join(", ")})`:""} \xB7 ${c.length} success \xB7 ${l.length} failure \xB7 ${d.length} partial_`,"",'**Canonical "what worked":**',`> ${v}`,"",L.length>0?"**Avoid (combined failures):**":"",...L.map(h=>`> \u274C ${h}`),(L.length>0,""),"**Suggested canonical lesson:**","```","learn_from_attempts(",` topic = "${e}",`,' outcome = "success",',` what_worked = "${v.replace(/"/g,"'")}",`,L.length>0?` what_failed = "${L[0].replace(/"/g,"'")}",`:"",` severity = "${b}",`,")","```","",n?`\u{1F4A1} _${s.length} team members contributed to this synthesis. Store the canonical version to replace individual entries._`:"\u{1F4A1} _Single author \u2014 more value when multiple team members contribute to the same topic._"].filter(h=>h!==void 0).join(`
|
|
19
|
+
`)}case"memory_crystalize":{const{instance_id:i,label:e=""}=y,a=await _(i),o=new Date,r=`${o.getFullYear()}-W${String(Math.ceil((o.getDate()-o.getDay()+10)/7)).padStart(2,"0")}`,t=e||`${o.toISOString().slice(0,7)} Crystal`,c=await a.lrange("cachly:session:history",0,49),l=[],d=a.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((w,b)=>{d.on("data",m=>l.push(...m)),d.on("end",w),d.on("error",b)});const s=[];if(l.length>0){const w=await a.mget(...l);for(const b of w){const m=C(b??null,null);m&&s.push(m)}}const n=new Map;for(const w of s){const b=w.topic.split(":")[0]||"misc";n.has(b)||n.set(b,[]),n.get(b).push(w)}const p=[];for(const[w,b]of[...n.entries()].sort((m,h)=>h[1].length-m[1].length).slice(0,8)){const h=b.filter(u=>u.outcome==="success")[0]??b[0];h&&p.push({category:w,insight:h.what_worked.slice(0,120),count:b.length})}const g={label:t,ts:o.toISOString(),session_count:c.length,lesson_count:s.length,top_patterns:p,categories:[...n.keys()],created_from:`${c.length} sessions, ${s.length} lessons`},v=JSON.stringify(g);return await a.set("cachly:crystal:latest",v),await a.expire("cachly:crystal:latest",90*86400),await a.set(`cachly:crystal:${r}`,v),await a.expire(`cachly:crystal:${r}`,365*86400),[`\u{1F48E} **Memory Crystal created: ${t}**`,"",`\u{1F4CA} Compressed: **${c.length} sessions** + **${s.length} lessons** \u2192 ${p.length} top patterns`,"","**Top patterns by category:**",...p.slice(0,6).map(w=>` \u2022 **${w.category}** (${w.count}\xD7): ${w.insight.slice(0,90)}`),"","\u{1F4A1} This crystal will appear in every future `session_start` briefing.","\u{1F4A1} Re-run `memory_crystalize` monthly to keep it fresh."].join(`
|
|
20
|
+
`)}case"team_crystallize":{const{instance_id:i,min_authors:e=2,label:a=""}=y,o=await _(i),r=new Date,t=Math.max(2,Number(e)||2),c=[],l=o.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((m,h)=>{l.on("data",u=>c.push(...u)),l.on("end",m),l.on("error",h)});const d=[];if(c.length>0){const m=await o.mget(...c);for(const h of m){const u=C(h??null,null);u&&u.outcome==="success"&&u.author&&d.push(u)}}const s=new Set(["the","and","for","with","from","that","this","fix","bug","error","issue","use","via","add","set","team"]),n=m=>{const[h,...u]=m.topic.split(":"),x=(m.tags??[]).map(S=>S.toLowerCase().trim()).filter(S=>S.length>3&&!s.has(S)).sort();return x.length>0?x[0]:u.join("-").toLowerCase().replace(/[^a-z0-9\s-]/g," ").split(/[\s-]+/).filter(S=>S.length>3&&!s.has(S)).sort((S,D)=>D.length-S.length||S.localeCompare(D))[0]??h},p=new Map;for(const m of d){const h=n(m);let u=p.get(h);u||(u={concept:h,authors:new Set,lessons:[],namespaces:new Set,bestFix:m},p.set(h,u)),u.authors.add(m.author.toLowerCase()),u.lessons.push(m),u.namespaces.add(m.topic.split(":")[0]||"misc");const x=j=>(j.endorsements??0)*10+(j.confirm_count??0)+(j.review_level==="senior"?5:0);x(m)>x(u.bestFix)&&(u.bestFix=m)}const v=[...p.values()].filter(m=>m.authors.size>=t).sort((m,h)=>h.authors.size-m.authors.size||h.lessons.length-m.lessons.length).slice(0,10).map(m=>({concept:m.concept,author_count:m.authors.size,authors:[...m.authors],lesson_count:m.lessons.length,spans:[...m.namespaces],convergent_fix:m.bestFix.what_worked.slice(0,200),fix_topic:m.bestFix.topic})),L=a||`${r.toISOString().slice(0,7)} Team Crystal`,w={label:L,ts:r.toISOString(),kind:"team",contributors:new Set(d.map(m=>m.author.toLowerCase())).size,analysed:d.length,patterns:v};if(await o.set("cachly:crystal:team:latest",JSON.stringify(w)),await o.expire("cachly:crystal:team:latest",180*86400),v.length===0)return["\u{1F4A0} **Team Crystal \u2014 not enough cross-person convergence yet**","",`Analysed **${d.length}** attributed success lessons from **${w.contributors}** contributor${w.contributors!==1?"s":""}, but no concept had **${t}+ distinct authors** converge on it yet.`,"","This is the one thing a per-user memory can't build \u2014 it needs your team to accumulate shared, attributed lessons.",'Keep using `learn_from_attempts(author="...")` / `team_learn`; re-run as the team grows.'].join(`
|
|
21
|
+
`);const b=[`\u{1F4A0} **Team Crystal created: ${L}**`,"",`\u{1F9EC} **${v.length} cross-person pattern${v.length!==1?"s":""}** \u2014 fixes that ${t}+ teammates independently converged on.`,`_Compressed from ${d.length} attributed lessons across ${w.contributors} contributors. This is the team-wide, causal layer a per-user "Dreaming" process structurally cannot produce._`,""];for(const m of v.slice(0,6)){const h=m.spans.length>1?` \xB7 spans ${m.spans.map(u=>`\`${u}\``).join(", ")}`:"";b.push(`### \u{1F9E9} \`${m.concept}\` \u2014 ${m.author_count} people converged${h}`,`**Convergent fix** (from \`${m.fix_topic}\`): ${m.convergent_fix}`,`**Who solved it together:** ${m.authors.map(u=>`@${u}`).join(", ")} _(ask them \u2014 they've each hit this)_`,"")}return b.push("---","\u{1F4A1} Surfaces in `crystal_view` and onboarding. Re-run `team_crystallize` monthly."),b.join(`
|
|
22
|
+
`)}case"brain_doctor":{const{instance_id:i,workspace_path:e=""}=y,a=await _(i),o=[],r=[],t=[],c=a.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((f,$)=>{c.on("data",O=>t.push(...O)),c.on("end",f),c.on("error",$)});let l=0;const d=a.scanStream({match:"cachly:ctx:*",count:200});await new Promise((f,$)=>{d.on("data",O=>{l+=O.filter(R=>!R.endsWith(":meta")).length}),d.on("end",f),d.on("error",$)});const s=[];if(t.length>0){const f=await a.mget(...t);for(const $ of f){const O=C($??null,null);O&&s.push(O)}}const n=await a.get("cachly:session:last"),p=C(n,null),g=s.filter(f=>f.outcome==="failure"||f.outcome==="partial"),v=s.filter(f=>(f.recall_count??0)===0),L=s.filter(f=>f.severity==="critical"),w=s.filter(f=>f.outcome==="success"&&G(f)<X),b=s.filter(f=>f.outcome==="success"&&G(f)>=X&&G(f)<me),m=s.filter(f=>(f.audit_trail??[]).length>1),h=s.filter(f=>f.author),u=new Set(s.map(f=>f.author).filter(Boolean)),x=s.reduce((f,$)=>f+($.recall_count??0),0),j=s.length>0?Math.min(100,Math.round(x/s.length*10)):0,A=async f=>{let $=0;const O=a.scanStream({match:f,count:200});return await new Promise((R,F)=>{O.on("data",M=>{$+=M.length}),O.on("end",R),O.on("error",F)}),$},S=await A("cachly:lesson:vec:*"),D=await A("cachly:lesson:vecname:*"),P=await A("cachly:lesson:eing:*"),I=await A("cachly:lesson:pfad:*"),J=t.length>0?Math.round(S/t.length*100):100,Z=pe(t.length,S);t.length>0&&(Z==="aus"?(o.push(`semantic search is OFF: not a single lesson has an embedding (0 of ${t.length}). Recall falls back to keyword matching only. Fix: run the backfill (sdk/mcp/src/eingaenge-nachruesten.ts).`),r.push(`\u{1F534} **Semantic search: OFF** \u2014 0 of ${t.length} lessons have an embedding`)):Z==="luecke"?(o.push(`only ${J}% of lessons have an embedding (${S} of ${t.length}) \u2014 the rest can never be found by meaning.`),r.push(`\u{1F7E1} **Semantic coverage: ${J}%** \u2014 ${t.length-S} lessons without an embedding`)):(r.push(`\u2705 **Semantic coverage: ${J}%** \u2014 ${S} full-text, ${D} topic-name, ${P} with error-text entries`+(I>0?`, ${I} with learned question paths`:"")),I===0&&t.length>=20&&r.push("\u{1F4A4} **No learned question paths yet** \u2014 a question that finds the same lesson twice becomes its own entry. Needs repeated real use.")));try{const f=await we(a);for(const $ of f??[])r.push(`\u26A0\uFE0F **Write-path failure noted:** ${$.grund} \u2014 ${$.anzahl}\xD7 (last: ${$.zuletzt})`+($.text?` \xB7 ${String($.text).slice(0,140)}`:"")),o.push(`write-path failures recorded for "${$.grund}" (${$.anzahl}\xD7) \u2014 see check above for the reason.`)}catch{}let T=50;t.length>=5&&(T+=10),t.length>=20&&(T+=10),l>=3&&(T+=10),l>=10&&(T+=5),p&&(T+=10),g.length===0&&(T+=5);const te=s.length>0?v.length/s.length:0;te<.5&&(T+=10),w.length===0&&(T+=5),u.size>=2&&(T+=5),Z!=="gut"&&(T-=20),T=Math.max(0,Math.min(100,T));const ie=T>=80?"\u{1F7E2}":T>=50?"\u{1F7E1}":"\u{1F534}",ce=j>=50?"\u{1F680}":j>=20?"\u{1F4C8}":"\u{1F4A4}";r.push(`${ie} **Brain Quality Score: ${T}/100**`),r.push(`${ce} **Effective IQ Boost: ${j}%** (${x} recalls across ${s.length} lessons)`);try{const $=(await a.lrange("cachly:recall:spread",0,199)).map(O=>C(O,null)?.spread).filter(O=>typeof O=="number"&&Number.isFinite(O));if($.length===0)r.push("\u{1F4CF} **Rank spread:** not measured yet \u2014 recorded from the next `smart_recall` on (score gap rank 1 \u2192 rank 5).");else{const O=$.reduce((M,K)=>M+K,0)/$.length,R=$.filter(M=>M<.05).length,F=`mean ${O.toFixed(3)} over ${$.length} recall${$.length===1?"":"s"} (${R} flat, < 0.05)`;$.length>=20&&O<.05?(r.push(`\u{1F7E1} **Rank spread: ${F}** \u2014 ranking has stopped separating hits; provisional threshold, see card 0u9s8qoopwhr`),o.push("Rank spread is flat: the top hits score alike. Check whether the store has grown past what the ranking separates (near-duplicates, one embedding for everything).")):r.push(`\u{1F4CF} **Rank spread:** ${F}`)}}catch{}r.push(`\u{1F4DA} **Lessons:** ${t.length} (${L.length} critical \xB7 ${m.length} with audit trail \xB7 ${h.length} from team)`),r.push(`\u{1F4BE} **Context entries:** ${l}`),r.push(`\u{1F3AF} **Confidence:** ${s.length-w.length-b.length} fresh \xB7 ${b.length} warn \xB7 ${w.length} stale`),r.push(`\u23F1\uFE0F **Decay config:** warn after ${ne}d \xB7 stale after ${oe}d`),r.push(he(ue));try{const f=await a.info("memory");let $=null;try{const R=await a.config("GET","maxmemory-policy");$=Array.isArray(R)&&R.length>1?String(R[1]):null}catch{$=null}const O={benutzt:se(f,"used_memory"),grenze:se(f,"maxmemory"),richtlinie:$};for(const R of de(O)){const M=`${R.art==="fehler"?"\u{1F534}":R.art==="hinweis"?"\u{1F7E1}":R.art==="ungemessen"?"\u26AA":"\u{1F7E2}"} **Speicher:** ${R.text}`;R.art==="fehler"||R.art==="ungemessen"?o.push(M):r.push(M)}}catch{o.push("\u26AA **Speicher:** Lage nicht abfragbar \u2014 unbekannt, ob dieser Speicher bei Platzmangel still loescht.")}u.size>=2&&r.push(`\u{1F465} **Team:** ${u.size} contributors (${[...u].join(", ")})`);try{const f=await a.get("cachly:index:last_run");if(f){const $=Math.round((Date.now()-new Date(f).getTime())/864e5);$>7?o.push(`\u{1F504} Index is ${$}d stale \u2014 run \`index_project\` to re-sync semantic search`):r.push(`\u{1F5C2}\uFE0F **Semantic index:** ${$}d old (fresh)`)}else o.push('\u{1F4A1} No semantic index \u2014 run `index_project(dir="<your-src>")` to enable semantic search')}catch{}try{const f=await a.get("cachly:crystal:latest");if(f){const $=JSON.parse(f),O=Math.round((Date.now()-new Date($.ts).getTime())/864e5);r.push(`\u{1F48E} **Memory Crystal:** ${$.label} (${O}d ago)`),O>30&&o.push(`\u{1F4A1} Memory Crystal is ${O}d old \u2014 re-run \`memory_crystalize\` to compress new sessions`)}else t.length>=10&&o.push(`\u{1F4A1} ${t.length} lessons but no Memory Crystal \u2014 run \`memory_crystalize\` to compress wisdom`)}catch{}if(e)try{const f=e.replace(/\/$/,"")+"/package.json",$=le(f,"utf-8"),O=JSON.parse($),R={...O.dependencies,...O.devDependencies},F=["openai","@anthropic-ai/sdk","@google/generative-ai","mistralai","cohere-ai"].some(K=>K in R),M="@cachly-dev/openclaw"in R;F&&!M?(o.push(`\u{1F4A1} **openclaw missing:** you use LLM APIs (${Object.keys(R).filter(K=>["openai","@anthropic-ai/sdk"].includes(K)).join(", ")}) but not \`@cachly-dev/openclaw\``),o.push(" \u2192 `npm install @cachly-dev/openclaw` cuts LLM costs 60\u201390% with 3 lines of code")):M&&r.push("\u2705 **@cachly-dev/openclaw installed** (LLM cost caching active)")}catch{}if(p){const f=Math.round((Date.now()-new Date(p.ts).getTime())/6e4),$=f<60?`${f}m`:f<1440?`${Math.round(f/60)}h`:`${Math.round(f/1440)}d`;r.push(`\u{1F550} **Last session:** ${$} ago`)}else o.push("\u274C No session history \u2014 call `session_start` + `session_end` to start tracking");t.length===0?o.push("\u274C No lessons \u2014 call `learn_from_attempts` after solving bugs"):t.length<5&&o.push(`\u{1F4A1} Only ${t.length} lessons \u2014 add more after each problem solved`),j===0&&s.length>=5&&o.push("\u{1F4A4} **IQ Boost is 0%** \u2014 lessons exist but are never recalled. Use `recall_best_solution` BEFORE tasks."),l===0&&o.push("\u{1F4A1} No context \u2014 use `remember_context` to cache architecture docs, ADRs, etc."),g.length>0&&o.push(`\u26A0\uFE0F ${g.length} unresolved failure${g.length>1?"s":""}: ${g.slice(0,3).map(f=>`\`${f.topic}\``).join(", ")}`),w.length>0&&(o.push(`\u{1F534} ${w.length} STALE lesson${w.length>1?"s":""} (>${oe}d, confidence <${X*100}%): ${w.slice(0,3).map(f=>`\`${f.topic}\``).join(", ")}`),o.push(" \u2192 Re-verify with `recall_best_solution` to reset confidence clock")),b.length>0&&o.push(`\u26A0\uFE0F ${b.length} lesson${b.length>1?"s":""} aging (>${ne}d): ${b.slice(0,3).map(f=>`\`${f.topic}\``).join(", ")}`),te>.7&&s.length>5&&o.push(`\u{1F4A1} ${v.length} lessons never recalled \u2014 verify topics match your workflow`);const z=["\u{1FA7A} **Brain Doctor**",`\`${"\u2500".repeat(48)}\``,"","**\u{1F4CA} Health Checks**",...r.map(f=>" "+f),""];return o.length>0?(z.push(`**\u{1F527} Action Items** (${o.length} issue${o.length>1?"s":""})`),o.forEach((f,$)=>z.push(` ${$+1}. ${f}`)),z.push(""),z.push("_Fix the action items above, then re-run `brain_doctor` to verify._")):(z.push("**\u2705 All checks passed** \u2014 Brain is healthy!"),z.push(""),z.push("_Keep the brain sharp: `session_start` \u2192 work \u2192 `learn_from_attempts` \u2192 `session_end`_")),z.join(`
|
|
23
|
+
`)}case"brain_hygiene":{const{instance_id:i,dry_run:e=!1,provisional_threshold:a=.5,archive_days:o=30}=y,r=await _(i),t=[],c=r.scanStream({match:"cachly:lesson:best:*",count:200});if(await new Promise((u,x)=>{c.on("data",j=>t.push(...j)),c.on("end",u),c.on("error",x)}),t.length===0)return"\u{1F9F9} **Brain Hygiene** \u2014 no lessons found. Nothing to sweep.";const l=await r.mget(...t),d=[];for(let u=0;u<t.length;u++){const x=l[u],j=C(x??null,null);j&&d.push({...j,_key:t[u]})}const s=Date.now(),n=[],p=[],g=[],v=new Map;for(const u of d){const x=u.state??"active";if(x==="archived")continue;const j=G(u),A=new Date(u.verified_at??u.ts).getTime(),S=(s-A)/864e5;if(j<=a){const D=u.recall_count??0;if(x==="provisional"&&D<2&&S>o){if(p.push(u.topic),!e){const I={...u,state:"archived",audit_trail:[...u.audit_trail??[],{ts:new Date().toISOString(),action:"archived",prev_state:"provisional"}]};v.set(u._key,I)}}else if(x==="active"&&(n.push(u.topic),!e)){const I={...u,state:"provisional",audit_trail:[...u.audit_trail??[],{ts:new Date().toISOString(),action:"flagged_provisional",prev_state:"active"}]};v.set(u._key,I)}}}for(const u of d){const x=u.state??"active";if(x==="archived"||v.has(u._key)&&v.get(u._key).state==="archived"||u.outcome!=="failure"||(u.recall_count??0)>0)continue;const A=new Date(u.verified_at??u.ts).getTime(),S=(s-A)/864e5;if(S<=o)continue;const D=n.indexOf(u.topic);if(D!==-1&&n.splice(D,1),g.push(`${u.topic} (failure, never recalled, ${Math.round(S)}d old)`),!e){const P=v.get(u._key)??u,I={...P,state:"archived",audit_trail:[...P.audit_trail??[],{ts:new Date().toISOString(),action:"orphan_failure_archived",prev_state:x}]};v.set(u._key,I)}}let L=0,w=0;const b=parseFloat(await r.get(`cachly:stats:time_saved_mins:${i}`)??"0")||0;for(const u of d){if(ge(u)){if((u.recall_count??0)>0){L++;const x=v.get(u._key)??u;v.set(u._key,{...x,recall_count:0})}continue}(u.recall_count??0)>0&&(w+=fe(u))}if(e||await r.set(`cachly:stats:time_saved_mins:${i}`,String(w)).catch(()=>{}),!e&&v.size>0){const u=r.pipeline();for(const[x,j]of v){const{_key:A,...S}=j;u.set(x,JSON.stringify(S))}await u.exec()}const h=[`${e?"\u{1F50D} **Brain Hygiene (dry run)**":"\u{1F9F9} **Brain Hygiene**"} \u2014 scanned **${d.length}** lessons`,"","| Action | Count |","|---|---|",`| Flagged provisional (confidence < ${a}) | ${n.length} |`,`| Archived (stale + low-recall + age > ${o}d) | ${p.length} |`,`| Contradictions auto-resolved | ${g.length} |`,`| Startvorrat entwertet (Z\xE4hler auf 0) | ${L} |`,""];return Math.round(b)!==Math.round(w)&&h.push(`**\u23F1\uFE0F Zeitersparnis neu gerechnet:** ${ae(b)} \u2192 **${ae(w)}**`,"_Nur eigene Lektionen, und jede genau einmal. Der mitgelieferte Startvorrat ist Allgemeinwissen, kein gelerntes; und wer dieselbe Lektion zum zweiten Mal liest, spart keine zweite Recherche._",""),n.length>0&&(h.push("**\u26A0\uFE0F Provisional (verify or re-learn):**"),n.slice(0,10).forEach(u=>h.push(` - \`${u}\``)),n.length>10&&h.push(` - _\u2026and ${n.length-10} more_`),h.push("")),p.length>0&&(h.push("**\u{1F4E6} Archived (excluded from recall):**"),p.slice(0,5).forEach(u=>h.push(` - \`${u}\``)),p.length>5&&h.push(` - _\u2026and ${p.length-5} more_`),h.push("")),g.length>0&&(h.push("**\u2694\uFE0F Contradictions resolved (failure archived, success kept):**"),g.slice(0,5).forEach(u=>h.push(` - ${u}`)),h.push("")),e?h.push("_Run `brain_hygiene(dry_run=false)` to apply these changes._"):n.length+p.length+g.length===0?h.push("\u2705 Brain is clean \u2014 no hygiene actions needed."):h.push("_Changes applied. Run `brain_doctor` to verify the updated health score._"),h.join(`
|
|
24
|
+
`)}case"recall_at":{const{instance_id:i,topic:e,date:a}=y,o=await _(i),r=new Date(a).getTime();if(isNaN(r))return`\u274C Invalid date "${a}". Use ISO format: "2026-01-15"`;const t=`cachly:lessons:${e}`,c=await o.lrange(t,0,-1);if(c.length===0)return`\u{1F4ED} No history found for \`${e}\`. Lessons are stored via \`learn_from_attempts\`.`;const l=c.map(s=>{try{return JSON.parse(s)}catch{return null}}).filter(s=>s!==null&&new Date(s.ts).getTime()<=r).sort((s,n)=>new Date(s.ts).getTime()-new Date(n.ts).getTime());if(l.length===0){const s=C(c[0]??null,null),n=s?.ts?new Date(s.ts).toLocaleDateString("de-DE"):"unknown";return`\u{1F4ED} No entries for \`${e}\` found before **${a}**. Earliest entry: ${n}.`}const d=[`\u{1F3FA} **Brain Archaeology: \`${e}\` before ${a}**`,`_${l.length} of ${c.length} total entries shown_`,""];for(const s of l.slice(-10)){const n=s.outcome==="success"?"\u2705":s.outcome==="partial"?"\u26A0\uFE0F":"\u274C",p=new Date(s.ts).toLocaleDateString("de-DE");d.push(`**${p}** ${n} ${s.outcome}`),s.what_worked&&d.push(` \u2192 ${s.what_worked.slice(0,100)}`),d.push("")}return d.push(`_Full evolution: ${c.map(s=>{try{const n=JSON.parse(s);return n.outcome==="success"?"\u2705":n.outcome==="partial"?"\u26A0\uFE0F":"\u274C"}catch{return"?"}}).join(" \u2192 ")}_`),d.join(`
|
|
25
25
|
`)}case"trace_dependency":{const{instance_id:i,dependency:e,mark_review:a=!1}=y,o=await _(i),r=`cachly:dep:${e}`,t=await o.get(r);if(!t)return`\u{1F4ED} No lessons found that depend on \`${e}\`.
|
|
26
26
|
|
|
27
|
-
Add dependencies via: \`learn_from_attempts(..., depends_on=["${e}"])\``;const c=
|
|
28
|
-
`)}case"global_learn":{const{instance_id:i,topic:e,lesson:a,severity:o="minor",tags:r=[]}=y,t=await _(i),c=`cachly:global:lesson:${e}`,l={topic:e,lesson:a,severity:o,tags:r,ts:new Date().toISOString(),scope:"global",recall_count:0},
|
|
27
|
+
Add dependencies via: \`learn_from_attempts(..., depends_on=["${e}"])\``;const c=C(t,[]),l=[`\u{1F517} **Causal Chain: \`${e}\`** \u2014 ${c.length} dependent lesson${c.length===1?"":"s"}`,""];for(const d of c){const s=await o.get(`cachly:lesson:best:${d}`);if(!s){l.push(` \u2022 \`${d}\` _(lesson deleted)_`);continue}const n=C(s,null);if(!n){l.push(` \u2022 \`${d}\` _(lesson corrupted)_`);continue}const p=n.outcome==="success"?"\u2705":n.outcome==="partial"?"\u26A0\uFE0F":"\u274C",g=n.needs_review?" \u{1F50D} **needs_review**":"";if(l.push(` ${p} \`${d}\` (${n.severity??"major"})${g}`),a){const v={...n,needs_review:!0};await o.set(`cachly:lesson:best:${d}`,JSON.stringify(v))}}return a?l.push("",`\u{1F50D} All ${c.length} lessons marked as **needs_review** \u2014 verify they still work with the changed dependency.`):l.push("","_Run with `mark_review: true` to flag all dependent lessons for re-verification._"),l.join(`
|
|
28
|
+
`)}case"global_learn":{const{instance_id:i,topic:e,lesson:a,severity:o="minor",tags:r=[]}=y,t=await _(i),c=`cachly:global:lesson:${e}`,l={topic:e,lesson:a,severity:o,tags:r,ts:new Date().toISOString(),scope:"global",recall_count:0},d=await t.get(c);if(d){const s=C(d,null);l.recall_count=s?.recall_count??0}return await t.set(c,JSON.stringify(l)),`\u{1F310} **Global lesson stored**: \`${e}\`
|
|
29
29
|
|
|
30
30
|
${a}
|
|
31
31
|
|
|
32
|
-
Recallable from any project via \`global_recall(topic="${e}")\`.`}case"global_recall":{const{instance_id:i,topic:e}=y,a=await _(i),o=[],r=a.scanStream({match:"cachly:global:lesson:*",count:200});await new Promise((l,
|
|
32
|
+
Recallable from any project via \`global_recall(topic="${e}")\`.`}case"global_recall":{const{instance_id:i,topic:e}=y,a=await _(i),o=[],r=a.scanStream({match:"cachly:global:lesson:*",count:200});await new Promise((l,d)=>{r.on("data",s=>o.push(...s)),r.on("end",l),r.on("error",d)});let t=[];if(o.length>0){const l=await a.mget(...o);for(const d of l){const s=C(d??null,null);s&&t.push(s)}}if(e){const l=e.toLowerCase();t=t.filter(d=>d.topic.toLowerCase().includes(l))}if(t.length===0)return`\u{1F4ED} No global lessons${e?` for \`${e}\``:""}.
|
|
33
33
|
|
|
34
|
-
Add cross-project knowledge with \`global_learn(topic="...", lesson="...")\`.`;for(const l of t){const
|
|
35
|
-
`)}case"publish_lesson":{const{instance_id:i,topic:e,lesson:a,framework:o="general",severity:r="minor"}=y,t=await _(i),c=a.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,"[email]").replace(/\b(sk-|cky_live_|Bearer\s)[A-Za-z0-9_-]{8,}/g,"[token]").replace(/\/Users\/[^\s/]+/g,"/Users/[user]").replace(/\/home\/[^\s/]+/g,"/home/[user]"),l={topic:e,lesson:c,framework:o,severity:r,ts:new Date().toISOString(),published:!0,votes:0},
|
|
34
|
+
Add cross-project knowledge with \`global_learn(topic="...", lesson="...")\`.`;for(const l of t){const d=`cachly:global:lesson:${l.topic}`,s=await a.get(d);if(s){const n=C(s,null);if(!n)continue;n.recall_count=(n.recall_count??0)+1,n.last_recalled_at=new Date().toISOString(),await a.set(d,JSON.stringify(n))}}const c=[`\u{1F310} **Global Brain** \u2014 ${t.length} lesson${t.length>1?"s":""}`,""];for(const l of t){const d=l.severity==="critical"?"\u{1F534} ":l.severity==="major"?"\u{1F7E1} ":"";c.push(`${d}**\`${l.topic}\`**`),c.push(l.lesson.slice(0,200)),c.push("")}return c.join(`
|
|
35
|
+
`)}case"publish_lesson":{const{instance_id:i,topic:e,lesson:a,framework:o="general",severity:r="minor"}=y,t=await _(i),c=a.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,"[email]").replace(/\b(sk-|cky_live_|Bearer\s)[A-Za-z0-9_-]{8,}/g,"[token]").replace(/\/Users\/[^\s/]+/g,"/Users/[user]").replace(/\/home\/[^\s/]+/g,"/home/[user]"),l={topic:e,lesson:c,framework:o,severity:r,ts:new Date().toISOString(),published:!0,votes:0},d=`cachly:public:lesson:${o}:${e}`;return await t.set(d,JSON.stringify(l),"EX",365*86400),["\u{1F4E2} **Lesson published!**","",`**Topic:** \`${e}\``,`**Framework:** ${o}`,`**Content:** ${c.slice(0,200)}${c.length>200?"\u2026":""}`,"","This lesson is now available in the Public Brain for other developers.",`Import it anywhere: \`import_public_brain(framework="${o}")\``].join(`
|
|
36
36
|
`)}case"import_public_brain":{const{instance_id:i,framework:e,limit:a=20}=y,o=await _(i),r={nextjs:[{topic:"nextjs:image-layout",lesson:'Use fill + relative parent instead of layout="fill" (deprecated since Next.js 13)',severity:"major"},{topic:"nextjs:app-router-fetch",lesson:'fetch() in Server Components is cached by default \u2014 add {cache:"no-store"} for dynamic data',severity:"major"},{topic:"nextjs:metadata-export",lesson:"Export metadata const or generateMetadata() \u2014 never both in same file",severity:"minor"},{topic:"nextjs:client-boundary",lesson:'"use client" propagates down \u2014 keep it at the lowest component, not at page level',severity:"major"},{topic:"nextjs:env-prefix",lesson:"Only NEXT_PUBLIC_* env vars are exposed to client \u2014 others are server-only",severity:"critical"},{topic:"nextjs:revalidate",lesson:"export const revalidate = 0 disables caching for entire route; use revalidatePath() for on-demand",severity:"minor"}],fastapi:[{topic:"fastapi:async-db",lesson:"Use async session with asyncpg \u2014 sync SQLAlchemy blocks the event loop",severity:"critical"},{topic:"fastapi:pydantic-v2",lesson:"Pydantic v2: use model_validate() instead of parse_obj(), .dict() \u2192 .model_dump()",severity:"major"},{topic:"fastapi:lifespan",lesson:"Use lifespan context manager instead of deprecated on_event startup/shutdown",severity:"minor"},{topic:"fastapi:background-tasks",lesson:"BackgroundTasks run after response is sent \u2014 not in a separate thread pool",severity:"major"},{topic:"fastapi:cors-order",lesson:"CORSMiddleware must be added before other middleware to work correctly",severity:"critical"}],go:[{topic:"go:context-cancel",lesson:"Always call cancel() from context.WithCancel \u2014 leak goroutines if not cancelled",severity:"critical"},{topic:"go:defer-in-loop",lesson:"defer in a loop runs at function return, not loop iteration \u2014 use IIFE or explicit close",severity:"major"},{topic:"go:nil-interface",lesson:"nil interface != interface containing nil pointer \u2014 use explicit nil checks",severity:"major"},{topic:"go:goroutine-leak",lesson:"Goroutines with channel sends block forever if receiver is gone \u2014 use select with done chan",severity:"critical"},{topic:"go:embed-path",lesson:"//go:embed path must be relative and known at compile time \u2014 no os.Getenv",severity:"minor"}],docker:[{topic:"docker:layer-cache",lesson:"Copy package.json before source code \u2014 Docker caches layers, npm install only reruns on dep changes",severity:"major"},{topic:"docker:non-root",lesson:"Run as non-root user (USER 1001) \u2014 some k8s clusters reject root containers by policy",severity:"critical"},{topic:"docker:build-arg-secret",lesson:"Never use ARG for secrets \u2014 visible in image history. Use --secret mount instead",severity:"critical"},{topic:"docker:entrypoint-exec",lesson:'Use exec form ["cmd","arg"] not shell form "cmd arg" \u2014 shell form ignores SIGTERM',severity:"major"},{topic:"docker:multi-stage",lesson:"Multi-stage builds: copy only built artifacts to final stage \u2014 keep image small",severity:"minor"}],kubernetes:[{topic:"k8s:resource-limits",lesson:"Always set resource limits \u2014 unbounded pods cause node evictions and OOMKill",severity:"critical"},{topic:"k8s:liveness-vs-readiness",lesson:"Liveness failures restart pod; Readiness failures remove from LB. Use different endpoints",severity:"major"},{topic:"k8s:imagepullpolicy",lesson:"imagePullPolicy: Always in production \u2014 IfNotPresent can serve stale images",severity:"major"},{topic:"k8s:configmap-env",lesson:"ConfigMap changes don't restart pods \u2014 use rollout restart or mount as volume",severity:"critical"},{topic:"k8s:pdb",lesson:"Set PodDisruptionBudget for stateful apps \u2014 node drains kill all pods without it",severity:"major"}],react:[{topic:"react:useeffect-deps",lesson:"Omitting dependencies from useEffect deps array causes stale closure bugs \u2014 use exhaustive-deps ESLint rule",severity:"critical"},{topic:"react:key-index",lesson:"Never use array index as key in lists \u2014 causes subtle re-render bugs on reorder/delete",severity:"major"},{topic:"react:setState-in-render",lesson:"setState() during render causes infinite loop \u2014 move to useEffect or event handler",severity:"critical"},{topic:"react:memo-reference",lesson:"Object/array literals in JSX recreate on every render \u2014 useMemo for expensive derived values",severity:"minor"}],typescript:[{topic:"ts:type-guard",lesson:'Use "x is Type" return type for type guard functions \u2014 not "boolean"',severity:"minor"},{topic:"ts:strict-null",lesson:"Enable strictNullChecks in tsconfig \u2014 catches 90% of runtime null errors at compile time",severity:"critical"},{topic:"ts:enum-avoid",lesson:'Prefer union types ("a"|"b") over enum \u2014 enums have surprising runtime behavior',severity:"minor"},{topic:"ts:satisfies",lesson:'Use "satisfies" operator to validate type without widening \u2014 more precise than explicit annotation',severity:"minor"}],python:[{topic:"python:mutable-default",lesson:"Never use mutable default arguments (def f(x=[])) \u2014 shared across all calls. Use None + guard",severity:"critical"},{topic:"python:walrus-operator",lesson:":= (walrus) assigns and returns \u2014 useful in while/comprehensions but hard to read in complex expr",severity:"minor"},{topic:"python:asyncio-run",lesson:"asyncio.run() creates new event loop \u2014 calling it inside an existing loop raises RuntimeError",severity:"major"},{topic:"python:typing-optional",lesson:"Optional[X] == Union[X, None] \u2014 in Python 3.10+ use X | None syntax",severity:"minor"}]},t=e.toLowerCase(),c=r[t];if(!c){const n=Object.keys(r).join(", ");return`\u274C No public brain available for \`${e}\`.
|
|
37
37
|
|
|
38
38
|
Available: ${n}
|
|
39
39
|
|
|
40
|
-
Or publish your own: \`publish_lesson(framework="${e}", ...)\``}const l=c.slice(0,a);let
|
|
41
|
-
`)}case"setup_ai_memory":{const{instance_id:i,project_dir:e,embed_provider:a="openai",project_description:o="a software project"}=y,r=await
|
|
40
|
+
Or publish your own: \`publish_lesson(framework="${e}", ...)\``}const l=c.slice(0,a);let d=0;for(const n of l){const p=`cachly:lesson:best:${n.topic}`;await o.get(p)||(await o.set(p,JSON.stringify({...n,what_worked:n.lesson,outcome:"success",ts:new Date().toISOString(),recall_count:0,source:"public_brain",version:2})),d++)}return[`\u{1F4E5} **Public Brain imported: ${e}**`,"",`${d} new lessons added (${l.length-d} already existed)`,"","**Imported topics:**",...l.map(n=>` ${n.severity==="critical"?"\u{1F534}":n.severity==="major"?"\u{1F7E1}":"\u{1F4A1}"} \`${n.topic}\``),"","These lessons will now appear in `session_start` when relevant.",`Recall any time: \`recall_best_solution(topic="${t}:...")\``].join(`
|
|
41
|
+
`)}case"setup_ai_memory":{const{instance_id:i,project_dir:e,embed_provider:a="openai",project_description:o="a software project"}=y,r=await N(`/api/v1/instances/${i}`),t={openai:{key:"OPENAI_API_KEY",hint:"Get at: https://platform.openai.com/api-keys"},mistral:{key:"MISTRAL_API_KEY",hint:"Get at: https://console.mistral.ai/api-keys"},cohere:{key:"COHERE_API_KEY",hint:"Get at: https://dashboard.cohere.com/api-keys"},ollama:{key:"OLLAMA_BASE_URL",hint:"Run: brew install ollama && ollama serve (free, local, no key needed)"},gemini:{key:"GEMINI_API_KEY",hint:"Get at: https://aistudio.google.com/app/apikey"}},c=t[a]??t.openai,l=!!r.vector_token,d=JSON.stringify({mcpServers:{cachly:{command:"npx",args:["-y","@cachly-dev/mcp-server@latest"],env:{CACHLY_JWT:"your-api-token-from-cachly.dev/settings",[c.key]:a==="ollama"?"http://localhost:11434":"your-key-here",...a!=="openai"?{CACHLY_EMBED_PROVIDER:a}:{}}}}},null,2),s=r.tier.toUpperCase(),n=l?'- `smart_recall("natural language query")` \u2014 semantic search (finds by meaning)':'- `recall_context("arch:*")` \u2014 exact/glob key lookup (upgrade to Speed/Business for smart_recall)',p=l?`Layer 3 (Semantic): smart_recall uses pgvector HNSW on your ${s} instance`:"Layer 3 (Autopilot): this file \u2014 upgrade to Speed/Business tier to unlock smart_recall",g=`# cachly AI Brain \u2014 ${o}
|
|
42
42
|
|
|
43
43
|
> AI memory system powered by [cachly.dev](${ye("/","team-readme")}). Works with GitHub Copilot, Claude Code, Cursor, Windsurf, Continue.dev, and any MCP-compatible editor.
|
|
44
44
|
|
|
@@ -170,17 +170,17 @@ Layer 3 \u2014 Autopilot: This file \u2014 AI reads it and runs tools automatica
|
|
|
170
170
|
\`\`\`
|
|
171
171
|
|
|
172
172
|
Result: Your AI **never solves the same problem twice** and always picks up exactly where it left off. \u{1F680}
|
|
173
|
-
`,v=["\u{1F9E0} **cachly AI Memory Setup Complete**","",`**Instance:** ${r.name} (${s}) \xB7 ID: \`${i}\``,`**Embedding Provider:** ${a}`,`**Semantic Search:** ${l?"\u2705 pgvector HNSW available":"\u26A0\uFE0F Not available on "+s+" \u2014 upgrade to Speed/Business"}`,"","\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500","**Step 1 \u2014 Add to .mcp.json:**","```json",
|
|
174
|
-
`)}case"team_assign_role":{const{instance_id:i,handle:e,role:a,assigned_by:o=""}=y;if(!i||!e||!a)return"\u274C Required: instance_id, handle, role";const r=["admin","reviewer","contributor","viewer"].includes(a)?a:null;if(!r)return`\u274C Unknown role \`${a}\`. Valid: admin \xB7 reviewer \xB7 contributor \xB7 viewer`;const t=await _(i),c=await t.hgetall(
|
|
175
|
-
`)}const
|
|
176
|
-
`)}case"team_whoami":{const{instance_id:i,handle:e}=y;if(!i||!e)return"\u274C Required: instance_id, handle";const a=await _(i),o=await
|
|
173
|
+
`,v=["\u{1F9E0} **cachly AI Memory Setup Complete**","",`**Instance:** ${r.name} (${s}) \xB7 ID: \`${i}\``,`**Embedding Provider:** ${a}`,`**Semantic Search:** ${l?"\u2705 pgvector HNSW available":"\u26A0\uFE0F Not available on "+s+" \u2014 upgrade to Speed/Business"}`,"","\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500","**Step 1 \u2014 Add to .mcp.json:**","```json",d,"```","",`**Step 2 \u2014 Set your ${a} key:**`,"```bash",`export ${c.key}="your-key-here"`,"```",`_(${c.hint})_`,"","\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500","**Step 3 \u2014 copilot-instructions.md (Layer 3 Autopilot):**","",...e?[`\u{1F50D} Detecting IDEs in \`${e}\`\u2026`]:["Copy this to `.github/copilot-instructions.md` (Copilot), `CLAUDE.md` (Claude Code), or `.cursor/rules` (Cursor) in your project:"],"","```markdown",g,"```","","\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500","**How the 3 layers work together:**"," Layer 1 \u2192 Your Valkey instance stores all lessons + context (persists forever)"," Layer 2 \u2192 MCP tools (learn_from_attempts, recall_best_solution, smart_recall) read/write it"," Layer 3 \u2192 copilot-instructions.md makes your AI run them automatically","","Result: Your AI never solves the same problem twice. \u{1F680}"];if(e){const{mkdir:L,writeFile:w,access:b}=await import("node:fs/promises"),{constants:m}=await import("node:fs"),h=async A=>b(A,m.F_OK).then(()=>!0).catch(()=>!1),u=[];let x=!1;if(u.push({ide:"GitHub Copilot",path:E(e,".github","copilot-instructions.md"),content:g}),await h(E(e,"CLAUDE.md"))||await h(E(e,".claude"))){u.push({ide:"Claude Code",path:E(e,"CLAUDE.md"),content:g});const A=E(e,".claude");await L(A,{recursive:!0});const S={hooks:{Stop:[{matcher:"",hooks:[{type:"command",command:`npx --yes @cachly-dev/mcp-server@latest checkpoint --instance-id ${i}`}]}]}},D=E(A,"settings.json");let P={};try{const{readFile:J}=await import("node:fs/promises");P=JSON.parse(await J(D,"utf-8"))}catch{}const I={...P,hooks:S.hooks};await w(D,JSON.stringify(I,null,2),"utf-8"),x=!0}if(await h(E(e,".cursor"))||await h(E(e,".cursorrules"))){const A=E(e,".cursor");await L(A,{recursive:!0}),u.push({ide:"Cursor",path:E(A,"rules"),content:g})}(await h(E(e,".windsurfrules"))||await h(E(e,".windsurf")))&&u.push({ide:"Windsurf",path:E(e,".windsurfrules"),content:g});const j=[];for(const A of u){const S=A.path.substring(0,A.path.lastIndexOf("/"));await L(S,{recursive:!0}),await w(A.path,A.content,"utf-8"),j.push(`\u2705 [${A.ide}] \u2192 \`${A.path.replace(e,".")}\``)}x&&j.push("\u2705 [Claude Code Stop-Hook] \u2192 `.claude/settings.json` (auto-checkpoint on stop)"),v.push(...j)}return v.join(`
|
|
174
|
+
`)}case"team_assign_role":{const{instance_id:i,handle:e,role:a,assigned_by:o=""}=y;if(!i||!e||!a)return"\u274C Required: instance_id, handle, role";const r=["admin","reviewer","contributor","viewer"].includes(a)?a:null;if(!r)return`\u274C Unknown role \`${a}\`. Valid: admin \xB7 reviewer \xB7 contributor \xB7 viewer`;const t=await _(i),c=await t.hgetall(B(i)).catch(()=>({})),l=Object.values(c).includes("admin");if(l){const p=o?await W(t,i,o):null;if(!q(p,"admin"))return["\u274C **Permission denied** \u2014 only an `admin` can assign roles.","","Ask an existing admin to run:",`\`team_assign_role(instance_id="${i}", handle="${e}", role="${a}", assigned_by="<their-handle>")\``].join(`
|
|
175
|
+
`)}const d=c[e.toLowerCase()]??null;await t.hset(B(i),e.toLowerCase(),r),await t.expire(B(i),365*2*86400),await re(t,i,{action:"team_assign_role",actor:o||"(bootstrap)",handle:e.toLowerCase(),from:d,to:r});const s=!l;return[`${U[r]} **Role assigned** \u2014 **${e}** is now a \`${r}\``,"",`**Capabilities:** ${Q[r]}`,"",s?"_First admin bootstrapped \u2014 governance is now active on this brain._":`_Assigned by ${o||"(bootstrap)"}_`,"","Run `team_roster` to see all current roles."].join(`
|
|
176
|
+
`)}case"team_whoami":{const{instance_id:i,handle:e}=y;if(!i||!e)return"\u274C Required: instance_id, handle";const a=await _(i),o=await W(a,i,e);if(!o){const t=await a.hgetall(B(i)).catch(()=>({})),c=Object.values(t).includes("admin");return[`\u{1F441}\uFE0F **${e}** has no assigned role on this brain.`,"",c?`Ask an admin to run \`team_assign_role(handle="${e}", role="contributor")\` to add you.`:`This brain has no roles set up yet. Establish governance:
|
|
177
177
|
\`team_assign_role(handle="${e}", role="admin")\``].join(`
|
|
178
|
-
`)}return[`## ${
|
|
179
|
-
`)}case"team_roster":{const{instance_id:i}=y;if(!i)return"\u274C Required: instance_id";const a=await(await _(i)).hgetall(
|
|
180
|
-
`);const o=Object.entries(a).map(([t,c])=>({handle:t,role:c})).sort((t,c)=>(
|
|
178
|
+
`)}return[`## ${U[o]} ${e} \u2014 \`${o}\``,"",`**You can:** ${Q[o]}`,"","Run `team_roster` to see all team members and their roles."].join(`
|
|
179
|
+
`)}case"team_roster":{const{instance_id:i}=y;if(!i)return"\u274C Required: instance_id";const a=await(await _(i)).hgetall(B(i)).catch(()=>({}));if(Object.keys(a).length===0)return["## \u{1F465} Team Roster","","No roles assigned yet. Establish governance with:",`\`team_assign_role(instance_id="${i}", handle="<your-handle>", role="admin")\``].join(`
|
|
180
|
+
`);const o=Object.entries(a).map(([t,c])=>({handle:t,role:c})).sort((t,c)=>(H[c.role]??0)-(H[t.role]??0)||t.handle.localeCompare(c.handle)),r=[`## \u{1F465} Team Roster (${o.length} member${o.length!==1?"s":""})`,"","| Role | Handle | Capabilities |","|---|---|---|"];for(const t of o){const c=U[t.role]??"?";r.push(`| ${c} ${t.role} | **${t.handle}** | ${Q[t.role].split(" \xB7 ").slice(0,2).join(", ")} |`)}return r.push("","_Manage with `team_assign_role`. Any admin can add or change roles._"),r.join(`
|
|
181
181
|
`)}case"team_audit":{const{instance_id:i,requester:e="",limit:a=50}=y;if(!i)return"\u274C Required: instance_id";const o=await _(i),r=await ee(o,i,e,"admin","view the governance audit log");if(r)return r;const t=Math.min(Math.max(1,Number(a)||50),200),c=await o.lrange(`cachly:team:audit:${i}`,-t,-1).catch(()=>[]);if(c.length===0)return["## \u{1F4DC} Governance Audit Log","","No governance events recorded yet.","Events are logged automatically on `team_assign_role` and `team_confirm`."].join(`
|
|
182
|
-
`);const l=c.map(s=>
|
|
183
|
-
`)}case"team_grant_scope":{const{instance_id:i,handle:e,group:a,action:o="add",assigned_by:r=""}=y;if(!i||!e||!a)return"\u274C Required: instance_id, handle, group";const t=String(a).toLowerCase().trim();if(!t)return'\u274C `group` must be a non-empty name (e.g. "backend", "security")';const c=await _(i),l=await c.hgetall(
|
|
184
|
-
`))}case"team_scopes":{const{instance_id:i,handle:e}=y;if(!i)return"\u274C Required: instance_id";const a=await _(i);if(e){const t=await Ae(a,i,e);return t.size===0?`\u{1F441}\uFE0F **${e}** is in no groups \u2014 sees only team-wide + public lessons.`:`\u{1F510} **${e}** belongs to: ${[...t].map(c=>`\`${c}\``).join(", ")}`}const o=await a.smembers(
|
|
185
|
-
`);const r=[`## \u{1F510} Team Scopes (${o.length} group${o.length!==1?"s":""})`,""];for(const t of o.sort()){const c=await a.smembers(
|
|
186
|
-
`)}default:return null}}export{
|
|
182
|
+
`);const l=c.map(s=>C(s,null)).filter(s=>s!==null).reverse(),d=s=>{const n=s.ts?new Date(s.ts).toISOString().replace("T"," ").slice(0,16):"?";return s.action==="team_assign_role"?`| ${n} | \u{1F451} role | **${s.actor}** set \`${s.handle}\`: ${s.from??"\u2014"} \u2192 **${s.to}** |`:s.action==="team_confirm"?`| ${n} | \u2705 confirm | **${s.actor}** confirmed \`${s.topic}\` (${s.level}) |`:`| ${n} | ${s.action??"?"} | ${s.actor??"?"} |`};return[`## \u{1F4DC} Governance Audit Log (${l.length} event${l.length!==1?"s":""})`,"","| When (UTC) | Type | Detail |","|---|---|---|",...l.map(d),"",`_Immutable trail \xB7 2-year retention \xB7 last ${t} events. Admin-only._`].join(`
|
|
183
|
+
`)}case"team_grant_scope":{const{instance_id:i,handle:e,group:a,action:o="add",assigned_by:r=""}=y;if(!i||!e||!a)return"\u274C Required: instance_id, handle, group";const t=String(a).toLowerCase().trim();if(!t)return'\u274C `group` must be a non-empty name (e.g. "backend", "security")';const c=await _(i),l=await c.hgetall(B(i)).catch(()=>({}));if(Object.values(l).includes("admin")){const s=r?await W(c,i,r):null;if(!q(s,"admin"))return"\u274C **Permission denied** \u2014 only an `admin` can manage group scopes. Ask an admin to set `assigned_by`."}return o==="remove"?(await c.srem(Y(i,t),e.toLowerCase()),`\u{1F513} **${e}** removed from group \`${t}\`.`):(await c.sadd(V(i),t),await c.sadd(Y(i,t),e.toLowerCase()),await c.expire(V(i),365*2*86400),await c.expire(Y(i,t),365*2*86400),[`\u{1F510} **Scope granted** \u2014 **${e}** is now in group \`${t}\`.`,"",`Lessons stored with \`group="${t}"\` will surface in recall for ${e} (and other \`${t}\` members + admins) only.`,"",`Store a scoped lesson: \`learn_from_attempts(topic="...", group="${t}", ...)\``].join(`
|
|
184
|
+
`))}case"team_scopes":{const{instance_id:i,handle:e}=y;if(!i)return"\u274C Required: instance_id";const a=await _(i);if(e){const t=await Ae(a,i,e);return t.size===0?`\u{1F441}\uFE0F **${e}** is in no groups \u2014 sees only team-wide + public lessons.`:`\u{1F510} **${e}** belongs to: ${[...t].map(c=>`\`${c}\``).join(", ")}`}const o=await a.smembers(V(i)).catch(()=>[]);if(o.length===0)return["## \u{1F510} Team Scopes","","No groups defined yet. Create one by adding a member:",`\`team_grant_scope(instance_id="${i}", handle="alice", group="backend")\``].join(`
|
|
185
|
+
`);const r=[`## \u{1F510} Team Scopes (${o.length} group${o.length!==1?"s":""})`,""];for(const t of o.sort()){const c=await a.smembers(Y(i,t)).catch(()=>[]);r.push(`- **\`${t}\`** (${c.length}): ${c.length?c.map(l=>`**${l}**`).join(", "):"_empty_"}`)}return r.push("",'_Scope a lesson to a group with `learn_from_attempts(group="...")`. Members + admins see it; others don\'t._'),r.join(`
|
|
186
|
+
`)}default:return null}}export{V as GROUPS_INDEX_KEY,Y as GROUP_KEY,be as PROPOSED_LESSON_PREFIX,$e as REVIEW_MODE_KEY,B as ROLES_KEY,U as ROLE_BADGE,Pe as TEAM_TOOL_NAMES,re as auditLog,W as getRole,Ae as getScopes,Be as handleTeamTool,q as hasPermission,ze as lessonVisibleToScope,ve as rbacActive,ee as requireRole,Se as reviewModeEnabled,_e as roleToReviewLevel,xe as storeLessonProposal};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createHash as s}from"node:crypto";let i="";function p(e,n){const t=String(e??"").trim(),r=String(n??"").trim();i=t?r?`${t}/${r}`:t:""}function a(){return i}function o(e){const n=e.split(".");if(n.length!==3)return"";try{const t=Buffer.from(n[1].replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8"),r=JSON.parse(t).sub;return typeof r=="string"?r.slice(0,64):""}catch{return""}}function g(e=process.env){const n=String(e.CACHLY_AGENT??"").trim()||i||"unbekannt",t=String(e.CACHLY_JWT??"").trim();let r;if(t){const c=o(t);r=c?`sub:${c}`:`key:${s("sha256").update(t).digest("hex").slice(0,12)}`}return{agent:n,...r?{principal:r}:{},via:"mcp"}}function f(e,n){const t=[];return e&&t.push(`@${e}`),n?.agent&&n.agent!=="unbekannt"&&t.push(`via ${n.agent}`),n?.principal&&t.push(n.principal),t.length>0?`\u{1F464} **Source:** ${t.join(" \xB7 ")}`:"\u{1F464} **Source:** Urheber unbekannt"}export{a as clientKennungFuerTests,g as ermittleQuelle,f as quelleZeile,p as setzeClientKennung};
|