@clauderecallhq/cli 0.61.4 → 0.61.6

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/README.md CHANGED
@@ -21,7 +21,7 @@ Claude Recall indexes every Claude Code session you've ever run, surfaces what m
21
21
  <br />
22
22
 
23
23
  [![npm](https://img.shields.io/npm/v/@clauderecallhq/cli?style=flat-square&color=f97316&label=npm)](https://www.npmjs.com/package/@clauderecallhq/cli)
24
- [![Node](https://img.shields.io/badge/node-%E2%89%A520-43853d?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org)
24
+ [![Node](https://img.shields.io/badge/node-%E2%89%A522-43853d?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org)
25
25
  [![TypeScript](https://img.shields.io/badge/typescript-strict-3178c6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
26
26
  [![Local-first](https://img.shields.io/badge/local--first-127.0.0.1%20only-0b0c0f?style=flat-square)](#privacy--security)
27
27
  [![License](https://img.shields.io/badge/license-proprietary-1a1a2e?style=flat-square)](#license)
@@ -187,8 +187,8 @@ Let Claude label your sessions automatically. Two modes: **MCP mode** (zero-setu
187
187
  </td>
188
188
  <td width="50%" valign="top">
189
189
 
190
- ### MCP Server &nbsp;<sub>31 tools</sub>
191
- Expose Recall to Claude Desktop, Claude Code, or any MCP client as native tools. **13 read tools always on**, **18 write tools** opt-in with rate limiting and append-only audit logging.
190
+ ### MCP Server &nbsp;<sub>33 tools</sub>
191
+ Expose Recall to Claude Desktop, Claude Code, or any MCP client as native tools. **14 read tools always on**, **19 write tools** opt-in with rate limiting and append-only audit logging.
192
192
 
193
193
  ```bash
194
194
  recall mcp
@@ -452,7 +452,7 @@ All writes are rate-limited (default 60/min), zod-validated, and audited to `~/.
452
452
  CLI HTTP server (Hono, 127.0.0.1 only)
453
453
  | |
454
454
  recall ... Web UI (React + Tailwind SPA, Vite-bundled)
455
- MCP server (stdio, 31 tools, opt-in writes)
455
+ MCP server (stdio, 33 tools, opt-in writes)
456
456
  ```
457
457
 
458
458
  ### Data stays local
@@ -600,7 +600,7 @@ recall audit-secrets # scan index for residual secrets
600
600
 
601
601
  | Layer | Technology |
602
602
  |---|---|
603
- | CLI + daemon | TypeScript (strict), Node 20+, ESM |
603
+ | CLI + daemon | TypeScript (strict), Node 22+, ESM |
604
604
  | HTTP server | Hono + @hono/node-server |
605
605
  | Database | better-sqlite3 with FTS5 full-text search |
606
606
  | Vector store | sqlite-vec (768d, on-device) |
@@ -652,7 +652,7 @@ Claude Recall ships on every surface a developer already works in.
652
652
  |---|---|---|
653
653
  | **npm** (CLI + MCP) | [`@clauderecallhq/cli`](https://www.npmjs.com/package/@clauderecallhq/cli) | `npm i -g @clauderecallhq/cli` |
654
654
  | **VS Code Marketplace** | [`clauderecallhq.clauderecall-vscode`](https://marketplace.visualstudio.com/items?itemName=clauderecallhq.clauderecall-vscode) | Search "Claude Recall" in Extensions |
655
- | **MCP Registry** | [`io.github.clauderecallhq/recall`](https://registry.modelcontextprotocol.io/) | Auto-discovered by MCP clients |
655
+ | **MCP Registry** | [`com.clauderecall/recall`](https://registry.modelcontextprotocol.io/v0/servers?search=clauderecall) | Auto-discovered by MCP clients |
656
656
  | **Claude Code Plugin Hub** | `claude-recall` | `/plugin install claude-recall` in Claude Code |
657
657
  | **Website** | [clauderecall.com](https://clauderecall.com) | -- |
658
658
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /* Claude Recall (proprietary). See LICENSE for terms. */
3
- var gn=Object.defineProperty;var H=(e,s)=>()=>(e&&(s=e(e=0)),s);var Ie=(e,s)=>{for(var t in s)gn(e,t,{get:s[t],enumerable:!0})};import{homedir as rt}from"node:os";import{join as xe,basename as xo}from"node:path";import{existsSync as _n,mkdirSync as fn,chmodSync as hn,readdirSync as vo,statSync as Do}from"node:fs";function x(){_n(S)||fn(S,{recursive:!0,mode:448}),process.platform!=="win32"&&hn(S,448)}var ko,S,oe,F=H(()=>{"use strict";ko=xe(rt(),".claude","projects"),S=process.env.RECALL_HOME?process.env.RECALL_HOME:xe(rt(),".recall"),oe=xe(S,"db.sqlite")});function at(e){let s=e.prepare("PRAGMA table_info(sessions)").all(),t=new Set(s.map(g=>g.name)),n=[["total_input_tokens","INTEGER"],["total_output_tokens","INTEGER"],["total_cache_create_tokens","INTEGER"],["total_cache_read_tokens","INTEGER"],["primary_model","TEXT"],["auto_title","TEXT"],["auto_title_source","TEXT"],["auto_title_generated_at","INTEGER"],["auto_title_history","TEXT"],["verification_status","TEXT"],["verification_computed_at","INTEGER"],["title_quality","TEXT"],["title_quality_computed_at","INTEGER"]];for(let[g,_]of n)t.has(g)||e.exec(`ALTER TABLE sessions ADD COLUMN ${g} ${_}`);let i=e.prepare("PRAGMA table_info(collection_sessions)").all(),r=new Set(i.map(g=>g.name)),a=[["source","TEXT NOT NULL DEFAULT 'manual'"],["rule_id","TEXT"]];for(let[g,_]of a)r.has(g)||e.exec(`ALTER TABLE collection_sessions ADD COLUMN ${g} ${_}`);let o=e.prepare("PRAGMA table_info(session_notes)").all(),c=new Set(o.map(g=>g.name)),l=[["auto_synopsis","TEXT"],["auto_synopsis_generated_at","INTEGER"],["auto_synopsis_history","TEXT"]];for(let[g,_]of l)c.has(g)||e.exec(`ALTER TABLE session_notes ADD COLUMN ${g} ${_}`);let d=e.prepare("PRAGMA table_info(threads)").all();new Set(d.map(g=>g.name)).has("folder_id")||(e.exec("ALTER TABLE threads ADD COLUMN folder_id TEXT REFERENCES thread_folders(id) ON DELETE SET NULL"),e.exec("CREATE INDEX IF NOT EXISTS idx_threads_folder ON threads(folder_id)"));let u=e.prepare("PRAGMA table_info(thread_folders)").all(),m=new Set(u.map(g=>g.name));m.has("project_scope")||(e.exec("ALTER TABLE thread_folders ADD COLUMN project_scope TEXT"),e.exec("CREATE INDEX IF NOT EXISTS idx_thread_folders_project ON thread_folders(project_scope)")),m.has("sort_order")||e.exec("ALTER TABLE thread_folders ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0"),e.exec(`
3
+ var gn=Object.defineProperty;var H=(e,s)=>()=>(e&&(s=e(e=0)),s);var Ie=(e,s)=>{for(var t in s)gn(e,t,{get:s[t],enumerable:!0})};import{homedir as rt}from"node:os";import{join as xe,basename as Fo}from"node:path";import{existsSync as _n,mkdirSync as fn,chmodSync as hn,readdirSync as Uo,statSync as Po}from"node:fs";function x(){_n(S)||fn(S,{recursive:!0,mode:448}),process.platform!=="win32"&&hn(S,448)}var jo,S,oe,F=H(()=>{"use strict";jo=xe(rt(),".claude","projects"),S=process.env.RECALL_HOME?process.env.RECALL_HOME:xe(rt(),".recall"),oe=xe(S,"db.sqlite")});function at(e){let s=e.prepare("PRAGMA table_info(sessions)").all(),t=new Set(s.map(g=>g.name)),n=[["total_input_tokens","INTEGER"],["total_output_tokens","INTEGER"],["total_cache_create_tokens","INTEGER"],["total_cache_read_tokens","INTEGER"],["primary_model","TEXT"],["auto_title","TEXT"],["auto_title_source","TEXT"],["auto_title_generated_at","INTEGER"],["auto_title_history","TEXT"],["verification_status","TEXT"],["verification_computed_at","INTEGER"],["title_quality","TEXT"],["title_quality_computed_at","INTEGER"]];for(let[g,_]of n)t.has(g)||e.exec(`ALTER TABLE sessions ADD COLUMN ${g} ${_}`);let i=e.prepare("PRAGMA table_info(collection_sessions)").all(),r=new Set(i.map(g=>g.name)),a=[["source","TEXT NOT NULL DEFAULT 'manual'"],["rule_id","TEXT"]];for(let[g,_]of a)r.has(g)||e.exec(`ALTER TABLE collection_sessions ADD COLUMN ${g} ${_}`);let o=e.prepare("PRAGMA table_info(session_notes)").all(),c=new Set(o.map(g=>g.name)),l=[["auto_synopsis","TEXT"],["auto_synopsis_generated_at","INTEGER"],["auto_synopsis_history","TEXT"]];for(let[g,_]of l)c.has(g)||e.exec(`ALTER TABLE session_notes ADD COLUMN ${g} ${_}`);let d=e.prepare("PRAGMA table_info(threads)").all();new Set(d.map(g=>g.name)).has("folder_id")||(e.exec("ALTER TABLE threads ADD COLUMN folder_id TEXT REFERENCES thread_folders(id) ON DELETE SET NULL"),e.exec("CREATE INDEX IF NOT EXISTS idx_threads_folder ON threads(folder_id)"));let u=e.prepare("PRAGMA table_info(thread_folders)").all(),m=new Set(u.map(g=>g.name));m.has("project_scope")||(e.exec("ALTER TABLE thread_folders ADD COLUMN project_scope TEXT"),e.exec("CREATE INDEX IF NOT EXISTS idx_thread_folders_project ON thread_folders(project_scope)")),m.has("sort_order")||e.exec("ALTER TABLE thread_folders ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0"),e.exec(`
4
4
  CREATE TABLE IF NOT EXISTS message_embeddings (
5
5
  message_uuid TEXT PRIMARY KEY REFERENCES messages(uuid) ON DELETE CASCADE,
6
6
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
@@ -637,7 +637,7 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
637
637
  `)}function Hn(e){let s=e.limit??5;return[`Find ${s} Recall sessions most similar to session ${e.sessionId}.`,"",`1. Call \`get_session\` with id "${e.sessionId}" \u2014 note its alias, first user message, tags, and git branch.`,'2. Derive 2-3 short search queries from that content (topic words, library names, error strings \u2014 NOT generic words like "fix" or "add").',`3. Call \`search\` once per query (limit: ${Math.max(5,s*2)}). Dedupe hits by session_id.`,"4. Also call `list_sessions` with the same tag(s) if the target session has any (pick the most specific tag).","5. Union the results. Exclude the target session itself. Rank by a mix of:"," - Shared tags (strongest signal)"," - Matching search hits across multiple queries"," - Recency as a tiebreaker","",`6. Return the top ${s} as a numbered list. For each:`," - **<short_id> \xB7 <project>** \u2014 <alias or first_user_message truncated>",' - One sentence on WHY it is similar (not a generic "same topic" \u2014 be specific).',"","If fewer than 2 genuinely-similar sessions exist, say so rather than padding with weak matches.","No preamble. Just the ranked list."].join(`
638
638
  `)}var Mn,Un,jn,Xn,Bn,Wn,Yn,Gn,ft,Fe=H(()=>{"use strict";Mn={project:j.string().optional().describe("Exact project name match (optional)."),collectionId:j.string().optional().describe("Restrict to sessions in this collection (optional)."),sessionId:j.string().optional().describe("Full session UUID to tag just one session (optional)."),untaggedOnly:j.boolean().optional().describe("Skip sessions that already have any tag (default: true)."),limit:j.number().int().min(1).max(500).optional().describe("Max sessions to process (default: 100)."),minTags:j.number().int().min(1).max(10).optional().describe("Minimum tags per session (default: 2)."),maxTags:j.number().int().min(1).max(10).optional().describe("Maximum tags per session (default: 4).")};Un={sessionId:j.string().describe("Session UUID (or 8+-char prefix) to summarize."),mode:j.enum(["brief","detailed"]).optional().describe("brief = 3-5 bullets; detailed = paragraph + bullets. Default: brief.")};jn={sessionId:j.string().describe("Session UUID (or 8+-char prefix) to extract decisions from.")};Xn={sessionId:j.string().describe("Session UUID (or 8+-char prefix) to find similar sessions to."),limit:j.number().int().min(1).max(20).optional().describe("How many similar sessions to surface (default: 5).")};Bn={name:"auto_tag_sessions",title:"Auto-tag Recall sessions",description:"Have the agent auto-tag Recall sessions using the Recall MCP tools. Scope can be restricted to a project, collection, or single session.",argsSchema:Mn,build:ke,allowedTools:["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"]},Wn={name:"summarize_session",title:"Summarize a session",description:"Produce a concise, concrete summary of one session \u2014 what shipped, what was tried, what's still open.",argsSchema:Un,build:Pn,allowedTools:["mcp__recall__get_session","mcp__recall__context_for_session"]},Yn={name:"extract_decisions",title:"Extract architectural decisions",description:"Scan a session and emit one structured block per architectural / product decision: what, why, alternatives, where it landed.",argsSchema:jn,build:$n,allowedTools:["mcp__recall__get_session","mcp__recall__context_for_session"]},Gn={name:"find_similar_sessions",title:"Find similar sessions",description:"Given a session, find other sessions that touched the same topic / library / error \u2014 ranked with reasons.",argsSchema:Xn,build:Hn,allowedTools:["mcp__recall__get_session","mcp__recall__search","mcp__recall__list_sessions","mcp__recall__list_tags"]},ft=[Bn,Wn,Yn,Gn]});function qt(e,s){let t=xi.get(e);if(!(!t||t.size===0))for(let n of t)try{n(s)}catch{}}var xi,Qt=H(()=>{"use strict";xi=new Map});var ts={};Ie(ts,{buildScanPrompt:()=>Zt,isClaudeCliAvailable:()=>Mi,runClaudeCliScan:()=>Xi,spawnClaudePrompt:()=>Hi});import{execFileSync as Ci,execSync as vi,spawn as Di}from"node:child_process";function Fi(){if(le)return le;try{le=vi("which claude",{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{le="claude"}return le}function Mi(){try{return Ci("command",["-v","claude"],{stdio:"ignore"}),!0}catch{return!1}}function Zt(e){return ke({project:e.project,collectionId:e.collectionId,sessionId:e.sessionIds&&e.sessionIds.length===1?e.sessionIds[0]:void 0,untaggedOnly:e.untaggedOnly,limit:e.limit})}function Ui(e,s){let t=s.get(e);return t||e.slice(0,8)}function Pi(e){try{return he(e).map(t=>({id:t.id,label:t.alias&&t.alias.trim().length>0?t.alias:t.first_user_message&&t.first_user_message.trim().length>0?t.first_user_message.slice(0,60):t.id.slice(0,8)}))}catch{return[]}}function ji(e){let{scanId:s,total:t,labelTable:n}=e,i=new Set;return r=>{let a=r.trim();if(!a.startsWith("{"))return;let o;try{o=JSON.parse(a)}catch{return}if(!o||typeof o!="object")return;let c=o;if(!(c.type!=="assistant"||!c.message?.content))for(let l of c.message.content){if(l?.type!=="tool_use"||l.name!=="mcp__recall__apply_tags")continue;let d=l.input,u=typeof d?.sessionId=="string"?d.sessionId:null;!u||i.has(u)||(i.add(u),qt(s,{type:"progress",current:i.size,total:t,sessionId:u,sessionLabel:Ui(u,n)}))}}}function $i(e){let s="";return t=>{s+=t.toString("utf8");let n=s.indexOf(`
639
639
  `);for(;n!==-1;){let i=s.slice(0,n);s=s.slice(n+1),i.length>0&&e(i),n=s.indexOf(`
640
- `)}}}async function Xi(e,s={},t){let n=!!s.scanId,i=n?Pi(e):[],r=new Map(i.map(c=>[c.id,c.label])),a=i.length,o;return n&&s.scanId&&(o=ji({scanId:s.scanId,total:a,labelTable:r})),es({prompt:Zt(e),allowedTools:ki.split(","),opts:s,onProgress:t,onStdoutLine:o,outputFormat:n?"stream-json":"json"})}async function Hi(e,s,t={},n){return es({prompt:e,allowedTools:s,opts:t,onProgress:n,outputFormat:"json"})}function es(e){let{prompt:s,allowedTools:t,opts:n,onProgress:i,onStdoutLine:r,outputFormat:a}=e,o=["-p",s,"--output-format",a,"--allowedTools",t.join(","),"--permission-mode","bypassPermissions"];return a==="stream-json"&&o.push("--verbose"),n.model&&o.push("--model",n.model),new Promise(c=>{let l=Di(Fi(),o,{stdio:["ignore","pipe","pipe"]}),d=[],u=[],m=r?$i(r):void 0;l.stdout.on("data",_=>{d.push(_),m&&m(_)}),l.stderr.on("data",_=>{if(u.push(_),i){let f=_.toString("utf8").trim();f&&i(f)}});let g=setTimeout(()=>{l.kill("SIGKILL")},1800*1e3);l.on("close",_=>{clearTimeout(g),c({success:_===0,stdout:Buffer.concat(d).toString("utf8"),stderr:Buffer.concat(u).toString("utf8"),exitCode:_})}),l.on("error",_=>{clearTimeout(g),c({success:!1,stdout:"",stderr:String(_),exitCode:null})})})}var ki,le,ss=H(()=>{"use strict";Ce();Fe();Qt();ki=["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"].join(",")});import Y from"chalk";import{formatDistanceToNowStrict as hl,parseISO as El}from"date-fns";var E,st=H(()=>{"use strict";E={dim:Y.gray,bold:Y.bold,project:Y.cyan,user:Y.blue,assistant:Y.green,tool:Y.magenta,warn:Y.yellow,err:Y.red,ok:Y.green,accent:Y.hex("#f97316")}});var dn={};Ie(dn,{buildHealthReport:()=>ln,buildPipelineDiagnostic:()=>cn,getFreeDiskBytes:()=>go,runDoctor:()=>mo});import{existsSync as en,readFileSync as io,statSync as tn,statfsSync as sn}from"node:fs";import{join as nn}from"node:path";import*as rn from"node:http";function ao(e){let s=[];for(let t of e)if(t.alias){if(oo.test(t.alias)){s.push({session_id:t.session_id,alias:t.alias,violation:"fabricated-origin-label",cwd:t.cwd});continue}if(t.cwd){let n=t.cwd.replace(/\/+$/,"").split("/").pop();n&&t.alias.startsWith(`${n} \xB7 `)&&s.push({session_id:t.session_id,alias:t.alias,violation:"fabricated-cwd-branch",cwd:t.cwd})}}return s}function co(){let e=nn(S,"daemon.port");if(!en(e))return null;try{let s=io(e,"utf8").trim();if(s.startsWith("{")){let n=JSON.parse(s);return typeof n.port=="number"?n.port:null}let t=Number.parseInt(s,10);return Number.isFinite(t)&&t>0&&t<65536?t:null}catch{return null}}function lo(e,s,t=1500){return new Promise(n=>{let i=rn.request({host:"127.0.0.1",port:e,path:s,method:"GET",timeout:t,headers:{host:"127.0.0.1","user-agent":"recall-doctor"}},r=>{let a=[];r.on("data",o=>a.push(Buffer.isBuffer(o)?o:Buffer.from(o))),r.on("end",()=>{if(!r.statusCode||r.statusCode<200||r.statusCode>=300){n(null);return}try{n(JSON.parse(Buffer.concat(a).toString("utf8")))}catch{n(null)}})});i.on("error",()=>n(null)),i.on("timeout",()=>{i.destroy(),n(null)}),i.end()})}function uo(){let e=nn(S,"terminals.json");if(!en(e))return{exists:!1,mtimeMs:null,ageSeconds:null};try{let s=tn(e),t=Math.floor((Date.now()-s.mtimeMs)/1e3);return{exists:!0,mtimeMs:s.mtimeMs,ageSeconds:t}}catch{return{exists:!1,mtimeMs:null,ageSeconds:null}}}function po(){let e=new Date(Date.now()-nt*36e5).toISOString();try{let s=p().prepare(`SELECT
640
+ `)}}}async function Xi(e,s={},t){let n=!!s.scanId,i=n?Pi(e):[],r=new Map(i.map(c=>[c.id,c.label])),a=i.length,o;return n&&s.scanId&&(o=ji({scanId:s.scanId,total:a,labelTable:r})),es({prompt:Zt(e),allowedTools:ki.split(","),opts:s,onProgress:t,onStdoutLine:o,outputFormat:n?"stream-json":"json"})}async function Hi(e,s,t={},n){return es({prompt:e,allowedTools:s,opts:t,onProgress:n,outputFormat:"json"})}function es(e){let{prompt:s,allowedTools:t,opts:n,onProgress:i,onStdoutLine:r,outputFormat:a}=e,o=["-p",s,"--output-format",a,"--allowedTools",t.join(","),"--permission-mode","bypassPermissions"];return a==="stream-json"&&o.push("--verbose"),n.model&&o.push("--model",n.model),new Promise(c=>{let l=Di(Fi(),o,{stdio:["ignore","pipe","pipe"]}),d=[],u=[],m=r?$i(r):void 0;l.stdout.on("data",_=>{d.push(_),m&&m(_)}),l.stderr.on("data",_=>{if(u.push(_),i){let f=_.toString("utf8").trim();f&&i(f)}});let g=setTimeout(()=>{l.kill("SIGKILL")},1800*1e3);l.on("close",_=>{clearTimeout(g),c({success:_===0,stdout:Buffer.concat(d).toString("utf8"),stderr:Buffer.concat(u).toString("utf8"),exitCode:_})}),l.on("error",_=>{clearTimeout(g),c({success:!1,stdout:"",stderr:String(_),exitCode:null})})})}var ki,le,ss=H(()=>{"use strict";Ce();Fe();Qt();ki=["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"].join(",")});import Y from"chalk";import{formatDistanceToNowStrict as Rl,parseISO as Nl}from"date-fns";var E,st=H(()=>{"use strict";E={dim:Y.gray,bold:Y.bold,project:Y.cyan,user:Y.blue,assistant:Y.green,tool:Y.magenta,warn:Y.yellow,err:Y.red,ok:Y.green,accent:Y.hex("#f97316")}});var dn={};Ie(dn,{buildHealthReport:()=>ln,buildPipelineDiagnostic:()=>cn,getFreeDiskBytes:()=>go,runDoctor:()=>mo});import{existsSync as en,readFileSync as io,statSync as tn,statfsSync as sn}from"node:fs";import{join as nn}from"node:path";import*as rn from"node:http";function ao(e){let s=[];for(let t of e)if(t.alias){if(oo.test(t.alias)){s.push({session_id:t.session_id,alias:t.alias,violation:"fabricated-origin-label",cwd:t.cwd});continue}if(t.cwd){let n=t.cwd.replace(/\/+$/,"").split("/").pop();n&&t.alias.startsWith(`${n} \xB7 `)&&s.push({session_id:t.session_id,alias:t.alias,violation:"fabricated-cwd-branch",cwd:t.cwd})}}return s}function co(){let e=nn(S,"daemon.port");if(!en(e))return null;try{let s=io(e,"utf8").trim();if(s.startsWith("{")){let n=JSON.parse(s);return typeof n.port=="number"?n.port:null}let t=Number.parseInt(s,10);return Number.isFinite(t)&&t>0&&t<65536?t:null}catch{return null}}function lo(e,s,t=1500){return new Promise(n=>{let i=rn.request({host:"127.0.0.1",port:e,path:s,method:"GET",timeout:t,headers:{host:"127.0.0.1","user-agent":"recall-doctor"}},r=>{let a=[];r.on("data",o=>a.push(Buffer.isBuffer(o)?o:Buffer.from(o))),r.on("end",()=>{if(!r.statusCode||r.statusCode<200||r.statusCode>=300){n(null);return}try{n(JSON.parse(Buffer.concat(a).toString("utf8")))}catch{n(null)}})});i.on("error",()=>n(null)),i.on("timeout",()=>{i.destroy(),n(null)}),i.end()})}function uo(){let e=nn(S,"terminals.json");if(!en(e))return{exists:!1,mtimeMs:null,ageSeconds:null};try{let s=tn(e),t=Math.floor((Date.now()-s.mtimeMs)/1e3);return{exists:!0,mtimeMs:s.mtimeMs,ageSeconds:t}}catch{return{exists:!1,mtimeMs:null,ageSeconds:null}}}function po(){let e=new Date(Date.now()-nt*36e5).toISOString();try{let s=p().prepare(`SELECT
641
641
  COUNT(*) AS total,
642
642
  SUM(CASE WHEN sa.alias IS NULL OR sa.alias = '' THEN 1 ELSE 0 END) AS without_alias,
643
643
  SUM(CASE WHEN (sa.alias IS NULL OR sa.alias = '')
@@ -654,21 +654,21 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
654
654
  WHERE sa.alias IS NOT NULL AND sa.alias != ''`).all(),t=ao(s),n=ln(),i=await cn();if(e.json){process.stdout.write(JSON.stringify({scanned:s.length,violations:t.length,items:t,health:n,pipeline:i},null,2)),process.stdout.write(`
655
655
  `);let o=i.state==="degraded";return t.length===0&&n.db.integrity==="ok"&&!o?0:1}if(console.log(E.dim("\u2014 System health \u2014")),console.log(` Database ${Z(n.db.sizeBytes)} (${n.rows.messages.toLocaleString()} messages across ${n.rows.sessions.toLocaleString()} sessions, ${n.rows.projects.toLocaleString()} projects)`),console.log(` WAL ${Z(n.db.walSizeBytes)} (capped at 64 MB; truncated on clean shutdown)`),console.log(` Free pages ${n.db.freelistCount.toLocaleString()} (${Z(n.db.freelistBytes)} reclaimable via VACUUM)`),console.log(` FTS segments messages=${n.fts.messages.fragments}, sessions=${n.fts.sessions.fragments} (lower is faster \u2014 \`recall optimize\` merges them)`),console.log(` Vector rows ${n.vectors.rows.toLocaleString()}`),n.disk.totalBytes>0){let o=n.disk.freeBytes/n.disk.totalBytes*100;console.log(` Disk free ${Z(n.disk.freeBytes)} of ${Z(n.disk.totalBytes)} (${o.toFixed(1)}%)`)}if(console.log(` Integrity ${n.db.integrity==="ok"?E.ok("ok"):E.err(n.db.integrity)}`),n.warnings.length>0){console.log("");for(let o of n.warnings)console.log(` ${E.warn("!")} ${o}`)}if(console.log(""),console.log(E.dim("\u2014 Pipeline health (tab-name \u2192 session alias) \u2014")),i.daemon.running?console.log(` Daemon ${E.ok("running")} (port ${i.daemon.port}, version ${i.daemon.version??"?"}, up ${i.daemon.uptimeSeconds!==null?Math.round(i.daemon.uptimeSeconds/60)+" min":"?"})`):console.log(` Daemon ${E.warn("not reachable")}`),i.runtime.silentTerminalRejections!==null){let o=i.runtime.silentTerminalRejections;console.log(` Auth rejections ${o===0?E.ok("0"):E.err(o.toLocaleString())} (extension /api/terminal/* requests denied without a valid X-Recall-Token)`)}if(i.runtime.lastTerminalSyncAt!==null){let o=Date.now()-Date.parse(i.runtime.lastTerminalSyncAt),c=Number.isFinite(o)?Math.round(o/6e4):null,l=c!==null&&o>on;console.log(` Last ext sync ${l?E.warn(`${c} min ago`):E.ok(c===0?"just now":`${c} min ago`)} (most recent successful POST /api/terminal/sync)`)}else i.daemon.running&&console.log(` Last ext sync ${E.warn("never")} (no extension has called /api/terminal/sync since the daemon started)`);if(i.terminalsJson.exists&&i.terminalsJson.ageSeconds!==null){let o=Math.round(i.terminalsJson.ageSeconds/3600),c=i.terminalsJson.ageSeconds>24*3600;console.log(` terminals.json ${c?E.warn(`${o}h old`):E.ok(`${o}h old`)} (persisted registry mtime \u2014 fresh means extensions are connecting)`)}let r=i.recentSessions;if(r.total>0){let o=Math.round(r.fractionHeuristic*100),c=r.fractionHeuristic>=an&&r.total>=3;console.log(` Recent titles ${c?E.err(`${o}% heuristic`):E.ok(`${o}% heuristic`)} (${r.heuristicOnly}/${r.total} sessions in last ${nt}h fell back to first-message title)`)}if(i.flags.length>0){console.log("");for(let o of i.flags)console.log(` ${E.warn("!")} ${o}`)}if(console.log(""),console.log(E.dim("\u2014 Tab-name invariant \u2014")),t.length===0)return console.log(E.ok(` \u2713 holds across ${s.length.toLocaleString()} aliased session${s.length===1?"":"s"}`)),console.log(E.dim(" No fabricated origin labels (`VS Code \xB7 cwd \xB7 branch`) found. No deprecated cwd-branch synthesis found.")),n.db.integrity==="ok"?0:1;console.log(E.err(`\u2717 ${t.length} invariant violation${t.length===1?"":"s"} found across ${s.length.toLocaleString()} aliased sessions`)),console.log("");let a=new Map;for(let o of t){let c=a.get(o.violation)??[];c.push(o),a.set(o.violation,c)}for(let[o,c]of a){console.log(E.warn(` ${o} (${c.length})`));for(let l of c.slice(0,10))console.log(` ${l.session_id.slice(0,8)} ${E.dim("\u2192")} ${JSON.stringify(l.alias)}`);c.length>10&&console.log(E.dim(` \u2026 and ${c.length-10} more (rerun with --json for the full list)`)),console.log("")}return console.log(E.dim('Remediation: `recall name <id-prefix> ""` clears a bad alias so the heuristic title takes over,\nor `recall name <id-prefix> "<actual tab name>"` sets it to the real value.')),1}function go(){try{let e=sn(S);return Number(e.bavail)*Number(e.bsize)}catch{return 0}}var ro,oo,on,nt,an,un=H(()=>{"use strict";st();L();F();ro=["VS Code","VS Code Insiders","Cursor","Windsurf","Warp","iTerm","Terminal","WezTerm","Windows Terminal","Kitty","Alacritty"],oo=new RegExp(`^(${ro.map(e=>e.replace(/ /g,"\\s")).join("|")})\\s\xB7\\s`);on=5*6e4,nt=24,an=.5});var pn={};Ie(pn,{runOptimize:()=>To});import{existsSync as _o,readFileSync as fo}from"node:fs";import{join as ho}from"node:path";function Eo(){let e=ho(S,"daemon.pid");if(!_o(e))return!1;try{let s=parseInt(fo(e,"utf-8").trim(),10);return!Number.isFinite(s)||s<=0?!1:(process.kill(s,0),!0)}catch{return!1}}async function ue(e,s){let t=Date.now();try{return s(),{step:e,ok:!0,durationMs:Date.now()-t}}catch(n){return{step:e,ok:!1,durationMs:Date.now()-t,error:n.message}}}async function To(e={}){let s=p(),t=[];if(e.vacuum&&Eo())return e.json?(process.stdout.write(JSON.stringify({ok:!1,error:"daemon-running",message:"VACUUM requires the daemon to be stopped. Run `recall stop`, then re-run with --vacuum."},null,2)+`
656
656
  `),2):(console.error(E.err("\u2717 VACUUM requires the daemon to be stopped. Run `recall stop` first, then re-run with --vacuum.")),2);t.push(await ue("wal_checkpoint(TRUNCATE)",()=>{s.pragma("wal_checkpoint(TRUNCATE)")})),t.push(await ue("messages_fts optimize",()=>{s.exec("INSERT INTO messages_fts(messages_fts) VALUES('optimize');")})),t.push(await ue("sessions_fts optimize",()=>{s.exec("INSERT INTO sessions_fts(sessions_fts) VALUES('optimize');")})),t.push(await ue("PRAGMA optimize",()=>{s.exec("PRAGMA optimize")})),e.vacuum&&t.push(await ue("VACUUM",()=>{s.exec("VACUUM")}));let n=t.filter(i=>!i.ok);if(e.json)return process.stdout.write(JSON.stringify({ok:n.length===0,steps:t,vacuum:!!e.vacuum},null,2)+`
657
- `),n.length===0?0:1;for(let i of t){let r=i.ok?E.ok("\u2713"):E.err("\u2717"),a=`${i.durationMs} ms`;console.log(` ${r} ${i.step.padEnd(28)} ${E.dim(a)}`),i.error&&console.log(` ${E.err(i.error)}`)}return n.length===0?(console.log(""),console.log(E.ok("All maintenance passes completed.")),e.vacuum||console.log(E.dim(" Run `recall optimize --vacuum` (with the daemon stopped) to reclaim disk pages from deleted rows.")),0):(console.log(""),console.log(E.warn(`${n.length} step(s) failed \u2014 review the errors above.`)),1)}var mn=H(()=>{"use strict";st();L();F()});L();import{McpServer as So}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as bo}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as T}from"zod";var Tn=/<(local-command-caveat|local-command-stdout|command-name|command-message|command-args|system-reminder|user-prompt-submit-hook|task-notification)[\s\S]*?<\/\1>/gi,Sn=/⚡ \*\*Tool call · `[^`]+`\*\*\s*\n+```json\n[\s\S]*?\n```/g,bn=/\*\*Tool result\*\*\s*\n+```\n[\s\S]*?\n```/g;function Rn(e){return e.replace(Tn,"").trim()}function Nn(e){let s=e.replace(Sn,"[tool call]");return s=s.replace(bn,"[tool result]"),s=s.replace(/_\(unknown block: thinking\)_/g,""),s=s.replace(/(?:\[tool call\]|\[tool result\])(?:\s*(?:\[tool call\]|\[tool result\]))+/g,"[tool activity]"),s=s.replace(/\n{3,}/g,`
657
+ `),n.length===0?0:1;for(let i of t){let r=i.ok?E.ok("\u2713"):E.err("\u2717"),a=`${i.durationMs} ms`;console.log(` ${r} ${i.step.padEnd(28)} ${E.dim(a)}`),i.error&&console.log(` ${E.err(i.error)}`)}return n.length===0?(console.log(""),console.log(E.ok("All maintenance passes completed.")),e.vacuum||console.log(E.dim(" Run `recall optimize --vacuum` (with the daemon stopped) to reclaim disk pages from deleted rows.")),0):(console.log(""),console.log(E.warn(`${n.length} step(s) failed \u2014 review the errors above.`)),1)}var mn=H(()=>{"use strict";st();L();F()});L();import{McpServer as So}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as bo}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as T}from"zod";import{fileURLToPath as Ro}from"node:url";var Tn=/<(local-command-caveat|local-command-stdout|command-name|command-message|command-args|system-reminder|user-prompt-submit-hook|task-notification)[\s\S]*?<\/\1>/gi,Sn=/⚡ \*\*Tool call · `[^`]+`\*\*\s*\n+```json\n[\s\S]*?\n```/g,bn=/\*\*Tool result\*\*\s*\n+```\n[\s\S]*?\n```/g;function Rn(e){return e.replace(Tn,"").trim()}function Nn(e){let s=e.replace(Sn,"[tool call]");return s=s.replace(bn,"[tool result]"),s=s.replace(/_\(unknown block: thinking\)_/g,""),s=s.replace(/(?:\[tool call\]|\[tool result\])(?:\s*(?:\[tool call\]|\[tool result\]))+/g,"[tool activity]"),s=s.replace(/\n{3,}/g,`
658
658
 
659
659
  `),s.trim()}function yn(e){return e.role??e.type??"message"}function ut(e,s,t={}){let n=t.mode??"condensed",i=t.includeSidechain===!0,r=t.since?Date.parse(t.since):0,a=s.filter(d=>!(!i&&d.is_sidechain===1||r&&d.timestamp&&Date.parse(d.timestamp)<r)),o=[];t.prelude&&(o.push(t.prelude.trim()),o.push("")),o.push(`# Claude Recall, past session context (${n})`),o.push(""),o.push(`- **Project**: ${e.project_name}`),e.decoded_path&&o.push(`- **Path**: \`${e.decoded_path}\``),o.push(`- **Session ID**: \`${e.id}\``),e.started_at&&o.push(`- **Started**: ${e.started_at}`),e.ended_at&&o.push(`- **Ended**: ${e.ended_at}`),e.git_branch&&o.push(`- **Branch**: \`${e.git_branch}\``),o.push(`- **Messages**: ${a.length}`),o.push(""),o.push("> This is a transcript of a previous Claude Code session, included for context. Refer back to it when the user asks about past decisions, code written, or problems debugged in this work."),o.push(""),o.push("---"),o.push("");let c=0,l=0;for(let d of a){let u=d.content_text??"",m=Rn(u);n==="condensed"&&(m=Nn(m));let g=m.length>0,_=!!d.tool_names&&d.tool_names.length>0;if(!g&&!_){l+=1;continue}let f=yn(d),h=d.timestamp?` \`${d.timestamp}\``:"";o.push(`## ${f}${h}`),o.push(""),_&&n==="condensed"&&(o.push(`_tools used: ${d.tool_names}_`),o.push("")),g&&(o.push(m),o.push("")),c+=1}return o.push("---"),o.push(""),o.push(`_${c} messages included_`+(l?`, ${l} empty skipped`:"")+(i?"":", subagent/sidechain hidden")+"."),o.join(`
660
- `)}fe();Ce();import{existsSync as In,mkdirSync as qo,readFileSync as xn,writeFileSync as Qo,chmodSync as Zo}from"node:fs";import{homedir as Cn}from"node:os";import{join as _t}from"node:path";import{z}from"zod";function vn(){return process.env.RECALL_HOME??_t(Cn(),".recall")}function Dn(){return _t(vn(),"config.json")}var kn=z.object({enabled:z.boolean().default(!1),backend:z.enum(["api","mcp"]).default("api"),apiKey:z.string().optional(),model:z.string().default("claude-opus-4-7"),maxTagsPerSession:z.number().int().min(1).max(10).default(4),minTagsPerSession:z.number().int().min(1).max(10).default(2),autopilot:z.boolean().default(!1)}),ve={enabled:!1,backend:"api",model:"claude-opus-4-7",maxTagsPerSession:4,minTagsPerSession:2,autopilot:!1};function Fn(){let e=Dn();if(!In(e))return{};try{return JSON.parse(xn(e,"utf8"))}catch(s){return console.error("[auto-tag-config] failed to parse config.json, using defaults:",s),{}}}function De(){let e=Fn().autoTag;if(!e)return{...ve};let s=kn.safeParse({...ve,...e});return s.success?s.data:{...ve}}Fe();L();fe();import{z as I}from"zod";L();F();import{writeFileSync as zn}from"node:fs";import{join as Kn}from"node:path";var Jn=Kn(S,"aliases.json");function ht(e){try{let s=JSON.parse(e);if(Array.isArray(s))return s}catch{}return[]}function Vn(){return p().prepare("SELECT session_id, alias, updated_at, previous_aliases FROM session_aliases").all().map(s=>({session_id:s.session_id,alias:s.alias,updated_at:s.updated_at,previous_aliases:ht(s.previous_aliases)}))}function Et(e,s){let t=s.trim();if(!t)throw new Error("alias must be non-empty");let n=p(),i=new Date().toISOString(),r=n.prepare("SELECT alias, previous_aliases FROM session_aliases WHERE session_id = ?").get(e),a=[];return r&&(a=ht(r.previous_aliases),r.alias!==t&&a.push({alias:r.alias,replaced_at:i})),n.prepare(`INSERT INTO session_aliases (session_id, alias, updated_at, previous_aliases)
660
+ `)}fe();Ce();import{existsSync as In,mkdirSync as sa,readFileSync as xn,writeFileSync as na,chmodSync as ia}from"node:fs";import{homedir as Cn}from"node:os";import{join as _t}from"node:path";import{z as K}from"zod";function vn(){return process.env.RECALL_HOME??_t(Cn(),".recall")}function Dn(){return _t(vn(),"config.json")}var kn=K.object({enabled:K.boolean().default(!1),backend:K.enum(["api","mcp"]).default("api"),apiKey:K.string().optional(),model:K.string().default("claude-opus-4-7"),maxTagsPerSession:K.number().int().min(1).max(10).default(4),minTagsPerSession:K.number().int().min(1).max(10).default(2),autopilot:K.boolean().default(!1)}),ve={enabled:!1,backend:"api",model:"claude-opus-4-7",maxTagsPerSession:4,minTagsPerSession:2,autopilot:!1};function Fn(){let e=Dn();if(!In(e))return{};try{return JSON.parse(xn(e,"utf8"))}catch(s){return console.error("[auto-tag-config] failed to parse config.json, using defaults:",s),{}}}function De(){let e=Fn().autoTag;if(!e)return{...ve};let s=kn.safeParse({...ve,...e});return s.success?s.data:{...ve}}Fe();L();fe();import{z as I}from"zod";L();F();import{writeFileSync as Kn}from"node:fs";import{join as zn}from"node:path";var Jn=zn(S,"aliases.json");function ht(e){try{let s=JSON.parse(e);if(Array.isArray(s))return s}catch{}return[]}function Vn(){return p().prepare("SELECT session_id, alias, updated_at, previous_aliases FROM session_aliases").all().map(s=>({session_id:s.session_id,alias:s.alias,updated_at:s.updated_at,previous_aliases:ht(s.previous_aliases)}))}function Et(e,s){let t=s.trim();if(!t)throw new Error("alias must be non-empty");let n=p(),i=new Date().toISOString(),r=n.prepare("SELECT alias, previous_aliases FROM session_aliases WHERE session_id = ?").get(e),a=[];return r&&(a=ht(r.previous_aliases),r.alias!==t&&a.push({alias:r.alias,replaced_at:i})),n.prepare(`INSERT INTO session_aliases (session_id, alias, updated_at, previous_aliases)
661
661
  VALUES (?, ?, ?, ?)
662
662
  ON CONFLICT(session_id) DO UPDATE SET
663
663
  alias = excluded.alias,
664
664
  updated_at = excluded.updated_at,
665
- previous_aliases = excluded.previous_aliases`).run(e,t,i,JSON.stringify(a)),qn(),{session_id:e,alias:t,updated_at:i,previous_aliases:a}}function qn(){try{x();let e=Vn(),s={schema:"claude-recall.aliases.v1",backed_up_at:new Date().toISOString(),aliases:e};zn(Jn,JSON.stringify(s,null,2))}catch(e){console.error("[aliases] backup failed:",e)}}L();F();import{writeFileSync as Qn,mkdirSync as Zn,existsSync as ei}from"node:fs";import{join as Tt}from"node:path";var Me=Tt(S,"notes");function St(e){try{let s=JSON.parse(e);if(Array.isArray(s))return s}catch{}return[]}function ti(e){if(!e)return[];try{let s=JSON.parse(e);return Array.isArray(s)?s.filter(t=>!!t&&typeof t=="object"&&typeof t.synopsis=="string"&&typeof t.replaced_at=="string"):[]}catch{return[]}}function si(){x(),ei(Me)||Zn(Me,{recursive:!0})}function ni(e){return{session_id:e.session_id,content:e.content,updated_at:e.updated_at,previous_versions:St(e.previous_versions),auto_synopsis:e.auto_synopsis??null,auto_synopsis_generated_at:e.auto_synopsis_generated_at??null,auto_synopsis_history:ti(e.auto_synopsis_history)}}var ii="session_id, content, updated_at, previous_versions, auto_synopsis, auto_synopsis_generated_at, auto_synopsis_history";function Ue(e){let s=p().prepare(`SELECT ${ii} FROM session_notes WHERE session_id = ?`).get(e);return s?ni(s):null}function bt(e,s){let t=p(),n=new Date().toISOString(),i=t.prepare("SELECT content, previous_versions FROM session_notes WHERE session_id = ?").get(e),r=[];return i&&(r=St(i.previous_versions),i.content!==s&&i.content.length>0&&r.push({content:i.content,replaced_at:n})),t.prepare(`INSERT INTO session_notes (session_id, content, updated_at, previous_versions)
665
+ previous_aliases = excluded.previous_aliases`).run(e,t,i,JSON.stringify(a)),qn(),{session_id:e,alias:t,updated_at:i,previous_aliases:a}}function qn(){try{x();let e=Vn(),s={schema:"claude-recall.aliases.v1",backed_up_at:new Date().toISOString(),aliases:e};Kn(Jn,JSON.stringify(s,null,2))}catch(e){console.error("[aliases] backup failed:",e)}}L();F();import{writeFileSync as Qn,mkdirSync as Zn,existsSync as ei}from"node:fs";import{join as Tt}from"node:path";var Me=Tt(S,"notes");function St(e){try{let s=JSON.parse(e);if(Array.isArray(s))return s}catch{}return[]}function ti(e){if(!e)return[];try{let s=JSON.parse(e);return Array.isArray(s)?s.filter(t=>!!t&&typeof t=="object"&&typeof t.synopsis=="string"&&typeof t.replaced_at=="string"):[]}catch{return[]}}function si(){x(),ei(Me)||Zn(Me,{recursive:!0})}function ni(e){return{session_id:e.session_id,content:e.content,updated_at:e.updated_at,previous_versions:St(e.previous_versions),auto_synopsis:e.auto_synopsis??null,auto_synopsis_generated_at:e.auto_synopsis_generated_at??null,auto_synopsis_history:ti(e.auto_synopsis_history)}}var ii="session_id, content, updated_at, previous_versions, auto_synopsis, auto_synopsis_generated_at, auto_synopsis_history";function Ue(e){let s=p().prepare(`SELECT ${ii} FROM session_notes WHERE session_id = ?`).get(e);return s?ni(s):null}function bt(e,s){let t=p(),n=new Date().toISOString(),i=t.prepare("SELECT content, previous_versions FROM session_notes WHERE session_id = ?").get(e),r=[];return i&&(r=St(i.previous_versions),i.content!==s&&i.content.length>0&&r.push({content:i.content,replaced_at:n})),t.prepare(`INSERT INTO session_notes (session_id, content, updated_at, previous_versions)
666
666
  VALUES (?, ?, ?, ?)
667
667
  ON CONFLICT(session_id) DO UPDATE SET
668
668
  content = excluded.content,
669
669
  updated_at = excluded.updated_at,
670
670
  previous_versions = excluded.previous_versions`).run(e,s,n,JSON.stringify(r)),ri(e,s,n),Ue(e)??{session_id:e,content:s,updated_at:n,previous_versions:r,auto_synopsis:null,auto_synopsis_generated_at:null,auto_synopsis_history:[]}}function ri(e,s,t){try{si();let n=Tt(Me,`${e}.md`),i=`<!-- Claude Recall note \xB7 session ${e} \xB7 updated ${t} -->
671
- `;Qn(n,i+s)}catch(n){console.error("[notes] mirror write failed:",n)}}L();F();import{randomUUID as oi}from"node:crypto";import{writeFileSync as ai,readFileSync as Ea,existsSync as Ta}from"node:fs";import{join as ci}from"node:path";var li=ci(S,"collections.json"),Rt=8;function di(e){return{...e}}function Pe(e,s,t,n=null,i=new Date().toISOString()){p().prepare(`INSERT INTO collection_events (collection_id, session_id, action, payload, at)
671
+ `;Qn(n,i+s)}catch(n){console.error("[notes] mirror write failed:",n)}}L();F();import{randomUUID as oi}from"node:crypto";import{writeFileSync as ai,readFileSync as Na,existsSync as ya}from"node:fs";import{join as ci}from"node:path";var li=ci(S,"collections.json"),Rt=8;function di(e){return{...e}}function Pe(e,s,t,n=null,i=new Date().toISOString()){p().prepare(`INSERT INTO collection_events (collection_id, session_id, action, payload, at)
672
672
  VALUES (?, ?, ?, ?, ?)`).run(e,n,s,t?JSON.stringify(t):null,i)}function ui(e){let s=p().prepare("SELECT * FROM collections WHERE id = ?").get(e);if(!s)throw new Error(`collection not found: ${e}`);return s}function pi(e){if(!e)return 0;let s=0,t=e,n=new Set,i=p();for(;t;){if(n.has(t))throw new Error("collection cycle detected");n.add(t);let r=i.prepare("SELECT parent_id FROM collections WHERE id = ?").get(t);if(!r)break;s+=1,t=r.parent_id}return s}function Nt(e){let s=p().prepare("SELECT * FROM collections WHERE id = ?").get(e);return s?di(s):null}function yt(e){let s=(e.name??"").trim();if(!s)throw new Error("name required");if(s.length>120)throw new Error("name too long (max 120 chars)");let t=p(),n=new Date().toISOString(),i=oi();if(e.parent_id){if(!Nt(e.parent_id))throw new Error("parent collection not found");if(pi(e.parent_id)>=Rt-1)throw new Error(`max collection depth is ${Rt}`)}return t.transaction(()=>{t.prepare(`INSERT INTO collections
673
673
  (id, name, description, icon, color, parent_id, sort_key, created_at, updated_at, archived_at)
674
674
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`).run(i,s,e.description??null,e.icon??null,e.color??null,e.parent_id??null,e.sort_key??"",n,n),Pe(i,"create",{name:s,parent_id:e.parent_id??null,icon:e.icon??null,color:e.color??null},null,n)})(),je(),Nt(i)}function wt(e,s,t=null,n={}){let i=p();if(ui(e),!i.prepare("SELECT 1 FROM sessions WHERE id = ?").get(s))throw new Error(`session not found: ${s}`);if(i.prepare("SELECT 1 FROM collection_sessions WHERE collection_id = ? AND session_id = ?").get(e,s))return{added:!1};let o=n.source??"manual",c=n.rule_id??null;if(o==="auto"&&!c)throw new Error("auto membership requires a rule_id");let l=new Date().toISOString();return i.transaction(()=>{i.prepare(`INSERT INTO collection_sessions (collection_id, session_id, added_at, note, source, rule_id)
@@ -680,11 +680,11 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
680
680
  ORDER BY COALESCE(parent_id, ''), sort_key, LOWER(name)`).all(),t=e.prepare(`SELECT collection_id, session_id, added_at, note, source, rule_id
681
681
  FROM collection_sessions
682
682
  ORDER BY collection_id, added_at`).all(),n=mi(),i={schema:"claude-recall.collections.v1",backed_up_at:new Date().toISOString(),collections:s,memberships:t,events:n};ai(li,JSON.stringify(i,null,2))}catch(e){console.error("[collections] backup failed:",e)}}L();var Xe=60,gi=6e4,se=class extends Error{retryAfterMs;constructor(s){super(`MCP write rate limit exceeded \u2014 try again in ${Math.ceil(s/1e3)}s`),this.name="RateLimitError",this.retryAfterMs=s}};function $e(e){let s=new Date().toISOString(),t;try{t=JSON.stringify(e.args??null)}catch{t='"<unserializable>"'}p().prepare(`INSERT INTO mcp_audit_events (tool, args_json, result, error_message, caller, at)
683
- VALUES (?, ?, ?, ?, ?, ?)`).run(e.tool,t,e.result,e.errorMessage??null,e.caller??null,s)}var K=class{capacity;windowMs;hits=[];constructor(s=Xe,t=gi){this.capacity=s,this.windowMs=t}consume(s=Date.now()){if(this.evict(s),this.hits.length>=this.capacity){let t=this.hits[0],n=Math.max(1,t+this.windowMs-s);throw new se(n)}this.hits.push(s)}remaining(s=Date.now()){return this.evict(s),Math.max(0,this.capacity-this.hits.length)}reset(){this.hits.length=0}evict(s){let t=s-this.windowMs;for(;this.hits.length>0&&this.hits[0]<t;)this.hits.shift()}};async function y(e){try{e.limiter.consume()}catch(s){throw s instanceof se&&$e({tool:e.tool,args:e.args,result:"rate_limited",errorMessage:s.message,caller:e.caller}),s}try{let s=await e.run();return $e({tool:e.tool,args:e.args,result:"ok",caller:e.caller}),s}catch(s){let t=s instanceof Error?s.message:String(s);throw $e({tool:e.tool,args:e.args,result:"error",errorMessage:t,caller:e.caller}),s}}L();import{z as _i}from"zod";function $(e){let s=e.trim();if(s.length<4)return null;let t=p();if(s.length>=32)return t.prepare("SELECT id FROM sessions WHERE id = ?").get(s)?.id??null;let n=t.prepare("SELECT id FROM sessions WHERE id LIKE ? LIMIT 2").all(`${s}%`);return n.length===1?n[0].id:null}function Ee(e){return{content:[{type:"text",text:e}],isError:!0}}function R(e){return e instanceof se?Ee(e.message):e instanceof _i.ZodError?Ee(`invalid input: ${e.message}`):e instanceof Error&&e.message.startsWith("SQLITE_")?Ee("database constraint error"):Ee(e instanceof Error?e.message:String(e))}function Q(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}function J(e){return{content:[{type:"text",text:e}],isError:!0}}var fi=`
683
+ VALUES (?, ?, ?, ?, ?, ?)`).run(e.tool,t,e.result,e.errorMessage??null,e.caller??null,s)}var z=class{capacity;windowMs;hits=[];constructor(s=Xe,t=gi){this.capacity=s,this.windowMs=t}consume(s=Date.now()){if(this.evict(s),this.hits.length>=this.capacity){let t=this.hits[0],n=Math.max(1,t+this.windowMs-s);throw new se(n)}this.hits.push(s)}remaining(s=Date.now()){return this.evict(s),Math.max(0,this.capacity-this.hits.length)}reset(){this.hits.length=0}evict(s){let t=s-this.windowMs;for(;this.hits.length>0&&this.hits[0]<t;)this.hits.shift()}};async function y(e){try{e.limiter.consume()}catch(s){throw s instanceof se&&$e({tool:e.tool,args:e.args,result:"rate_limited",errorMessage:s.message,caller:e.caller}),s}try{let s=await e.run();return $e({tool:e.tool,args:e.args,result:"ok",caller:e.caller}),s}catch(s){let t=s instanceof Error?s.message:String(s);throw $e({tool:e.tool,args:e.args,result:"error",errorMessage:t,caller:e.caller}),s}}L();import{z as _i}from"zod";function $(e){let s=e.trim();if(s.length<4)return null;let t=p();if(s.length>=32)return t.prepare("SELECT id FROM sessions WHERE id = ?").get(s)?.id??null;let n=t.prepare("SELECT id FROM sessions WHERE id LIKE ? LIMIT 2").all(`${s}%`);return n.length===1?n[0].id:null}function Ee(e){return{content:[{type:"text",text:e}],isError:!0}}function R(e){return e instanceof se?Ee(e.message):e instanceof _i.ZodError?Ee(`invalid input: ${e.message}`):e instanceof Error&&e.message.startsWith("SQLITE_")?Ee("database constraint error"):Ee(e instanceof Error?e.message:String(e))}function Q(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}function J(e){return{content:[{type:"text",text:e}],isError:!0}}var fi=`
684
684
 
685
685
  ---
686
686
 
687
- `,hi=5e5;function At(e,s={}){let t=s.limiter??new K;e.registerTool("add_tag",{title:"Add tag to a session",description:"Apply a single tag to a session. Tags are normalized server-side (lowercase, hyphens, strip #). Idempotent.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),tag:I.string().min(1).describe("Tag to add. Normalized: lowercase, dashes, max 40 chars.")}},async n=>{try{let i=$(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await y({tool:"add_tag",args:{sessionId:i,tag:n.tag},limiter:t,run:()=>ge(i,n.tag)});return Q({sessionId:i,...r})}catch(i){return R(i)}}),e.registerTool("remove_tag",{title:"Remove tag from a session",description:"Remove a single tag from a session. The removal is recorded in tag_events (append-only) so history is preserved.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),tag:I.string().min(1).describe("Tag to remove (normalized server-side).")}},async n=>{try{let i=$(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=q(n.tag);if(!r)return J("tag must contain at least one alphanumeric character");let a=await y({tool:"remove_tag",args:{sessionId:i,tag:r},limiter:t,run:()=>pt(i,r)});return Q({sessionId:i,...a})}catch(i){return R(i)}}),e.registerTool("set_alias",{title:"Set session alias",description:"Set a human-friendly alias for a session. Previous alias is archived to previous_aliases (never destroyed). Session UUID remains the immutable primary key.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),alias:I.string().min(1).max(120).describe("New alias (non-empty, max 120 chars).")}},async n=>{try{let i=$(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await y({tool:"set_alias",args:{sessionId:i,alias:n.alias},limiter:t,run:()=>Et(i,n.alias)});return Q(r)}catch(i){return R(i)}}),e.registerTool("append_note",{title:"Append to session note",description:"Append markdown to a session note. If a note already exists, the new content is added below a `---` separator. Previous version is archived in previous_versions.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),markdown:I.string().min(1).max(5e4).describe("Markdown to append.")}},async n=>{try{let i=$(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await y({tool:"append_note",args:{sessionId:i,length:n.markdown.length},limiter:t,run:()=>{let a=Ue(i),o=a&&a.content.length>0?`${a.content}${fi}${n.markdown}`:n.markdown;if(o.length>hi)throw new Error(`note would exceed the 500 KB cumulative limit (current: ${a?.content.length??0} bytes, adding: ${n.markdown.length} bytes)`);return bt(i,o)}});return Q(r)}catch(i){return R(i)}}),e.registerTool("create_collection",{title:"Create a collection",description:"Create a new collection for grouping sessions. Optionally nest under a parent. Soft-deletion only.",inputSchema:{name:I.string().min(1).max(120),parent_id:I.string().optional().describe("Parent collection id (omit for root)."),icon:I.string().max(32).optional(),color:I.string().max(32).optional(),description:I.string().max(2e3).optional()}},async n=>{try{let i=await y({tool:"create_collection",args:n,limiter:t,run:()=>yt({name:n.name,parent_id:n.parent_id??null,icon:n.icon??null,color:n.color??null,description:n.description??null})});return Q(i)}catch(i){return R(i)}}),e.registerTool("add_session_to_collection",{title:"Add session to collection",description:"Add a session to a collection. Idempotent: re-adding returns added=false.",inputSchema:{collectionId:I.string().min(1),sessionId:I.string().describe("Full session UUID or 8+-character prefix."),note:I.string().max(2e3).optional()}},async n=>{try{let i=$(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);if(!p().prepare("SELECT 1 FROM collections WHERE id = ?").get(n.collectionId))return J("collection not found");let o=await y({tool:"add_session_to_collection",args:{collectionId:n.collectionId,sessionId:i,note:n.note??null},limiter:t,run:()=>wt(n.collectionId,i,n.note??null)});return Q({collectionId:n.collectionId,sessionId:i,...o})}catch(i){return R(i)}}),e.registerTool("remove_session_from_collection",{title:"Remove session from collection",description:"Remove a session from a collection. The removal is recorded in collection_events (append-only); the underlying session is untouched.",inputSchema:{collectionId:I.string().min(1),sessionId:I.string().describe("Full session UUID or 8+-character prefix.")}},async n=>{try{let i=$(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await y({tool:"remove_session_from_collection",args:{collectionId:n.collectionId,sessionId:i},limiter:t,run:()=>Lt(n.collectionId,i)});return Q({collectionId:n.collectionId,sessionId:i,...r})}catch(i){return R(i)}})}import{z as M}from"zod";L();F();import{randomUUID as Ot}from"node:crypto";import{writeFileSync as It,readFileSync as Xa,existsSync as Ei,mkdirSync as Ti}from"node:fs";import{join as He}from"node:path";var Te=He(S,"threads"),Si=He(Te,"index.json");function xt(){x(),Ei(Te)||Ti(Te,{recursive:!0})}function Be(e,s,t){return{id:e.id,name:e.name,summary:e.summary,created_at:e.created_at,closed_at:e.closed_at,archived:e.archived===1,session_count:s.session_count,origin_count:s.origin_count,project:t?.project??null,project_count:t?.project_count??0,folder_id:e.folder_id??null}}function Ct(e){let s=new Map;if(e.length===0)return s;let t=p(),n=e.map(()=>"?").join(","),i=t.prepare(`SELECT te.thread_id AS thread_id,
687
+ `,hi=5e5;function At(e,s={}){let t=s.limiter??new z;e.registerTool("add_tag",{title:"Add tag to a session",description:"Apply a single tag to a session. Tags are normalized server-side (lowercase, hyphens, strip #). Idempotent.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),tag:I.string().min(1).describe("Tag to add. Normalized: lowercase, dashes, max 40 chars.")}},async n=>{try{let i=$(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await y({tool:"add_tag",args:{sessionId:i,tag:n.tag},limiter:t,run:()=>ge(i,n.tag)});return Q({sessionId:i,...r})}catch(i){return R(i)}}),e.registerTool("remove_tag",{title:"Remove tag from a session",description:"Remove a single tag from a session. The removal is recorded in tag_events (append-only) so history is preserved.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),tag:I.string().min(1).describe("Tag to remove (normalized server-side).")}},async n=>{try{let i=$(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=q(n.tag);if(!r)return J("tag must contain at least one alphanumeric character");let a=await y({tool:"remove_tag",args:{sessionId:i,tag:r},limiter:t,run:()=>pt(i,r)});return Q({sessionId:i,...a})}catch(i){return R(i)}}),e.registerTool("set_alias",{title:"Set session alias",description:"Set a human-friendly alias for a session. Previous alias is archived to previous_aliases (never destroyed). Session UUID remains the immutable primary key.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),alias:I.string().min(1).max(120).describe("New alias (non-empty, max 120 chars).")}},async n=>{try{let i=$(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await y({tool:"set_alias",args:{sessionId:i,alias:n.alias},limiter:t,run:()=>Et(i,n.alias)});return Q(r)}catch(i){return R(i)}}),e.registerTool("append_note",{title:"Append to session note",description:"Append markdown to a session note. If a note already exists, the new content is added below a `---` separator. Previous version is archived in previous_versions.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),markdown:I.string().min(1).max(5e4).describe("Markdown to append.")}},async n=>{try{let i=$(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await y({tool:"append_note",args:{sessionId:i,length:n.markdown.length},limiter:t,run:()=>{let a=Ue(i),o=a&&a.content.length>0?`${a.content}${fi}${n.markdown}`:n.markdown;if(o.length>hi)throw new Error(`note would exceed the 500 KB cumulative limit (current: ${a?.content.length??0} bytes, adding: ${n.markdown.length} bytes)`);return bt(i,o)}});return Q(r)}catch(i){return R(i)}}),e.registerTool("create_collection",{title:"Create a collection",description:"Create a new collection for grouping sessions. Optionally nest under a parent. Soft-deletion only.",inputSchema:{name:I.string().min(1).max(120),parent_id:I.string().optional().describe("Parent collection id (omit for root)."),icon:I.string().max(32).optional(),color:I.string().max(32).optional(),description:I.string().max(2e3).optional()}},async n=>{try{let i=await y({tool:"create_collection",args:n,limiter:t,run:()=>yt({name:n.name,parent_id:n.parent_id??null,icon:n.icon??null,color:n.color??null,description:n.description??null})});return Q(i)}catch(i){return R(i)}}),e.registerTool("add_session_to_collection",{title:"Add session to collection",description:"Add a session to a collection. Idempotent: re-adding returns added=false.",inputSchema:{collectionId:I.string().min(1),sessionId:I.string().describe("Full session UUID or 8+-character prefix."),note:I.string().max(2e3).optional()}},async n=>{try{let i=$(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);if(!p().prepare("SELECT 1 FROM collections WHERE id = ?").get(n.collectionId))return J("collection not found");let o=await y({tool:"add_session_to_collection",args:{collectionId:n.collectionId,sessionId:i,note:n.note??null},limiter:t,run:()=>wt(n.collectionId,i,n.note??null)});return Q({collectionId:n.collectionId,sessionId:i,...o})}catch(i){return R(i)}}),e.registerTool("remove_session_from_collection",{title:"Remove session from collection",description:"Remove a session from a collection. The removal is recorded in collection_events (append-only); the underlying session is untouched.",inputSchema:{collectionId:I.string().min(1),sessionId:I.string().describe("Full session UUID or 8+-character prefix.")}},async n=>{try{let i=$(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await y({tool:"remove_session_from_collection",args:{collectionId:n.collectionId,sessionId:i},limiter:t,run:()=>Lt(n.collectionId,i)});return Q({collectionId:n.collectionId,sessionId:i,...r})}catch(i){return R(i)}})}import{z as M}from"zod";L();F();import{randomUUID as Ot}from"node:crypto";import{writeFileSync as It,readFileSync as Ga,existsSync as Ei,mkdirSync as Ti}from"node:fs";import{join as He}from"node:path";var Te=He(S,"threads"),Si=He(Te,"index.json");function xt(){x(),Ei(Te)||Ti(Te,{recursive:!0})}function Be(e,s,t){return{id:e.id,name:e.name,summary:e.summary,created_at:e.created_at,closed_at:e.closed_at,archived:e.archived===1,session_count:s.session_count,origin_count:s.origin_count,project:t?.project??null,project_count:t?.project_count??0,folder_id:e.folder_id??null}}function Ct(e){let s=new Map;if(e.length===0)return s;let t=p(),n=e.map(()=>"?").join(","),i=t.prepare(`SELECT te.thread_id AS thread_id,
688
688
  p.name AS project,
689
689
  COUNT(*) AS n,
690
690
  SUM(CASE WHEN te.role = 'origin' THEN 1 ELSE 0 END) AS origin_n
@@ -763,7 +763,7 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
763
763
  LEFT JOIN session_aliases sa ON sa.session_id = e.session_id
764
764
  WHERE e.thread_id = ?
765
765
  AND e.session_id NOT IN (${r})
766
- ORDER BY e.added_at ASC`).all(t.thread_id,...i).map(c=>({id:c.session_id,title:c.id?Wt(c):c.session_id.slice(0,8)}));return{thread_id:t.thread_id,thread_name:t.thread_name,parent_session:n,siblings:o}}var ze=Gt(S,"titles");var ce=5,Re=15,Li=500;function zt(e){if(!e)return[];try{let s=JSON.parse(e);if(Array.isArray(s))return s.filter(t=>!!t&&typeof t=="object"&&typeof t.title=="string"&&typeof t.replaced_at=="string")}catch{}return[]}function Kt(e){let s=p(),t=s.prepare(`SELECT rowid AS rid, content_text
766
+ ORDER BY e.added_at ASC`).all(t.thread_id,...i).map(c=>({id:c.session_id,title:c.id?Wt(c):c.session_id.slice(0,8)}));return{thread_id:t.thread_id,thread_name:t.thread_name,parent_session:n,siblings:o}}var Ke=Gt(S,"titles");var ce=5,Re=15,Li=500;function Kt(e){if(!e)return[];try{let s=JSON.parse(e);if(Array.isArray(s))return s.filter(t=>!!t&&typeof t=="object"&&typeof t.title=="string"&&typeof t.replaced_at=="string")}catch{}return[]}function zt(e){let s=p(),t=s.prepare(`SELECT rowid AS rid, content_text
767
767
  FROM messages
768
768
  WHERE session_id = ? AND role = 'user' AND is_sidechain = 0
769
769
  AND content_text IS NOT NULL AND content_text != ''
@@ -778,17 +778,17 @@ ${u+1}. ${m}`:`${u+1}. ${m}`}).join(`
778
778
  `),c=null;try{c=Yt(e)}catch(d){console.error("[autoTitle] thread context resolution failed:",d),c=null}let l=[];return c&&(l.push(Ai(c)),l.push("")),l.push(`You will receive a sample of user messages from a Claude Code session: the first ${ce}`,`messages (initial intent) and the last ${Re} messages (current direction).`,"Write a single descriptive title, max 50 characters, focused on what the user is","currently trying to accomplish. If initial intent and current direction differ, prefer","the current direction. Output ONLY the title, with no quotes and no trailing punctuation.","","Messages:",o),l.join(`
779
779
  `)}var Ge=5;function Ai(e){let s=[];s.push("Thread context:"),s.push(`- This session is part of thread "${e.thread_name}".`),e.parent_session&&s.push(`- Parent session: "${e.parent_session.title}"`);let t=e.siblings.length;if(t>0){let i=e.siblings.slice(0,Ge).map(a=>`"${a.title}"`).join(", "),r=t>Ge?`, and ${t-Ge} more`:"";s.push(`- Sibling sessions (${t}): ${i}${r}`)}return s.push(""),s.push("Generate a title that reflects this session's role in the thread."),s.push('If siblings use a pattern like "Phase A / Phase B" or "Wave 1 of 4",'),s.push("follow the same pattern."),s.join(`
780
780
  `)}function Jt(e,s,t){let n=s.trim();if(!n)return;let i=p(),r=i.prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
781
- FROM sessions WHERE id = ?`).get(e);if(!r||t==="heuristic"&&r.auto_title_source==="agent"&&r.auto_title||r.auto_title===n&&r.auto_title_source===t)return;let a=zt(r.auto_title_history),o=new Date().toISOString();r.auto_title&&r.auto_title_source&&a.push({title:r.auto_title,source:r.auto_title_source,replaced_at:o}),i.prepare(`UPDATE sessions
781
+ FROM sessions WHERE id = ?`).get(e);if(!r||t==="heuristic"&&r.auto_title_source==="agent"&&r.auto_title||r.auto_title===n&&r.auto_title_source===t)return;let a=Kt(r.auto_title_history),o=new Date().toISOString();r.auto_title&&r.auto_title_source&&a.push({title:r.auto_title,source:r.auto_title_source,replaced_at:o}),i.prepare(`UPDATE sessions
782
782
  SET auto_title = ?,
783
783
  auto_title_source = ?,
784
784
  auto_title_generated_at = ?,
785
785
  auto_title_history = ?
786
786
  WHERE id = ?`).run(n,t,Date.now(),JSON.stringify(a),e),Ii(e,n,t,o)}function Vt(e){let s=p().prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
787
- FROM sessions WHERE id = ?`).get(e);return s?{auto_title:s.auto_title,auto_title_source:s.auto_title_source??null,auto_title_generated_at:s.auto_title_generated_at,auto_title_history:zt(s.auto_title_history)}:null}function Oi(){x(),wi(ze)||yi(ze,{recursive:!0})}function Ii(e,s,t,n){try{Oi();let i=Gt(ze,`${e}.txt`),r=`# Claude Recall auto-title \xB7 session ${e} \xB7 source ${t} \xB7 updated ${n}
787
+ FROM sessions WHERE id = ?`).get(e);return s?{auto_title:s.auto_title,auto_title_source:s.auto_title_source??null,auto_title_generated_at:s.auto_title_generated_at,auto_title_history:Kt(s.auto_title_history)}:null}function Oi(){x(),wi(Ke)||yi(Ke,{recursive:!0})}function Ii(e,s,t,n){try{Oi();let i=Gt(Ke,`${e}.txt`),r=`# Claude Recall auto-title \xB7 session ${e} \xB7 source ${t} \xB7 updated ${n}
788
788
  `;Ni(i,r+s+`
789
- `)}catch(i){console.error("[autoTitle] mirror write failed:",i)}}var Bi=50;function Wi(e){if(e.length===0)return[];let s=[...e].sort((c,l)=>c.added_at<l.added_at?-1:c.added_at>l.added_at?1:0),t=new Map,n=new Set,i=[];for(let c of s)if(c.parent_session_id){let l=t.get(c.parent_session_id)??[];l.push(c),t.set(c.parent_session_id,l)}let r=s.filter(c=>c.role==="origin"),o=[...r.length>0?r:s.filter(c=>!c.parent_session_id)];for(;o.length>0;){let c=o.shift();if(n.has(c.session_id))continue;n.add(c.session_id),i.push(c.session_id);let l=t.get(c.session_id)??[];for(let d of l)n.has(d.session_id)||o.push(d)}for(let c of s)n.has(c.session_id)||(n.add(c.session_id),i.push(c.session_id));return i}function Yi(e){let s=Kt(e.sessionId),t=["",`BULK CONTEXT: You are titling session ${e.current} of ${e.total} in this thread.`,"The parent and earlier siblings already have titles (shown above). YOUR JOB:","",'1. Identify the naming pattern. If parent is "Build Feature X" and earlier',' siblings are "Phase A: API design" and "Phase B: client wiring", the pattern',' is "Phase {LETTER}: {topic}".',"2. If no pattern is yet established (this is the first child), INVENT a pattern",' that will scale to N children. Good patterns: "Phase A/B/C", "Wave 1 of M",',' "Step N", or a domain-specific structure if the thread name suggests one.',"3. Output a title that follows the pattern AND describes what THIS session"," does in 50 characters max.","","Output ONLY the title, no quotes, no trailing punctuation."].join(`
789
+ `)}catch(i){console.error("[autoTitle] mirror write failed:",i)}}var Bi=50;function Wi(e){if(e.length===0)return[];let s=[...e].sort((c,l)=>c.added_at<l.added_at?-1:c.added_at>l.added_at?1:0),t=new Map,n=new Set,i=[];for(let c of s)if(c.parent_session_id){let l=t.get(c.parent_session_id)??[];l.push(c),t.set(c.parent_session_id,l)}let r=s.filter(c=>c.role==="origin"),o=[...r.length>0?r:s.filter(c=>!c.parent_session_id)];for(;o.length>0;){let c=o.shift();if(n.has(c.session_id))continue;n.add(c.session_id),i.push(c.session_id);let l=t.get(c.session_id)??[];for(let d of l)n.has(d.session_id)||o.push(d)}for(let c of s)n.has(c.session_id)||(n.add(c.session_id),i.push(c.session_id));return i}function Yi(e){let s=zt(e.sessionId),t=["",`BULK CONTEXT: You are titling session ${e.current} of ${e.total} in this thread.`,"The parent and earlier siblings already have titles (shown above). YOUR JOB:","",'1. Identify the naming pattern. If parent is "Build Feature X" and earlier',' siblings are "Phase A: API design" and "Phase B: client wiring", the pattern',' is "Phase {LETTER}: {topic}".',"2. If no pattern is yet established (this is the first child), INVENT a pattern",' that will scale to N children. Good patterns: "Phase A/B/C", "Wave 1 of M",',' "Step N", or a domain-specific structure if the thread name suggests one.',"3. Output a title that follows the pattern AND describes what THIS session"," does in 50 characters max.","","Output ONLY the title, no quotes, no trailing punctuation."].join(`
790
790
  `);return`${s}
791
- ${t}`}function Gi(e){return Vt(e)?.auto_title_source??null}async function zi(e){let{spawnClaudePrompt:s,isClaudeCliAvailable:t}=await Promise.resolve().then(()=>(ss(),ts));if(!t())throw new Error("claude CLI not found on PATH");let n=await s(e.prompt,[],{model:e.model});if(!n.success)throw new Error(`claude CLI exited ${n.exitCode}: ${n.stderr.slice(-500)}`);let i=Ki(n.stdout);if(!i)throw new Error("claude CLI returned an empty title");return i.slice(0,Bi)}function Ki(e){let s=e.trim();if(!s)return"";try{let t=JSON.parse(s);if(t&&typeof t=="object"){let n=t.result;if(typeof n=="string")return ns(n)}}catch{}return ns(s)}function ns(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}async function rs(e,s={}){let t=D(e);if(!t)throw new Error(`thread not found: ${e}`);let n=Wi(t.edges),i=n.length,r=s.force??!1,a=s.signal,o=[],c=[],l=[];for(let d=0;d<n.length;d++){let u=n[d],m=d+1;if(a?.aborted){let _={sessionId:u,reason:"cancelled"};c.push(_),s.onSkipped?.(_);continue}if(!r&&Gi(u)==="agent"){let _={sessionId:u,reason:"already-titled"};c.push(_),s.onSkipped?.(_);continue}let g;try{if(is)g=await is({sessionId:u,current:m,total:i});else{let _=Yi({sessionId:u,current:m,total:i});g=await zi({prompt:_,model:s.model})}}catch(_){let f=_ instanceof Error?_.message:String(_??"unknown error"),h={sessionId:u,error:f};l.push(h),s.onFailed?.(h);continue}try{Jt(u,g,"agent")}catch(_){let f=_ instanceof Error?_.message:String(_??"unknown error"),h={sessionId:u,error:`setAutoTitle failed: ${f}`};l.push(h),s.onFailed?.(h);continue}o.push(u),s.onProgress?.({current:m,total:i,sessionId:u,title:g})}return{generated:o,skipped:c,failed:l}}var is=null;L();import{execFile as lr}from"node:child_process";import{promisify as dr}from"node:util";import{readlink as ur,readFile as us}from"node:fs/promises";import{platform as ye}from"node:os";import{readFileSync as Ji,statSync as Vi}from"node:fs";var qi=200*1024*1024;var Je=.5,cs=Je,Qi=[{maxGapMs:3600*1e3,weight:.7,label:"<1h gap"},{maxGapMs:14400*1e3,weight:.4,label:"<4h gap"},{maxGapMs:1440*60*1e3,weight:.2,label:"<24h gap"}],Zi=["let's commit","lets commit","now commit","inspect the diff","inspect this diff","review the diff","review this diff","diff of all changes","diff of changes","based on what we just did","based on the things done","based on all the things","continue from","continuing from","pick up where","next step","now fix","now lets","now let's","from the previous session","from our last session","from the last session"];function os(e){if(!e)return null;if(e.startsWith("/")){let t=e.split(" \xB7 ");if(t.length>1)return t[1].trim().toLowerCase()}let s=e.split(" \xB7 ");return s.length>1?s[s.length-1].trim().toLowerCase():null}function er(e,s){let t=s-e;if(t<0)return{weight:0,label:null};for(let n of Qi)if(t<=n.maxGapMs)return{weight:n.weight,label:n.label};return{weight:0,label:null}}function tr(e){if(e.length===0)return{weight:0,matched:null,matchedIndex:-1};let s=[.4,.35,.3,.25,.2,.15,.1],t=Math.min(e.length,s.length);for(let n=0;n<t;n++){let i=e[n];if(!i)continue;let r=i.toLowerCase();for(let a of Zi)if(r.includes(a))return{weight:s[n],matched:a,matchedIndex:n}}return{weight:0,matched:null,matchedIndex:-1}}function Ke(e,s){if(!e||!s||e.length!==s.length)return 0;let t=0;for(let n=0;n<e.length;n++)t+=e[n]*s[n];return t<-1?-1:t>1?1:t}function sr(e,s){if(e.length===0||s.length===0)return 0;let t=0;for(let n of e)for(let i of s){let r=Ke(n,i);r>t&&(t=r)}return t}function nr(e,s){let t=Ke(e.mean_embedding,s.mean_embedding),n=Ke(e.tail_pool,s.head_pool),i=sr(e.sample_chunks,s.sample_chunks),r=0,a=null;if(t>r&&(r=t,a="mean"),n>r&&(r=n,a="asymmetric"),i>r&&(r=i,a="max_pool"),r<.65)return{weight:0,cosine:r,mode:null};if(r>=.85)return{weight:.3,cosine:r,mode:a};let o=(r-.65)/.2*.3;return{weight:Math.round(o*100)/100,cosine:r,mode:a}}function ir(e,s){return e.cluster_id===null||s.cluster_id===null?{weight:0,same:!1}:e.cluster_id!==s.cluster_id?{weight:0,same:!1}:{weight:.05,same:!0}}function rr(e,s){if(e.size===0||s.size===0)return{weight:0,count:0};let t=0;for(let i of s)e.has(i)&&t++;return t===0?{weight:0,count:0}:{weight:Math.min(.4,t*.1),count:t}}function or(e,s){let t=os(e),n=os(s);return t&&n&&t===n?{weight:.1,brand:t}:{weight:0,brand:null}}function as(e){return e.replace(/\s+/g," ").trim().toLowerCase()}function ar(e,s){let t=0,n=null,i=!1;if(e.authored_paths.size>0){let r=s.recent_user_messages.slice(0,3).join(`
791
+ ${t}`}function Gi(e){return Vt(e)?.auto_title_source??null}async function Ki(e){let{spawnClaudePrompt:s,isClaudeCliAvailable:t}=await Promise.resolve().then(()=>(ss(),ts));if(!t())throw new Error("claude CLI not found on PATH");let n=await s(e.prompt,[],{model:e.model});if(!n.success)throw new Error(`claude CLI exited ${n.exitCode}: ${n.stderr.slice(-500)}`);let i=zi(n.stdout);if(!i)throw new Error("claude CLI returned an empty title");return i.slice(0,Bi)}function zi(e){let s=e.trim();if(!s)return"";try{let t=JSON.parse(s);if(t&&typeof t=="object"){let n=t.result;if(typeof n=="string")return ns(n)}}catch{}return ns(s)}function ns(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}async function rs(e,s={}){let t=D(e);if(!t)throw new Error(`thread not found: ${e}`);let n=Wi(t.edges),i=n.length,r=s.force??!1,a=s.signal,o=[],c=[],l=[];for(let d=0;d<n.length;d++){let u=n[d],m=d+1;if(a?.aborted){let _={sessionId:u,reason:"cancelled"};c.push(_),s.onSkipped?.(_);continue}if(!r&&Gi(u)==="agent"){let _={sessionId:u,reason:"already-titled"};c.push(_),s.onSkipped?.(_);continue}let g;try{if(is)g=await is({sessionId:u,current:m,total:i});else{let _=Yi({sessionId:u,current:m,total:i});g=await Ki({prompt:_,model:s.model})}}catch(_){let f=_ instanceof Error?_.message:String(_??"unknown error"),h={sessionId:u,error:f};l.push(h),s.onFailed?.(h);continue}try{Jt(u,g,"agent")}catch(_){let f=_ instanceof Error?_.message:String(_??"unknown error"),h={sessionId:u,error:`setAutoTitle failed: ${f}`};l.push(h),s.onFailed?.(h);continue}o.push(u),s.onProgress?.({current:m,total:i,sessionId:u,title:g})}return{generated:o,skipped:c,failed:l}}var is=null;L();import{execFile as lr}from"node:child_process";import{promisify as dr}from"node:util";import{readlink as ur,readFile as us}from"node:fs/promises";import{platform as ye}from"node:os";import{readFileSync as Ji,statSync as Vi}from"node:fs";var qi=200*1024*1024;var Je=.5,cs=Je,Qi=[{maxGapMs:3600*1e3,weight:.7,label:"<1h gap"},{maxGapMs:14400*1e3,weight:.4,label:"<4h gap"},{maxGapMs:1440*60*1e3,weight:.2,label:"<24h gap"}],Zi=["let's commit","lets commit","now commit","inspect the diff","inspect this diff","review the diff","review this diff","diff of all changes","diff of changes","based on what we just did","based on the things done","based on all the things","continue from","continuing from","pick up where","next step","now fix","now lets","now let's","from the previous session","from our last session","from the last session"];function os(e){if(!e)return null;if(e.startsWith("/")){let t=e.split(" \xB7 ");if(t.length>1)return t[1].trim().toLowerCase()}let s=e.split(" \xB7 ");return s.length>1?s[s.length-1].trim().toLowerCase():null}function er(e,s){let t=s-e;if(t<0)return{weight:0,label:null};for(let n of Qi)if(t<=n.maxGapMs)return{weight:n.weight,label:n.label};return{weight:0,label:null}}function tr(e){if(e.length===0)return{weight:0,matched:null,matchedIndex:-1};let s=[.4,.35,.3,.25,.2,.15,.1],t=Math.min(e.length,s.length);for(let n=0;n<t;n++){let i=e[n];if(!i)continue;let r=i.toLowerCase();for(let a of Zi)if(r.includes(a))return{weight:s[n],matched:a,matchedIndex:n}}return{weight:0,matched:null,matchedIndex:-1}}function ze(e,s){if(!e||!s||e.length!==s.length)return 0;let t=0;for(let n=0;n<e.length;n++)t+=e[n]*s[n];return t<-1?-1:t>1?1:t}function sr(e,s){if(e.length===0||s.length===0)return 0;let t=0;for(let n of e)for(let i of s){let r=ze(n,i);r>t&&(t=r)}return t}function nr(e,s){let t=ze(e.mean_embedding,s.mean_embedding),n=ze(e.tail_pool,s.head_pool),i=sr(e.sample_chunks,s.sample_chunks),r=0,a=null;if(t>r&&(r=t,a="mean"),n>r&&(r=n,a="asymmetric"),i>r&&(r=i,a="max_pool"),r<.65)return{weight:0,cosine:r,mode:null};if(r>=.85)return{weight:.3,cosine:r,mode:a};let o=(r-.65)/.2*.3;return{weight:Math.round(o*100)/100,cosine:r,mode:a}}function ir(e,s){return e.cluster_id===null||s.cluster_id===null?{weight:0,same:!1}:e.cluster_id!==s.cluster_id?{weight:0,same:!1}:{weight:.05,same:!0}}function rr(e,s){if(e.size===0||s.size===0)return{weight:0,count:0};let t=0;for(let i of s)e.has(i)&&t++;return t===0?{weight:0,count:0}:{weight:Math.min(.4,t*.1),count:t}}function or(e,s){let t=os(e),n=os(s);return t&&n&&t===n?{weight:.1,brand:t}:{weight:0,brand:null}}function as(e){return e.replace(/\s+/g," ").trim().toLowerCase()}function ar(e,s){let t=0,n=null,i=!1;if(e.authored_paths.size>0){let r=s.recent_user_messages.slice(0,3).join(`
792
792
  `).toLowerCase();for(let a of e.authored_paths){let o=a.toLowerCase();if(s.touched_files.has(a)||r.includes(o)){t+=.5,n=a;break}}}if(e.authored_content.length>0&&s.recent_user_messages[0]){let r=as(s.recent_user_messages[0]);if(r.length>=200)for(let a of e.authored_content){let o=as(a);if(o.length<200)continue;let c=Math.min(o.length,240),l=o.slice(0,c);if(r.includes(l)){t+=.4,i=!0;break}}}return{weight:Math.min(.6,t),pathMatch:n,contentMatch:i}}function cr(e,s,t=cs){if(s.started_at_ms<=e.started_at_ms)return null;let n=e.ended_at_ms??e.started_at_ms;if(s.started_at_ms<n)return null;let i=er(n,s.started_at_ms),r=s.recent_user_messages.length>0?s.recent_user_messages:s.first_user_message?[s.first_user_message]:[],a=tr(r),o=rr(e.touched_files,s.touched_files),c=or(e.auto_title,s.auto_title),l=nr(e,s),d=ir(e,s),u=ar(e,s),m=i.weight+a.weight+o.weight+c.weight+l.weight+d.weight+u.weight;if(m<t)return null;let g=[];if(i.label&&g.push(`temporal ${i.label} (+${i.weight})`),a.matched){let _=a.matchedIndex===0?"opening message":`message #${a.matchedIndex+1}`;g.push(`continuation phrase "${a.matched}" in ${_} (+${a.weight})`)}if(o.count>0&&g.push(`${o.count} file${o.count===1?"":"s"} overlap (+${o.weight.toFixed(1)})`),c.brand&&g.push(`shared brand "${c.brand}" (+${c.weight})`),l.weight>0&&l.mode&&g.push(`semantic ${l.mode==="asymmetric"?"tail\u2192head":l.mode==="max_pool"?"best-chunk":"mean"} ${l.cosine.toFixed(2)} (+${l.weight.toFixed(2)})`),d.same&&g.push(`same cluster (+${d.weight})`),u.weight>0){let _=[];u.pathMatch&&_.push(`opened authored path "${u.pathMatch.split("/").pop()}"`),u.contentMatch&&_.push("verbatim-paste of authored content"),g.push(`doc-authorship: ${_.join(" + ")} (+${u.weight.toFixed(2)})`)}return{parent_id:e.id,child_id:s.id,confidence:Math.min(1,m),signals:{temporal:i.weight,continuation:a.weight,file_overlap:o.weight,same_brand:c.weight,semantic:l.weight,cluster:d.weight,doc_authorship:u.weight},reasons:g}}function ls(e,s=cs){let t=[];for(let n=0;n<e.length;n++){let i=e[n],r=null;for(let a=0;a<n;a++){let o=e[a],c=cr(o,i,s);c&&(!r||c.confidence>r.confidence)&&(r=c)}r&&t.push(r)}return t}function ds(e,s={}){let t=s.maxUserMessages??5,n=s.userMessageMaxLen??2e3,i=new Set,r=[],a=new Set,o=[],c;try{if(Vi(e).size>qi)return{touched_files:i,recent_user_messages:r,authored_paths:a,authored_content:o};c=Ji(e,"utf8")}catch{return{touched_files:i,recent_user_messages:r,authored_paths:a,authored_content:o}}let l=0;for(;l<c.length;){let d=c.indexOf(`
793
793
  `,l),u=d===-1?c.length:d,m=c.slice(l,u);if(l=d===-1?c.length:d+1,!m.trim())continue;let g;try{g=JSON.parse(m)}catch{continue}let _=g;if(_.type==="user"&&_.message?.role==="user"&&typeof _.message.content=="string"&&r.length<t){let h=_.message.content.trim();h&&r.push(h.length>n?h.slice(0,n):h)}let f=_.message?.content;if(Array.isArray(f))for(let h of f){if(!h||typeof h!="object")continue;let N=h;if(N.type!=="tool_use")continue;let w=N.input??{},U=typeof w.file_path=="string"?w.file_path:null;if(U){let b=Ne(U);b&&i.add(b)}if((N.name==="Write"||N.name==="Edit"||N.name==="MultiEdit")&&U){let b=Ne(U);b&&a.add(b);let G=typeof w.content=="string"?w.content:typeof w.new_string=="string"?w.new_string:null;G&&G.length>=200&&o.push(G.length>4096?G.slice(0,4096):G)}if(N.name==="Bash"&&typeof w.command=="string")for(let b of w.command.matchAll(/(?:^|[\s'"`(=])((?:\.\.?\/|\/|src\/|test\/|docs\/|site\/)[A-Za-z0-9_./-]+)/g)){let G=Ne(b[1]);G&&i.add(G)}if((N.name==="Glob"||N.name==="Grep")&&typeof w.pattern=="string"){let b=Ne(w.pattern);b&&!b.includes("*")&&i.add(b)}}}return{touched_files:i,recent_user_messages:r,authored_paths:a,authored_content:o}}function Ne(e){let s=e.trim().replace(/^['"]|['"]$/g,"");return!s||s.length<4||/[<>|;&\$`]/.test(s)?null:s}var qe=dr(lr),pr=6,ps="Active ",ms=" sessions \u2014 ",mr=6e4;async function gr(){if(ye()==="win32")return[];for(let s of["/bin/ps","/usr/bin/ps"])try{let{stdout:t}=await qe(s,["-eo","pid=,comm="],{timeout:2e3}),n=[];for(let i of t.split(`
794
794
  `)){let r=i.trim().match(/^(\d+)\s+(.+)$/);if(!r)continue;let a=Number(r[1]),o=r[2].trim();(o==="claude"||o.endsWith("/claude")||o.endsWith("/bin/claude"))&&Number.isFinite(a)&&n.push(a)}return n}catch{continue}return[]}async function _r(e){let s=ye();if(s==="linux")try{return(await ur(`/proc/${e}/cwd`)).replace(/\/+$/,"")}catch{return null}if(s==="darwin"||s==="freebsd"||s==="openbsd")for(let t of["/usr/sbin/lsof","/usr/bin/lsof"])try{let{stdout:n}=await qe(t,["-a","-p",String(e),"-d","cwd","-Fn"],{timeout:2e3});for(let i of n.split(`
@@ -829,18 +829,18 @@ ${t}`}function Gi(e){return Vt(e)?.auto_title_source??null}async function zi(e){
829
829
  WHERE thread_id = ?
830
830
  AND source = 'auto-active'
831
831
  AND parent_session_id IS NULL
832
- AND role = 'child'`).all(t);for(let a of r)try{ae(t,a.session_id,null)}catch{}return s}function v(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}function Rr(e){return{content:[{type:"text",text:e}],isError:!0}}function k(e,s){return M.object(e).strict().parse(s)}var Os=M.string().uuid(),W=Os.describe("Thread id (UUID)."),V=Os.describe("Session UUID (exact, not a prefix)."),fs={include_archived:M.boolean().optional().describe("Include archived threads (default false).")},hs={id:W},Es={session_id:V},Ts={name:M.string().min(1).max(200).describe("Human-readable thread name."),summary:M.string().max(4e3).optional().describe("Optional short description."),origin_session_id:V.optional().describe("Seed the thread with this session as its origin.")},Ss={thread_id:W,session_id:V,parent_session_id:V.optional().describe("If present, the edge is role=child with this parent; otherwise role=origin."),role:M.enum(["origin","child"]).optional()},bs={thread_id:W,session_id:V,parent_session_id:V.nullable().describe("New parent (null to clear and promote the edge back to role=origin).")},Rs={thread_id:W,session_id:V},Ns={thread_id:W,name:M.string().min(1).max(200)},ne={thread_id:W},ys={source_id:W.describe("Thread to dissolve \u2014 its edges move into dest_id."),dest_id:W.describe("Thread that absorbs source_id.")},ws={thread_id:W,session_ids:M.array(V).min(1).max(500),new_thread_name:M.string().min(1).max(200)},Ls={thread_id:W,force:M.boolean().optional().describe("When true, regenerate titles for sessions that already have an agent-sourced title. Default false skips them.")},As={project_id:M.number().int().positive().describe("Numeric project id from list_projects. The sync is repo-scoped and never crosses projects."),mode:M.enum(["preflight","apply"]).describe("preflight returns the proposed plan without writing; apply writes the thread + edges and is idempotent."),window_hours:M.number().min(.5).max(168).optional().describe("Rolling activity window in hours. Default 6.")};function Is(e){e.registerTool("thread_list",{title:"List threads",description:"All threads (v0.15a intent groups), newest first. Excludes archived threads unless include_archived is true.",inputSchema:fs},async s=>{try{let{include_archived:t}=k(fs,s);return v(Ye({includeArchived:t??!1}))}catch(t){return R(t)}}),e.registerTool("thread_get",{title:"Get thread with edges",description:"Full thread detail including every session edge (origin + children).",inputSchema:hs},async s=>{try{let{id:t}=k(hs,s),n=D(t);return n?v(n):Rr(`thread not found: ${t}`)}catch(t){return R(t)}}),e.registerTool("thread_for_session",{title:"List threads containing a session",description:"Return non-archived threads that reference this session (as origin or child).",inputSchema:Es},async s=>{try{let{session_id:t}=k(Es,s);return v(Ft(t))}catch(t){return R(t)}})}function xs(e,s={}){let t=s.limiter??new K;e.registerTool("thread_create",{title:"Create a thread",description:"Create a new thread. Optionally seed it with an origin session. Name is required; summary is optional.",inputSchema:Ts},async n=>{try{let i=k(Ts,n),r=await y({tool:"thread_create",args:i,limiter:t,run:()=>Se({name:i.name,summary:i.summary??null,originSessionId:i.origin_session_id})});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_add_session",{title:"Add session to thread",description:"Attach a session to a thread. If parent_session_id is provided the edge is role=child; otherwise role=origin. Upsert: re-adding updates the edge.",inputSchema:Ss},async n=>{try{let i=k(Ss,n),r=await y({tool:"thread_add_session",args:i,limiter:t,run:()=>be({threadId:i.thread_id,sessionId:i.session_id,parentSessionId:i.parent_session_id??null,role:i.role,source:"manual",confidence:1})});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_set_parent",{title:"Set edge parent within thread",description:"Change the parent session of an existing thread edge. Pass null to clear the parent and promote the edge to role=origin.",inputSchema:bs},async n=>{try{let i=k(bs,n),r=await y({tool:"thread_set_parent",args:i,limiter:t,run:()=>ae(i.thread_id,i.session_id,i.parent_session_id)});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_remove_session",{title:"Remove session from thread",description:"Detach a session from a thread. The session itself is untouched; only the edge is removed. The updated thread is re-mirrored to disk.",inputSchema:Rs},async n=>{try{let i=k(Rs,n),r=await y({tool:"thread_remove_session",args:i,limiter:t,run:()=>Mt(i.thread_id,i.session_id)});return v({thread_id:i.thread_id,session_id:i.session_id,...r})}catch(i){return R(i)}}),e.registerTool("thread_rename",{title:"Rename thread",description:"Change the display name of a thread.",inputSchema:Ns},async n=>{try{let i=k(Ns,n),r=await y({tool:"thread_rename",args:i,limiter:t,run:()=>Ut(i.thread_id,i.name)});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_close",{title:"Close thread",description:"Mark the thread as closed (sets closed_at). Thread remains listed; reopen to clear.",inputSchema:ne},async n=>{try{let i=k(ne,n),r=await y({tool:"thread_close",args:i,limiter:t,run:()=>Pt(i.thread_id)});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_reopen",{title:"Reopen thread",description:"Clear closed_at on a closed thread.",inputSchema:ne},async n=>{try{let i=k(ne,n),r=await y({tool:"thread_reopen",args:i,limiter:t,run:()=>jt(i.thread_id)});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_archive",{title:"Archive thread",description:"Soft-delete a thread by setting archived=1. Archived threads are hidden from thread_list by default but never hard-deleted; data is preserved in SQLite and the JSON mirror.",inputSchema:ne},async n=>{try{let i=k(ne,n),r=await y({tool:"thread_archive",args:i,limiter:t,run:()=>$t(i.thread_id)});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_merge",{title:"Merge two threads",description:"Move every edge from source_id into dest_id, then delete source_id. Origin roles are preserved when present on either side.",inputSchema:ys},async n=>{try{let i=k(ys,n),r=await y({tool:"thread_merge",args:i,limiter:t,run:()=>Xt(i.source_id,i.dest_id)});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_split",{title:"Split sessions into a new thread",description:"Peel the specified session_ids out of thread_id into a brand-new thread named new_thread_name. Non-member session_ids are silently skipped.",inputSchema:ws},async n=>{try{let i=k(ws,n),r=await y({tool:"thread_split",args:i,limiter:t,run:()=>Ht({threadId:i.thread_id,sessionIds:i.session_ids,newThreadName:i.new_thread_name})});return v(r)}catch(i){return R(i)}}),e.registerTool("sync_active_sessions",{title:"Sync currently active sessions in a repo into a thread",description:'Captures the Captain Code Pattern: scans every Claude session whose JSONL was touched within the rolling window in the given project, infers parent/child edges via the 7-signal scorer (temporal, continuation, file overlap, brand, semantic, cluster, doc-authorship), and stitches them into one thread. Idempotent: re-running reuses the existing "Active <project> sessions \u2014 *" thread, only appends new sessions, and never overwrites edges with source=manual. Use mode=preflight first to show the user the proposed plan, then mode=apply to write.',inputSchema:As},async n=>{try{let i=k(As,n);if(i.mode==="preflight"){let a=await Qe(i.project_id,{windowHours:i.window_hours});return v({plan:a})}let r=await y({tool:"sync_active_sessions",args:i,limiter:t,run:async()=>{let a=await Qe(i.project_id,{windowHours:i.window_hours}),o=_s(a);return{plan:a,result:o}}});return v(r)}catch(i){return R(i)}}),e.registerTool("generate_thread_titles",{title:"Generate titles for every session in a thread",description:"Walk a thread DAG in topology order (origins first by added_at, then children breadth-first) and generate a coherent agent title for each session. Each successive title sees already-titled ancestors and earlier siblings as strong context so a naming pattern emerges. Set force=true to regenerate already-titled sessions; default false skips them. Returns the final {generated, skipped, failed} summary after the whole walk completes.",inputSchema:Ls},async n=>{try{let i=k(Ls,n),r=await y({tool:"generate_thread_titles",args:i,limiter:t,run:async()=>{let a=await rs(i.thread_id,{force:i.force??!1});if(a.failed.length>0&&a.generated.length===0&&a.skipped.length===0)throw new Error(`all ${a.failed.length} session(s) failed title generation`);return a}});return v(r)}catch(i){return R(i)}})}F();var Nr="BAAI/bge-base-en-v1.5",yr=768,Cs=16,wr="Represent this sentence for searching relevant passages: ",vs=null,Lr=!1;function de(){return{loaded:Lr,modelId:Nr,dim:yr}}async function Ds(e){if(!vs)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");let s=[];for(let t=0;t<e.length;t+=Cs){let n=e.slice(t,t+Cs),r=(await vs(n,{pooling:"cls",normalize:!0})).tolist();for(let a=0;a<n.length;a++){let o=r[a],c=Array.isArray(o[0])?o[0]:o;s.push(new Float32Array(c))}}return s}async function ks(e){let s=wr+e,[t]=await Ds([s]);return t}L();F();import{writeFileSync as Ar}from"node:fs";import{join as Or}from"node:path";var Ir=Or(S,"recall-events.json");function Fs(e,s,t,n="cli"){p().prepare(`
832
+ AND role = 'child'`).all(t);for(let a of r)try{ae(t,a.session_id,null)}catch{}return s}function v(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}function Rr(e){return{content:[{type:"text",text:e}],isError:!0}}function k(e,s){return M.object(e).strict().parse(s)}var Os=M.string().uuid(),W=Os.describe("Thread id (UUID)."),V=Os.describe("Session UUID (exact, not a prefix)."),fs={include_archived:M.boolean().optional().describe("Include archived threads (default false).")},hs={id:W},Es={session_id:V},Ts={name:M.string().min(1).max(200).describe("Human-readable thread name."),summary:M.string().max(4e3).optional().describe("Optional short description."),origin_session_id:V.optional().describe("Seed the thread with this session as its origin.")},Ss={thread_id:W,session_id:V,parent_session_id:V.optional().describe("If present, the edge is role=child with this parent; otherwise role=origin."),role:M.enum(["origin","child"]).optional()},bs={thread_id:W,session_id:V,parent_session_id:V.nullable().describe("New parent (null to clear and promote the edge back to role=origin).")},Rs={thread_id:W,session_id:V},Ns={thread_id:W,name:M.string().min(1).max(200)},ne={thread_id:W},ys={source_id:W.describe("Thread to dissolve \u2014 its edges move into dest_id."),dest_id:W.describe("Thread that absorbs source_id.")},ws={thread_id:W,session_ids:M.array(V).min(1).max(500),new_thread_name:M.string().min(1).max(200)},Ls={thread_id:W,force:M.boolean().optional().describe("When true, regenerate titles for sessions that already have an agent-sourced title. Default false skips them.")},As={project_id:M.number().int().positive().describe("Numeric project id from list_projects. The sync is repo-scoped and never crosses projects."),mode:M.enum(["preflight","apply"]).describe("preflight returns the proposed plan without writing; apply writes the thread + edges and is idempotent."),window_hours:M.number().min(.5).max(168).optional().describe("Rolling activity window in hours. Default 6.")};function Is(e){e.registerTool("thread_list",{title:"List threads",description:"All threads (v0.15a intent groups), newest first. Excludes archived threads unless include_archived is true.",inputSchema:fs},async s=>{try{let{include_archived:t}=k(fs,s);return v(Ye({includeArchived:t??!1}))}catch(t){return R(t)}}),e.registerTool("thread_get",{title:"Get thread with edges",description:"Full thread detail including every session edge (origin + children).",inputSchema:hs},async s=>{try{let{id:t}=k(hs,s),n=D(t);return n?v(n):Rr(`thread not found: ${t}`)}catch(t){return R(t)}}),e.registerTool("thread_for_session",{title:"List threads containing a session",description:"Return non-archived threads that reference this session (as origin or child).",inputSchema:Es},async s=>{try{let{session_id:t}=k(Es,s);return v(Ft(t))}catch(t){return R(t)}})}function xs(e,s={}){let t=s.limiter??new z;e.registerTool("thread_create",{title:"Create a thread",description:"Create a new thread. Optionally seed it with an origin session. Name is required; summary is optional.",inputSchema:Ts},async n=>{try{let i=k(Ts,n),r=await y({tool:"thread_create",args:i,limiter:t,run:()=>Se({name:i.name,summary:i.summary??null,originSessionId:i.origin_session_id})});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_add_session",{title:"Add session to thread",description:"Attach a session to a thread. If parent_session_id is provided the edge is role=child; otherwise role=origin. Upsert: re-adding updates the edge.",inputSchema:Ss},async n=>{try{let i=k(Ss,n),r=await y({tool:"thread_add_session",args:i,limiter:t,run:()=>be({threadId:i.thread_id,sessionId:i.session_id,parentSessionId:i.parent_session_id??null,role:i.role,source:"manual",confidence:1})});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_set_parent",{title:"Set edge parent within thread",description:"Change the parent session of an existing thread edge. Pass null to clear the parent and promote the edge to role=origin.",inputSchema:bs},async n=>{try{let i=k(bs,n),r=await y({tool:"thread_set_parent",args:i,limiter:t,run:()=>ae(i.thread_id,i.session_id,i.parent_session_id)});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_remove_session",{title:"Remove session from thread",description:"Detach a session from a thread. The session itself is untouched; only the edge is removed. The updated thread is re-mirrored to disk.",inputSchema:Rs},async n=>{try{let i=k(Rs,n),r=await y({tool:"thread_remove_session",args:i,limiter:t,run:()=>Mt(i.thread_id,i.session_id)});return v({thread_id:i.thread_id,session_id:i.session_id,...r})}catch(i){return R(i)}}),e.registerTool("thread_rename",{title:"Rename thread",description:"Change the display name of a thread.",inputSchema:Ns},async n=>{try{let i=k(Ns,n),r=await y({tool:"thread_rename",args:i,limiter:t,run:()=>Ut(i.thread_id,i.name)});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_close",{title:"Close thread",description:"Mark the thread as closed (sets closed_at). Thread remains listed; reopen to clear.",inputSchema:ne},async n=>{try{let i=k(ne,n),r=await y({tool:"thread_close",args:i,limiter:t,run:()=>Pt(i.thread_id)});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_reopen",{title:"Reopen thread",description:"Clear closed_at on a closed thread.",inputSchema:ne},async n=>{try{let i=k(ne,n),r=await y({tool:"thread_reopen",args:i,limiter:t,run:()=>jt(i.thread_id)});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_archive",{title:"Archive thread",description:"Soft-delete a thread by setting archived=1. Archived threads are hidden from thread_list by default but never hard-deleted; data is preserved in SQLite and the JSON mirror.",inputSchema:ne},async n=>{try{let i=k(ne,n),r=await y({tool:"thread_archive",args:i,limiter:t,run:()=>$t(i.thread_id)});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_merge",{title:"Merge two threads",description:"Move every edge from source_id into dest_id, then delete source_id. Origin roles are preserved when present on either side.",inputSchema:ys},async n=>{try{let i=k(ys,n),r=await y({tool:"thread_merge",args:i,limiter:t,run:()=>Xt(i.source_id,i.dest_id)});return v(r)}catch(i){return R(i)}}),e.registerTool("thread_split",{title:"Split sessions into a new thread",description:"Peel the specified session_ids out of thread_id into a brand-new thread named new_thread_name. Non-member session_ids are silently skipped.",inputSchema:ws},async n=>{try{let i=k(ws,n),r=await y({tool:"thread_split",args:i,limiter:t,run:()=>Ht({threadId:i.thread_id,sessionIds:i.session_ids,newThreadName:i.new_thread_name})});return v(r)}catch(i){return R(i)}}),e.registerTool("sync_active_sessions",{title:"Sync currently active sessions in a repo into a thread",description:'Captures the Captain Code Pattern: scans every Claude session whose JSONL was touched within the rolling window in the given project, infers parent/child edges via the 7-signal scorer (temporal, continuation, file overlap, brand, semantic, cluster, doc-authorship), and stitches them into one thread. Idempotent: re-running reuses the existing "Active <project> sessions \u2014 *" thread, only appends new sessions, and never overwrites edges with source=manual. Use mode=preflight first to show the user the proposed plan, then mode=apply to write.',inputSchema:As},async n=>{try{let i=k(As,n);if(i.mode==="preflight"){let a=await Qe(i.project_id,{windowHours:i.window_hours});return v({plan:a})}let r=await y({tool:"sync_active_sessions",args:i,limiter:t,run:async()=>{let a=await Qe(i.project_id,{windowHours:i.window_hours}),o=_s(a);return{plan:a,result:o}}});return v(r)}catch(i){return R(i)}}),e.registerTool("generate_thread_titles",{title:"Generate titles for every session in a thread",description:"Walk a thread DAG in topology order (origins first by added_at, then children breadth-first) and generate a coherent agent title for each session. Each successive title sees already-titled ancestors and earlier siblings as strong context so a naming pattern emerges. Set force=true to regenerate already-titled sessions; default false skips them. Returns the final {generated, skipped, failed} summary after the whole walk completes.",inputSchema:Ls},async n=>{try{let i=k(Ls,n),r=await y({tool:"generate_thread_titles",args:i,limiter:t,run:async()=>{let a=await rs(i.thread_id,{force:i.force??!1});if(a.failed.length>0&&a.generated.length===0&&a.skipped.length===0)throw new Error(`all ${a.failed.length} session(s) failed title generation`);return a}});return v(r)}catch(i){return R(i)}})}F();var Nr="BAAI/bge-base-en-v1.5",yr=768,Cs=16,wr="Represent this sentence for searching relevant passages: ",vs=null,Lr=!1;function de(){return{loaded:Lr,modelId:Nr,dim:yr}}async function Ds(e){if(!vs)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");let s=[];for(let t=0;t<e.length;t+=Cs){let n=e.slice(t,t+Cs),r=(await vs(n,{pooling:"cls",normalize:!0})).tolist();for(let a=0;a<n.length;a++){let o=r[a],c=Array.isArray(o[0])?o[0]:o;s.push(new Float32Array(c))}}return s}async function ks(e){let s=wr+e,[t]=await Ds([s]);return t}L();F();import{writeFileSync as Ar}from"node:fs";import{join as Or}from"node:path";var Ir=Or(S,"recall-events.json");function Fs(e,s,t,n="cli"){p().prepare(`
833
833
  INSERT INTO recall_events (session_id, recalled_at, token_count, mode, caller)
834
834
  VALUES (?, datetime('now'), ?, ?, ?)
835
835
  `).run(e,s,t,n),xr()}function xr(){x();let s=p().prepare("SELECT id, session_id, recalled_at, token_count, mode, caller FROM recall_events ORDER BY recalled_at DESC").all();Ar(Ir,JSON.stringify(s,null,2)+`
836
836
  `,"utf-8")}L();async function Ms(e,s=50){let t=await ks(e),n=p(),i=Buffer.from(t.buffer,t.byteOffset,t.byteLength);return n.prepare(`SELECT v.rowid, v.distance, cm.session_id, cm.text, cm.message_uuids
837
837
  FROM vec_chunks v JOIN chunk_meta cm ON cm.rowid = v.rowid
838
838
  WHERE v.embedding MATCH ? AND k = ? ORDER BY v.distance`).all(i,s).map(a=>({sessionId:a.session_id,chunkRowid:a.rowid,distance:a.distance,text:a.text,messageUuids:JSON.parse(a.message_uuids)}))}async function Us(e,s=10,t=.65){let n=p(),i=n.prepare("SELECT rowid FROM chunk_meta WHERE session_id = ? ORDER BY rowid LIMIT 1").get(e);if(!i)return[];let r=n.prepare("SELECT embedding FROM vec_chunks WHERE rowid = ?").get(i.rowid);if(!r)return[];let a=n.prepare(`SELECT v.rowid, v.distance, cm.session_id FROM vec_chunks v JOIN chunk_meta cm ON cm.rowid = v.rowid
839
- WHERE v.embedding MATCH ? AND k = ? ORDER BY v.distance`).all(r.embedding,s*5),o=new Map;for(let l of a){if(l.session_id===e)continue;let d=o.get(l.session_id);(d===void 0||l.distance<d)&&o.set(l.session_id,l.distance)}let c=[];for(let[l,d]of o){let u=1-d;u>=t&&c.push({sessionId:l,similarity:u})}return c.sort((l,d)=>d.similarity-l.similarity),c.slice(0,s)}function Cr(){let e=process.env.RECALL_RRF_K;if(e){let s=parseInt(e,10);if(!isNaN(s)&&s>=1&&s<=1e3)return s}return 60}function Ps(e){let s=Cr(),t=new Map;for(let i of e)for(let r=0;r<i.length;r++){let a=i[r],o=1/(s+r+1),c=t.get(a.id);c?(c.score+=o,c.lanes.push(a.lane),r+1<c.bestRank&&(c.bestRank=r+1,c.bestData=a.data)):t.set(a.id,{score:o,lanes:[a.lane],bestRank:r+1,bestData:a.data})}let n=[];for(let[i,r]of t)n.push({id:i,score:r.score,lanes:r.lanes,data:r.bestData});return n.sort((i,r)=>r.score-i.score),n}L();L();var vr=!1,Dr=null;function kr(){return p().prepare("SELECT COUNT(*) AS n FROM chunk_queue").get().n}function js(){return{running:vr,queueDepth:kr(),lastProcessedAt:Dr}}import{existsSync as Fr,mkdirSync as Zc,rmSync as el,createWriteStream as tl,statSync as sl}from"node:fs";import{join as $s}from"node:path";F();var Mr=[{path:"config.json",sha256:"bc00af31a4a31b74040d73370aa83b62da34c90b75eb77bfa7db039d90abd591"},{path:"tokenizer.json",sha256:"d241a60d5e8f04cc1b2b3e9ef7a4921b27bf526d9f6050ab90f9267a1f9e5c66"},{path:"tokenizer_config.json",sha256:"9261e7d79b44c8195c1cada2b453e55b00aeb81e907a6664974b4d7776172ab3"},{path:"onnx/model.onnx",sha256:"9bc579acdba21c253c62a9bf866891355a63ffa3442b52c8a37d75b2ccb91848"}];function Ur(){return $s(S,"models","BAAI","bge-base-en-v1.5")}function Xs(){let e=Ur();return Mr.every(s=>Fr($s(e,s.path)))}L();L();F();import{join as Ze}from"node:path";var Pr=new Set(["pending","approved","rejected"]),jr=new Set(["L1","L2","L3","L4","user"]),ll=Ze(S,"links"),$r=Ze(S,"suggestions"),dl=Ze($r,"index.json");function Hs(e){try{return JSON.parse(e)}catch{return e}}function Xr(e){return{id:e.id,source_session_id:e.source_session_id,target_session_id:e.target_session_id,link_type:e.link_type,confidence:e.confidence,source:e.source,evidence:Hs(e.evidence),approved:e.approved===1,created_at:e.created_at,updated_at:e.updated_at}}function Hr(e){return{id:e.id,source_session_id:e.source_session_id,target_session_id:e.target_session_id,link_type:e.link_type,confidence:e.confidence,evidence:Hs(e.evidence),status:e.status,inferred_by:e.inferred_by,created_at:e.created_at,decided_at:e.decided_at}}function Br(e){if(!jr.has(e))throw new Error(`invalid inferred_by: ${e}`)}function et(e){return p().prepare(`SELECT * FROM session_links
839
+ WHERE v.embedding MATCH ? AND k = ? ORDER BY v.distance`).all(r.embedding,s*5),o=new Map;for(let l of a){if(l.session_id===e)continue;let d=o.get(l.session_id);(d===void 0||l.distance<d)&&o.set(l.session_id,l.distance)}let c=[];for(let[l,d]of o){let u=1-d;u>=t&&c.push({sessionId:l,similarity:u})}return c.sort((l,d)=>d.similarity-l.similarity),c.slice(0,s)}function Cr(){let e=process.env.RECALL_RRF_K;if(e){let s=parseInt(e,10);if(!isNaN(s)&&s>=1&&s<=1e3)return s}return 60}function Ps(e){let s=Cr(),t=new Map;for(let i of e)for(let r=0;r<i.length;r++){let a=i[r],o=1/(s+r+1),c=t.get(a.id);c?(c.score+=o,c.lanes.push(a.lane),r+1<c.bestRank&&(c.bestRank=r+1,c.bestData=a.data)):t.set(a.id,{score:o,lanes:[a.lane],bestRank:r+1,bestData:a.data})}let n=[];for(let[i,r]of t)n.push({id:i,score:r.score,lanes:r.lanes,data:r.bestData});return n.sort((i,r)=>r.score-i.score),n}L();L();var vr=!1,Dr=null;function kr(){return p().prepare("SELECT COUNT(*) AS n FROM chunk_queue").get().n}function js(){return{running:vr,queueDepth:kr(),lastProcessedAt:Dr}}import{existsSync as Fr,mkdirSync as il,rmSync as rl,createWriteStream as ol,statSync as al}from"node:fs";import{join as $s}from"node:path";F();var Mr=[{path:"config.json",sha256:"bc00af31a4a31b74040d73370aa83b62da34c90b75eb77bfa7db039d90abd591"},{path:"tokenizer.json",sha256:"d241a60d5e8f04cc1b2b3e9ef7a4921b27bf526d9f6050ab90f9267a1f9e5c66"},{path:"tokenizer_config.json",sha256:"9261e7d79b44c8195c1cada2b453e55b00aeb81e907a6664974b4d7776172ab3"},{path:"onnx/model.onnx",sha256:"9bc579acdba21c253c62a9bf866891355a63ffa3442b52c8a37d75b2ccb91848"}];function Ur(){return $s(S,"models","BAAI","bge-base-en-v1.5")}function Xs(){let e=Ur();return Mr.every(s=>Fr($s(e,s.path)))}L();L();F();import{join as Ze}from"node:path";var Pr=new Set(["pending","approved","rejected"]),jr=new Set(["L1","L2","L3","L4","user"]),gl=Ze(S,"links"),$r=Ze(S,"suggestions"),_l=Ze($r,"index.json");function Hs(e){try{return JSON.parse(e)}catch{return e}}function Xr(e){return{id:e.id,source_session_id:e.source_session_id,target_session_id:e.target_session_id,link_type:e.link_type,confidence:e.confidence,source:e.source,evidence:Hs(e.evidence),approved:e.approved===1,created_at:e.created_at,updated_at:e.updated_at}}function Hr(e){return{id:e.id,source_session_id:e.source_session_id,target_session_id:e.target_session_id,link_type:e.link_type,confidence:e.confidence,evidence:Hs(e.evidence),status:e.status,inferred_by:e.inferred_by,created_at:e.created_at,decided_at:e.decided_at}}function Br(e){if(!jr.has(e))throw new Error(`invalid inferred_by: ${e}`)}function et(e){return p().prepare(`SELECT * FROM session_links
840
840
  WHERE source_session_id = ? OR target_session_id = ?
841
841
  ORDER BY confidence DESC, updated_at DESC`).all(e,e).map(Xr)}function tt(e={}){let s=p(),t=[],n=[];if(e.status){if(!Pr.has(e.status))throw new Error(`invalid status: ${e.status}`);t.push("status = ?"),n.push(e.status)}e.sourceSessionId&&(t.push("source_session_id = ?"),n.push(e.sourceSessionId)),e.targetSessionId&&(t.push("target_session_id = ?"),n.push(e.targetSessionId)),e.inferredBy&&(Br(e.inferredBy),t.push("inferred_by = ?"),n.push(e.inferredBy));let i=t.length?`WHERE ${t.join(" AND ")}`:"",r=Math.max(1,Math.min(5e3,e.limit??1e3));return s.prepare(`SELECT * FROM session_link_suggestions ${i}
842
842
  ORDER BY confidence DESC, created_at DESC
843
- LIMIT ?`).all(...n,r).map(Hr)}var Wr=4e3,Yr=2,Gr=30,zr=.2,Kr={citation:1,wiki_link:.9,similar:.7,skill_track:.5,bug_pattern:.4,temporal_proximity:.3};function we(e){return e?Math.ceil(e.length/4):0}function Bs(e){if(!Number.isFinite(e)||e<0)return 1;let s=Math.exp(-e/Gr);return Math.max(zr,s)}function Ws(e,s){if(!e||!s)return 0;let t=Date.parse(e),n=Date.parse(s);return!Number.isFinite(t)||!Number.isFinite(n)?0:Math.abs(n-t)/(1e3*60*60*24)}function Ys(e){return p().prepare(`SELECT s.id,
843
+ LIMIT ?`).all(...n,r).map(Hr)}var Wr=4e3,Yr=2,Gr=30,Kr=.2,zr={citation:1,wiki_link:.9,similar:.7,skill_track:.5,bug_pattern:.4,temporal_proximity:.3};function we(e){return e?Math.ceil(e.length/4):0}function Bs(e){if(!Number.isFinite(e)||e<0)return 1;let s=Math.exp(-e/Gr);return Math.max(Kr,s)}function Ws(e,s){if(!e||!s)return 0;let t=Date.parse(e),n=Date.parse(s);return!Number.isFinite(t)||!Number.isFinite(n)?0:Math.abs(n-t)/(1e3*60*60*24)}function Ys(e){return p().prepare(`SELECT s.id,
844
844
  NULLIF(sa.alias, '') AS alias,
845
845
  s.auto_title,
846
846
  s.auto_title_source,
@@ -851,18 +851,18 @@ ${t}`}function Gi(e){return Vt(e)?.auto_title_source??null}async function zi(e){
851
851
  FROM sessions s
852
852
  LEFT JOIN session_aliases sa ON sa.session_id = s.id
853
853
  LEFT JOIN projects p ON p.id = s.project_id
854
- WHERE s.id = ?`).get(e)??null}function Gs(e){let t=p().prepare("SELECT summary FROM session_semantic WHERE session_id = ?").get(e);if(!t||!t.summary)return null;let n=t.summary.trim();return n.length>0?n:null}function zs(e){let s=e.alias?.trim(),t=e.auto_title?.trim(),n=e.first_user_message?.trim();return t&&e.auto_title_source==="agent"?t:s||t||(n?n.slice(0,80):e.id.slice(0,8))}function Jr(e){let t=p().prepare(`SELECT id, auto_title, started_at
854
+ WHERE s.id = ?`).get(e)??null}function Gs(e){let t=p().prepare("SELECT summary FROM session_semantic WHERE session_id = ?").get(e);if(!t||!t.summary)return null;let n=t.summary.trim();return n.length>0?n:null}function Ks(e){let s=e.alias?.trim(),t=e.auto_title?.trim(),n=e.first_user_message?.trim();return t&&e.auto_title_source==="agent"?t:s||t||(n?n.slice(0,80):e.id.slice(0,8))}function Jr(e){let t=p().prepare(`SELECT id, auto_title, started_at
855
855
  FROM sessions
856
856
  WHERE project_id = ?
857
- ORDER BY COALESCE(started_at, ''), id`).all(e),n=new Set,i=new Set,r=[];for(let m of t){if(!m.auto_title||!m.auto_title.startsWith("/")){r.push({id:m.id,brand:null,skill:null});continue}let g=m.auto_title.split(" \xB7 "),_=g[0].trim(),f=g.length>1?g.slice(1).join(" \xB7 ").trim():null;r.push({id:m.id,brand:f||null,skill:_||null}),f&&n.add(f),_&&i.add(_)}let a=[...n].sort(),o=new Map;a.forEach((m,g)=>o.set(m,g));let c=[...i].sort(),l=new Map;c.forEach((m,g)=>l.set(m,g));let d=new Map,u=new Map;for(let m of r){if(!m.brand||!m.skill)continue;let g=o.get(m.brand),_=l.get(m.skill);if(g===void 0||_===void 0)continue;let f=`${g}.${_}`,h=(d.get(f)??0)+1;d.set(f,h),u.set(m.id,`${g}.${_}.${h}`)}return{byId:u}}function Vr(e){return{table:e!==null?Jr(e):null,originProjectId:e,cache:new Map}}function Le(e,s){let t=e.cache.get(s);if(t)return t;let n=Ys(s);if(!n)return null;let i=e.table&&n.project_id===e.originProjectId?e.table.byId.get(s)??null:null,r={session_id:n.id,title:zs(n),decimal:i,summary:Gs(n.id),project:n.project,started_at:n.started_at};return e.cache.set(s,r),r}function qr(e,s){let n=p().prepare(`SELECT DISTINCT te.parent_session_id AS pid
857
+ ORDER BY COALESCE(started_at, ''), id`).all(e),n=new Set,i=new Set,r=[];for(let m of t){if(!m.auto_title||!m.auto_title.startsWith("/")){r.push({id:m.id,brand:null,skill:null});continue}let g=m.auto_title.split(" \xB7 "),_=g[0].trim(),f=g.length>1?g.slice(1).join(" \xB7 ").trim():null;r.push({id:m.id,brand:f||null,skill:_||null}),f&&n.add(f),_&&i.add(_)}let a=[...n].sort(),o=new Map;a.forEach((m,g)=>o.set(m,g));let c=[...i].sort(),l=new Map;c.forEach((m,g)=>l.set(m,g));let d=new Map,u=new Map;for(let m of r){if(!m.brand||!m.skill)continue;let g=o.get(m.brand),_=l.get(m.skill);if(g===void 0||_===void 0)continue;let f=`${g}.${_}`,h=(d.get(f)??0)+1;d.set(f,h),u.set(m.id,`${g}.${_}.${h}`)}return{byId:u}}function Vr(e){return{table:e!==null?Jr(e):null,originProjectId:e,cache:new Map}}function Le(e,s){let t=e.cache.get(s);if(t)return t;let n=Ys(s);if(!n)return null;let i=e.table&&n.project_id===e.originProjectId?e.table.byId.get(s)??null:null,r={session_id:n.id,title:Ks(n),decimal:i,summary:Gs(n.id),project:n.project,started_at:n.started_at};return e.cache.set(s,r),r}function qr(e,s){let n=p().prepare(`SELECT DISTINCT te.parent_session_id AS pid
858
858
  FROM thread_edges te
859
859
  WHERE te.session_id = ?
860
860
  AND te.parent_session_id IS NOT NULL`).all(s),i=[];for(let r of n){if(!r.pid)continue;let a=Le(e,r.pid);a&&i.push(a)}return i}function Qr(e,s){let n=p().prepare(`SELECT DISTINCT te.session_id AS sid
861
861
  FROM thread_edges te
862
- WHERE te.parent_session_id = ?`).all(s),i=[];for(let r of n){if(!r.sid)continue;let a=Le(e,r.sid);a&&i.push(a)}return i}function Ks(e){let s=Kr[e.linkType]??.5,t=ie(e.confidence),n=s*t,i=Bs(e.daysApart),r=e.embeddingCosine??.5,a=ie(e.pagerank);if(e.scoring==="pagerank")return ie(a);if(e.scoring==="embedding-rerank")return e.embeddingCosine===null?ie(n):ie(r);let o=.35*n+.2*i+.2*r+.25*a;return ie(o)}function ie(e){return!Number.isFinite(e)||e<0?0:e>1?1:e}function Zr(e,s,t,n,i){let r=new Map;function a(o,c){if(o===c)return;let l=r.get(o);l||(l=new Set,r.set(o,l)),l.add(c)}for(let o of s)a(o.source_session_id,o.target_session_id),a(o.target_session_id,o.source_session_id);for(let o of t)a(e,o.session_id);for(let o of t)a(o.session_id,e);for(let o of n)a(e,o.session_id);for(let o of n)a(o.session_id,e);if(i>1){let o=new Set([e]),c=new Set([e]);for(let l=1;l<i;l++){let d=new Set;for(let u of o){let m=r.get(u);if(m)for(let g of m){if(c.has(g))continue;let _=et(g).filter(f=>f.approved);for(let f of _)a(f.source_session_id,f.target_session_id),a(f.target_session_id,f.source_session_id);c.add(g),d.add(g)}}if(d.size===0)break;for(let u of d)o.add(u)}}return{edges:r}}function eo(e,s={}){let t=s.iterations??12,n=s.damping??.85,i=Array.from(e.edges.keys());if(i.length===0)return new Map;let r=1/i.length,a=new Map(i.map(l=>[l,r]));for(let l=0;l<t;l++){let d=new Map(i.map(u=>[u,(1-n)/i.length]));for(let u of i){let m=e.edges.get(u);if(!m||m.size===0)continue;let g=(a.get(u)??0)/m.size;for(let _ of m)d.set(_,(d.get(_)??0)+n*g)}a=d}let o=0;for(let l of a.values())l>o&&(o=l);if(o<=0)return a;let c=new Map;for(let[l,d]of a)c.set(l,d/o);return c}var Js=240;function Vs(e,s){let t=e.replace(/\s+/g," ").trim();return t.length<=s?t:`${t.slice(0,s-1).trimEnd()}\u2026`}function to(e){let s=e.decimal?`${e.decimal} `:"",t=e.session_id.slice(0,8),n="evidence"in e&&e.evidence?` \u2014 ${e.evidence}`:"",i=`- ${s}${e.title} (${t})${n}`;if(e.summary){let r=Vs(e.summary,Js);return`${i}
862
+ WHERE te.parent_session_id = ?`).all(s),i=[];for(let r of n){if(!r.sid)continue;let a=Le(e,r.sid);a&&i.push(a)}return i}function zs(e){let s=zr[e.linkType]??.5,t=ie(e.confidence),n=s*t,i=Bs(e.daysApart),r=e.embeddingCosine??.5,a=ie(e.pagerank);if(e.scoring==="pagerank")return ie(a);if(e.scoring==="embedding-rerank")return e.embeddingCosine===null?ie(n):ie(r);let o=.35*n+.2*i+.2*r+.25*a;return ie(o)}function ie(e){return!Number.isFinite(e)||e<0?0:e>1?1:e}function Zr(e,s,t,n,i){let r=new Map;function a(o,c){if(o===c)return;let l=r.get(o);l||(l=new Set,r.set(o,l)),l.add(c)}for(let o of s)a(o.source_session_id,o.target_session_id),a(o.target_session_id,o.source_session_id);for(let o of t)a(e,o.session_id);for(let o of t)a(o.session_id,e);for(let o of n)a(e,o.session_id);for(let o of n)a(o.session_id,e);if(i>1){let o=new Set([e]),c=new Set([e]);for(let l=1;l<i;l++){let d=new Set;for(let u of o){let m=r.get(u);if(m)for(let g of m){if(c.has(g))continue;let _=et(g).filter(f=>f.approved);for(let f of _)a(f.source_session_id,f.target_session_id),a(f.target_session_id,f.source_session_id);c.add(g),d.add(g)}}if(d.size===0)break;for(let u of d)o.add(u)}}return{edges:r}}function eo(e,s={}){let t=s.iterations??12,n=s.damping??.85,i=Array.from(e.edges.keys());if(i.length===0)return new Map;let r=1/i.length,a=new Map(i.map(l=>[l,r]));for(let l=0;l<t;l++){let d=new Map(i.map(u=>[u,(1-n)/i.length]));for(let u of i){let m=e.edges.get(u);if(!m||m.size===0)continue;let g=(a.get(u)??0)/m.size;for(let _ of m)d.set(_,(d.get(_)??0)+n*g)}a=d}let o=0;for(let l of a.values())l>o&&(o=l);if(o<=0)return a;let c=new Map;for(let[l,d]of a)c.set(l,d/o);return c}var Js=240;function Vs(e,s){let t=e.replace(/\s+/g," ").trim();return t.length<=s?t:`${t.slice(0,s-1).trimEnd()}\u2026`}function to(e){let s=e.decimal?`${e.decimal} `:"",t=e.session_id.slice(0,8),n="evidence"in e&&e.evidence?` \u2014 ${e.evidence}`:"",i=`- ${s}${e.title} (${t})${n}`;if(e.summary){let r=Vs(e.summary,Js);return`${i}
863
863
  ${r}`}return i}function so(e,s,t){let n=[],i=[],r=0,a=e.decimal?`${e.decimal}: `:"",o=`# Neighborhood for ${e.session_id} (${a}${e.title})`;if(n.push(o),r+=we(o),e.summary){let c=Vs(e.summary,Js*4);n.push(c),r+=we(c)}n.push("");for(let c of s){if(c.refs.length===0)continue;let l=`## ${c.heading}`,d=we(l),u=[],m=0;for(let g of c.refs){let _=to(g),f=we(_);if(r+d+m+f>t){i.push({session_id:g.session_id,title:g.title,decimal:g.decimal,summary:g.summary,project:g.project,started_at:g.started_at});continue}u.push(_),m+=f}if(u.length>0){n.push(l);for(let g of u)n.push(g);n.push(""),r+=d+m}}for(;n.length>0&&n[n.length-1]==="";)n.pop();return{bundle:n.join(`
864
864
  `)+`
865
- `,budgetUsed:r,truncated:i}}function no(e,s,t,n,i,r){let a=[];for(let o of t){if(n&&!n.has(o.link_type))continue;let c=null;if(o.source_session_id===s.session_id?c=o.target_session_id:o.target_session_id===s.session_id&&(c=o.source_session_id),!c)continue;let l=Le(e,c);if(!l)continue;let d=Ws(s.started_at,l.started_at),u=Ks({confidence:o.confidence,linkType:o.link_type,daysApart:d,embeddingCosine:null,pagerank:r.get(c)??0,scoring:i});a.push({...l,score:u,evidence:`(suggestion, ${o.inferred_by}) confidence=${o.confidence.toFixed(2)} ${Math.round(d)}d apart`,link_type:o.link_type})}return a}function qs(e,s={}){let t=Math.max(100,Math.floor(s.budget??Wr)),n=s.scoring??"hybrid",i=Math.max(1,Math.min(5,s.maxDepth??Yr)),r=s.includeWikiLinks??!0,a=s.includeSuggestions??!1,o=s.edgeTypes?new Set(s.edgeTypes):null,c=Ys(e);if(!c)throw new Error(`session not found: ${e}`);let l=Vr(c.project_id),d={session_id:c.id,title:zs(c),decimal:l.table?.byId.get(c.id)??null,summary:Gs(c.id),project:c.project,started_at:c.started_at};l.cache.set(c.id,d);let u=qr(l,e),m=Qr(l,e),g=et(e).filter(A=>A.approved).filter(A=>!o||o.has(A.link_type)).filter(A=>r||A.link_type!=="wiki_link"),_=Zr(e,g,u,m,i),f=eo(_),h=[],N=[],w=[],U=[];for(let A of g){let ee=A.source_session_id===e?A.target_session_id:A.source_session_id,re=Le(l,ee);if(!re)continue;let te=Ws(d.started_at,re.started_at),Ae=Ks({confidence:A.confidence,linkType:A.link_type,daysApart:te,embeddingCosine:null,pagerank:f.get(ee)??0,scoring:n}),Oe=Bs(te),P=`${A.link_type} confidence=${A.confidence.toFixed(2)} recency=${Oe.toFixed(2)} (${Math.round(te)}d apart)`,me={...re,score:Ae,evidence:P,link_type:A.link_type};A.link_type==="citation"?h.push(me):A.link_type==="similar"?N.push(me):A.link_type==="wiki_link"?U.push(me):w.push(me)}if(a){let A=tt({sourceSessionId:e,status:"pending",limit:100}),ee=tt({targetSessionId:e,status:"pending",limit:100}),re=[...A,...ee],te=new Set,Ae=re.filter(P=>te.has(P.id)?!1:(te.add(P.id),!0)),Oe=no(l,d,Ae,o,n,f);for(let P of Oe)P.link_type==="citation"?h.push(P):P.link_type==="similar"?N.push(P):P.link_type==="wiki_link"?U.push(P):w.push(P)}let b=(A,ee)=>ee.score-A.score;h.sort(b),N.sort(b),w.sort(b),U.sort(b);let pe=so(d,[{heading:"Parents",refs:u},{heading:"Children",refs:m},{heading:"Citations (approved)",refs:h},{heading:"Similar sessions",refs:N},{heading:"Cousins (skill track + temporal)",refs:w},{heading:"Wiki links (manual)",refs:U}],t);return{origin:d,parents:u,children:m,citations:h,similar:N,cousins:w,wikiLinks:U,bundle:pe.bundle,budgetUsed:pe.budgetUsed,budgetRemaining:Math.max(0,t-pe.budgetUsed),truncated:pe.truncated}}var Ro="0.12.0";function No(){let e=process.env.RECALL_MCP_ALLOW_WRITES;return e==="1"||e==="true"}function C(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}function it(e){return{content:[{type:"text",text:e}]}}function X(e){return{content:[{type:"text",text:e}],isError:!0}}function yo(){let e=new So({name:"claude-recall",version:Ro}),s=No();if(e.registerTool("list_projects",{title:"List projects",description:"Every Claude Code project currently indexed by Recall, with session and message counts and the most recent activity timestamp.",inputSchema:{}},async()=>{let n=p().prepare(`SELECT p.name,
865
+ `,budgetUsed:r,truncated:i}}function no(e,s,t,n,i,r){let a=[];for(let o of t){if(n&&!n.has(o.link_type))continue;let c=null;if(o.source_session_id===s.session_id?c=o.target_session_id:o.target_session_id===s.session_id&&(c=o.source_session_id),!c)continue;let l=Le(e,c);if(!l)continue;let d=Ws(s.started_at,l.started_at),u=zs({confidence:o.confidence,linkType:o.link_type,daysApart:d,embeddingCosine:null,pagerank:r.get(c)??0,scoring:i});a.push({...l,score:u,evidence:`(suggestion, ${o.inferred_by}) confidence=${o.confidence.toFixed(2)} ${Math.round(d)}d apart`,link_type:o.link_type})}return a}function qs(e,s={}){let t=Math.max(100,Math.floor(s.budget??Wr)),n=s.scoring??"hybrid",i=Math.max(1,Math.min(5,s.maxDepth??Yr)),r=s.includeWikiLinks??!0,a=s.includeSuggestions??!1,o=s.edgeTypes?new Set(s.edgeTypes):null,c=Ys(e);if(!c)throw new Error(`session not found: ${e}`);let l=Vr(c.project_id),d={session_id:c.id,title:Ks(c),decimal:l.table?.byId.get(c.id)??null,summary:Gs(c.id),project:c.project,started_at:c.started_at};l.cache.set(c.id,d);let u=qr(l,e),m=Qr(l,e),g=et(e).filter(A=>A.approved).filter(A=>!o||o.has(A.link_type)).filter(A=>r||A.link_type!=="wiki_link"),_=Zr(e,g,u,m,i),f=eo(_),h=[],N=[],w=[],U=[];for(let A of g){let ee=A.source_session_id===e?A.target_session_id:A.source_session_id,re=Le(l,ee);if(!re)continue;let te=Ws(d.started_at,re.started_at),Ae=zs({confidence:A.confidence,linkType:A.link_type,daysApart:te,embeddingCosine:null,pagerank:f.get(ee)??0,scoring:n}),Oe=Bs(te),P=`${A.link_type} confidence=${A.confidence.toFixed(2)} recency=${Oe.toFixed(2)} (${Math.round(te)}d apart)`,me={...re,score:Ae,evidence:P,link_type:A.link_type};A.link_type==="citation"?h.push(me):A.link_type==="similar"?N.push(me):A.link_type==="wiki_link"?U.push(me):w.push(me)}if(a){let A=tt({sourceSessionId:e,status:"pending",limit:100}),ee=tt({targetSessionId:e,status:"pending",limit:100}),re=[...A,...ee],te=new Set,Ae=re.filter(P=>te.has(P.id)?!1:(te.add(P.id),!0)),Oe=no(l,d,Ae,o,n,f);for(let P of Oe)P.link_type==="citation"?h.push(P):P.link_type==="similar"?N.push(P):P.link_type==="wiki_link"?U.push(P):w.push(P)}let b=(A,ee)=>ee.score-A.score;h.sort(b),N.sort(b),w.sort(b),U.sort(b);let pe=so(d,[{heading:"Parents",refs:u},{heading:"Children",refs:m},{heading:"Citations (approved)",refs:h},{heading:"Similar sessions",refs:N},{heading:"Cousins (skill track + temporal)",refs:w},{heading:"Wiki links (manual)",refs:U}],t);return{origin:d,parents:u,children:m,citations:h,similar:N,cousins:w,wikiLinks:U,bundle:pe.bundle,budgetUsed:pe.budgetUsed,budgetRemaining:Math.max(0,t-pe.budgetUsed),truncated:pe.truncated}}import{readFileSync as No}from"node:fs";import{dirname as yo,join as wo}from"node:path";var Lo=(()=>{try{let e=yo(Ro(import.meta.url));return wo(e,"..","..","package.json")}catch{return"package.json"}})(),Ao=(()=>{try{return JSON.parse(No(Lo,"utf8")).version??"0.0.0"}catch{return"0.0.0"}})();function Oo(){let e=process.env.RECALL_MCP_ALLOW_WRITES;return e==="1"||e==="true"}function C(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}function it(e){return{content:[{type:"text",text:e}]}}function X(e){return{content:[{type:"text",text:e}],isError:!0}}function Io(){let e=new So({name:"claude-recall",version:Ao}),s=Oo();if(e.registerTool("list_projects",{title:"List projects",description:"Every Claude Code project currently indexed by Recall, with session and message counts and the most recent activity timestamp.",inputSchema:{}},async()=>{let n=p().prepare(`SELECT p.name,
866
866
  COUNT(s.id) AS session_count,
867
867
  COALESCE(SUM(s.message_count), 0) AS message_count,
868
868
  MAX(s.started_at) AS latest
@@ -927,4 +927,4 @@ ${t}`}function Gi(e){return Vt(e)?.auto_title_source??null}async function zi(e){
927
927
  FROM sessions s JOIN projects p ON p.id = s.project_id
928
928
  WHERE s.id = ?`).get(o);if(!l)return X(`session metadata missing for ${o}`);let d=c.prepare(`SELECT uuid, type, role, timestamp, is_sidechain, content_text, tool_names
929
929
  FROM messages WHERE session_id = ?
930
- ORDER BY COALESCE(timestamp, ''), rowid`).all(o),u=ut(l,d,{mode:n??"condensed",includeSidechain:i??!1,prelude:r??null,since:a??null}),m=a?"since":n??"condensed";return Fs(o,Math.ceil(u.length/4),m,"mcp"),it(u)}),e.registerTool("recall_neighborhood",{title:"Recall: neighborhood context bundle",description:"Build a ranked, budget-bounded markdown bundle for a session: parents + children from thread_edges, plus approved citations / similar / cousins / wiki_links from the cognitive graph. Pipe-friendly output ready to seed a fresh conversation. Reads only approved edges by default \u2014 pending suggestions are opt-in.",inputSchema:{session_id:T.string().describe("Session UUID or 8+-character prefix."),budget:T.number().int().min(100).max(5e4).optional().describe("Token budget for the assembled bundle. Default 4000."),scoring:T.enum(["pagerank","embedding-rerank","hybrid"]).optional().describe("Scoring mode (default hybrid)."),edge_types:T.array(T.enum(["citation","similar","skill_track","bug_pattern","wiki_link","temporal_proximity"])).optional().describe("Restrict to certain link types. Default: all approved types."),max_depth:T.number().int().min(1).max(5).optional().describe("Pagerank traversal depth on the local subgraph (default 2)."),include_wiki_links:T.boolean().optional().describe("Include manual wiki_link rows. Default true."),include_suggestions:T.boolean().optional().describe("Surface pending session_link_suggestions. Debug only; default false."),format:T.enum(["markdown","json"]).optional().describe("markdown (default) returns the bundle as text; json returns the full NeighborhoodResult.")}},async({session_id:t,budget:n,scoring:i,edge_types:r,max_depth:a,include_wiki_links:o,include_suggestions:c,format:l})=>{let d=$(t);if(!d)return X(`session not found or prefix ambiguous: ${t}`);try{let u=qs(d,{budget:n,scoring:i,edgeTypes:r,maxDepth:a,includeWikiLinks:o,includeSuggestions:c});return l==="json"?C(u):it(u.bundle)}catch(u){return X(u instanceof Error?u.message:String(u))}}),e.registerTool("doctor",{title:"Health check",description:"Read-only diagnostic snapshot of the local Claude Recall database: total size, WAL size, free pages, FTS5 fragmentation for messages and sessions, vector index row count, free disk space, integrity check, and row counts per surface table. Returns structured JSON. Equivalent to running `recall doctor --json` from the shell. Safe to call as often as needed; no side effects.",inputSchema:{}},async()=>{let{buildHealthReport:t}=await Promise.resolve().then(()=>(un(),dn));return C(t())}),Is(e),s){let t=Number(process.env.RECALL_MCP_RATE_LIMIT),n=Number.isFinite(t)&&t>0?t:Xe,i=p(),r=new Date(Date.now()-6e4).toISOString(),a=i.prepare("SELECT at FROM mcp_audit_events WHERE at >= ? ORDER BY at ASC").all(r),o=new K(n);for(let c of a){let l=new Date(c.at).getTime();if(Number.isFinite(l))try{o.consume(l)}catch{break}}e.registerTool("list_sessions_to_tag",{title:"List sessions to tag",description:"Return session summaries and sampled messages formatted for an agent to propose tags. Respects the same scope filters used by the Recall UI scan. Only returns data if auto-tagging is enabled in config.",inputSchema:{untaggedOnly:T.boolean().optional().describe("Only sessions with zero tags (default false)."),project:T.string().optional().describe("Exact project name match."),collectionId:T.string().optional().describe("Only sessions in this collection."),limit:T.number().int().min(1).max(200).optional()}},async c=>{let l=De();if(!l.enabled)return X("auto-tagging is disabled; enable it in Recall settings before scanning");let d=he({untaggedOnly:c.untaggedOnly,project:c.project,collectionId:c.collectionId,limit:c.limit??50});return C({count:d.length,sessions:d,guidance:`Produce ${l.minTagsPerSession}-${l.maxTagsPerSession} tags per session. Prefer existing tags from list_tags for consistency. Use apply_tags to write results.`})}),e.registerTool("apply_tags",{title:"Apply tags to a session",description:"Add one or more tags to a session (merge-mode, never removes existing). Only works when auto-tagging is enabled. Accepts full UUID or 8+-character id prefix.",inputSchema:{sessionId:T.string().describe("Full session UUID or 8+-character prefix."),tags:T.array(T.string()).min(1).max(10).describe("Tags to add. Normalized server-side (lowercase, hyphens, strip #).")}},async({sessionId:c,tags:l})=>{if(!De().enabled)return X("auto-tagging is disabled; enable it in Recall settings before writing tags");let u=$(c);if(!u)return X(`session not found or prefix ambiguous: ${c}`);try{let m=await y({tool:"apply_tags",args:{sessionId:u,tags:l},limiter:o,run:()=>{let g=[],_=[];for(let f of l)try{let{tag:h,added:N}=ge(u,f);N?g.push(h):_.push({tag:h,reason:"already present"})}catch(h){_.push({tag:f,reason:h instanceof Error?h.message:String(h)})}return{sessionId:u,applied:g,skipped:_}}});return C(m)}catch(m){return m instanceof Error&&m.message.startsWith("SQLITE_")?X("database constraint error"):X(m instanceof Error?m.message:String(m))}}),e.registerTool("optimize",{title:"Optimize the database",description:"Run the local maintenance pass: WAL checkpoint (TRUNCATE), FTS5 segment merge for messages and sessions, refresh planner stats. Equivalent to `recall optimize` from the shell. Safe while the daemon is running. Set vacuum=true to also reclaim free pages \u2014 but VACUUM rewrites the entire DB and requires the daemon to be stopped, so it errors out if the daemon is up. Write-mode only.",inputSchema:{vacuum:T.boolean().optional().describe("Also run VACUUM. Requires the daemon to be stopped.")}},async({vacuum:c})=>{let{runOptimize:l}=await Promise.resolve().then(()=>(mn(),pn)),d=process.stdout.write.bind(process.stdout),u="";process.stdout.write=m=>(u+=typeof m=="string"?m:Buffer.from(m).toString("utf-8"),!0);try{await l({vacuum:!!c,json:!0})}finally{process.stdout.write=d}try{return C(JSON.parse(u.trim()))}catch{return it(u.trim())}}),At(e,{limiter:o}),xs(e,{limiter:o}),console.error(`[claude-recall-mcp] MCP writes ENABLED \u2014 write tools registered (rate limit ${n}/min)`),console.error("[claude-recall-mcp] MCP thread writes ENABLED")}else console.error("[claude-recall-mcp] MCP writes DISABLED (read-only) \u2014 pass --allow-writes to enable"),console.error("[claude-recall-mcp] MCP thread writes DISABLED (read-only)");for(let t of ft)e.registerPrompt(t.name,{title:t.title,description:t.description,argsSchema:t.argsSchema},async n=>({messages:[{role:"user",content:{type:"text",text:t.build(n)}}]}));return e}async function wo(){let e=yo(),s=new bo;await e.connect(s);let t=async()=>{try{await e.close()}catch{}dt(),process.exit(0)};process.on("SIGINT",t),process.on("SIGTERM",t)}var Lo=(()=>{try{let e=process.argv[1];return e?import.meta.url===new URL(`file://${e}`).href:!1}catch{return!1}})();Lo&&wo().catch(e=>{console.error("[claude-recall-mcp] fatal:",e),process.exit(1)});export{yo as buildServer};
930
+ ORDER BY COALESCE(timestamp, ''), rowid`).all(o),u=ut(l,d,{mode:n??"condensed",includeSidechain:i??!1,prelude:r??null,since:a??null}),m=a?"since":n??"condensed";return Fs(o,Math.ceil(u.length/4),m,"mcp"),it(u)}),e.registerTool("recall_neighborhood",{title:"Recall: neighborhood context bundle",description:"Build a ranked, budget-bounded markdown bundle for a session: parents + children from thread_edges, plus approved citations / similar / cousins / wiki_links from the cognitive graph. Pipe-friendly output ready to seed a fresh conversation. Reads only approved edges by default \u2014 pending suggestions are opt-in.",inputSchema:{session_id:T.string().describe("Session UUID or 8+-character prefix."),budget:T.number().int().min(100).max(5e4).optional().describe("Token budget for the assembled bundle. Default 4000."),scoring:T.enum(["pagerank","embedding-rerank","hybrid"]).optional().describe("Scoring mode (default hybrid)."),edge_types:T.array(T.enum(["citation","similar","skill_track","bug_pattern","wiki_link","temporal_proximity"])).optional().describe("Restrict to certain link types. Default: all approved types."),max_depth:T.number().int().min(1).max(5).optional().describe("Pagerank traversal depth on the local subgraph (default 2)."),include_wiki_links:T.boolean().optional().describe("Include manual wiki_link rows. Default true."),include_suggestions:T.boolean().optional().describe("Surface pending session_link_suggestions. Debug only; default false."),format:T.enum(["markdown","json"]).optional().describe("markdown (default) returns the bundle as text; json returns the full NeighborhoodResult.")}},async({session_id:t,budget:n,scoring:i,edge_types:r,max_depth:a,include_wiki_links:o,include_suggestions:c,format:l})=>{let d=$(t);if(!d)return X(`session not found or prefix ambiguous: ${t}`);try{let u=qs(d,{budget:n,scoring:i,edgeTypes:r,maxDepth:a,includeWikiLinks:o,includeSuggestions:c});return l==="json"?C(u):it(u.bundle)}catch(u){return X(u instanceof Error?u.message:String(u))}}),e.registerTool("doctor",{title:"Health check",description:"Read-only diagnostic snapshot of the local Claude Recall database: total size, WAL size, free pages, FTS5 fragmentation for messages and sessions, vector index row count, free disk space, integrity check, and row counts per surface table. Returns structured JSON. Equivalent to running `recall doctor --json` from the shell. Safe to call as often as needed; no side effects.",inputSchema:{}},async()=>{let{buildHealthReport:t}=await Promise.resolve().then(()=>(un(),dn));return C(t())}),Is(e),s){let t=Number(process.env.RECALL_MCP_RATE_LIMIT),n=Number.isFinite(t)&&t>0?t:Xe,i=p(),r=new Date(Date.now()-6e4).toISOString(),a=i.prepare("SELECT at FROM mcp_audit_events WHERE at >= ? ORDER BY at ASC").all(r),o=new z(n);for(let c of a){let l=new Date(c.at).getTime();if(Number.isFinite(l))try{o.consume(l)}catch{break}}e.registerTool("list_sessions_to_tag",{title:"List sessions to tag",description:"Return session summaries and sampled messages formatted for an agent to propose tags. Respects the same scope filters used by the Recall UI scan. Only returns data if auto-tagging is enabled in config.",inputSchema:{untaggedOnly:T.boolean().optional().describe("Only sessions with zero tags (default false)."),project:T.string().optional().describe("Exact project name match."),collectionId:T.string().optional().describe("Only sessions in this collection."),limit:T.number().int().min(1).max(200).optional()}},async c=>{let l=De();if(!l.enabled)return X("auto-tagging is disabled; enable it in Recall settings before scanning");let d=he({untaggedOnly:c.untaggedOnly,project:c.project,collectionId:c.collectionId,limit:c.limit??50});return C({count:d.length,sessions:d,guidance:`Produce ${l.minTagsPerSession}-${l.maxTagsPerSession} tags per session. Prefer existing tags from list_tags for consistency. Use apply_tags to write results.`})}),e.registerTool("apply_tags",{title:"Apply tags to a session",description:"Add one or more tags to a session (merge-mode, never removes existing). Only works when auto-tagging is enabled. Accepts full UUID or 8+-character id prefix.",inputSchema:{sessionId:T.string().describe("Full session UUID or 8+-character prefix."),tags:T.array(T.string()).min(1).max(10).describe("Tags to add. Normalized server-side (lowercase, hyphens, strip #).")}},async({sessionId:c,tags:l})=>{if(!De().enabled)return X("auto-tagging is disabled; enable it in Recall settings before writing tags");let u=$(c);if(!u)return X(`session not found or prefix ambiguous: ${c}`);try{let m=await y({tool:"apply_tags",args:{sessionId:u,tags:l},limiter:o,run:()=>{let g=[],_=[];for(let f of l)try{let{tag:h,added:N}=ge(u,f);N?g.push(h):_.push({tag:h,reason:"already present"})}catch(h){_.push({tag:f,reason:h instanceof Error?h.message:String(h)})}return{sessionId:u,applied:g,skipped:_}}});return C(m)}catch(m){return m instanceof Error&&m.message.startsWith("SQLITE_")?X("database constraint error"):X(m instanceof Error?m.message:String(m))}}),e.registerTool("optimize",{title:"Optimize the database",description:"Run the local maintenance pass: WAL checkpoint (TRUNCATE), FTS5 segment merge for messages and sessions, refresh planner stats. Equivalent to `recall optimize` from the shell. Safe while the daemon is running. Set vacuum=true to also reclaim free pages \u2014 but VACUUM rewrites the entire DB and requires the daemon to be stopped, so it errors out if the daemon is up. Write-mode only.",inputSchema:{vacuum:T.boolean().optional().describe("Also run VACUUM. Requires the daemon to be stopped.")}},async({vacuum:c})=>{let{runOptimize:l}=await Promise.resolve().then(()=>(mn(),pn)),d=process.stdout.write.bind(process.stdout),u="";process.stdout.write=m=>(u+=typeof m=="string"?m:Buffer.from(m).toString("utf-8"),!0);try{await l({vacuum:!!c,json:!0})}finally{process.stdout.write=d}try{return C(JSON.parse(u.trim()))}catch{return it(u.trim())}}),At(e,{limiter:o}),xs(e,{limiter:o}),console.error(`[claude-recall-mcp] MCP writes ENABLED \u2014 write tools registered (rate limit ${n}/min)`),console.error("[claude-recall-mcp] MCP thread writes ENABLED")}else console.error("[claude-recall-mcp] MCP writes DISABLED (read-only) \u2014 pass --allow-writes to enable"),console.error("[claude-recall-mcp] MCP thread writes DISABLED (read-only)");for(let t of ft)e.registerPrompt(t.name,{title:t.title,description:t.description,argsSchema:t.argsSchema},async n=>({messages:[{role:"user",content:{type:"text",text:t.build(n)}}]}));return e}async function xo(){let e=Io(),s=new bo;await e.connect(s);let t=async()=>{try{await e.close()}catch{}dt(),process.exit(0)};process.on("SIGINT",t),process.on("SIGTERM",t)}var Co=(()=>{try{let e=process.argv[1];return e?import.meta.url===new URL(`file://${e}`).href:!1}catch{return!1}})();Co&&xo().catch(e=>{console.error("[claude-recall-mcp] fatal:",e),process.exit(1)});export{Io as buildServer};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clauderecallhq/cli",
3
- "version": "0.61.4",
3
+ "version": "0.61.6",
4
4
  "description": "Never lose a Claude Code session again. Local, fast, searchable memory over every session you've ever run.",
5
5
  "type": "module",
6
6
  "homepage": "https://clauderecall.com",
@@ -48,7 +48,9 @@
48
48
  "version-bump:dry": "node scripts/bump-versions.mjs --dry-run",
49
49
  "installs": "node scripts/installs.mjs",
50
50
  "prepare": "[ -d .git ] && git config core.hooksPath .githooks || true",
51
- "postinstall": "node scripts/postinstall.mjs"
51
+ "postinstall": "node scripts/postinstall.mjs",
52
+ "verify:bundle-deps": "node scripts/verify-bundle-deps.mjs",
53
+ "prepublishOnly": "npm run build:cli && npm run verify:bundle-deps"
52
54
  },
53
55
  "keywords": [
54
56
  "claude",
@@ -77,34 +79,20 @@
77
79
  "@anthropic-ai/sdk": "^0.94.0",
78
80
  "@hono/node-server": "^2.0.0",
79
81
  "@modelcontextprotocol/sdk": "^1.29.0",
80
- "@tanstack/react-virtual": "^3.13.24",
81
- "@types/d3": "^7.4.3",
82
82
  "better-sqlite3": "^12.9.0",
83
- "bullmq": "^5.76.3",
84
83
  "chalk": "^5.3.0",
85
84
  "chokidar": "^5.0.0",
86
85
  "cli-table3": "^0.6.5",
87
86
  "commander": "^14.0.3",
88
- "d3": "^7.9.0",
89
87
  "date-fns": "^4.1.0",
90
- "dompurify": "^3.4.0",
91
- "dotenv": "^17.4.2",
92
88
  "hono": "^4.6.3",
93
- "html-to-image": "^1.11.13",
94
- "html-to-text": "^9.0.5",
95
- "imapflow": "^1.3.2",
96
89
  "ink": "^7.0.1",
97
90
  "ink-text-input": "^6.0.0",
98
- "ioredis": "^5.10.1",
99
91
  "jose": "^6.2.2",
100
- "mailparser": "^3.9.8",
101
- "nodemailer": "^8.0.7",
102
92
  "react": "^19.2.5",
103
93
  "satori": "^0.26.0",
104
94
  "sharp": "^0.34.5",
105
95
  "sqlite-vec": "^0.1.6",
106
- "ulid": "^3.0.2",
107
- "wink-bm25-text-search": "^3.1.2",
108
96
  "zod": "^4.3.6"
109
97
  },
110
98
  "optionalDependencies": {
@@ -115,9 +103,10 @@
115
103
  "@fontsource/jetbrains-mono": "^5.2.8",
116
104
  "@playwright/test": "^1.59.1",
117
105
  "@tanstack/react-query": "^5.99.1",
106
+ "@tanstack/react-virtual": "^3.13.24",
118
107
  "@types/better-sqlite3": "^7.6.11",
108
+ "@types/d3": "^7.4.3",
119
109
  "@types/dompurify": "^3.0.5",
120
- "@types/html-to-text": "^9.0.4",
121
110
  "@types/mailparser": "^3.4.6",
122
111
  "@types/node": "^25.6.0",
123
112
  "@types/nodemailer": "^8.0.0",
@@ -125,16 +114,27 @@
125
114
  "@types/react-dom": "^19.2.3",
126
115
  "@vitejs/plugin-react": "^6.0.1",
127
116
  "autoprefixer": "^10.5.0",
117
+ "bullmq": "^5.76.3",
128
118
  "clsx": "^2.1.1",
119
+ "d3": "^7.9.0",
120
+ "dompurify": "^3.4.0",
121
+ "dotenv": "^17.4.2",
129
122
  "esbuild": "^0.28.0",
130
123
  "highlight.js": "^11.11.1",
124
+ "html-to-image": "^1.11.13",
125
+ "imapflow": "^1.3.2",
126
+ "ioredis": "^5.10.1",
127
+ "mailparser": "^3.9.8",
131
128
  "marked": "^18.0.2",
129
+ "nodemailer": "^8.0.7",
132
130
  "patchright": "^1.59.4",
133
131
  "postcss": "^8.5.10",
134
132
  "react-dom": "^19.2.5",
135
133
  "tailwindcss": "^3.4.19",
136
134
  "tsx": "^4.19.1",
137
135
  "typescript": "^6.0.3",
138
- "vite": "^8.0.8"
136
+ "ulid": "^3.0.2",
137
+ "vite": "^8.0.8",
138
+ "wink-bm25-text-search": "^3.1.2"
139
139
  }
140
140
  }
@@ -1,12 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { existsSync } from 'node:fs';
3
- import { execSync, spawnSync } from 'node:child_process';
3
+ import { spawnSync } from 'node:child_process';
4
4
 
5
5
  const cliPath = 'dist/cli.js';
6
6
 
7
+ // dist/cli.js is in the npm `files` allowlist and ships in every published
8
+ // tarball. If it's missing here we're in a linked dev install, not an
9
+ // end-user `npm install -g`, and the dev should run `npm run build:cli`
10
+ // themselves. Never invoke a build toolchain on an end-user machine.
7
11
  if (!existsSync(cliPath)) {
8
- console.log('[recall] dist/cli.js missing — rebuilding so the linked CLI keeps working');
9
- execSync('npm run build:cli', { stdio: 'inherit' });
12
+ console.log('[recall] dist/cli.js missing — run `npm run build:cli` (dev) or reinstall the package (end-user)');
13
+ process.exit(0);
10
14
  }
11
15
 
12
16
  if (process.env.RECALL_SKIP_MODEL_DOWNLOAD === '1') {