@clauderecallhq/cli 0.77.1 → 0.77.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +234 -234
- package/package.json +5 -28
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/* Claude Recall (proprietary). See LICENSE for terms. */
|
|
3
|
-
var
|
|
3
|
+
var dm=Object.defineProperty;var N=(e,t)=>()=>(e&&(t=e(e=0)),t);var he=(e,t)=>{for(var s in t)dm(e,s,{get:t[s],enumerable:!0})};import{createRequire as um}from"node:module";var pm,mm,gm,Hn,Wn,bs,Xn=N(()=>{"use strict";{let e=process.emit.bind(process);process.emit=function(t,...s){let n=s[0];return t==="warning"&&n instanceof Error&&n.name==="ExperimentalWarning"&&/SQLite/i.test(n.message)?!1:e(t,...s)}}pm=um(import.meta.url),mm=["node","sqlite"].join(":"),gm=pm(mm),Hn=class{inner;constructor(t){this.inner=t}get(...t){return t.length===0?this.inner.get():this.inner.get(...t)}all(...t){return t.length===0?this.inner.all():this.inner.all(...t)}run(...t){let s=t.length===0?this.inner.run():this.inner.run(...t);return{changes:s.changes,lastInsertRowid:s.lastInsertRowid}}iterate(...t){return t.length===0?this.inner.iterate():this.inner.iterate(...t)}},Wn=class{inner;extensionLoadingEnabled=!1;txDepth=0;constructor(t,s={}){this.inner=new gm.DatabaseSync(t,{readOnly:s.readonly??!1,allowExtension:!0})}prepare(t){return new Hn(this.inner.prepare(t))}exec(t){this.inner.exec(t)}close(){this.inner.close()}pragma(t,s={}){if(t.includes("=")){this.inner.exec(`PRAGMA ${t}`);return}if(s.simple){let n=this.inner.prepare(`PRAGMA ${t}`).get();return n&&typeof n=="object"?Object.values(n)[0]:void 0}return this.inner.prepare(`PRAGMA ${t}`).all()}transaction(t){return((...n)=>{this.txDepth===0?this.inner.exec("BEGIN"):this.inner.exec(`SAVEPOINT sp_${this.txDepth}`),this.txDepth+=1;try{let r=t(...n);return this.txDepth-=1,this.txDepth===0?this.inner.exec("COMMIT"):this.inner.exec(`RELEASE sp_${this.txDepth}`),r}catch(r){this.txDepth-=1;try{this.txDepth===0?this.inner.exec("ROLLBACK"):(this.inner.exec(`ROLLBACK TO sp_${this.txDepth}`),this.inner.exec(`RELEASE sp_${this.txDepth}`))}catch{}throw r}})}loadExtension(t,s){this.extensionLoadingEnabled||(this.inner.enableLoadExtension(!0),this.extensionLoadingEnabled=!0),s===void 0?this.inner.loadExtension(t):this.inner.loadExtension(t,s)}},bs=Wn});import{homedir as hi}from"node:os";import{join as Gt,basename as fm}from"node:path";import{existsSync as Ei,mkdirSync as _m,chmodSync as hm,readdirSync as _i,statSync as Em}from"node:fs";function F(){Ei(x)||_m(x,{recursive:!0,mode:448}),process.platform!=="win32"&&hm(x,448)}function Jn(e){return e.replace(/^-/,"/").replace(/-/g,"/")}function bi(e){let t=Jn(e);return fm(t)||t}function Si(){if(!Ei(Lt))return[];let e=[],t=_i(Lt,{withFileTypes:!0}).filter(s=>s.isDirectory()).map(s=>s.name);for(let s of t){let n=Gt(Lt,s),r=_i(n,{withFileTypes:!0});for(let o of r){if(!o.isFile()||!o.name.endsWith(".jsonl"))continue;let i=Gt(n,o.name),a=Em(i);e.push({sessionFile:i,encodedProject:s,mtime:a.mtimeMs,size:a.size})}}return e}var Lt,x,se,P=N(()=>{"use strict";Lt=process.env.CLAUDE_PROJECTS_DIR?process.env.CLAUDE_PROJECTS_DIR:Gt(hi(),".claude","projects"),x=process.env.RECALL_HOME?process.env.RECALL_HOME:Gt(hi(),".recall"),se=Gt(x,"db.sqlite")});function wi(e){let t=e.prepare("PRAGMA table_info(sessions)").all(),s=new Set(t.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"],["archive_status","TEXT NOT NULL DEFAULT 'live'"],["archived_at","TEXT"],["skipped_reason","TEXT"]];for(let[g,f]of n)s.has(g)||e.exec(`ALTER TABLE sessions ADD COLUMN ${g} ${f}`);let r=e.prepare("PRAGMA table_info(collection_sessions)").all(),o=new Set(r.map(g=>g.name)),i=[["source","TEXT NOT NULL DEFAULT 'manual'"],["rule_id","TEXT"]];for(let[g,f]of i)o.has(g)||e.exec(`ALTER TABLE collection_sessions ADD COLUMN ${g} ${f}`);let a=e.prepare("PRAGMA table_info(session_notes)").all(),d=new Set(a.map(g=>g.name)),l=[["auto_synopsis","TEXT"],["auto_synopsis_generated_at","INTEGER"],["auto_synopsis_history","TEXT"]];for(let[g,f]of l)d.has(g)||e.exec(`ALTER TABLE session_notes ADD COLUMN ${g} ${f}`);let u=e.prepare("PRAGMA table_info(threads)").all();new Set(u.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 p=e.prepare("PRAGMA table_info(thread_folders)").all(),m=new Set(p.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,
|
|
@@ -12,7 +12,7 @@ var Qp=Object.defineProperty;var N=(e,t)=>()=>(e&&(t=e(e=0)),t);var he=(e,t)=>{f
|
|
|
12
12
|
);
|
|
13
13
|
CREATE INDEX IF NOT EXISTS idx_message_embeddings_session ON message_embeddings(session_id);
|
|
14
14
|
CREATE INDEX IF NOT EXISTS idx_message_embeddings_generated ON message_embeddings(generated_at DESC);
|
|
15
|
-
`)}var
|
|
15
|
+
`)}var yi,Ti=N(()=>{"use strict";yi=`
|
|
16
16
|
PRAGMA journal_mode = WAL;
|
|
17
17
|
PRAGMA synchronous = NORMAL;
|
|
18
18
|
PRAGMA foreign_keys = ON;
|
|
@@ -668,33 +668,33 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_target
|
|
|
668
668
|
ON bug_synthesis_results(scope, target_id, created_at DESC);
|
|
669
669
|
CREATE INDEX IF NOT EXISTS idx_synth_results_created
|
|
670
670
|
ON bug_synthesis_results(created_at DESC);
|
|
671
|
-
`});import*as
|
|
672
|
-
`,{mode:384})}function
|
|
671
|
+
`});import*as Ri from"sqlite-vec";function _(){if(le)return le;F(),le=new bs(se),Ri.load(le),le.pragma("cache_size = -64000"),le.pragma("mmap_size = 268435456"),le.pragma("temp_store = MEMORY"),le.pragma("busy_timeout = 5000"),le.pragma("journal_size_limit = 67108864"),le.pragma("wal_autocheckpoint = 1000"),le.exec(yi),wi(le);try{le.exec("PRAGMA optimize")}catch{}return le}var le,w=N(()=>{"use strict";Xn();P();Ti();le=null});import Le from"chalk";import{formatDistanceToNowStrict as xm,parseISO as km}from"date-fns";function K(e,t){if(!e)return"";let s=e.replace(/\s+/g," ").trim();return s.length<=t?s:s.slice(0,t-1)+"\u2026"}function J(e){if(!e)return"";try{return xm(km(e),{addSuffix:!0})}catch{return""}}function X(e){return e.slice(0,8)}function Kn(e,t){if(!t)return e;let s=t.split(/\s+/).filter(r=>r.length>1).map(r=>r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"));if(s.length===0)return e;let n=new RegExp(`(${s.join("|")})`,"gi");return e.replace(n,r=>Le.bgYellow.black(r))}var c,v=N(()=>{"use strict";c={dim:Le.gray,bold:Le.bold,project:Le.cyan,user:Le.blue,assistant:Le.green,tool:Le.magenta,warn:Le.yellow,err:Le.red,ok:Le.green,accent:Le.hex("#f97316")}});import{existsSync as Bi,readFileSync as Tg,writeFileSync as Rg,unlinkSync as xg}from"node:fs";import{join as kg}from"node:path";function Kt(){if(!Bi(ht))return null;try{let e=Tg(ht,"utf8"),t=JSON.parse(e);return typeof t.license_jwt!="string"||t.license_jwt.length===0?null:t}catch{return null}}function Ts(e){F(),Rg(ht,JSON.stringify(e,null,2)+`
|
|
672
|
+
`,{mode:384})}function Hi(){Bi(ht)&&xg(ht)}var ht,At=N(()=>{"use strict";P();ht=kg(x,"license.json")});var Wi,cr,Xi,Ji,Gi=N(()=>{"use strict";Wi=`-----BEGIN PUBLIC KEY-----
|
|
673
673
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZysO2FffTLdyxQnTmnt78/ayvqz9
|
|
674
674
|
kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
|
|
675
675
|
-----END PUBLIC KEY-----
|
|
676
|
-
`,
|
|
677
|
-
`,{mode:384})}async function
|
|
676
|
+
`,cr="ES256",Xi="clauderecall.com",Ji="clauderecall-cli"});import{jwtVerify as Cg,importSPKI as Lg}from"jose";async function Ng(){return Rs||(Rs=await Lg(Wi,cr),Rs)}async function Ot(e){try{let t=await Ng(),{payload:s}=await Cg(e,t,{issuer:Xi,audience:Ji,algorithms:[cr]});return{valid:!0,claims:s}}catch(t){return{valid:!1,reason:t instanceof Error?t.message:"verification failed"}}}var Rs,xs=N(()=>{"use strict";Gi();Rs=null});import{createHash as Ag}from"node:crypto";import{hostname as Og,userInfo as vg,platform as Ig,arch as Mg}from"node:os";function vt(){let e="unknown";try{e=vg().username}catch{}let t=[Og(),e,Ig(),Mg()];return Ag("sha256").update(t.join("\0")).digest("hex")}var ks=N(()=>{"use strict"});function tt(){let e=process.env.RECALL_API_BASE;if(e&&e.length>0){let t=e.replace(/\/$/,""),s;try{s=new URL(t)}catch{throw new Error(`RECALL_API_BASE is not a valid URL: ${t}`)}let n=s.hostname==="127.0.0.1"||s.hostname==="localhost"||s.hostname==="::1";if(s.protocol==="https:"||s.protocol==="http:"&&n)return t;throw new Error(`RECALL_API_BASE must be HTTPS, or HTTP with loopback hostname. Got: ${t}`)}return"https://clauderecall.com"}var Vt=N(()=>{"use strict"});import{existsSync as Dg,readFileSync as $g,writeFileSync as Pg}from"node:fs";import{join as Fg}from"node:path";function Cs(){if(!Dg(lr))return null;try{let e=JSON.parse($g(lr,"utf8"));return typeof e.license_key!="string"||typeof e.last_checked_at!="string"||typeof e.revoked!="boolean"?null:e}catch{return null}}function Hg(e){F(),Pg(lr,JSON.stringify(e,null,2)+`
|
|
677
|
+
`,{mode:384})}async function Wg(e,t){let s=null,n=null;try{s=new AbortController,n=setTimeout(()=>s?.abort(),Bg);let r=await fetch(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({license_key:e}),signal:s.signal});if(!r.ok)return null;let o=await r.json();return typeof o?.revoked!="boolean"?null:o}catch{return null}finally{n&&clearTimeout(n)}}async function Yi(e,t={}){let s=Cs(),n=t.apiUrl??`${tt()}/api/license/check`,r=s?.license_key===e,o=!s||!r||Date.now()-new Date(s.last_checked_at).getTime()>=jg;if(!t.force&&!o)return s;let i=await Wg(e,n);if(!i)return r?s:null;let a={license_key:e,last_checked_at:new Date().toISOString(),revoked:i.revoked,reason:i.reason??null};return Hg(a),a}function zi(e){let t=Cs();return!t||t.license_key!==e?null:t.revoked?{revoked:!0,reason:t.reason?`license revoked: ${t.reason}`:"license revoked by issuer"}:Date.now()-new Date(t.last_checked_at).getTime()>Ug?{revoked:!0,reason:"license has not been validated with the server in 30+ days. Reconnect to the internet and run `recall license check`"}:null}var lr,jg,Ug,Bg,dr=N(()=>{"use strict";P();Vt();lr=Fg(x,"license-check.json"),jg=1440*60*1e3,Ug=720*60*60*1e3,Bg=1e4});function Jg(e=Date.now()){return e<ur}function Ki(e=Date.now()){return Jg(e)?`(one-time purchase, $${Xg} through May 2026 Founder pricing, $${qi} from June, lifetime updates either way)`:`(one-time purchase, $${qi}, lifetime updates)`}var ur,Xg,qi,pr=N(()=>{"use strict";ur=Date.UTC(2026,5,1,7,0,0),Xg="29.69",qi="49.69"});function fr(e){let t=e.now??Date.now(),s=e.status.expires_at??null,n=s?new Date(s).getTime():null,r=n!==null&&n-t<Gg;if(e.status.tier==="pro"&&r&&n!==null){let o=Math.max(0,Math.floor((n-t)/36e5));if(o<=24)return{kind:"trial-near-expiry",message:`Your Pro trial ends in ${o} hours. Lock in Founder pricing ($29.69 lifetime, ends May 31): ${mr}`}}if(e.status.tier==="free"&&r&&n!==null&&n<t&&Math.floor((t-n)/36e5)<720)return{kind:"trial-expired",message:`Your Pro trial ended. Your data is intact; only Pro features are gated. Founder pricing ($29.69 lifetime, ends May 31): ${mr}`};if(e.status.tier==="free"){let o=ur-t;if(o>0&&o<=3*gr){let i=Math.max(1,Math.ceil(o/gr));return{kind:"founder-near-deadline",message:`Founder pricing ($29.69 lifetime) ends in ${i} day${i===1?"":"s"}. After May 31, lifetime is $49.69: ${mr}`}}}return null}var mr,gr,Gg,Vi=N(()=>{"use strict";pr();mr="https://clauderecall.com/pricing",gr=1440*60*1e3,Gg=60*gr});var Zt={};he(Zt,{getLicenseStatus:()=>Te,isPro:()=>zg,performRevocationCheck:()=>_r,printTrialBannerIfAny:()=>qg,requireProOrExit:()=>Ne});async function Te(){let e=Kt();if(!e)return{tier:"free"};let t=await Ot(e.license_jwt);if(!t.valid||!t.claims)return{tier:"free",invalid_reason:t.reason};if(t.claims.machine_fp&&t.claims.machine_fp!==vt())return{tier:"free",invalid_reason:"machine fingerprint mismatch \u2014 re-activate on this device"};let s=zi(e.license_key);return s?.revoked?{tier:"free",invalid_reason:s.reason}:Yg(e,t.claims)}async function _r(e){let t=Kt();if(!t)return{ran:!1,revoked:!1,reason:null,last_checked_at:null};let s=await Yi(t.license_key,{force:e?.force??!1});return s?{ran:!0,revoked:s.revoked,reason:s.reason,last_checked_at:s.last_checked_at}:{ran:!0,revoked:!1,reason:null,last_checked_at:null}}function Yg(e,t){let s=t.test_mode===!0&&process.env.NODE_ENV==="production";return{tier:s?"free":"pro",key_short:e.key_short,customer_email:e.customer_email,activated_at:e.activated_at,test_mode:e.test_mode,...s?{test_mode_blocked:!0}:{},expires_at:typeof t.exp=="number"?new Date(t.exp*1e3).toISOString():null}}async function zg(){return(await Te()).tier==="pro"}async function Ne(e){let t=await Te();if(t.tier==="pro")return;let s=fr({status:t});s&&process.stderr.write(`
|
|
678
678
|
${s.message}
|
|
679
679
|
`),process.stderr.write(`
|
|
680
680
|
${e} is a Pro feature.
|
|
681
681
|
|
|
682
682
|
Buy a license at https://clauderecall.com/pricing
|
|
683
|
-
${
|
|
683
|
+
${Ki()}.
|
|
684
684
|
Then run: recall activate <license-key>
|
|
685
685
|
|
|
686
686
|
`),t.invalid_reason&&process.stderr.write(`(stored license is invalid: ${t.invalid_reason})
|
|
687
687
|
|
|
688
|
-
`),process.exit(1)}async function
|
|
688
|
+
`),process.exit(1)}async function qg(){let e=await Te(),t=fr({status:e});return t?(process.stderr.write(`
|
|
689
689
|
${t.message}
|
|
690
690
|
|
|
691
|
-
`),!0):!1}var de=N(()=>{"use strict";At();xs();ks();
|
|
691
|
+
`),!0):!1}var de=N(()=>{"use strict";At();xs();ks();dr();pr();Vi()});import{writeFileSync as Uk,readFileSync as Kg,existsSync as Vg}from"node:fs";import{join as Zg}from"node:path";function Zi(){if(!Vg(Ls))return null;try{return Kg(Ls,"utf8").trim()}catch{return null}}var Ls,hr=N(()=>{"use strict";P();Ls=Zg(x,"daemon.token")});import{existsSync as sa,readFileSync as nf,writeFileSync as tC,unlinkSync as rf}from"node:fs";import{join as br}from"node:path";function af(){if(!sa(Er))return null;try{let e=JSON.parse(nf(Er,"utf8"));return typeof e.pid!="number"||typeof e.port!="number"?null:e}catch{return null}}function Sr(){for(let e of[Er,of,Ls])if(sa(e))try{rf(e)}catch{}}function yr(e){try{return process.kill(e,0),!0}catch{return!1}}function Q(){let e=af();return e?yr(e.pid)?e:(Sr(),null):null}var Er,of,Ns,qe=N(()=>{"use strict";P();hr();Er=br(x,"daemon.pid"),of=br(x,"daemon.port"),Ns=br(x,"daemon.log")});import{existsSync as uf}from"node:fs";import{dirname as oa}from"node:path";import{fileURLToPath as pf}from"node:url";function ee(){if(As)return As;let e=oa(pf(import.meta.url));for(;!uf(`${e}/package.json`);){let t=oa(e);if(t===e)throw new Error(`package.json not found walking up from ${import.meta.url}`);e=t}return As=e,As}var As,Ke=N(()=>{"use strict";As=null});import{writeFileSync as c_}from"node:fs";import{join as l_}from"node:path";function Lr(e){try{let t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return[]}function Ps(e){return _().prepare("SELECT alias FROM session_aliases WHERE session_id = ?").get(e)?.alias??null}function u_(){return _().prepare("SELECT session_id, alias, updated_at, previous_aliases FROM session_aliases").all().map(t=>({session_id:t.session_id,alias:t.alias,updated_at:t.updated_at,previous_aliases:Lr(t.previous_aliases)}))}function Mt(e,t){let s=t.trim();if(!s)throw new Error("alias must be non-empty");let n=_(),r=new Date().toISOString(),o=n.prepare("SELECT alias, previous_aliases FROM session_aliases WHERE session_id = ?").get(e),i=[];return o&&(i=Lr(o.previous_aliases),o.alias!==s&&i.push({alias:o.alias,replaced_at:r})),n.prepare(`INSERT INTO session_aliases (session_id, alias, updated_at, previous_aliases)
|
|
692
692
|
VALUES (?, ?, ?, ?)
|
|
693
693
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
694
694
|
alias = excluded.alias,
|
|
695
695
|
updated_at = excluded.updated_at,
|
|
696
|
-
previous_aliases = excluded.previous_aliases`).run(e,s,r,JSON.stringify(i)),
|
|
697
|
-
WHERE session_id = ?`).run(s,JSON.stringify(r),e),
|
|
696
|
+
previous_aliases = excluded.previous_aliases`).run(e,s,r,JSON.stringify(i)),Ba(),{session_id:e,alias:s,updated_at:r,previous_aliases:i}}function Ua(e){let t=_(),s=new Date().toISOString(),n=t.prepare("SELECT alias, previous_aliases FROM session_aliases WHERE session_id = ?").get(e);if(!n)return;let r=Lr(n.previous_aliases);r.push({alias:n.alias,replaced_at:s}),t.prepare(`UPDATE session_aliases SET alias = '', updated_at = ?, previous_aliases = ?
|
|
697
|
+
WHERE session_id = ?`).run(s,JSON.stringify(r),e),Ba()}function Ba(){try{F();let e=u_(),t={schema:"claude-recall.aliases.v1",backed_up_at:new Date().toISOString(),aliases:e};c_(d_,JSON.stringify(t,null,2))}catch(e){console.error("[aliases] backup failed:",e)}}var d_,Et=N(()=>{"use strict";w();P();d_=l_(x,"aliases.json")});import{writeFileSync as $_}from"node:fs";import{join as P_}from"node:path";function j_(e){return e.trim().toLowerCase().replace(/^#+/,"").replace(/[\s/\\]+/g,"-").replace(/[^a-z0-9\-._]/g,"").slice(0,40)}function Za(e,t){let s=j_(t);if(!s)throw new Error("tag must contain at least one alphanumeric character");let n=_(),r=new Date().toISOString();return n.prepare("SELECT 1 FROM session_tags WHERE session_id = ? AND tag = ?").get(e,s)?{tag:s,added:!1}:(n.transaction(()=>{n.prepare("INSERT INTO session_tags (session_id, tag, created_at) VALUES (?, ?, ?)").run(e,s,r),n.prepare("INSERT INTO tag_events (session_id, tag, action, at) VALUES (?, ?, 'add', ?)").run(e,s,r)})(),U_(),{tag:s,added:!0})}function Qa(e){return _().prepare("SELECT tag FROM session_tags WHERE session_id = ? ORDER BY tag").all(e).map(t=>t.tag)}function U_(){try{F();let e=_(),t=e.prepare("SELECT session_id, tag, created_at FROM session_tags ORDER BY session_id, tag").all(),s=e.prepare("SELECT id, session_id, tag, action, at FROM tag_events ORDER BY at ASC, id ASC").all(),n={schema:"claude-recall.tags.v1",backed_up_at:new Date().toISOString(),current:t,events:s};$_(F_,JSON.stringify(n,null,2))}catch(e){console.error("[tags] backup failed:",e)}}var F_,Ar=N(()=>{"use strict";w();P();F_=P_(x,"tags.json")});function B_(e,t){let s=e.filter(o=>o.content_text&&o.content_text.trim().length>0);if(s.length<=t)return s;let n=new Set;n.add(0),n.add(s.length-1);let r=(s.length-2)/Math.max(1,t-2);for(let o=1;o<t-1;o++)n.add(Math.floor(o*r));return Array.from(n).sort((o,i)=>o-i).slice(0,t).map(o=>s[o])}function ec(e){let t=_(),s={limit:e.limit??500},n=e.sessionIds&&e.sessionIds.length>0,r=n?"1=1":"s.message_count > 2";if(n){let i=e.sessionIds.map((a,d)=>`@sid_${d}`).join(", ");r+=` AND s.id IN (${i})`,e.sessionIds.forEach((a,d)=>{s[`sid_${d}`]=a})}return e.untaggedOnly&&(r+=" AND NOT EXISTS (SELECT 1 FROM session_tags st WHERE st.session_id = s.id)"),e.project&&(r+=" AND p.name = @project",s.project=e.project),e.collectionId&&(r+=" AND s.id IN (SELECT session_id FROM collection_sessions WHERE collection_id = @col)",s.col=e.collectionId),t.prepare(`SELECT s.id, p.name AS project, s.git_branch,
|
|
698
698
|
NULLIF(sa.alias, '') AS alias,
|
|
699
699
|
COALESCE(s.first_user_message, '') AS first_user_message
|
|
700
700
|
FROM sessions s
|
|
@@ -704,17 +704,17 @@ ${t.message}
|
|
|
704
704
|
ORDER BY COALESCE(s.started_at, '') DESC
|
|
705
705
|
LIMIT @limit`).all(s).map(i=>{let a=t.prepare(`SELECT role, COALESCE(content_text, '') AS content_text
|
|
706
706
|
FROM messages WHERE session_id = ?
|
|
707
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(i.id),l=
|
|
707
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(i.id),l=B_(a,5).map(u=>`${u.role}: ${u.content_text.slice(0,400)}`).join(`
|
|
708
708
|
---
|
|
709
|
-
`);return{id:i.id,project:i.project,git_branch:i.git_branch,alias:i.alias,first_user_message:i.first_user_message,message_sample:l,current_tags:
|
|
710
|
-
`)}var
|
|
709
|
+
`);return{id:i.id,project:i.project,git_branch:i.git_branch,alias:i.alias,first_user_message:i.first_user_message,message_sample:l,current_tags:Qa(i.id)}})}var tc=N(()=>{"use strict";w();Ar()});import{z as Re}from"zod";function sc(e){let t=e.minTags??2,s=e.maxTags??4,n=e.untaggedOnly??!e.sessionId,r=["Auto-tag my Claude Recall sessions using the MCP tools available to you.","","1. Call `list_tags` first to see which tags I already use (prefer those for consistency over inventing new ones).","2. Call `list_sessions_to_tag` with these filters:"],o=[];return e.sessionId?(o.push("limit: 1"),r.push(` ${o.join(", ")}`),r.push(` Then match the session id ${e.sessionId} from the returned list.`)):(n&&o.push("untaggedOnly: true"),e.project&&o.push(`project: "${e.project}"`),e.collectionId&&o.push(`collectionId: "${e.collectionId}"`),o.push(`limit: ${e.limit??100}`),r.push(` ${o.join(", ")}`)),r.push(""),r.push(`3. For each session returned, look at the alias, first user message, git branch, and sampled messages. Pick ${t}-${s} concise, lowercase, hyphen-separated tags describing:`),r.push(" - domain/subsystem (auth, db, frontend, billing, etc.)"),r.push(" - kind of work (bugfix, feature, refactor, research)"),r.push(" - prominent tools or libraries if relevant"),r.push(""),r.push("4. Call `apply_tags` once per session to write the tags."),r.push(""),r.push("Work through EVERY session returned \u2014 do not stop partway. When done, reply with a short summary of how many sessions were tagged. Do not ask clarifying questions; just do the work."),r.join(`
|
|
710
|
+
`)}var TN,RN,xN,kN,nc=N(()=>{"use strict";TN={project:Re.string().optional().describe("Exact project name match (optional)."),collectionId:Re.string().optional().describe("Restrict to sessions in this collection (optional)."),sessionId:Re.string().optional().describe("Full session UUID to tag just one session (optional)."),untaggedOnly:Re.boolean().optional().describe("Skip sessions that already have any tag (default: true)."),limit:Re.number().int().min(1).max(500).optional().describe("Max sessions to process (default: 100)."),minTags:Re.number().int().min(1).max(10).optional().describe("Minimum tags per session (default: 2)."),maxTags:Re.number().int().min(1).max(10).optional().describe("Maximum tags per session (default: 4).")};RN={sessionId:Re.string().describe("Session UUID (or 8+-char prefix) to summarize."),mode:Re.enum(["brief","detailed"]).optional().describe("brief = 3-5 bullets; detailed = paragraph + bullets. Default: brief.")},xN={sessionId:Re.string().describe("Session UUID (or 8+-char prefix) to extract decisions from.")},kN={sessionId:Re.string().describe("Session UUID (or 8+-char prefix) to find similar sessions to."),limit:Re.number().int().min(1).max(20).optional().describe("How many similar sessions to surface (default: 5).")}});function rc(e,t){let s=H_.get(e);if(!(!s||s.size===0))for(let n of s)try{n(t)}catch{}}var H_,oc=N(()=>{"use strict";H_=new Map});import{existsSync as W_,statSync as X_}from"node:fs";import{delimiter as J_,join as G_}from"node:path";function ic(e){if(e.includes("/")||e.includes("\\")||e.includes(".."))return null;let t=(process.env.PATH??"").split(J_).filter(Boolean),s=process.platform==="win32"?[e,`${e}.exe`,`${e}.cmd`,`${e}.bat`]:[e];for(let n of t)for(let r of s){let o=G_(n,r);try{if(W_(o)&&X_(o).isFile())return o}catch{}}return null}function ac(e){if(process.platform!=="win32"||!e)return!1;let t=e.toLowerCase();return t.endsWith(".cmd")||t.endsWith(".bat")}var cc=N(()=>{"use strict"});var Us={};he(Us,{_resetClaudePathCacheForTests:()=>K_,buildScanPrompt:()=>dc,isClaudeCliAvailable:()=>ne,runClaudeCliScan:()=>th,spawnClaudePrompt:()=>bt});import{spawn as Y_}from"node:child_process";function lc(){if(Dt!==void 0&&ts!==void 0)return{path:Dt,available:ts};let e=ic("claude");return Dt=e??"claude",ts=e!==null,{path:Dt,available:ts}}function q_(){return lc().path}function ne(){return lc().available}function K_(){Dt=void 0,ts=void 0}function dc(e){return sc({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 V_(e,t){let s=t.get(e);return s||e.slice(0,8)}function Z_(e){try{return ec(e).map(s=>({id:s.id,label:s.alias&&s.alias.trim().length>0?s.alias:s.first_user_message&&s.first_user_message.trim().length>0?s.first_user_message.slice(0,60):s.id.slice(0,8)}))}catch{return[]}}function Q_(e){let{scanId:t,total:s,labelTable:n}=e,r=new Set;return o=>{let i=o.trim();if(!i.startsWith("{"))return;let a;try{a=JSON.parse(i)}catch{return}if(!a||typeof a!="object")return;let d=a;if(!(d.type!=="assistant"||!d.message?.content))for(let l of d.message.content){if(l?.type!=="tool_use"||l.name!=="mcp__recall__apply_tags")continue;let u=l.input,p=typeof u?.sessionId=="string"?u.sessionId:null;!p||r.has(p)||(r.add(p),rc(t,{type:"progress",current:r.size,total:s,sessionId:p,sessionLabel:V_(p,n)}))}}}function eh(e){let t="";return s=>{t+=s.toString("utf8");let n=t.indexOf(`
|
|
711
711
|
`);for(;n!==-1;){let r=t.slice(0,n);t=t.slice(n+1),r.length>0&&e(r),n=t.indexOf(`
|
|
712
|
-
`)}}}async function
|
|
713
|
-
`)}function
|
|
712
|
+
`)}}}async function th(e,t={},s){let n=!!t.scanId,r=n?Z_(e):[],o=new Map(r.map(d=>[d.id,d.label])),i=r.length,a;return n&&t.scanId&&(a=Q_({scanId:t.scanId,total:i,labelTable:o})),uc({prompt:dc(e),allowedTools:z_.split(","),opts:t,onProgress:s,onStdoutLine:a,outputFormat:n?"stream-json":"json"})}async function bt(e,t,s={},n){return uc({prompt:e,allowedTools:t,opts:s,onProgress:n,outputFormat:"json"})}function uc(e){let{prompt:t,allowedTools:s,opts:n,onProgress:r,onStdoutLine:o,outputFormat:i}=e,a=["-p",t,"--output-format",i,"--allowedTools",s.join(","),"--permission-mode","bypassPermissions","--no-session-persistence"];return i==="stream-json"&&a.push("--verbose"),n.model&&a.push("--model",n.model),new Promise(d=>{let l=q_(),u=Y_(l,a,{stdio:["ignore","pipe","pipe"],shell:ac(l)||process.platform==="win32"&&Dt==="claude"}),p=[],m=[],g=o?eh(o):void 0;u.stdout.on("data",h=>{p.push(h),g&&g(h)}),u.stderr.on("data",h=>{if(m.push(h),r){let E=h.toString("utf8").trim();E&&r(E)}});let f=setTimeout(()=>{u.kill("SIGKILL")},1800*1e3);u.on("close",h=>{clearTimeout(f),d({success:h===0,stdout:Buffer.concat(p).toString("utf8"),stderr:Buffer.concat(m).toString("utf8"),exitCode:h})}),u.on("error",h=>{clearTimeout(f),d({success:!1,stdout:"",stderr:String(h),exitCode:null})})})}var z_,Dt,ts,Be=N(()=>{"use strict";tc();nc();oc();cc();z_=["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"].join(",")});var _c={};he(_c,{downloadModel:()=>Mr,getModelDir:()=>Xs,isModelInstalled:()=>St,uninstallModel:()=>Dr});import{existsSync as Ir,mkdirSync as gc,rmSync as vr,createWriteStream as Sh,statSync as yh}from"node:fs";import{join as Ws}from"node:path";import{createHash as wh}from"node:crypto";import{readFile as Th}from"node:fs/promises";function Xs(){return Ws(x,"models","BAAI","bge-base-en-v1.5")}function St(){let e=Xs();return fc.every(t=>Ir(Ws(e,t.path)))}async function Mr(e){let t=Xs();gc(t,{recursive:!0}),gc(Ws(t,"onnx"),{recursive:!0});for(let s of fc){let n=Ws(t,s.path),r=Rh+s.path,o=0;Ir(n)&&(o=yh(n).size);let i={};o>0&&(i.Range=`bytes=${o}-`);let a=await fetch(r,{headers:i});if(!a.ok&&a.status!==206)throw new Error(`Failed to download ${s.path}: HTTP ${a.status}`);let d=a.headers.get("content-length"),l=d?o+Number(d):0,u=a.body;if(!u)throw new Error(`No response body for ${s.path}`);let p=Sh(n,{flags:o>0?"a":"w"}),m=u.getReader(),g=o;for(;;){let{done:E,value:b}=await m.read();if(E)break;p.write(Buffer.from(b)),g+=b.byteLength,e?.(s.path,g,l)}if(p.end(),await new Promise((E,b)=>{p.on("finish",E),p.on("error",b)}),s.sha256==="TODO_PLACEHOLDER")throw vr(n),new Error(`Refusing to install: SHA-256 not pinned for ${s.path}. Update model-download.ts.`);let f=await Th(n);if(wh("sha256").update(f).digest("hex")!==s.sha256)throw vr(n),new Error(`SHA-256 mismatch for ${s.path}`)}}function Dr(){let e=Xs();Ir(e)&&vr(e,{recursive:!0,force:!0})}var Rh,fc,Js=N(()=>{"use strict";P();Rh="https://huggingface.co/BAAI/bge-base-en-v1.5/resolve/main/",fc=[{path:"config.json",sha256:"bc00af31a4a31b74040d73370aa83b62da34c90b75eb77bfa7db039d90abd591"},{path:"tokenizer.json",sha256:"d241a60d5e8f04cc1b2b3e9ef7a4921b27bf526d9f6050ab90f9267a1f9e5c66"},{path:"tokenizer_config.json",sha256:"9261e7d79b44c8195c1cada2b453e55b00aeb81e907a6664974b4d7776172ab3"},{path:"onnx/model.onnx",sha256:"9bc579acdba21c253c62a9bf866891355a63ffa3442b52c8a37d75b2ccb91848"}]});var hc,Ec=N(()=>{"use strict";hc="BAAI/bge-base-en-v1.5"});var Fr={};he(Fr,{EmbedderUnavailableError:()=>ns,embed:()=>$t,embedQuery:()=>vh,getEmbedderStatus:()=>Ze,loadEmbedder:()=>nt,unloadEmbedder:()=>Ih});import{Worker as kh}from"node:worker_threads";import{join as Ch}from"node:path";import{existsSync as Lh}from"node:fs";function Nh(){return Ch(ee(),"dist","daemon","embedder-worker.js")}function Ah(e){return["Semantic search is unavailable on this platform.","",`Reason: ${e.detail}`,"",`Platform: ${process.platform}/${process.arch}, Node ${process.version}`,"","Claude Recall supports macOS (arm64/x64), Linux (x64/arm64), and Windows (x64).","Core CLI features (search, list, context, daemon) work everywhere.","Only `recall semantic *` requires the on-device embedder.","","See: https://clauderecall.com/docs (Supported platforms) \u2014 or file an issue at","https://gitlab.com/clauderecallhq/clauderecallhq/-/issues with the platform line above."].join(`
|
|
713
|
+
`)}function bc(e){for(let t of ss.values())t.reject(e);ss.clear()}function Oh(){if(Ve)return Ve;let e=Nh();if(!Lh(e))throw new ns({kind:"bundle-missing",detail:"embedder-worker bundle not found \u2014 run `npm run build:cli` to emit it",path:e});let t=new kh(e);return t.on("message",s=>{let n=ss.get(s.id);n&&(ss.delete(s.id),n.resolve(s))}),t.on("error",s=>{console.error("[embedder-worker] thread error:",s);let n=s instanceof Error?s:new Error(String(s));bc(n),Ve=null,He=!1}),t.on("exit",s=>{s!==0&&(console.error(`[embedder-worker] exited with code ${s}`),bc(new Error(`embedder worker exited with code ${s}`))),Ve=null,He=!1}),Ve=t,t}function Gs(e){return new Promise((t,s)=>{let n;try{n=Oh()}catch(r){s(r instanceof Error?r:new Error(String(r)));return}ss.set(e.id,{resolve:t,reject:s}),n.postMessage(e)})}function Ys(){return $r=$r+1>>>0,String($r)}function Pr(e){if(!e.ok)throw e.unavailable?new ns({kind:"platform-unsupported",detail:"embedder worker reports the runtime is unavailable on this platform",underlying:new Error(e.error)}):new Error(e.error);return e}async function nt(){if(!(He&&Ve))try{Pr(await Gs({id:Ys(),type:"load"})),He=!0}catch(e){throw He=!1,e}}function Ze(){return{loaded:He,modelId:hc,dim:768}}async function $t(e){if(!He)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");return Pr(await Gs({id:Ys(),type:"embed",texts:e})).embeddings.map(s=>new Float32Array(s))}async function vh(e){if(!He)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");let t=Pr(await Gs({id:Ys(),type:"embedQuery",text:e}));return new Float32Array(t.embedding)}async function Ih(){if(!Ve){He=!1;return}try{await Gs({id:Ys(),type:"unload"})}catch{}He=!1;let e=Ve;Ve=null;try{await e.terminate()}catch{}}var Ve,ss,$r,He,ns,yt=N(()=>{"use strict";Ke();Ec();Ve=null,ss=new Map,$r=0,He=!1,ns=class extends Error{kind;path;underlying;cause;constructor(t){super(Ah(t)),this.name="EmbedderUnavailableError",this.kind=t.kind,this.path=t.path,this.underlying=t.underlying,this.cause=t}}});var Lc={};he(Lc,{applyIntelMacOnnxFallback:()=>Hr,detectRunningClaudeOnWindows:()=>Cc,ensureTransformersInstalled:()=>qh});import{execFileSync as qs,spawn as jh}from"node:child_process";import{existsSync as Ur,mkdtempSync as Uh,mkdirSync as Bh,readdirSync as kc,rmSync as wc}from"node:fs";import{createRequire as Hh}from"node:module";import{tmpdir as Wh}from"node:os";import{dirname as Br,join as Ks,resolve as Xh}from"node:path";import{fileURLToPath as Jh}from"node:url";function Rc(e){let t=Ks(e,"bin");if(!Ur(t))return!1;try{for(let s of kc(t)){if(!s.startsWith("napi-"))continue;let n=Ks(t,s,"darwin","x64","onnxruntime_binding.node");if(Ur(n))return!0}}catch{return!1}return!1}function xc(){let e=Br(Jh(import.meta.url));return Xh(e,"..","..")}async function Yh(){try{return await import("@huggingface/transformers"),!0}catch{return!1}}function zh(e){return new Promise(t=>{let s=["install","--no-save","--no-audit","--no-fund","--prefix",e,`@huggingface/transformers@${Gh}`],n=jh("npm",s,{stdio:["ignore","inherit","inherit"],cwd:e,shell:process.platform==="win32"});n.on("error",r=>{t({ok:!1,error:`npm spawn failed: ${r instanceof Error?r.message:String(r)}`})}),n.on("exit",r=>{t(r===0?{ok:!0,action:"installed"}:{ok:!1,error:`npm install exited with code ${r}`})})})}function Cc(){if(process.platform!=="win32")return!1;try{return qs("tasklist",["/FI","IMAGENAME eq claude.exe"],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).toLowerCase().includes("claude.exe")}catch{return!1}}async function Hr(e){if(process.platform!=="darwin"||process.arch!=="x64")return;let t;try{let n=Tc.resolve("@huggingface/transformers/package.json",{paths:[e]}),r=Tc.resolve("onnxruntime-node/package.json",{paths:[Br(n)]});t=Br(r)}catch{return}if(Rc(t))return;console.log(`[recall] Intel macOS detected: onnxruntime-node at ${t} is missing the darwin/x64 binding. Applying ${zs} fallback (npm overrides did not propagate through the scoped-package hoisting).`);let s=Uh(Ks(Wh(),"recall-ort-pin-"));try{qs("npm",["pack","--pack-destination",s,`onnxruntime-node@${zs}`],{cwd:s,stdio:["ignore","inherit","inherit"]});let n=kc(s).find(r=>r.startsWith("onnxruntime-node-")&&r.endsWith(".tgz"));if(!n){console.warn("[recall] npm pack did not produce a tarball; Intel-Mac fallback skipped. Tier 2 may be unavailable.");return}if(Rc(t)){console.log("[recall] onnxruntime-node@"+zs+" already in place (concurrent install raced us). Nothing to do.");return}wc(t,{recursive:!0}),Bh(t,{recursive:!0}),qs("tar",["-xzf",Ks(s,n),"-C",t,"--strip-components=1"],{stdio:["ignore","inherit","inherit"]});try{qs("node",["./script/install"],{cwd:t,stdio:["ignore","inherit","inherit"]})}catch{console.warn("[recall] onnxruntime-node postinstall returned non-zero; binding files are still in place. Continuing.")}console.log(`[recall] onnxruntime-node@${zs} fallback applied at ${t}.`)}catch(n){console.error("[recall] Intel macOS onnxruntime-node fallback failed:",n instanceof Error?n.message:String(n)),console.error("[recall] Tier 2 (vector search) will be unavailable on this machine. Core CLI features remain functional.")}finally{try{wc(s,{recursive:!0,force:!0})}catch{}}}async function qh(){if(await Yh())return await Hr(xc()),{ok:!0,action:"already-installed"};if(Cc())return{ok:!1,error:"Close all Claude Code windows before running `recall semantic install` on Windows. claude.exe holds exclusive locks on shared dependency files (Windows kernel default), which causes the underlying npm install to fail with EBUSY. After closing Claude Code, rerun `recall semantic install` to complete the install."};let e=xc();if(!Ur(e))return{ok:!1,error:`package root not found at ${e}`};console.log(`Installing @huggingface/transformers into ${e} ...`);let t=await zh(e);return t.ok&&await Hr(e),t}var Tc,Gh,zs,Nc=N(()=>{"use strict";Tc=Hh(import.meta.url),Gh="^4.2.0",zs="1.23.2"});import{writeFileSync as Wc,readFileSync as sO,existsSync as Xc,mkdirSync as Jc,readdirSync as nO}from"node:fs";import{join as Qs}from"node:path";function DE(){F(),Xc(zr)||Jc(zr,{recursive:!0})}function $E(){F(),Xc(qr)||Jc(qr,{recursive:!0})}function Gc(e){try{return JSON.parse(e)}catch{return e}}function Kr(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:Gc(e.evidence),approved:e.approved===1,created_at:e.created_at,updated_at:e.updated_at}}function Vr(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:Gc(e.evidence),status:e.status,inferred_by:e.inferred_by,created_at:e.created_at,decided_at:e.decided_at}}function PE(e){if(!Number.isFinite(e)||e<0||e>1)throw new Error("confidence must be a number in [0, 1]")}function Yc(e){if(!AE.has(e))throw new Error(`invalid link_type: ${e}`)}function zc(e){if(!IE.has(e))throw new Error(`invalid inferred_by: ${e}`)}function FE(e,t){if(!e||!t)throw new Error("source_session_id and target_session_id are required");if(e===t)throw new Error("a session cannot link to itself")}function jE(e={}){let t=_(),s=[],n=[];e.sourceSessionId&&(s.push("source_session_id = ?"),n.push(e.sourceSessionId)),e.targetSessionId&&(s.push("target_session_id = ?"),n.push(e.targetSessionId)),e.linkType&&(Yc(e.linkType),s.push("link_type = ?"),n.push(e.linkType)),e.approvedOnly&&s.push("approved = 1");let r=s.length?`WHERE ${s.join(" AND ")}`:"",o=Math.max(1,Math.min(5e3,e.limit??1e3));return t.prepare(`SELECT * FROM session_links ${r}
|
|
714
714
|
ORDER BY confidence DESC, updated_at DESC
|
|
715
|
-
LIMIT ?`).all(...n,o).map(
|
|
715
|
+
LIMIT ?`).all(...n,o).map(Kr)}function Zr(e){return _().prepare(`SELECT * FROM session_links
|
|
716
716
|
WHERE source_session_id = ? OR target_session_id = ?
|
|
717
|
-
ORDER BY confidence DESC, updated_at DESC`).all(e,e).map(
|
|
717
|
+
ORDER BY confidence DESC, updated_at DESC`).all(e,e).map(Kr)}function ot(e){FE(e.source_session_id,e.target_session_id),Yc(e.link_type),PE(e.confidence),zc(e.inferred_by);let t=_(),s=new Date().toISOString(),n=JSON.stringify(e.evidence??null);t.prepare(`INSERT INTO session_link_suggestions
|
|
718
718
|
(source_session_id, target_session_id, link_type,
|
|
719
719
|
confidence, evidence, status, inferred_by,
|
|
720
720
|
created_at, decided_at)
|
|
@@ -734,9 +734,9 @@ ${t.message}
|
|
|
734
734
|
WHERE source_session_id = ?
|
|
735
735
|
AND target_session_id = ?
|
|
736
736
|
AND link_type = ?
|
|
737
|
-
AND inferred_by = ?`).get(e.source_session_id,e.target_session_id,e.link_type,e.inferred_by);if(!r)throw new Error("createSuggestion succeeded but read-back failed");return
|
|
737
|
+
AND inferred_by = ?`).get(e.source_session_id,e.target_session_id,e.link_type,e.inferred_by);if(!r)throw new Error("createSuggestion succeeded but read-back failed");return Kc(),Vr(r)}function en(e={}){let t=_(),s=[],n=[];if(e.status){if(!vE.has(e.status))throw new Error(`invalid status: ${e.status}`);s.push("status = ?"),n.push(e.status)}e.sourceSessionId&&(s.push("source_session_id = ?"),n.push(e.sourceSessionId)),e.targetSessionId&&(s.push("target_session_id = ?"),n.push(e.targetSessionId)),e.inferredBy&&(zc(e.inferredBy),s.push("inferred_by = ?"),n.push(e.inferredBy));let r=s.length?`WHERE ${s.join(" AND ")}`:"",o=Math.max(1,Math.min(5e3,e.limit??1e3));return t.prepare(`SELECT * FROM session_link_suggestions ${r}
|
|
738
738
|
ORDER BY confidence DESC, created_at DESC
|
|
739
|
-
LIMIT ?`).all(...n,o).map(
|
|
739
|
+
LIMIT ?`).all(...n,o).map(Vr)}function qc(e,t,s={}){if(t!=="approved"&&t!=="rejected")throw new Error(`invalid decision: ${t}`);let n=s.source??"manual";if(!OE.has(n))throw new Error(`invalid source: ${n}`);let r=_(),o=r.prepare("SELECT * FROM session_link_suggestions WHERE id = ?").get(e);if(!o)throw new Error(`suggestion ${e} not found`);if(o.status!=="pending")throw new Error(`suggestion ${e} already decided as ${o.status}`);let i=new Date().toISOString(),a;r.transaction(()=>{r.prepare(`UPDATE session_link_suggestions
|
|
740
740
|
SET status = ?, decided_at = ?
|
|
741
741
|
WHERE id = ?`).run(t,i,e),t==="approved"&&(r.prepare(`INSERT INTO session_links
|
|
742
742
|
(source_session_id, target_session_id, link_type,
|
|
@@ -751,7 +751,7 @@ ${t.message}
|
|
|
751
751
|
updated_at = excluded.updated_at`).run(o.source_session_id,o.target_session_id,o.link_type,o.confidence,n,o.evidence,i,i),a=r.prepare(`SELECT * FROM session_links
|
|
752
752
|
WHERE source_session_id = ?
|
|
753
753
|
AND target_session_id = ?
|
|
754
|
-
AND link_type = ?`).get(o.source_session_id,o.target_session_id,o.link_type))})(),
|
|
754
|
+
AND link_type = ?`).get(o.source_session_id,o.target_session_id,o.link_type))})(),Kc(),t==="approved"&&UE(o.source_session_id);let d=r.prepare("SELECT * FROM session_link_suggestions WHERE id = ?").get(e);return{suggestion:Vr(d),link:a?Kr(a):null}}function UE(e){try{DE();let t=jE({sourceSessionId:e}),s=Qs(zr,`${e}.json`);if(t.length===0)return;let n={schema:"claude-recall.session-links.v1",source_session_id:e,backed_up_at:new Date().toISOString(),links:t};Wc(s,JSON.stringify(n,null,2))}catch(t){console.error("[session-links] backup failed:",t)}}function Kc(){try{$E();let e=en({limit:5e3}),t={schema:"claude-recall.session-link-suggestions.v1",backed_up_at:new Date().toISOString(),suggestions:e};Wc(ME,JSON.stringify(t,null,2))}catch(e){console.error("[session-links] suggestions backup failed:",e)}}var AE,OE,vE,IE,zr,qr,ME,as=N(()=>{"use strict";w();P();AE=new Set(["citation","similar","skill_track","bug_pattern","wiki_link","temporal_proximity"]),OE=new Set(["regex","llm","embedding","manual","auto","citation","git","terminal-registry"]),vE=new Set(["pending","approved","rejected"]),IE=new Set(["L1","L2","L3","L4","user"]),zr=Qs(x,"links"),qr=Qs(x,"suggestions"),ME=Qs(qr,"index.json")});function on(e){return e?Math.ceil(e.length/4):0}function yl(e){if(!Number.isFinite(e)||e<0)return 1;let t=Math.exp(-e/rS);return Math.max(oS,t)}function wl(e,t){if(!e||!t)return 0;let s=Date.parse(e),n=Date.parse(t);return!Number.isFinite(s)||!Number.isFinite(n)?0:Math.abs(n-s)/(1e3*60*60*24)}function Tl(e){return _().prepare(`SELECT s.id,
|
|
755
755
|
NULLIF(sa.alias, '') AS alias,
|
|
756
756
|
s.auto_title,
|
|
757
757
|
s.auto_title_source,
|
|
@@ -762,18 +762,18 @@ ${t.message}
|
|
|
762
762
|
FROM sessions s
|
|
763
763
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
764
764
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
765
|
-
WHERE s.id = ?`).get(e)??null}function
|
|
765
|
+
WHERE s.id = ?`).get(e)??null}function Rl(e){let s=_().prepare("SELECT summary FROM session_semantic WHERE session_id = ?").get(e);if(!s||!s.summary)return null;let n=s.summary.trim();return n.length>0?n:null}function xl(e){let t=e.alias?.trim(),s=e.auto_title?.trim(),n=e.first_user_message?.trim();return s&&e.auto_title_source==="agent"?s:t||s||(n?n.slice(0,80):e.id.slice(0,8))}function aS(e){let s=_().prepare(`SELECT id, auto_title, started_at
|
|
766
766
|
FROM sessions
|
|
767
767
|
WHERE project_id = ?
|
|
768
|
-
ORDER BY COALESCE(started_at, ''), id`).all(e),n=new Set,r=new Set,o=[];for(let m of s){if(!m.auto_title||!m.auto_title.startsWith("/")){o.push({id:m.id,brand:null,skill:null});continue}let g=m.auto_title.split(" \xB7 "),f=g[0].trim(),h=g.length>1?g.slice(1).join(" \xB7 ").trim():null;o.push({id:m.id,brand:h||null,skill:f||null}),h&&n.add(h),f&&r.add(f)}let i=[...n].sort(),a=new Map;i.forEach((m,g)=>a.set(m,g));let d=[...r].sort(),l=new Map;d.forEach((m,g)=>l.set(m,g));let u=new Map,p=new Map;for(let m of o){if(!m.brand||!m.skill)continue;let g=a.get(m.brand),f=l.get(m.skill);if(g===void 0||f===void 0)continue;let h=`${g}.${f}`,E=(u.get(h)??0)+1;u.set(h,E),p.set(m.id,`${g}.${f}.${E}`)}return{byId:p}}function
|
|
768
|
+
ORDER BY COALESCE(started_at, ''), id`).all(e),n=new Set,r=new Set,o=[];for(let m of s){if(!m.auto_title||!m.auto_title.startsWith("/")){o.push({id:m.id,brand:null,skill:null});continue}let g=m.auto_title.split(" \xB7 "),f=g[0].trim(),h=g.length>1?g.slice(1).join(" \xB7 ").trim():null;o.push({id:m.id,brand:h||null,skill:f||null}),h&&n.add(h),f&&r.add(f)}let i=[...n].sort(),a=new Map;i.forEach((m,g)=>a.set(m,g));let d=[...r].sort(),l=new Map;d.forEach((m,g)=>l.set(m,g));let u=new Map,p=new Map;for(let m of o){if(!m.brand||!m.skill)continue;let g=a.get(m.brand),f=l.get(m.skill);if(g===void 0||f===void 0)continue;let h=`${g}.${f}`,E=(u.get(h)??0)+1;u.set(h,E),p.set(m.id,`${g}.${f}.${E}`)}return{byId:p}}function cS(e){return{table:e!==null?aS(e):null,originProjectId:e,cache:new Map}}function an(e,t){let s=e.cache.get(t);if(s)return s;let n=Tl(t);if(!n)return null;let r=e.table&&n.project_id===e.originProjectId?e.table.byId.get(t)??null:null,o={session_id:n.id,title:xl(n),decimal:r,summary:Rl(n.id),project:n.project,started_at:n.started_at};return e.cache.set(t,o),o}function lS(e,t){let n=_().prepare(`SELECT DISTINCT te.parent_session_id AS pid
|
|
769
769
|
FROM thread_edges te
|
|
770
770
|
WHERE te.session_id = ?
|
|
771
|
-
AND te.parent_session_id IS NOT NULL`).all(t),r=[];for(let o of n){if(!o.pid)continue;let i=
|
|
771
|
+
AND te.parent_session_id IS NOT NULL`).all(t),r=[];for(let o of n){if(!o.pid)continue;let i=an(e,o.pid);i&&r.push(i)}return r}function dS(e,t){let n=_().prepare(`SELECT DISTINCT te.session_id AS sid
|
|
772
772
|
FROM thread_edges te
|
|
773
|
-
WHERE te.parent_session_id = ?`).all(t),r=[];for(let o of n){if(!o.sid)continue;let i=
|
|
774
|
-
${o}`}return r}function
|
|
773
|
+
WHERE te.parent_session_id = ?`).all(t),r=[];for(let o of n){if(!o.sid)continue;let i=an(e,o.sid);i&&r.push(i)}return r}function kl(e){let t=iS[e.linkType]??.5,s=Pt(e.confidence),n=t*s,r=yl(e.daysApart),o=e.embeddingCosine??.5,i=Pt(e.pagerank);if(e.scoring==="pagerank")return Pt(i);if(e.scoring==="embedding-rerank")return e.embeddingCosine===null?Pt(n):Pt(o);let a=.35*n+.2*r+.2*o+.25*i;return Pt(a)}function Pt(e){return!Number.isFinite(e)||e<0?0:e>1?1:e}function uS(e,t,s,n,r){let o=new Map;function i(a,d){if(a===d)return;let l=o.get(a);l||(l=new Set,o.set(a,l)),l.add(d)}for(let a of t)i(a.source_session_id,a.target_session_id),i(a.target_session_id,a.source_session_id);for(let a of s)i(e,a.session_id);for(let a of s)i(a.session_id,e);for(let a of n)i(e,a.session_id);for(let a of n)i(a.session_id,e);if(r>1){let a=new Set([e]),d=new Set([e]);for(let l=1;l<r;l++){let u=new Set;for(let p of a){let m=o.get(p);if(m)for(let g of m){if(d.has(g))continue;let f=Zr(g).filter(h=>h.approved);for(let h of f)i(h.source_session_id,h.target_session_id),i(h.target_session_id,h.source_session_id);d.add(g),u.add(g)}}if(u.size===0)break;for(let p of u)a.add(p)}}return{edges:o}}function pS(e,t={}){let s=t.iterations??12,n=t.damping??.85,r=Array.from(e.edges.keys());if(r.length===0)return new Map;let o=1/r.length,i=new Map(r.map(l=>[l,o]));for(let l=0;l<s;l++){let u=new Map(r.map(p=>[p,(1-n)/r.length]));for(let p of r){let m=e.edges.get(p);if(!m||m.size===0)continue;let g=(i.get(p)??0)/m.size;for(let f of m)u.set(f,(u.get(f)??0)+n*g)}i=u}let a=0;for(let l of i.values())l>a&&(a=l);if(a<=0)return i;let d=new Map;for(let[l,u]of i)d.set(l,u/a);return d}function Ll(e,t){let s=e.replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,t-1).trimEnd()}\u2026`}function mS(e){let t=e.decimal?`${e.decimal} `:"",s=e.session_id.slice(0,8),n="evidence"in e&&e.evidence?` \u2014 ${e.evidence}`:"",r=`- ${t}${e.title} (${s})${n}`;if(e.summary){let o=Ll(e.summary,Cl);return`${r}
|
|
774
|
+
${o}`}return r}function gS(e,t,s){let n=[],r=[],o=0,i=e.decimal?`${e.decimal}: `:"",a=`# Neighborhood for ${e.session_id} (${i}${e.title})`;if(n.push(a),o+=on(a),e.summary){let d=Ll(e.summary,Cl*4);n.push(d),o+=on(d)}n.push("");for(let d of t){if(d.refs.length===0)continue;let l=`## ${d.heading}`,u=on(l),p=[],m=0;for(let g of d.refs){let f=mS(g),h=on(f);if(o+u+m+h>s){r.push({session_id:g.session_id,title:g.title,decimal:g.decimal,summary:g.summary,project:g.project,started_at:g.started_at});continue}p.push(f),m+=h}if(p.length>0){n.push(l);for(let g of p)n.push(g);n.push(""),o+=u+m}}for(;n.length>0&&n[n.length-1]==="";)n.pop();return{bundle:n.join(`
|
|
775
775
|
`)+`
|
|
776
|
-
`,budgetUsed:o,truncated:r}}function
|
|
776
|
+
`,budgetUsed:o,truncated:r}}function fS(e,t,s,n,r,o){let i=[];for(let a of s){if(n&&!n.has(a.link_type))continue;let d=null;if(a.source_session_id===t.session_id?d=a.target_session_id:a.target_session_id===t.session_id&&(d=a.source_session_id),!d)continue;let l=an(e,d);if(!l)continue;let u=wl(t.started_at,l.started_at),p=kl({confidence:a.confidence,linkType:a.link_type,daysApart:u,embeddingCosine:null,pagerank:o.get(d)??0,scoring:r});i.push({...l,score:p,evidence:`(suggestion, ${a.inferred_by}) confidence=${a.confidence.toFixed(2)} ${Math.round(u)}d apart`,link_type:a.link_type})}return i}function cn(e,t={}){let s=Math.max(100,Math.floor(t.budget??sS)),n=t.scoring??"hybrid",r=Math.max(1,Math.min(5,t.maxDepth??nS)),o=t.includeWikiLinks??!0,i=t.includeSuggestions??!1,a=t.edgeTypes?new Set(t.edgeTypes):null,d=Tl(e);if(!d)throw new Error(`session not found: ${e}`);let l=cS(d.project_id),u={session_id:d.id,title:xl(d),decimal:l.table?.byId.get(d.id)??null,summary:Rl(d.id),project:d.project,started_at:d.started_at};l.cache.set(d.id,u);let p=lS(l,e),m=dS(l,e),g=Zr(e).filter(A=>A.approved).filter(A=>!a||a.has(A.link_type)).filter(A=>o||A.link_type!=="wiki_link"),f=uS(e,g,p,m,r),h=pS(f),E=[],b=[],S=[],R=[];for(let A of g){let $=A.source_session_id===e?A.target_session_id:A.source_session_id,U=an(l,$);if(!U)continue;let y=wl(u.started_at,U.started_at),B=kl({confidence:A.confidence,linkType:A.link_type,daysApart:y,embeddingCosine:null,pagerank:h.get($)??0,scoring:n}),M=yl(y),V=`${A.link_type} confidence=${A.confidence.toFixed(2)} recency=${M.toFixed(2)} (${Math.round(y)}d apart)`,Ge={...U,score:B,evidence:V,link_type:A.link_type};A.link_type==="citation"?E.push(Ge):A.link_type==="similar"?b.push(Ge):A.link_type==="wiki_link"?R.push(Ge):S.push(Ge)}if(i){let A=en({sourceSessionId:e,status:"pending",limit:100}),$=en({targetSessionId:e,status:"pending",limit:100}),U=[...A,...$],y=new Set,B=U.filter(V=>y.has(V.id)?!1:(y.add(V.id),!0)),M=fS(l,u,B,a,n,h);for(let V of M)V.link_type==="citation"?E.push(V):V.link_type==="similar"?b.push(V):V.link_type==="wiki_link"?R.push(V):S.push(V)}let T=(A,$)=>$.score-A.score;E.sort(T),b.sort(T),S.sort(T),R.sort(T);let D=gS(u,[{heading:"Parents",refs:p},{heading:"Children",refs:m},{heading:"Citations (approved)",refs:E},{heading:"Similar sessions",refs:b},{heading:"Cousins (skill track + temporal)",refs:S},{heading:"Wiki links (manual)",refs:R}],s);return{origin:u,parents:p,children:m,citations:E,similar:b,cousins:S,wikiLinks:R,bundle:D.bundle,budgetUsed:D.budgetUsed,budgetRemaining:Math.max(0,s-D.budgetUsed),truncated:D.truncated}}var sS,nS,rS,oS,iS,Cl,io=N(()=>{"use strict";w();as();sS=4e3,nS=2,rS=30,oS=.2,iS={citation:1,wiki_link:.9,similar:.7,skill_track:.5,bug_pattern:.4,temporal_proximity:.3};Cl=240});var Md={};he(Md,{computeAllHealthScores:()=>Ao,computeHealthScore:()=>Lo,computeHealthScoreByName:()=>No});function ls(e){return Math.max(0,Math.min(1,e))}function Lo(e){let t=_(),s=t.prepare("SELECT id, name FROM projects WHERE id = ?").get(e);if(!s)return null;let n=t.prepare(`SELECT COUNT(*) AS cnt,
|
|
777
777
|
MAX(started_at) AS latest
|
|
778
778
|
FROM sessions WHERE project_id = ?`).get(e),r=n.cnt;if(r===0)return{projectId:e,projectName:s.name,score:0,breakdown:{sessionCount:{raw:0,score:0,weight:.2},recency:{daysSinceLastSession:1/0,score:0,weight:.25},fragmentation:{avgMessages:0,score:0,weight:.15},searchCoverage:{ratio:0,score:0,weight:.2},tagCoverage:{ratio:0,score:0,weight:.2}}};let o=ls(r/10),i=n.latest?(Date.now()-new Date(n.latest).getTime())/(1e3*60*60*24):90,a=ls(1-i/90),l=t.prepare(`SELECT AVG(message_count) AS avg_msgs
|
|
779
779
|
FROM sessions WHERE project_id = ?`).get(e).avg_msgs??0,u=ls((l-2)/3),p=t.prepare(`SELECT COUNT(*) AS total,
|
|
@@ -784,15 +784,15 @@ ${t.message}
|
|
|
784
784
|
COUNT(DISTINCT st.session_id) AS tagged
|
|
785
785
|
FROM sessions s
|
|
786
786
|
LEFT JOIN session_tags st ON st.session_id = s.id
|
|
787
|
-
WHERE s.project_id = ?`).get(e),h=f.total>0?f.tagged/f.total:0,E=ls(h),b=Math.round((o*.2+a*.25+u*.15+g*.2+E*.2)*100);return{projectId:e,projectName:s.name,score:b,breakdown:{sessionCount:{raw:r,score:o,weight:.2},recency:{daysSinceLastSession:Math.round(i),score:a,weight:.25},fragmentation:{avgMessages:Math.round(l*10)/10,score:u,weight:.15},searchCoverage:{ratio:Math.round(m*100)/100,score:g,weight:.2},tagCoverage:{ratio:Math.round(h*100)/100,score:E,weight:.2}}}}function To(e){let s=_().prepare("SELECT id FROM projects WHERE name = ?").get(e);return s?wo(s.id):null}function Ro(){let t=_().prepare("SELECT id FROM projects ORDER BY name").all(),s=[];for(let n of t){let r=wo(n.id);r&&s.push(r)}return s}var xo=N(()=>{"use strict";w()});function Iu(e,t){return{type:"svg",props:{width:e,height:e,viewBox:"0 0 64 64",style:{display:"flex"},children:[{type:"line",props:{x1:14,y1:16,x2:46,y2:16,stroke:t,strokeWidth:3,strokeLinecap:"round"}},{type:"line",props:{x1:14,y1:24,x2:38,y2:24,stroke:t,strokeWidth:3,strokeLinecap:"round"}},{type:"line",props:{x1:10,y1:32,x2:54,y2:32,stroke:xt,strokeWidth:5,strokeLinecap:"round"}},{type:"line",props:{x1:14,y1:40,x2:42,y2:40,stroke:t,strokeWidth:3,strokeLinecap:"round"}},{type:"line",props:{x1:14,y1:48,x2:36,y2:48,stroke:t,strokeWidth:3,strokeLinecap:"round"}}]}}}function at(e,t){return{type:"div",props:{style:{display:"flex",alignItems:"center",justifyContent:"space-between",width:"100%"},children:[{type:"div",props:{style:{display:"flex",alignItems:"center",gap:"18px"},children:[Iu(72,e.markStroke),{type:"span",props:{style:{fontSize:"36px",fontWeight:700,color:e.wordmarkFg,letterSpacing:"-0.01em"},children:"Claude Recall"}}]}},{type:"span",props:{style:{fontSize:"17px",color:e.wordmarkFg,opacity:.6,textTransform:"uppercase",letterSpacing:"0.18em",fontWeight:600},children:t}}]}}}function ct(e){return{type:"div",props:{style:{display:"flex",alignItems:"center",justifyContent:"space-between",width:"100%"},children:[{type:"div",props:{style:{display:"flex",alignItems:"center",gap:"14px"},children:[Iu(52,e.markStroke),{type:"span",props:{style:{fontSize:"24px",fontWeight:700,color:e.wordmarkFg},children:"clauderecall.com"}}]}},{type:"span",props:{style:{fontSize:"20px",color:e.wordmarkFg,opacity:.7,fontWeight:500},children:"local memory for Claude Code"}}]}}}function YT(e){return e>=1e9?`${(e/1e9).toFixed(2).replace(/\.?0+$/,"")}B`:e>=1e6?`${(e/1e6).toFixed(2).replace(/\.?0+$/,"")}M`:e>=1e3?`${(e/1e3).toFixed(1).replace(/\.0$/,"")}k`:String(e)}function zT(e){return e.toLocaleString("en-US")}function lt(e){return e<1e3?String(e):`${YT(e)} (${zT(e)})`}function dt(e){return new Date(e).toLocaleDateString("en-US",{month:"long",day:"numeric",year:"numeric"})}var xt,us=N(()=>{"use strict";xt="#f97316"});var Du={};he(Du,{render:()=>VT});function Tn(e,t){return{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"4px"},children:[{type:"span",props:{style:{fontSize:"34px",fontWeight:700,color:ps,fontFamily:"JetBrains Mono"},children:e}},{type:"span",props:{style:{fontSize:"14px",color:Rn,textTransform:"uppercase",letterSpacing:"0.16em"},children:t}}]}}}function VT(e){let s=[dt(e.sessionDate)];e.durationLabel&&s.push(e.durationLabel);let n=s.join(" \xB7 "),r=e.costDollars??"$\u2014",o=typeof e.cachedTokens=="number"&&e.cachedTokens>=1e5?`+ ${lt(e.cachedTokens)} cached`:null,i=[at(Mu,"session receipt"),{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"8px"},children:[{type:"div",props:{style:{fontSize:"44px",fontWeight:700,color:ps,lineHeight:1.15,wordBreak:"break-word"},children:e.sessionTitle}},{type:"div",props:{style:{fontSize:"20px",color:Rn},children:n}}]}},{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",padding:"32px 0"},children:[{type:"span",props:{style:{fontSize:"180px",fontWeight:700,color:xt,fontFamily:"JetBrains Mono",lineHeight:1,letterSpacing:"-0.04em"},children:r}},{type:"span",props:{style:{marginTop:"16px",fontSize:"16px",color:Rn,textTransform:"uppercase",letterSpacing:"0.24em",fontWeight:600},children:"session cost"}}]}},{type:"div",props:{style:{display:"flex",width:"100%",height:"1px",backgroundColor:KT}}},{type:"div",props:{style:{display:"flex",justifyContent:"space-between",gap:"24px"},children:[Tn(String(e.messageCount),"msgs"),Tn(String(e.filesReferenced),"files"),Tn(String(e.toolCallCount),"tools"),Tn(e.model??"\u2014","model")]}}];return o&&i.push({type:"div",props:{style:{fontSize:"14px",color:Rn,fontFamily:"JetBrains Mono",opacity:.7},children:o}}),e.verdict&&i.push({type:"div",props:{style:{display:"flex",fontSize:"24px",fontStyle:"italic",color:ps,borderLeft:`3px solid ${xt}`,paddingLeft:"20px",marginTop:"8px"},children:`"${e.verdict}"`}}),i.push({type:"div",props:{style:{display:"flex",flex:1}}}),i.push(ct(Mu)),{type:"div",props:{style:{display:"flex",flexDirection:"column",width:"100%",height:"100%",backgroundColor:qT,padding:"64px 72px",fontFamily:"Inter",gap:"28px"},children:i}}}var qT,ps,Rn,KT,Mu,$u=N(()=>{"use strict";us();qT="#1a1b1e",ps="#e7e9ee",Rn="#8b9098",KT="#2a2c33",Mu={markStroke:ps,wordmarkFg:ps}});var Fu={};he(Fu,{render:()=>eR});function xn(e,t){return{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"4px"},children:[{type:"span",props:{style:{fontSize:"34px",fontWeight:700,color:ms,fontFamily:"JetBrains Mono"},children:e}},{type:"span",props:{style:{fontSize:"14px",color:kn,fontFamily:"JetBrains Mono",textTransform:"lowercase"},children:`// ${t}`}}]}}}function eR(e){let t=[dt(e.sessionDate),e.durationLabel].filter(Boolean).join(" \xB7 "),s=e.costDollars??"$\u2014",n=typeof e.cachedTokens=="number"&&e.cachedTokens>=1e5?`# ${lt(e.cachedTokens)} tokens cache-replayed (free)`:null,r=[at(Pu,"recall.cli/share"),{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"6px"},children:[{type:"div",props:{style:{fontSize:"18px",color:Po,fontFamily:"JetBrains Mono"},children:"$ recall share --session"}},{type:"div",props:{style:{fontSize:"40px",fontWeight:700,color:ms,lineHeight:1.15,wordBreak:"break-word",fontFamily:"JetBrains Mono"},children:e.sessionTitle}},{type:"div",props:{style:{fontSize:"18px",color:kn,fontFamily:"JetBrains Mono"},children:t}}]}},{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"flex-start",padding:"12px 0"},children:[{type:"span",props:{style:{fontSize:"20px",color:kn,fontFamily:"JetBrains Mono"},children:"> total_cost:"}},{type:"span",props:{style:{fontSize:"180px",fontWeight:700,color:Po,fontFamily:"JetBrains Mono",lineHeight:1,letterSpacing:"-0.04em"},children:s}}]}},{type:"div",props:{style:{display:"flex",width:"100%",height:"1px",backgroundColor:QT}}},{type:"div",props:{style:{display:"flex",justifyContent:"space-between",gap:"24px"},children:[xn(String(e.messageCount),"msgs"),xn(String(e.filesReferenced),"files"),xn(String(e.toolCallCount),"tools"),xn(e.model??"\u2014","model")]}}];return n&&r.push({type:"div",props:{style:{fontSize:"14px",color:kn,fontFamily:"JetBrains Mono",opacity:.7},children:n}}),e.verdict&&r.push({type:"div",props:{style:{display:"flex",fontSize:"22px",color:ms,fontFamily:"JetBrains Mono",borderLeft:`3px solid ${Po}`,paddingLeft:"20px",marginTop:"8px"},children:`// ${e.verdict}`}}),r.push({type:"div",props:{style:{display:"flex",flex:1}}}),r.push(ct(Pu)),{type:"div",props:{style:{display:"flex",flexDirection:"column",width:"100%",height:"100%",backgroundColor:ZT,padding:"64px 72px",fontFamily:"Inter",gap:"28px"},children:r}}}var ZT,ms,kn,QT,Po,Pu,ju=N(()=>{"use strict";us();ZT="#0d0d0f",ms="#e1e7ee",kn="#6b7480",QT="#1f2229",Po="#7ee787",Pu={markStroke:ms,wordmarkFg:ms}});var Bu={};he(Bu,{render:()=>sR});function Cn(e,t){return{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"4px"},children:[{type:"span",props:{style:{fontSize:"34px",fontWeight:700,color:kt,fontFamily:"JetBrains Mono"},children:e}},{type:"span",props:{style:{fontSize:"14px",color:Ln,textTransform:"uppercase",letterSpacing:"0.16em",fontWeight:600},children:t}}]}}}function sR(e){let t=[dt(e.sessionDate),e.durationLabel].filter(Boolean).join(" \xB7 "),s=e.costDollars??"$\u2014",n=typeof e.cachedTokens=="number"&&e.cachedTokens>=1e5?`+ ${lt(e.cachedTokens)} cached`:null,r=[at(Uu,"session receipt"),{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"8px"},children:[{type:"div",props:{style:{fontSize:"44px",fontWeight:700,color:kt,lineHeight:1.15,wordBreak:"break-word"},children:e.sessionTitle}},{type:"div",props:{style:{fontSize:"20px",color:Ln},children:t}}]}},{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",padding:"32px 0"},children:[{type:"span",props:{style:{fontSize:"180px",fontWeight:700,color:kt,fontFamily:"JetBrains Mono",lineHeight:1,letterSpacing:"-0.04em"},children:s}},{type:"span",props:{style:{marginTop:"16px",fontSize:"16px",color:Ln,textTransform:"uppercase",letterSpacing:"0.24em",fontWeight:600},children:"session cost"}}]}},{type:"div",props:{style:{display:"flex",width:"100%",height:"1px",backgroundColor:tR}}},{type:"div",props:{style:{display:"flex",justifyContent:"space-between",gap:"24px"},children:[Cn(String(e.messageCount),"msgs"),Cn(String(e.filesReferenced),"files"),Cn(String(e.toolCallCount),"tools"),Cn(e.model??"\u2014","model")]}}];return n&&r.push({type:"div",props:{style:{fontSize:"14px",color:Ln,fontFamily:"JetBrains Mono",opacity:.85},children:n}}),e.verdict&&r.push({type:"div",props:{style:{display:"flex",fontSize:"24px",fontStyle:"italic",color:kt,borderLeft:`3px solid ${kt}`,paddingLeft:"20px",marginTop:"8px"},children:`"${e.verdict}"`}}),r.push({type:"div",props:{style:{display:"flex",flex:1}}}),r.push(ct(Uu)),{type:"div",props:{style:{display:"flex",flexDirection:"column",width:"100%",height:"100%",backgroundImage:"linear-gradient(135deg, #b1370f 0%, #f97316 55%, #fbbf24 100%)",padding:"64px 72px",fontFamily:"Inter",gap:"28px"},children:r}}}var kt,Ln,tR,Uu,Hu=N(()=>{"use strict";us();kt="#ffffff",Ln="rgba(255,255,255,0.7)",tR="rgba(255,255,255,0.18)",Uu={markStroke:kt,wordmarkFg:kt}});var Xu={};he(Xu,{render:()=>aR});function Nn(e,t){return{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"4px"},children:[{type:"span",props:{style:{fontSize:"34px",fontWeight:800,color:Bt,fontFamily:"JetBrains Mono"},children:e}},{type:"span",props:{style:{fontSize:"14px",color:gs,textTransform:"uppercase",letterSpacing:"0.16em",fontWeight:600},children:t}}]}}}function iR(e){let t=e.reduce((s,n)=>s+n.value,0)||1;return{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"8px"},children:[{type:"div",props:{style:{display:"flex",width:"100%",height:"10px",borderRadius:"5px",backgroundColor:oR,overflow:"hidden"},children:e.map(s=>({type:"div",props:{style:{display:"flex",width:`${s.value/t*100}%`,height:"100%",backgroundColor:s.color}}}))}},{type:"div",props:{style:{display:"flex",justifyContent:"space-between",fontSize:"12px",color:gs,fontWeight:600,textTransform:"uppercase",letterSpacing:"0.14em"},children:e.map(s=>({type:"span",props:{style:{display:"flex"},children:`${s.label} \xB7 ${s.value}`}}))}}]}}}function aR(e){let t=[dt(e.sessionDate),e.durationLabel].filter(Boolean).join(" \xB7 "),s=e.costDollars??"$\u2014",n=typeof e.cachedTokens=="number"&&e.cachedTokens>=1e5?`+ ${lt(e.cachedTokens)} cached`:null,r=[at(Wu,"session receipt"),{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"8px"},children:[{type:"div",props:{style:{fontSize:"40px",fontWeight:700,color:Bt,lineHeight:1.15,wordBreak:"break-word"},children:e.sessionTitle}},{type:"div",props:{style:{fontSize:"18px",color:gs},children:t}}]}},{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"flex-start",padding:"8px 0"},children:[{type:"span",props:{style:{fontSize:"14px",color:gs,textTransform:"uppercase",letterSpacing:"0.24em",fontWeight:700},children:"session cost"}},{type:"span",props:{style:{fontSize:"180px",fontWeight:800,color:Bt,fontFamily:"JetBrains Mono",lineHeight:1,letterSpacing:"-0.04em"},children:s}}]}},{type:"div",props:{style:{display:"flex",width:"100%",height:"1px",backgroundColor:rR}}},{type:"div",props:{style:{display:"flex",justifyContent:"space-between",gap:"24px"},children:[Nn(String(e.messageCount),"msgs"),Nn(String(e.filesReferenced),"files"),Nn(String(e.toolCallCount),"tools"),Nn(e.model??"\u2014","model")]}},iR([{value:e.messageCount,color:xt,label:"msgs"},{value:e.filesReferenced,color:"#0a0a0a",label:"files"},{value:e.toolCallCount,color:"#5a6068",label:"tools"}])];return n&&r.push({type:"div",props:{style:{fontSize:"14px",color:gs,fontFamily:"JetBrains Mono",opacity:.85},children:n}}),e.verdict&&r.push({type:"div",props:{style:{display:"flex",fontSize:"22px",fontStyle:"italic",color:Bt,borderLeft:`3px solid ${xt}`,paddingLeft:"20px",marginTop:"8px"},children:`"${e.verdict}"`}}),r.push({type:"div",props:{style:{display:"flex",flex:1}}}),r.push(ct(Wu)),{type:"div",props:{style:{display:"flex",flexDirection:"column",width:"100%",height:"100%",backgroundColor:nR,padding:"64px 72px",fontFamily:"Inter",gap:"24px"},children:r}}}var nR,Bt,gs,rR,oR,Wu,Ju=N(()=>{"use strict";us();nR="#f6f7f9",Bt="#0a0a0a",gs="#5a6068",rR="#e3e6eb",oR="#dde1e7",Wu={markStroke:Bt,wordmarkFg:Bt}});var Gu={};he(Gu,{render:()=>dR});function Fo(e,t){return{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:"8px",flex:1},children:[{type:"div",props:{style:{fontSize:"52px",fontWeight:700,color:An},children:t}},{type:"div",props:{style:{fontSize:"16px",color:Wt,textTransform:"uppercase",letterSpacing:"2px"},children:e}}]}}}function jo(e,t){return{type:"div",props:{style:{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"16px 24px",backgroundColor:Uo,borderRadius:"12px"},children:[{type:"span",props:{style:{fontSize:"18px",color:Wt},children:e}},{type:"span",props:{style:{fontSize:"20px",fontWeight:700,color:An},children:t}}]}}}function dR(e){let t=Math.round(e.healthScore),s=[{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:"8px"},children:[{type:"div",props:{style:{fontSize:"16px",color:Wt,textTransform:"uppercase",letterSpacing:"6px"},children:"YOUR MONTH IN CODE"}},{type:"div",props:{style:{fontSize:"44px",fontWeight:700,color:An},children:e.month}}]}},{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:"12px",backgroundColor:Uo,borderRadius:"16px",padding:"28px",border:`1px solid ${Ht}40`},children:[{type:"div",props:{style:{fontSize:"16px",color:lR,textTransform:"uppercase",letterSpacing:"2px"},children:"You are a"}},{type:"div",props:{style:{fontSize:"36px",fontWeight:700,color:Ht},children:e.archetype}}]}},{type:"div",props:{style:{display:"flex",gap:"16px",width:"100%"},children:[Fo("Recalls",String(e.totalRecalls)),Fo("Tokens piped",e.totalTokensPiped>999999?`${(e.totalTokensPiped/1e6).toFixed(1)}M`:e.totalTokensPiped.toLocaleString()),Fo("Sessions",String(e.sessionsIndexed))]}},{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"8px",width:"100%"},children:[{type:"div",props:{style:{fontSize:"14px",color:Wt,textTransform:"uppercase",letterSpacing:"2px",marginBottom:"4px"},children:"Highlights"}},jo("Most recalled",e.mostRecalledSession),jo("Biggest recall",`${e.biggestRecallTokens.toLocaleString()} tokens`),jo("Peak hours",e.mostActiveHours)]}},{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:"12px"},children:[{type:"div",props:{style:{width:"100px",height:"100px",borderRadius:"50%",border:`6px solid ${Ht}`,display:"flex",alignItems:"center",justifyContent:"center"},children:{type:"span",props:{style:{fontSize:"32px",fontWeight:700,color:Ht},children:`${t}`}}}},{type:"div",props:{style:{fontSize:"14px",color:Wt,textTransform:"uppercase",letterSpacing:"2px"},children:"Memory health"}}]}}];return e.verdict&&s.push({type:"div",props:{style:{backgroundColor:Uo,borderLeft:`3px solid ${Ht}`,borderRadius:"8px",padding:"20px 24px",fontSize:"20px",color:An,fontStyle:"italic"},children:`"${e.verdict}"`}}),s.push({type:"div",props:{style:{display:"flex",alignItems:"center",justifyContent:"center",gap:"12px",marginTop:"auto"},children:[{type:"span",props:{style:{fontSize:"22px",fontWeight:700,color:Ht},children:"Claude Recall"}},{type:"span",props:{style:{fontSize:"18px",color:Wt},children:"clauderecall.com"}}]}}),{type:"div",props:{style:{display:"flex",flexDirection:"column",width:"100%",height:"100%",backgroundColor:cR,padding:"72px 64px",fontFamily:"Inter",gap:"36px"},children:s}}}var cR,Uo,An,Wt,Ht,lR,Yu=N(()=>{"use strict";cR="#0b0c0f",Uo="#15171c",An="#e7e9ee",Wt="#8b9098",Ht="#f97316",lR="#fb923c"});var Jo,ap,z,k,te,De,cp,lp,dp,up,Go,Yo,ut=N(()=>{"use strict";Jo=[String.raw` ___ _ _ _ _ ___ ___ ___ ___ ___ _ _ _ `,String.raw` / __| | /_\ | | | | \| __| | _ \ __/ __| /_\ | | | | `,String.raw`| (__| |__ / _ \| |_| | |) | _| | / _| (__ / _ \| |__| |__ `,String.raw` \___|____/_/ \_\\___/|___/|___| |_|_\___\___/_/ \_\____|____|`],ap=Jo[0]?.length??64,z="#f97316",k="#8b9098",te="#10b981",De="#f59e0b",cp="#60a5fa",lp="#34d399",dp="#c084fc",up="CLAUDE RECALL",Go=100,Yo=30});import{useEffect as BR,useState as HR}from"react";import{Box as pp,Text as ge}from"ink";import{createRequire as WR}from"node:module";import{existsSync as XR}from"node:fs";import{jsx as ie,jsxs as gp}from"react/jsx-runtime";function mp({cols:e}){let[t,s]=HR(YR);BR(()=>{let r=!1,o=Q(),i=0,a=0;if(XR(se))try{let l=_().prepare(`SELECT
|
|
787
|
+
WHERE s.project_id = ?`).get(e),h=f.total>0?f.tagged/f.total:0,E=ls(h),b=Math.round((o*.2+a*.25+u*.15+g*.2+E*.2)*100);return{projectId:e,projectName:s.name,score:b,breakdown:{sessionCount:{raw:r,score:o,weight:.2},recency:{daysSinceLastSession:Math.round(i),score:a,weight:.25},fragmentation:{avgMessages:Math.round(l*10)/10,score:u,weight:.15},searchCoverage:{ratio:Math.round(m*100)/100,score:g,weight:.2},tagCoverage:{ratio:Math.round(h*100)/100,score:E,weight:.2}}}}function No(e){let s=_().prepare("SELECT id FROM projects WHERE name = ?").get(e);return s?Lo(s.id):null}function Ao(){let t=_().prepare("SELECT id FROM projects ORDER BY name").all(),s=[];for(let n of t){let r=Lo(n.id);r&&s.push(r)}return s}var Oo=N(()=>{"use strict";w()});function Xu(e,t){return{type:"svg",props:{width:e,height:e,viewBox:"0 0 64 64",style:{display:"flex"},children:[{type:"line",props:{x1:14,y1:16,x2:46,y2:16,stroke:t,strokeWidth:3,strokeLinecap:"round"}},{type:"line",props:{x1:14,y1:24,x2:38,y2:24,stroke:t,strokeWidth:3,strokeLinecap:"round"}},{type:"line",props:{x1:10,y1:32,x2:54,y2:32,stroke:xt,strokeWidth:5,strokeLinecap:"round"}},{type:"line",props:{x1:14,y1:40,x2:42,y2:40,stroke:t,strokeWidth:3,strokeLinecap:"round"}},{type:"line",props:{x1:14,y1:48,x2:36,y2:48,stroke:t,strokeWidth:3,strokeLinecap:"round"}}]}}}function at(e,t){return{type:"div",props:{style:{display:"flex",alignItems:"center",justifyContent:"space-between",width:"100%"},children:[{type:"div",props:{style:{display:"flex",alignItems:"center",gap:"18px"},children:[Xu(72,e.markStroke),{type:"span",props:{style:{fontSize:"36px",fontWeight:700,color:e.wordmarkFg,letterSpacing:"-0.01em"},children:"Claude Recall"}}]}},{type:"span",props:{style:{fontSize:"17px",color:e.wordmarkFg,opacity:.6,textTransform:"uppercase",letterSpacing:"0.18em",fontWeight:600},children:t}}]}}}function ct(e){return{type:"div",props:{style:{display:"flex",alignItems:"center",justifyContent:"space-between",width:"100%"},children:[{type:"div",props:{style:{display:"flex",alignItems:"center",gap:"14px"},children:[Xu(52,e.markStroke),{type:"span",props:{style:{fontSize:"24px",fontWeight:700,color:e.wordmarkFg},children:"clauderecall.com"}}]}},{type:"span",props:{style:{fontSize:"20px",color:e.wordmarkFg,opacity:.7,fontWeight:500},children:"local memory for Claude Code"}}]}}}function rR(e){return e>=1e9?`${(e/1e9).toFixed(2).replace(/\.?0+$/,"")}B`:e>=1e6?`${(e/1e6).toFixed(2).replace(/\.?0+$/,"")}M`:e>=1e3?`${(e/1e3).toFixed(1).replace(/\.0$/,"")}k`:String(e)}function oR(e){return e.toLocaleString("en-US")}function lt(e){return e<1e3?String(e):`${rR(e)} (${oR(e)})`}function dt(e){return new Date(e).toLocaleDateString("en-US",{month:"long",day:"numeric",year:"numeric"})}var xt,us=N(()=>{"use strict";xt="#f97316"});var Gu={};he(Gu,{render:()=>cR});function kn(e,t){return{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"4px"},children:[{type:"span",props:{style:{fontSize:"34px",fontWeight:700,color:ps,fontFamily:"JetBrains Mono"},children:e}},{type:"span",props:{style:{fontSize:"14px",color:Cn,textTransform:"uppercase",letterSpacing:"0.16em"},children:t}}]}}}function cR(e){let s=[dt(e.sessionDate)];e.durationLabel&&s.push(e.durationLabel);let n=s.join(" \xB7 "),r=e.costDollars??"$\u2014",o=typeof e.cachedTokens=="number"&&e.cachedTokens>=1e5?`+ ${lt(e.cachedTokens)} cached`:null,i=[at(Ju,"session receipt"),{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"8px"},children:[{type:"div",props:{style:{fontSize:"44px",fontWeight:700,color:ps,lineHeight:1.15,wordBreak:"break-word"},children:e.sessionTitle}},{type:"div",props:{style:{fontSize:"20px",color:Cn},children:n}}]}},{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",padding:"32px 0"},children:[{type:"span",props:{style:{fontSize:"180px",fontWeight:700,color:xt,fontFamily:"JetBrains Mono",lineHeight:1,letterSpacing:"-0.04em"},children:r}},{type:"span",props:{style:{marginTop:"16px",fontSize:"16px",color:Cn,textTransform:"uppercase",letterSpacing:"0.24em",fontWeight:600},children:"session cost"}}]}},{type:"div",props:{style:{display:"flex",width:"100%",height:"1px",backgroundColor:aR}}},{type:"div",props:{style:{display:"flex",justifyContent:"space-between",gap:"24px"},children:[kn(String(e.messageCount),"msgs"),kn(String(e.filesReferenced),"files"),kn(String(e.toolCallCount),"tools"),kn(e.model??"\u2014","model")]}}];return o&&i.push({type:"div",props:{style:{fontSize:"14px",color:Cn,fontFamily:"JetBrains Mono",opacity:.7},children:o}}),e.verdict&&i.push({type:"div",props:{style:{display:"flex",fontSize:"24px",fontStyle:"italic",color:ps,borderLeft:`3px solid ${xt}`,paddingLeft:"20px",marginTop:"8px"},children:`"${e.verdict}"`}}),i.push({type:"div",props:{style:{display:"flex",flex:1}}}),i.push(ct(Ju)),{type:"div",props:{style:{display:"flex",flexDirection:"column",width:"100%",height:"100%",backgroundColor:iR,padding:"64px 72px",fontFamily:"Inter",gap:"28px"},children:i}}}var iR,ps,Cn,aR,Ju,Yu=N(()=>{"use strict";us();iR="#1a1b1e",ps="#e7e9ee",Cn="#8b9098",aR="#2a2c33",Ju={markStroke:ps,wordmarkFg:ps}});var qu={};he(qu,{render:()=>uR});function Ln(e,t){return{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"4px"},children:[{type:"span",props:{style:{fontSize:"34px",fontWeight:700,color:ms,fontFamily:"JetBrains Mono"},children:e}},{type:"span",props:{style:{fontSize:"14px",color:Nn,fontFamily:"JetBrains Mono",textTransform:"lowercase"},children:`// ${t}`}}]}}}function uR(e){let t=[dt(e.sessionDate),e.durationLabel].filter(Boolean).join(" \xB7 "),s=e.costDollars??"$\u2014",n=typeof e.cachedTokens=="number"&&e.cachedTokens>=1e5?`# ${lt(e.cachedTokens)} tokens cache-replayed (free)`:null,r=[at(zu,"recall.cli/share"),{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"6px"},children:[{type:"div",props:{style:{fontSize:"18px",color:Wo,fontFamily:"JetBrains Mono"},children:"$ recall share --session"}},{type:"div",props:{style:{fontSize:"40px",fontWeight:700,color:ms,lineHeight:1.15,wordBreak:"break-word",fontFamily:"JetBrains Mono"},children:e.sessionTitle}},{type:"div",props:{style:{fontSize:"18px",color:Nn,fontFamily:"JetBrains Mono"},children:t}}]}},{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"flex-start",padding:"12px 0"},children:[{type:"span",props:{style:{fontSize:"20px",color:Nn,fontFamily:"JetBrains Mono"},children:"> total_cost:"}},{type:"span",props:{style:{fontSize:"180px",fontWeight:700,color:Wo,fontFamily:"JetBrains Mono",lineHeight:1,letterSpacing:"-0.04em"},children:s}}]}},{type:"div",props:{style:{display:"flex",width:"100%",height:"1px",backgroundColor:dR}}},{type:"div",props:{style:{display:"flex",justifyContent:"space-between",gap:"24px"},children:[Ln(String(e.messageCount),"msgs"),Ln(String(e.filesReferenced),"files"),Ln(String(e.toolCallCount),"tools"),Ln(e.model??"\u2014","model")]}}];return n&&r.push({type:"div",props:{style:{fontSize:"14px",color:Nn,fontFamily:"JetBrains Mono",opacity:.7},children:n}}),e.verdict&&r.push({type:"div",props:{style:{display:"flex",fontSize:"22px",color:ms,fontFamily:"JetBrains Mono",borderLeft:`3px solid ${Wo}`,paddingLeft:"20px",marginTop:"8px"},children:`// ${e.verdict}`}}),r.push({type:"div",props:{style:{display:"flex",flex:1}}}),r.push(ct(zu)),{type:"div",props:{style:{display:"flex",flexDirection:"column",width:"100%",height:"100%",backgroundColor:lR,padding:"64px 72px",fontFamily:"Inter",gap:"28px"},children:r}}}var lR,ms,Nn,dR,Wo,zu,Ku=N(()=>{"use strict";us();lR="#0d0d0f",ms="#e1e7ee",Nn="#6b7480",dR="#1f2229",Wo="#7ee787",zu={markStroke:ms,wordmarkFg:ms}});var Zu={};he(Zu,{render:()=>mR});function An(e,t){return{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"4px"},children:[{type:"span",props:{style:{fontSize:"34px",fontWeight:700,color:kt,fontFamily:"JetBrains Mono"},children:e}},{type:"span",props:{style:{fontSize:"14px",color:On,textTransform:"uppercase",letterSpacing:"0.16em",fontWeight:600},children:t}}]}}}function mR(e){let t=[dt(e.sessionDate),e.durationLabel].filter(Boolean).join(" \xB7 "),s=e.costDollars??"$\u2014",n=typeof e.cachedTokens=="number"&&e.cachedTokens>=1e5?`+ ${lt(e.cachedTokens)} cached`:null,r=[at(Vu,"session receipt"),{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"8px"},children:[{type:"div",props:{style:{fontSize:"44px",fontWeight:700,color:kt,lineHeight:1.15,wordBreak:"break-word"},children:e.sessionTitle}},{type:"div",props:{style:{fontSize:"20px",color:On},children:t}}]}},{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",padding:"32px 0"},children:[{type:"span",props:{style:{fontSize:"180px",fontWeight:700,color:kt,fontFamily:"JetBrains Mono",lineHeight:1,letterSpacing:"-0.04em"},children:s}},{type:"span",props:{style:{marginTop:"16px",fontSize:"16px",color:On,textTransform:"uppercase",letterSpacing:"0.24em",fontWeight:600},children:"session cost"}}]}},{type:"div",props:{style:{display:"flex",width:"100%",height:"1px",backgroundColor:pR}}},{type:"div",props:{style:{display:"flex",justifyContent:"space-between",gap:"24px"},children:[An(String(e.messageCount),"msgs"),An(String(e.filesReferenced),"files"),An(String(e.toolCallCount),"tools"),An(e.model??"\u2014","model")]}}];return n&&r.push({type:"div",props:{style:{fontSize:"14px",color:On,fontFamily:"JetBrains Mono",opacity:.85},children:n}}),e.verdict&&r.push({type:"div",props:{style:{display:"flex",fontSize:"24px",fontStyle:"italic",color:kt,borderLeft:`3px solid ${kt}`,paddingLeft:"20px",marginTop:"8px"},children:`"${e.verdict}"`}}),r.push({type:"div",props:{style:{display:"flex",flex:1}}}),r.push(ct(Vu)),{type:"div",props:{style:{display:"flex",flexDirection:"column",width:"100%",height:"100%",backgroundImage:"linear-gradient(135deg, #b1370f 0%, #f97316 55%, #fbbf24 100%)",padding:"64px 72px",fontFamily:"Inter",gap:"28px"},children:r}}}var kt,On,pR,Vu,Qu=N(()=>{"use strict";us();kt="#ffffff",On="rgba(255,255,255,0.7)",pR="rgba(255,255,255,0.18)",Vu={markStroke:kt,wordmarkFg:kt}});var tp={};he(tp,{render:()=>ER});function vn(e,t){return{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"4px"},children:[{type:"span",props:{style:{fontSize:"34px",fontWeight:800,color:Bt,fontFamily:"JetBrains Mono"},children:e}},{type:"span",props:{style:{fontSize:"14px",color:gs,textTransform:"uppercase",letterSpacing:"0.16em",fontWeight:600},children:t}}]}}}function hR(e){let t=e.reduce((s,n)=>s+n.value,0)||1;return{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"8px"},children:[{type:"div",props:{style:{display:"flex",width:"100%",height:"10px",borderRadius:"5px",backgroundColor:_R,overflow:"hidden"},children:e.map(s=>({type:"div",props:{style:{display:"flex",width:`${s.value/t*100}%`,height:"100%",backgroundColor:s.color}}}))}},{type:"div",props:{style:{display:"flex",justifyContent:"space-between",fontSize:"12px",color:gs,fontWeight:600,textTransform:"uppercase",letterSpacing:"0.14em"},children:e.map(s=>({type:"span",props:{style:{display:"flex"},children:`${s.label} \xB7 ${s.value}`}}))}}]}}}function ER(e){let t=[dt(e.sessionDate),e.durationLabel].filter(Boolean).join(" \xB7 "),s=e.costDollars??"$\u2014",n=typeof e.cachedTokens=="number"&&e.cachedTokens>=1e5?`+ ${lt(e.cachedTokens)} cached`:null,r=[at(ep,"session receipt"),{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"8px"},children:[{type:"div",props:{style:{fontSize:"40px",fontWeight:700,color:Bt,lineHeight:1.15,wordBreak:"break-word"},children:e.sessionTitle}},{type:"div",props:{style:{fontSize:"18px",color:gs},children:t}}]}},{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"flex-start",padding:"8px 0"},children:[{type:"span",props:{style:{fontSize:"14px",color:gs,textTransform:"uppercase",letterSpacing:"0.24em",fontWeight:700},children:"session cost"}},{type:"span",props:{style:{fontSize:"180px",fontWeight:800,color:Bt,fontFamily:"JetBrains Mono",lineHeight:1,letterSpacing:"-0.04em"},children:s}}]}},{type:"div",props:{style:{display:"flex",width:"100%",height:"1px",backgroundColor:fR}}},{type:"div",props:{style:{display:"flex",justifyContent:"space-between",gap:"24px"},children:[vn(String(e.messageCount),"msgs"),vn(String(e.filesReferenced),"files"),vn(String(e.toolCallCount),"tools"),vn(e.model??"\u2014","model")]}},hR([{value:e.messageCount,color:xt,label:"msgs"},{value:e.filesReferenced,color:"#0a0a0a",label:"files"},{value:e.toolCallCount,color:"#5a6068",label:"tools"}])];return n&&r.push({type:"div",props:{style:{fontSize:"14px",color:gs,fontFamily:"JetBrains Mono",opacity:.85},children:n}}),e.verdict&&r.push({type:"div",props:{style:{display:"flex",fontSize:"22px",fontStyle:"italic",color:Bt,borderLeft:`3px solid ${xt}`,paddingLeft:"20px",marginTop:"8px"},children:`"${e.verdict}"`}}),r.push({type:"div",props:{style:{display:"flex",flex:1}}}),r.push(ct(ep)),{type:"div",props:{style:{display:"flex",flexDirection:"column",width:"100%",height:"100%",backgroundColor:gR,padding:"64px 72px",fontFamily:"Inter",gap:"24px"},children:r}}}var gR,Bt,gs,fR,_R,ep,sp=N(()=>{"use strict";us();gR="#f6f7f9",Bt="#0a0a0a",gs="#5a6068",fR="#e3e6eb",_R="#dde1e7",ep={markStroke:Bt,wordmarkFg:Bt}});var np={};he(np,{render:()=>yR});function Xo(e,t){return{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:"8px",flex:1},children:[{type:"div",props:{style:{fontSize:"52px",fontWeight:700,color:In},children:t}},{type:"div",props:{style:{fontSize:"16px",color:Wt,textTransform:"uppercase",letterSpacing:"2px"},children:e}}]}}}function Jo(e,t){return{type:"div",props:{style:{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"16px 24px",backgroundColor:Go,borderRadius:"12px"},children:[{type:"span",props:{style:{fontSize:"18px",color:Wt},children:e}},{type:"span",props:{style:{fontSize:"20px",fontWeight:700,color:In},children:t}}]}}}function yR(e){let t=Math.round(e.healthScore),s=[{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:"8px"},children:[{type:"div",props:{style:{fontSize:"16px",color:Wt,textTransform:"uppercase",letterSpacing:"6px"},children:"YOUR MONTH IN CODE"}},{type:"div",props:{style:{fontSize:"44px",fontWeight:700,color:In},children:e.month}}]}},{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:"12px",backgroundColor:Go,borderRadius:"16px",padding:"28px",border:`1px solid ${Ht}40`},children:[{type:"div",props:{style:{fontSize:"16px",color:SR,textTransform:"uppercase",letterSpacing:"2px"},children:"You are a"}},{type:"div",props:{style:{fontSize:"36px",fontWeight:700,color:Ht},children:e.archetype}}]}},{type:"div",props:{style:{display:"flex",gap:"16px",width:"100%"},children:[Xo("Recalls",String(e.totalRecalls)),Xo("Tokens piped",e.totalTokensPiped>999999?`${(e.totalTokensPiped/1e6).toFixed(1)}M`:e.totalTokensPiped.toLocaleString()),Xo("Sessions",String(e.sessionsIndexed))]}},{type:"div",props:{style:{display:"flex",flexDirection:"column",gap:"8px",width:"100%"},children:[{type:"div",props:{style:{fontSize:"14px",color:Wt,textTransform:"uppercase",letterSpacing:"2px",marginBottom:"4px"},children:"Highlights"}},Jo("Most recalled",e.mostRecalledSession),Jo("Biggest recall",`${e.biggestRecallTokens.toLocaleString()} tokens`),Jo("Peak hours",e.mostActiveHours)]}},{type:"div",props:{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:"12px"},children:[{type:"div",props:{style:{width:"100px",height:"100px",borderRadius:"50%",border:`6px solid ${Ht}`,display:"flex",alignItems:"center",justifyContent:"center"},children:{type:"span",props:{style:{fontSize:"32px",fontWeight:700,color:Ht},children:`${t}`}}}},{type:"div",props:{style:{fontSize:"14px",color:Wt,textTransform:"uppercase",letterSpacing:"2px"},children:"Memory health"}}]}}];return e.verdict&&s.push({type:"div",props:{style:{backgroundColor:Go,borderLeft:`3px solid ${Ht}`,borderRadius:"8px",padding:"20px 24px",fontSize:"20px",color:In,fontStyle:"italic"},children:`"${e.verdict}"`}}),s.push({type:"div",props:{style:{display:"flex",alignItems:"center",justifyContent:"center",gap:"12px",marginTop:"auto"},children:[{type:"span",props:{style:{fontSize:"22px",fontWeight:700,color:Ht},children:"Claude Recall"}},{type:"span",props:{style:{fontSize:"18px",color:Wt},children:"clauderecall.com"}}]}}),{type:"div",props:{style:{display:"flex",flexDirection:"column",width:"100%",height:"100%",backgroundColor:bR,padding:"72px 64px",fontFamily:"Inter",gap:"36px"},children:s}}}var bR,Go,In,Wt,Ht,SR,rp=N(()=>{"use strict";bR="#0b0c0f",Go="#15171c",In="#e7e9ee",Wt="#8b9098",Ht="#f97316",SR="#fb923c"});var Vo,Ep,z,k,te,De,bp,Sp,yp,wp,Zo,Qo,ut=N(()=>{"use strict";Vo=[String.raw` ___ _ _ _ _ ___ ___ ___ ___ ___ _ _ _ `,String.raw` / __| | /_\ | | | | \| __| | _ \ __/ __| /_\ | | | | `,String.raw`| (__| |__ / _ \| |_| | |) | _| | / _| (__ / _ \| |__| |__ `,String.raw` \___|____/_/ \_\\___/|___/|___| |_|_\___\___/_/ \_\____|____|`],Ep=Vo[0]?.length??64,z="#f97316",k="#8b9098",te="#10b981",De="#f59e0b",bp="#60a5fa",Sp="#34d399",yp="#c084fc",wp="CLAUDE RECALL",Zo=100,Qo=30});import{useEffect as ZR,useState as QR}from"react";import{Box as Tp,Text as ge}from"ink";import{createRequire as e0}from"node:module";import{existsSync as t0}from"node:fs";import{jsx as ie,jsxs as xp}from"react/jsx-runtime";function Rp({cols:e}){let[t,s]=QR(r0);ZR(()=>{let r=!1,o=Q(),i=0,a=0;if(t0(se))try{let l=_().prepare(`SELECT
|
|
788
788
|
(SELECT COUNT(*) FROM sessions) AS sessions,
|
|
789
|
-
(SELECT COUNT(*) FROM projects) AS projects`).get();i=l.sessions,a=l.projects}catch{}return s(d=>({...d,daemon:o,sessions:i,projects:a})),Te().then(d=>{r||s(l=>({...l,tier:d.tier}))}).catch(()=>{}),()=>{r=!0}},[]);let n=e>=
|
|
789
|
+
(SELECT COUNT(*) FROM projects) AS projects`).get();i=l.sessions,a=l.projects}catch{}return s(d=>({...d,daemon:o,sessions:i,projects:a})),Te().then(d=>{r||s(l=>({...l,tier:d.tier}))}).catch(()=>{}),()=>{r=!0}},[]);let n=e>=Ep+2;return xp(Tp,{flexDirection:"column",children:[n?Vo.map((r,o)=>ie(ge,{color:z,bold:!0,children:r},o)):ie(ge,{color:z,bold:!0,children:wp}),ie(o0,{state:t})]})}function o0({state:e}){let t=ie(ge,{color:k,children:" \xB7 "});return xp(Tp,{children:[ie(ge,{color:k,children:"v"}),ie(ge,{color:z,children:n0}),t,ie(ge,{color:k,children:"daemon: "}),e.daemon?ie(ge,{color:te,children:`running 127.0.0.1:${e.daemon.port}`}):ie(ge,{color:De,children:"stopped"}),t,ie(ge,{color:k,children:"sessions: "}),ie(ge,{color:z,children:e.sessions}),e.projects>0?ie(ge,{color:k,children:` across ${e.projects} ${e.projects===1?"project":"projects"}`}):null,t,ie(ge,{color:k,children:"license: "}),e.tier==="pro"?ie(ge,{color:te,children:"Pro"}):ie(ge,{color:k,children:"Free"})]})}var s0,n0,r0,kp=N(()=>{"use strict";qe();P();w();de();ut();s0=e0(import.meta.url),n0=s0("../../../package.json").version,r0={daemon:null,sessions:0,projects:0,tier:"free"}});import{Box as et,Text as ae}from"ink";import{jsx as q,jsxs as $n}from"react/jsx-runtime";function a0(e,t,s){if(t<=s)return{start:0,end:t};let n=e-Math.floor(s/2);return n<0&&(n=0),n+s>t&&(n=t-s),{start:n,end:n+s}}function pt(e,t){if(t<=0)return"";let s=e.replace(/\s+/g," ").trim();return s.length<=t?s:s.slice(0,Math.max(0,t-1))+"\u2026"}function Cp({sessions:e,total:t,selected:s,width:n,height:r,loading:o,dbExists:i,filter:a,sortMode:d,groupByProject:l}){let u=Math.max(20,n-2),g=Math.max(1,r-1-1-2),f=a0(s,e.length,g),h=e.slice(f.start,f.end);if(!i)return $n(et,{flexDirection:"column",width:n,height:r,borderStyle:"round",borderColor:k,children:[q(ae,{color:z,bold:!0,children:" Sessions "}),q(et,{paddingX:1,paddingY:1,children:q(ae,{color:k,wrap:"wrap",children:"No sessions indexed yet. Quit (q) and run `recall index` first."})})]});let S=[i0[d],l?"grouped":""].filter(Boolean).join(" \xB7 "),R=o?" Sessions \xB7 loading\u2026 ":a?` Sessions \xB7 ${e.length} of ${t} match "${pt(a,18)}" \xB7 ${S} `:` Sessions \xB7 ${e.length} of ${t} \xB7 ${S} `,T=null;return l&&f.start>0&&(T=e[f.start-1]?.project_name??null),$n(et,{flexDirection:"column",width:n,height:r,borderStyle:"round",borderColor:k,children:[q(et,{width:u+2,paddingLeft:1,children:q(ae,{color:z,bold:!0,children:pt(R,u)})}),q(et,{flexDirection:"column",flexGrow:1,paddingX:1,children:h.length===0?q(ae,{color:k,children:a?"no matches.":"no sessions."}):h.flatMap((L,D)=>{let A=f.start+D,$=A===s,U=[];return l&&L.project_name!==T&&(U.push(q(l0,{project:L.project_name,width:u},`hdr-${L.project_name}-${A}`)),T=L.project_name),U.push(q(c0,{session:L,width:u,isSelected:$,indented:l},L.id)),U})}),q(et,{width:u+2,paddingLeft:1,children:q(ae,{color:k,children:e.length>0?pt(` ${s+1} / ${e.length}${f.end<e.length?" \xB7 scroll for more":""} `,u):""})})]})}function c0({session:e,width:t,isSelected:s,indented:n}){let r=J(e.started_at)||"",o=n?" ":"",i=s?"\u258C ":" ";if(n){let g=e.alias||e.auto_title||e.first_user_message||"(no title)",f=Math.min(14,Math.max(6,Math.floor(t*.25))),h=Math.max(10,t-i.length-o.length-f-1),E=pt(g,h),b=pt(r,f),S=Math.max(1,t-i.length-o.length-E.length-b.length);return $n(et,{children:[q(ae,{children:o}),q(ae,{color:s?z:k,bold:s,children:i}),q(ae,{color:s?z:"#fefce8",bold:s,children:E}),q(ae,{children:" ".repeat(S)}),q(ae,{color:k,children:b})]})}let a=e.project_name||"?",d=Math.min(28,Math.floor(t*.7)),l=Math.min(14,t-d-i.length-1),u=pt(a,d),p=pt(r,l),m=Math.max(1,t-i.length-u.length-p.length);return $n(et,{children:[q(ae,{color:s?z:k,bold:s,children:i}),q(ae,{color:s?z:"#67e8f9",bold:s,children:u}),q(ae,{children:" ".repeat(m)}),q(ae,{color:k,children:p})]})}function l0({project:e,width:t}){return q(et,{children:q(ae,{color:"#67e8f9",bold:!0,children:pt(`\u25BE ${e}`,t)})})}var i0,Lp=N(()=>{"use strict";v();ut();i0={recent:"recent",longest:"longest",busiest:"busiest project"}});import{useEffect as d0,useState as ei}from"react";function Np(e,t,s){let n=s?"":"AND is_sidechain = 0";return e.prepare(`SELECT uuid, role, type, timestamp, content_text
|
|
790
790
|
FROM messages
|
|
791
791
|
WHERE session_id = @id
|
|
792
792
|
${n}
|
|
793
793
|
ORDER BY timestamp DESC
|
|
794
|
-
LIMIT @limit`).all({id:t,limit:
|
|
795
|
-
`),u=l.slice(0,Math.max(1,a-1)),p=l.length-u.length;return fs(Pe,{flexDirection:"column",width:t,height:s,borderStyle:"round",borderColor:te,children:[fs(Pe,{paddingX:1,flexDirection:"column",children:[fe(Pe,{width:o,children:fe(mt,{color:te,bold:!0,wrap:"truncate-end",children:"Neighborhood"})}),fe(Pe,{width:o,children:fe(mt,{color:k,wrap:"truncate-end",children:`budget=${n} used=${d.budgetUsed} remaining=${d.budgetRemaining} truncated=${d.truncated.length}`})})]}),fs(Pe,{flexDirection:"column",paddingX:1,flexGrow:1,children:[u.map((m,g)=>fe(mt,{wrap:"truncate-end",children:m||" "},g)),p>0?fe(mt,{color:k,children:`\u2026 ${p} more line${p===1?"":"s"} (resize the terminal or use \`recall neighborhood ${e.id.slice(0,8)}\` for the full bundle)`}):null]})]})}var
|
|
794
|
+
LIMIT @limit`).all({id:t,limit:u0})}function Ap(e){let[t,s]=ei([]),[n,r]=ei(!1),[o,i]=ei(!1);return d0(()=>{if(!e){s([]),i(!1);return}r(!0);try{let a=_(),d=Np(a,e,!1),l=!1;d.length===0&&(d=Np(a,e,!0),l=d.length>0),s([...d].reverse()),i(l)}catch{s([]),i(!1)}finally{r(!1)}},[e]),{messages:t,loading:n,fallbackToSidechain:o}}var u0,Op=N(()=>{"use strict";w();u0=6});import{Box as $e,Text as Xe}from"ink";import{Fragment as f0,jsx as Z,jsxs as Jt}from"react/jsx-runtime";function p0(e){let t=(e.role??"").toLowerCase();return t==="user"?{label:"user",color:bp}:t==="assistant"?{label:"asst",color:Sp}:t==="tool"?{label:"tool",color:yp}:t==="system"?{label:"sys ",color:k}:e.type==="summary"?{label:"sum ",color:k}:{label:"----",color:k}}function m0(e,t){return e?e.replace(/\s+/g," ").trim().slice(0,t+1).replace(/.{1}$/,s=>e.length>t?"\u2026":s):"(empty)"}function vp({session:e,width:t,height:s}){let n=e?.id??null,{messages:r,loading:o,fallbackToSidechain:i}=Ap(n),a=Math.max(20,t-2),l=Math.max(4,s-2-2),u=Math.max(2,Math.floor(l/Math.max(1,r.length)));return e?Jt($e,{flexDirection:"column",width:t,height:s,borderStyle:"round",borderColor:k,children:[Jt($e,{paddingX:1,flexDirection:"column",children:[Z($e,{width:a,children:Z(Xe,{color:z,bold:!0,wrap:"truncate-end",children:e.alias||e.auto_title||e.first_user_message||"(no title)"})}),Z($e,{width:a,children:Z(Xe,{color:k,wrap:"truncate-end",children:`${e.id.slice(0,8)} \xB7 ${e.message_count} msgs \xB7 ${J(e.started_at)}`})})]}),Z($e,{flexDirection:"column",paddingX:1,flexGrow:1,children:o?Z(Xe,{color:k,children:"loading messages\u2026"}):r.length===0?Z(Xe,{color:k,children:"no messages indexed for this session."}):Jt(f0,{children:[i?Z(Xe,{color:k,children:"(showing sidechain / subagent messages)"}):null,r.map(p=>Z(g0,{message:p,width:a,perMessageLines:u},p.uuid))]})})]}):Jt($e,{flexDirection:"column",width:t,height:s,borderStyle:"round",borderColor:k,children:[Z($e,{paddingX:1,children:Z(Xe,{color:z,bold:!0,children:"Preview"})}),Z($e,{paddingX:1,paddingY:1,children:Z(Xe,{color:k,children:"Select a session on the left to preview."})})]})}function g0({message:e,width:t,perMessageLines:s}){let n=p0(e),r=Math.max(1,s-1),o=Math.max(20,t-2),i=r*o,a=m0(e.content_text,i);return Jt($e,{flexDirection:"column",marginBottom:0,children:[Jt($e,{children:[Z(Xe,{color:n.color,bold:!0,children:n.label}),Z(Xe,{color:k,children:` ${e.timestamp?J(e.timestamp):""}`})]}),Z($e,{width:t,children:Z(Xe,{wrap:"truncate-end",children:a})})]})}var Ip=N(()=>{"use strict";v();Op();ut()});import{useMemo as _0}from"react";import{Box as Pe,Text as mt}from"ink";import{jsx as fe,jsxs as fs}from"react/jsx-runtime";function h0(e,t){try{return{ok:!0,result:cn(e,{budget:t})}}catch(s){return{ok:!1,error:s instanceof Error?s.message:String(s)}}}function Mp({session:e,width:t,height:s,budget:n}){let r=_0(()=>e?h0(e.id,n):null,[e?.id,n]),o=Math.max(20,t-2),a=Math.max(4,s-2-2);if(!e)return fs(Pe,{flexDirection:"column",width:t,height:s,borderStyle:"round",borderColor:k,children:[fe(Pe,{paddingX:1,children:fe(mt,{color:z,bold:!0,children:"Neighborhood"})}),fe(Pe,{paddingX:1,paddingY:1,children:fe(mt,{color:k,children:"Select a session and press `n` to assemble its neighborhood."})})]});if(!r)return null;if(!r.ok)return fs(Pe,{flexDirection:"column",width:t,height:s,borderStyle:"round",borderColor:De,children:[fe(Pe,{paddingX:1,children:fe(mt,{color:De,bold:!0,children:"Neighborhood error"})}),fe(Pe,{paddingX:1,paddingY:1,children:fe(mt,{color:De,children:r.error})})]});let{result:d}=r,l=d.bundle.replace(/\n+$/,"").split(`
|
|
795
|
+
`),u=l.slice(0,Math.max(1,a-1)),p=l.length-u.length;return fs(Pe,{flexDirection:"column",width:t,height:s,borderStyle:"round",borderColor:te,children:[fs(Pe,{paddingX:1,flexDirection:"column",children:[fe(Pe,{width:o,children:fe(mt,{color:te,bold:!0,wrap:"truncate-end",children:"Neighborhood"})}),fe(Pe,{width:o,children:fe(mt,{color:k,wrap:"truncate-end",children:`budget=${n} used=${d.budgetUsed} remaining=${d.budgetRemaining} truncated=${d.truncated.length}`})})]}),fs(Pe,{flexDirection:"column",paddingX:1,flexGrow:1,children:[u.map((m,g)=>fe(mt,{wrap:"truncate-end",children:m||" "},g)),p>0?fe(mt,{color:k,children:`\u2026 ${p} more line${p===1?"":"s"} (resize the terminal or use \`recall neighborhood ${e.id.slice(0,8)}\` for the full bundle)`}):null]})]})}var Dp=N(()=>{"use strict";io();ut()});import{useEffect as E0,useState as $p}from"react";import{Box as _s,Text as gt}from"ink";import ti from"ink-text-input";import{jsx as Se,jsxs as si}from"react/jsx-runtime";function Pp({mode:e,query:t,onQueryChange:s,onSearchSubmit:n,onAliasSubmit:r,onTagSubmit:o,aliasInitial:i,toast:a}){let[d,l]=$p(""),[u,p]=$p("");return E0(()=>{e==="alias"&&l(i),e==="tag"&&p("")},[e,i]),a&&e==="normal"?Se(_s,{children:Se(gt,{color:De,children:`\u25B6 ${a}`})}):e==="search"?si(_s,{children:[Se(gt,{color:z,bold:!0,children:"/ "}),Se(ti,{value:t,onChange:s,onSubmit:n,placeholder:"filter sessions by project, alias, or opening message..."}),Se(gt,{color:k,children:" esc clear \xB7 enter confirm"})]}):e==="alias"?si(_s,{children:[Se(gt,{color:te,bold:!0,children:"alias \u203A "}),Se(ti,{value:d,onChange:l,onSubmit:m=>r(m),placeholder:"terminal-tab name for this session"}),Se(gt,{color:k,children:" esc cancel \xB7 enter save"})]}):e==="tag"?si(_s,{children:[Se(gt,{color:te,bold:!0,children:"tag \u203A "}),Se(ti,{value:u,onChange:p,onSubmit:m=>o(m),placeholder:"add a tag to this session"}),Se(gt,{color:k,children:" esc cancel \xB7 enter save"})]}):Se(_s,{children:Se(gt,{color:k,children:"\u2191\u2193 nav \xB7 / filter \xB7 enter view \xB7 a alias \xB7 t tag \xB7 s sort \xB7 g group \xB7 n bundle \xB7 o browser \xB7 ? help \xB7 q quit"})})}var Fp=N(()=>{"use strict";ut()});import{Box as _e,Text as W}from"ink";import{jsx as j,jsxs as ye}from"react/jsx-runtime";function jp({width:e,height:t}){return ye(_e,{flexDirection:"column",width:e,height:t,borderStyle:"round",borderColor:z,paddingX:2,paddingY:1,children:[ye(_e,{children:[j(W,{color:z,bold:!0,children:"Claude Recall TUI \u2014 Help"}),j(W,{color:k,children:" \xB7 press ? or esc to close"})]}),ye(_e,{marginTop:1,flexDirection:"column",children:[j(W,{color:te,bold:!0,children:"Navigation"}),b0.map(s=>j(ni,{row:s},s.key))]}),ye(_e,{marginTop:1,flexDirection:"column",children:[j(W,{color:te,bold:!0,children:"Actions on the selected session"}),S0.map(s=>j(ni,{row:s},s.key))]}),ye(_e,{marginTop:1,flexDirection:"column",children:[j(W,{color:te,bold:!0,children:"Right-pane views"}),y0.map(s=>j(ni,{row:s},s.key))]}),ye(_e,{marginTop:1,flexDirection:"column",children:[j(W,{color:te,bold:!0,children:"What is Neighborhood?"}),j(W,{color:k,wrap:"wrap",children:"Neighborhood assembles a ranked, token-budgeted bundle of context around the selected session: its parent and child sessions, citations (sessions it referenced), similar sessions (by embedding), and any related bug-pattern matches. Press `n` in the TUI to preview it on the right."})]}),ye(_e,{marginTop:1,flexDirection:"column",children:[j(W,{color:te,bold:!0,children:"Maintenance & diagnostics"}),j(W,{color:k,wrap:"wrap",children:"Two commands cover everything. Quit the TUI with `q` first, then:"}),ye(_e,{marginTop:1,children:[j(W,{color:k,children:" \u2022 "}),j(W,{children:"recall doctor"})]}),j(W,{color:k,wrap:"wrap",children:" Health report: DB size, WAL, FTS fragmentation, integrity, disk free."}),j(W,{color:k,wrap:"wrap",children:" Run this first if anything feels slow or wrong. Add --json for machine output."}),ye(_e,{marginTop:1,children:[j(W,{color:k,children:" \u2022 "}),j(W,{children:"recall optimize"})]}),j(W,{color:k,wrap:"wrap",children:" Maintenance: WAL truncate + FTS5 merge + planner stats. Safe while daemon is running."}),j(W,{color:k,wrap:"wrap",children:" Add --vacuum (with daemon stopped) to also reclaim disk pages from deleted rows."})]}),ye(_e,{marginTop:1,flexDirection:"column",children:[j(W,{color:te,bold:!0,children:"How to pipe a session into Claude"}),j(W,{color:k,wrap:"wrap",children:"Two recipes. Both work in any shell \u2014 exit this TUI with `q` first, then run them."}),ye(_e,{marginTop:1,children:[j(W,{color:k,children:" 1. "}),j(W,{children:"recall context <id> | claude"})]}),j(W,{color:k,wrap:"wrap",children:" Pipes one session's full transcript as markdown into a fresh `claude` chat."}),j(W,{color:k,wrap:"wrap",children:" Use when you want to continue exactly where one session left off."}),ye(_e,{marginTop:1,children:[j(W,{color:k,children:" 2. "}),j(W,{children:"recall neighborhood <id> | claude"})]}),j(W,{color:k,wrap:"wrap",children:" Pipes the bundle (parents + children + citations + similar + bug matches)."}),j(W,{color:k,wrap:"wrap",children:" Use when you want broader context, not just one transcript."})]})]})}function ni({row:e}){return ye(_e,{children:[j(_e,{width:16,children:j(W,{color:z,children:e.key})}),j(W,{color:k,wrap:"truncate-end",children:e.description})]})}var b0,S0,y0,Up=N(()=>{"use strict";ut();b0=[{key:"\u2191 / k",description:"move selection up"},{key:"\u2193 / j",description:"move selection down"},{key:"PgUp / PgDn",description:"jump 10 sessions"},{key:"/",description:"filter by project, alias, or first user message"},{key:"g",description:"toggle project-grouped view"},{key:"s",description:"cycle sort order: recent \u2192 longest \u2192 most-active"}],S0=[{key:"enter",description:"open the full transcript (`recall show <id>`) and exit the TUI"},{key:"a",description:"set or update the alias for the selected session"},{key:"t",description:"add a tag to the selected session"},{key:"o",description:"open the selected session in your browser (daemon required)"}],y0=[{key:"n",description:"toggle the right pane between Preview and Neighborhood"},{key:"?",description:"open this help screen"},{key:"esc",description:"dismiss filter or this help screen"},{key:"q / Ctrl-C",description:"quit the TUI"}]});import{useEffect as w0,useMemo as T0,useState as Pn}from"react";import{existsSync as R0}from"node:fs";function Bp(e){let[t,s]=Pn([]),[n,r]=Pn(!0),[o,i]=Pn(null),[a,d]=Pn(!0);return w0(()=>{if(!R0(se)){d(!1),r(!1);return}try{let p=_().prepare(`SELECT s.id, p.name AS project_name, s.started_at,
|
|
796
796
|
s.message_count, s.first_user_message,
|
|
797
797
|
NULLIF(sa.alias, '') AS alias,
|
|
798
798
|
s.auto_title
|
|
@@ -804,20 +804,20 @@ ${t.message}
|
|
|
804
804
|
AND COALESCE(s.auto_title, '') NOT LIKE '[output-index]%'
|
|
805
805
|
AND COALESCE(s.auto_title, '') NOT LIKE '[skill]%'
|
|
806
806
|
ORDER BY COALESCE(s.started_at, '') DESC
|
|
807
|
-
LIMIT @limit`).all({limit:
|
|
807
|
+
LIMIT @limit`).all({limit:x0});s(p)}catch(u){i(u instanceof Error?u.message:String(u))}finally{r(!1)}},[]),{sessions:T0(()=>{let u=e.trim().toLowerCase();return u?t.filter(p=>{let m=p.project_name?.toLowerCase()??"",g=p.first_user_message?.toLowerCase()??"";return m.includes(u)||g.includes(u)}):t},[t,e]),total:t.length,loading:n,error:o,dbExists:a}}var x0,Hp=N(()=>{"use strict";w();P();x0=200});import{useEffect as ri,useMemo as k0,useState as Je}from"react";import{Box as ft,Text as Fn,useApp as C0,useInput as L0}from"ink";import{spawn as N0}from"node:child_process";import{platform as jn}from"node:os";import{jsx as ce,jsxs as ii}from"react/jsx-runtime";function Xp(){return{cols:process.stdout.columns??100,rows:process.stdout.rows??30}}function M0(e){let t=jn()==="darwin"?"open":jn()==="win32"?"start":"xdg-open",s=jn()==="win32"?["",e]:[e];N0(t,s,{detached:!0,stdio:"ignore",shell:jn()==="win32"}).unref()}function Jp({onShowSession:e}){let{exit:t}=C0(),s=Xp(),[n,r]=Je(s.cols),[o,i]=Je(s.rows),[a,d]=Je(0),[l,u]=Je(""),[p,m]=Je("normal"),[g,f]=Je(null),[h,E]=Je("preview"),[b,S]=Je("recent"),[R,T]=Je(!1),[L,D]=Je(!1),{sessions:A,total:$,loading:U,error:y,dbExists:B}=Bp(l),M=k0(()=>{if(A.length===0)return A;let H=new Map;if(b==="busiest"||R)for(let O of A)H.set(O.project_name,(H.get(O.project_name)??0)+1);let G=[...A];if(b==="longest"?G.sort((O,Y)=>(Y.message_count??0)-(O.message_count??0)):b==="busiest"&&G.sort((O,Y)=>{let ke=H.get(O.project_name)??0,Ce=H.get(Y.project_name)??0;return Ce!==ke?Ce-ke:(Y.started_at??"").localeCompare(O.started_at??"")}),R){let O=new Map;for(let Ce of G){let Ye=O.get(Ce.project_name);Ye||(Ye=[],O.set(Ce.project_name,Ye)),Ye.push(Ce)}let Y=Array.from(O.keys()).sort((Ce,Ye)=>{if(b==="busiest"){let gi=H.get(Ce)??0,fi=H.get(Ye)??0;if(fi!==gi)return fi-gi}return Ce.localeCompare(Ye)}),ke=[];for(let Ce of Y)for(let Ye of O.get(Ce)??[])ke.push(Ye);return ke}return G},[A,b,R]);ri(()=>{let H=()=>{let G=Xp();r(G.cols),i(G.rows)};return process.stdout.on("resize",H),()=>{process.stdout.off("resize",H)}},[]),ri(()=>{if(M.length===0){a!==0&&d(0);return}a>=M.length&&d(M.length-1)},[M.length,a]),ri(()=>{if(!g)return;let H=setTimeout(()=>f(null),2500);return()=>clearTimeout(H)},[g]),L0((H,G)=>{if(p==="search"){G.escape&&(m("normal"),u(""));return}if(p==="alias"||p==="tag"){G.escape&&m("normal");return}if(L){(H==="?"||G.escape||H==="q")&&D(!1);return}if(H==="q"||G.ctrl&&H==="c"){t();return}if(H==="?"){D(!0);return}if(!(M.length===0&&H!=="/")){if(G.upArrow||H==="k"){d(O=>Math.max(0,O-1));return}if(G.downArrow||H==="j"){d(O=>Math.min(M.length-1,O+1));return}if(G.pageUp){d(O=>Math.max(0,O-10));return}if(G.pageDown){d(O=>Math.min(M.length-1,O+10));return}if(G.return){let O=M[a];O&&(e(O.id),t());return}if(H==="o"){let O=M[a];if(!O)return;let Y=Q();if(!Y){f("start the daemon first (`recall start`)");return}let ke=`http://127.0.0.1:${Y.port}/sessions/${O.id}`;M0(ke),f(`opened ${ke}`);return}if(H==="n"){E(Y=>Y==="neighborhood"?"preview":"neighborhood");let O=M[a];O&&f(h==="neighborhood"?"preview view":`neighborhood for ${O.id.slice(0,8)}`);return}if(H==="/"){m("search");return}if(H==="a"){M[a]&&m("alias");return}if(H==="t"){M[a]&&m("tag");return}if(H==="s"){S(O=>{let Y=oi.indexOf(O),ke=oi[(Y+1)%oi.length];return f(`sort: ${D0[ke]}`),ke});return}if(H==="g"){T(O=>(f(O?"flat view":"grouped by project"),!O));return}}});function V(H){m("normal");let G=M[a];if(!G)return;let O=H.trim();if(!O){f("alias unchanged (empty input)");return}try{Mt(G.id,O),f(`alias set: ${O}`)}catch(Y){f(`alias failed: ${Y instanceof Error?Y.message:"unknown"}`)}}function Ge(H){m("normal");let G=M[a];if(!G)return;let O=H.trim();if(!O){f("tag unchanged (empty input)");return}try{let Y=Za(G.id,O);f(Y.added?`tag added: ${Y.tag}`:`tag exists: ${Y.tag}`)}catch(Y){f(`tag failed: ${Y instanceof Error?Y.message:"unknown"}`)}}if(n<Zo||o<Qo)return ii(ft,{flexDirection:"column",padding:1,children:[ce(Fn,{color:De,children:"Terminal too small."}),ce(Fn,{color:k,children:`Resize to at least ${Zo} cols x ${Qo} rows. Current: ${n} x ${o}.`}),ce(Fn,{color:k,children:"Press q to quit."})]});let Fe=Math.max(10,o-I0-Wp),_t=Math.max(36,Math.floor(n*.4)),mi=n-_t-1,Bn=M[a]??null;return ii(ft,{flexDirection:"column",width:n,height:o,children:[ce(Rp,{cols:n}),ce(ft,{height:1}),y?ce(ft,{paddingX:1,children:ce(Fn,{color:De,children:`Error loading sessions: ${y}`})}):L?ce(ft,{height:Fe,children:ce(jp,{width:n,height:Fe})}):ii(ft,{flexDirection:"row",height:Fe,children:[ce(Cp,{sessions:M,total:$,selected:a,width:_t,height:Fe,loading:U,dbExists:B,filter:l,sortMode:b,groupByProject:R}),ce(ft,{width:1,height:Fe}),h==="neighborhood"?ce(Mp,{session:Bn,width:mi,height:Fe,budget:4e3}):ce(vp,{session:Bn,width:mi,height:Fe})]}),ce(ft,{height:Wp,width:n,paddingX:1,children:ce(Pp,{mode:p,query:l,onQueryChange:u,onSearchSubmit:()=>m("normal"),onAliasSubmit:V,onTagSubmit:Ge,aliasInitial:Bn?.alias??"",toast:g})})]})}var A0,O0,v0,Wp,I0,oi,D0,Gp=N(()=>{"use strict";kp();Lp();Ip();Dp();Fp();Up();Hp();qe();Et();Ar();ut();A0=4,O0=1,v0=1,Wp=1,I0=A0+O0+v0,oi=["recent","longest","busiest"];D0={recent:"most recent first",longest:"longest sessions first",busiest:"busiest project first"}});import{render as $0}from"ink";import{jsx as P0}from"react/jsx-runtime";async function Yp(){let e={showSessionId:null};return await $0(P0(Jp,{onShowSession:n=>{e.showSessionId=n}})).waitUntilExit(),e}var zp=N(()=>{"use strict";Gp()});var qp={};he(qp,{runTui:()=>j0});import{spawn as F0}from"node:child_process";async function j0(){if(!process.stdin.isTTY||!process.stdout.isTTY){console.error("recall tui requires an interactive terminal. Run it directly (no pipes)."),process.exitCode=1;return}let e=await Yp();if(!e.showSessionId)return;let t=process.argv[1];t&&await new Promise(s=>{let n=F0(process.execPath,[t,"show",e.showSessionId],{stdio:"inherit"});n.on("exit",()=>s()),n.on("error",()=>s())})}var Kp=N(()=>{"use strict";zp()});function ci(e,t={}){let s=t.limit??U0()??75e3,r=e.prepare("SELECT COUNT(*) AS n FROM vec_chunks").get()?.n??0;if(r>s)throw new ai(r,s)}function U0(){let e=process.env.RECALL_KNN_MAX_CORPUS;if(!e)return;let t=Number(e);if(!(!Number.isFinite(t)||t<=0||t>1e7))return Math.floor(t)}var ai,Vp=N(()=>{"use strict";ai=class extends Error{name="CorpusTooLargeError";code="CORPUS_TOO_LARGE";rowCount;limit;constructor(t,s){super(`semantic search refused: vec_chunks has ${t} rows (cap ${s}). An unindexed kNN over this corpus can pin the SQLite WAL for hours. Workarounds: (a) scope the search to a smaller project, (b) raise the cap via RECALL_KNN_MAX_CORPUS at your own risk, (c) wait for ANN indexing (tracked in the WAL death-spiral plan).`),this.rowCount=t,this.limit=s}}});import{Worker as B0}from"node:worker_threads";import{join as H0}from"node:path";function X0(){let e=process.env.RECALL_KNN_TIMEOUT_MS;if(!e)return;let t=Number(e);if(!(!Number.isFinite(t)||t<=0||t>36e5))return Math.floor(t)}async function di(e){let t=e.timeoutMs??X0()??W0,s=(e.workerFactory??J0)();return new Promise((n,r)=>{let o=setTimeout(()=>{s.terminate().catch(()=>{}),r(new li(t))},t);s.once("message",i=>{clearTimeout(o);let a=i;a&&a.ok===!0&&Array.isArray(a.hits)?n(a.hits):r(new Error(a?.error??"worker returned malformed reply")),s.terminate().catch(()=>{})}),s.once("error",i=>{clearTimeout(o),s.terminate().catch(()=>{}),r(i)}),s.postMessage({query:e.query,precomputedVector:e.precomputedVector,limit:e.limit})})}function J0(){let e=H0(ee(),"dist","daemon","query-worker.js");return new B0(e)}var li,W0,Zp=N(()=>{"use strict";Ke();li=class extends Error{constructor(s){super(`semantic kNN exceeded ${s}ms wall-clock budget -- query aborted`);this.timeoutMs=s}timeoutMs;name="KnnTimeoutError";code="KNN_TIMEOUT"},W0=1e4});var Qp={};he(Qp,{findSimilarSessions:()=>Y0,vectorSearch:()=>G0});async function G0(e,t=50){return ci(_()),di({query:e,limit:t})}async function Y0(e,t=10,s=.65){let n=_();ci(n);let r=n.prepare("SELECT rowid FROM chunk_meta WHERE session_id = ? ORDER BY rowid LIMIT 1").get(e);if(!r)return[];let o=n.prepare("SELECT embedding FROM vec_chunks WHERE rowid = ?").get(r.rowid);if(!o)return[];let i=await di({precomputedVector:o.embedding,limit:t*5}),a=new Map;for(let l of i){if(l.sessionId===e)continue;let u=a.get(l.sessionId);(u===void 0||l.distance<u)&&a.set(l.sessionId,l.distance)}let d=[];for(let[l,u]of a){let p=1-u;p>=s&&d.push({sessionId:l,similarity:p})}return d.sort((l,u)=>u.similarity-l.similarity),d.slice(0,t)}var em=N(()=>{"use strict";w();Vp();Zp()});import{createRequire as z0}from"module";import{Command as q0}from"commander";w();P();import{basename as sg}from"node:path";import{createReadStream as bm}from"node:fs";import{createInterface as Sm}from"node:readline";function Ss(e){return typeof e=="number"&&Number.isFinite(e)?e:0}function zn(e){let t=e?.usage;if(!t||typeof t!="object")return null;let s={inputTokens:Ss(t.input_tokens),outputTokens:Ss(t.output_tokens),cacheCreateTokens:Ss(t.cache_creation_input_tokens),cacheReadTokens:Ss(t.cache_read_input_tokens)};return s.inputTokens===0&&s.outputTokens===0&&s.cacheCreateTokens===0&&s.cacheReadTokens===0?null:s}var ym=/\x1B\[[0-9;]*[a-zA-Z]/g;function Gn(e){return e.replace(ym,"")}var Yn=12e3;function xi(e,t){if(e.length<=Yn)return e;let s=e.slice(0,Yn),n=e.length-Yn;return`${s}
|
|
808
808
|
|
|
809
|
-
\u27E8\u2026 ${n.toLocaleString()} more chars in ${t}; see raw JSONL for full content \u27E9`}function
|
|
810
|
-
`)}return""}function
|
|
809
|
+
\u27E8\u2026 ${n.toLocaleString()} more chars in ${t}; see raw JSONL for full content \u27E9`}function wm(e){try{return JSON.stringify(e,null,2)}catch{return String(e)}}function Tm(e){if(typeof e.content=="string")return e.content;if(Array.isArray(e.content)){let t=[];for(let s of e.content)if(s&&typeof s=="object"){let n=s;n.type==="text"&&typeof n.text=="string"?t.push(n.text):n.type==="image"&&t.push("[image]")}return t.join(`
|
|
810
|
+
`)}return""}function Rm(e){if(!e)return{text:"",toolNames:[]};if(typeof e.content=="string")return{text:Gn(e.content),toolNames:[]};if(!Array.isArray(e.content))return{text:"",toolNames:[]};let t=[],s=[];for(let n of e.content)if(!(!n||typeof n!="object")){if(n.type==="text"&&typeof n.text=="string"){t.push(Gn(n.text));continue}if(n.type==="tool_use"&&typeof n.name=="string"){s.push(n.name);let r=n.input!=null?wm(n.input):"",o=xi(r,"tool input");t.push(`\u26A1 **Tool call \xB7 \`${n.name}\`**
|
|
811
811
|
|
|
812
812
|
\`\`\`json
|
|
813
813
|
${o}
|
|
814
|
-
\`\`\``);continue}if(n.type==="tool_result"){let r=
|
|
814
|
+
\`\`\``);continue}if(n.type==="tool_result"){let r=Gn(Tm(n));if(r){let o=xi(r,"tool result");t.push(`**Tool result**
|
|
815
815
|
|
|
816
816
|
\`\`\`
|
|
817
817
|
${o}
|
|
818
818
|
\`\`\``)}else t.push("_(tool result was empty or image-only)_");continue}if(n.type==="image"){t.push("_(image)_");continue}t.push(`_(unknown block: ${n.type})_`)}return{text:t.join(`
|
|
819
819
|
|
|
820
|
-
`),toolNames:s}}async function*
|
|
820
|
+
`),toolNames:s}}async function*qn(e){let t=bm(e,{encoding:"utf8"}),s=Sm({input:t,crlfDelay:1/0});for await(let n of s){if(!n.trim())continue;let r;try{r=JSON.parse(n)}catch{continue}if(!r.uuid||!r.sessionId)continue;let{text:o,toolNames:i}=Rm(r.message);yield{uuid:r.uuid,parentUuid:r.parentUuid??null,sessionId:r.sessionId,type:r.type??"unknown",role:r.message?.role??null,timestamp:r.timestamp??null,isSidechain:r.isSidechain===!0,cwd:r.cwd??null,gitBranch:r.gitBranch??null,version:r.version??null,contentText:o,toolNames:i,raw:n,usage:zn(r.message),model:r.message?.model??null}}}v();var ki=[{name:"Anthropic API key",regex:/sk-ant-[a-zA-Z0-9_\-]{40,}/g,severity:"high"},{name:"OpenAI API key",regex:/sk-(?:proj-)?[a-zA-Z0-9]{32,}/g,severity:"high"},{name:"AWS access key ID",regex:/AKIA[0-9A-Z]{16}/g,severity:"high"},{name:"GitHub PAT",regex:/gh[pousr]_[A-Za-z0-9]{20,}/g,severity:"high"},{name:"Stripe live/test key",regex:/(?:sk|rk|pk)_(?:live|test)_[a-zA-Z0-9]{24,}/g,severity:"high"},{name:"Slack token",regex:/xox[abprs]-[A-Za-z0-9\-]{10,}/g,severity:"high"},{name:"Google API key",regex:/AIza[0-9A-Za-z_\-]{35}/g,severity:"high"},{name:"Private key block",regex:/-----BEGIN (?:RSA |DSA |EC |OPENSSH |ENCRYPTED )?PRIVATE KEY-----/g,severity:"high"},{name:"Apify token",regex:/apify_api_[A-Za-z0-9]{20,}/g,severity:"high"},{name:"Notion integration",regex:/(?:secret_|ntn_)[A-Za-z0-9]{40,}/g,severity:"high"},{name:"Vercel token",regex:/vercel_[A-Za-z0-9]{24,}/g,severity:"high"},{name:"Supabase service key",regex:/sbp_[A-Za-z0-9]{40,}/g,severity:"high"},{name:"SendGrid key",regex:/SG\.[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{20,}/g,severity:"high"},{name:"Mailgun key",regex:/key-[a-f0-9]{32}/g,severity:"high"},{name:"Twilio SID",regex:/AC[a-f0-9]{32}/g,severity:"high"},{name:"Discord bot token",regex:/[MN][A-Za-z\d]{23}\.[\w-]{6}\.[\w-]{27,38}/g,severity:"high"},{name:"npm token",regex:/npm_[A-Za-z0-9]{36}/g,severity:"high"},{name:"HuggingFace token",regex:/hf_[A-Za-z0-9]{30,}/g,severity:"high"},{name:"Replicate token",regex:/r8_[A-Za-z0-9]{32,}/g,severity:"high"},{name:"Figma token",regex:/figd_[A-Za-z0-9_\-]{30,}/g,severity:"high"},{name:"Linear key",regex:/lin_api_[A-Za-z0-9]{30,}/g,severity:"high"},{name:"DigitalOcean token",regex:/dop_v1_[a-f0-9]{64}/g,severity:"high"},{name:"Generic provider token",regex:/\b[a-z][a-z0-9]{2,20}_(?:api|pat|token|sk|pk|key|auth)_(?=[A-Za-z0-9_\-]*\d)[A-Za-z0-9_\-]{20,}\b/g,severity:"high"},{name:"Bearer token",regex:/\b[Bb]earer\s+[A-Za-z0-9_\-\.=]{24,}\b/g,severity:"medium"},{name:"Slack webhook URL",regex:/https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]{20,}/g,severity:"high"},{name:"Discord webhook URL",regex:/https:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[A-Za-z0-9_\-]{40,}/g,severity:"high"},{name:"Teams webhook URL",regex:/https:\/\/[a-zA-Z0-9.\-]+\.webhook\.office\.com\/webhookb2\/[A-Za-z0-9@_\-\/]{30,}/g,severity:"high"},{name:"Secret near keyword",regex:/\b(?:webhook[_\s\-]?secret|signing[_\s\-]?secret|webhook[_\s\-]?signing[_\s\-]?secret|api[_\s\-]?secret|client[_\s\-]?secret|private[_\s\-]?key|access[_\s\-]?token|auth[_\s\-]?token|api[_\s\-]?key)\b[\s\S]{0,200}?\b(?:[a-fA-F0-9]{32,}|[A-Za-z0-9+/_\-]{20,}(?:\.[A-Za-z0-9+/_\-]{10,}){1,2}|[A-Za-z0-9+/_\-]{40,}={0,2})\b/gi,severity:"high"},{name:"JWT",regex:/eyJ[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,}/g,severity:"medium"},{name:"URL with password",regex:/https?:\/\/[^:\s/@]+:[^@\s]{6,}@[^\s/]+/g,severity:"high"},{name:"Password assignment",regex:/(?<![A-Za-z])(?:password|passwd|pwd|secret|token|api[_\-]?key|access[_\-]?key|auth[_\-]?token|webhook[_\-]?secret|client[_\-]?secret|private[_\-]?key)\b\s*[:=]\s*["']?[A-Za-z0-9_\-+/=]{16,}/gi,severity:"high"}];function Ci(e){if(e.length<=8)return e.slice(0,2)+"\u2022".repeat(Math.max(0,e.length-4))+e.slice(-2);let t=e.slice(0,Math.min(6,Math.floor(e.length/3))),s=e.slice(-Math.min(4,Math.floor(e.length/4)));return`${t}${"\u2022".repeat(Math.max(3,e.length-t.length-s.length))}${s}`}function Yt(e){if(!e)return[];let t=new Set,s=[];for(let n of ki){n.regex.lastIndex=0;for(let r of e.matchAll(n.regex)){let o=r[0],i=`${n.name}::${Li(o)}`;t.has(i)||(t.add(i),s.push({pattern:n.name,maskedPreview:Ci(o),offset:r.index??0,severity:n.severity}))}}return s}function Li(e){let t=5381;for(let s=0;s<e.length;s++)t=(t<<5)+t+e.charCodeAt(s)|0;return(t>>>0).toString(36)}function we(e){if(!e)return{redacted:e,count:0};let t=e,s=0,n=new Set;for(let r of ki)r.regex.lastIndex=0,t=t.replace(r.regex,o=>{let i=`${r.name}::${Li(o)}`;return n.has(i)||(n.add(i),s+=1),`[REDACTED ${r.name}: ${Ci(o)}]`});return{redacted:t,count:s}}function Vn(e,t,s){e.prepare("DELETE FROM message_usage WHERE session_id = ?").run(t);let n=e.prepare(`
|
|
821
821
|
INSERT INTO message_usage (
|
|
822
822
|
message_uuid, session_id, model,
|
|
823
823
|
input_tokens, output_tokens, cache_create_tokens, cache_read_tokens,
|
|
@@ -849,17 +849,17 @@ ${o}
|
|
|
849
849
|
total_cache_create_tokens = @cc,
|
|
850
850
|
total_cache_read_tokens = @cr,
|
|
851
851
|
primary_model = @model
|
|
852
|
-
WHERE id = @id`).run({id:t,input:s.input_tokens,output:s.output_tokens,cc:s.cache_create_tokens,cr:s.cache_read_tokens,model:n?.model??null})}w();P();import{writeFileSync as
|
|
852
|
+
WHERE id = @id`).run({id:t,input:s.input_tokens,output:s.output_tokens,cc:s.cache_create_tokens,cr:s.cache_read_tokens,model:n?.model??null})}w();P();import{writeFileSync as Om,mkdirSync as vm,existsSync as Im}from"node:fs";import{join as Oi}from"node:path";w();var Cm=[/^You will receive a sample of user messages from a Claude Cod/i,/^You will receive the first \d+ user messages from a Claude/i,/^Base directory for this skill:/i,/^You are Claude Code in a fresh terminal/i,/^You are extracting a structured Output Index from a Claude/i];var Nt=[/^You are summarizing a Claude Code session/i,/^You are extracting a structured Output Index from a Claude/i,/^You will receive a sample of user messages from a Claude Cod/i,/^You will receive the first \d+ user messages from a Claude/i,/^Thread context:\n- This session is part of thread/i];var Lm=[/^Score this person'?s relevance/i,/^You are a [^\n.]{1,60}co-pilot/i,/^Return ONLY valid JSON/i,/^You are summarizing a Claude Code session/i],Nm=[/^Draft (a|an|marketing) [a-z]/i,/^Read the file at /i,/^Read this and then begin/i,/^read this and then begin/i],Am=20;function Ni(e){if(e.auto_title_source==="agent"&&e.auto_title)return"agent";if(e.has_alias)return"manual_alias";if(e.auto_title&&e.auto_title.includes(" \xB7 "))return"fixed_v0.16.1";if(!e.auto_title||e.auto_title.length<Am)return"low_signal";for(let t of Cm)if(t.test(e.auto_title))return"recursive_meta";for(let t of Lm)if(t.test(e.auto_title))return"programmatic";for(let t of Nm)if(t.test(e.auto_title))return"template_pending";return"clean"}function Zn(e){if(!e)return"";let t=e.split("|")[0];return t=t.replace(/\s*\([^)]*\)\s*$/,""),t=t.replace(/\s+/g," ").trim(),t}var er=Oi(x,"titles"),Mm=80,Dm=60,$m=100;function Pm(e){if(!e)return[];try{let t=JSON.parse(e);if(Array.isArray(t))return t.filter(s=>!!s&&typeof s=="object"&&typeof s.title=="string"&&typeof s.replaced_at=="string")}catch{}return[]}function sr(e){if(!e)return null;let t=e;if(t=t.replace(/```[\s\S]*?```/g," "),t=t.replace(/`[^`]+`/g," "),t=t.replace(/https?:\/\/\S+/g,"[url]"),t=t.replace(/\s+/g," ").trim(),!t)return null;let s=Fm(t,e);if(s)return s;let n=t.match(/^[^.!?\n]{8,}?[.!?]/)?.[0]?.trim();return(n&&n.length<=Mm?n:t.slice(0,Dm)).trim()||null}function Fm(e,t){let s=e.match(/^\/([A-Za-z0-9][A-Za-z0-9_-]*)\s+([\s\S]*)$/);if(s){let n=`/${s[1]}`,r=t.replace(/^\s*\/[A-Za-z0-9][A-Za-z0-9_-]*\s+/,""),o=tr(r);return o?qt(`${n} \xB7 ${o}`):n}for(let n of jm){if(!e.match(n.match))continue;let o=n.prefix,i=n.extract?n.extract(e,t):tr(t);return i?n.completeFromExtract?qt(i):qt(`${o} \xB7 ${i}`):o}for(let n of Hm){if(!e.match(n.match))continue;let o=n.extract?n.extract(e,t):ys(t);return o?n.completeFromExtract?qt(o):qt(`${n.prefix} \xB7 ${o}`):n.prefix}return null}var jm=[{match:/^Draft (?:a|an) brand brief\b/i,prefix:"Draft brand brief"},{match:/^Draft (?:a|an) sales brief\b/i,prefix:"Draft sales brief"},{match:/^Draft (?:a|an) leave-behind\b/i,prefix:"Draft leave-behind"},{match:/^Draft (?:a|an) audio identity brief\b/i,prefix:"Draft audio identity brief"},{match:/^Draft marketing ideas\b/i,prefix:"Draft marketing ideas"},{match:/^Draft (?:a|an) ([a-z]{3,30}(?:\s+[a-z]{3,30}){0,2})\b/i,prefix:"Draft",extract:(e,t)=>{let n=e.match(/^Draft (?:a|an) ([a-z]{3,30}(?:\s+[a-z]{3,30}){0,2})\b/i)?.[1]?.trim(),r=tr(t);return n&&r?`${n} \xB7 ${r}`:n||r}},{match:/^Read the file at /i,prefix:"Read",extract:e=>{let s=e.match(/^Read the file at\s+([^\s]+)/i)?.[1];return s?s.split("/").filter(Boolean).slice(-2).join("/")||s:null}},{match:/^(?:read|inspect) this and then begin\b/i,prefix:"Begin from preamble",completeFromExtract:!0,extract:(e,t)=>Bm(t)},{match:/^Base directory for this skill:/i,prefix:"[skill]",completeFromExtract:!0,extract:(e,t)=>{let s=t.match(/\.claude\/skills\/([^/\s]+)/);return s?.[1]?`[skill] ${s[1]}`:null}},{match:/^You are extracting a structured Output Index/i,prefix:"[output-index]",completeFromExtract:!0,extract:(e,t)=>Um(t)},{match:/^You will receive a sample of user messages from a Claude Code session:/i,prefix:"[meta]",completeFromExtract:!0,extract:(e,t)=>Ai(t,"[meta] auto-title (full-arc)")},{match:/^You will receive the first \d+ user messages from a Claude Code session/i,prefix:"[meta]",completeFromExtract:!0,extract:(e,t)=>Ai(t,"[meta] auto-title (initial)")},{match:/^You are implementing v\d+\.\d+/i,prefix:"Implementing",completeFromExtract:!0,extract:e=>{let t=e.match(/^You are implementing (v\d+\.\d+\w*)\s*(?:\(([^)]+)\))?/i);if(!t)return null;let s=t[1],n=t[2]?.trim();return n?`Implementing ${s} \xB7 ${n}`:`Implementing ${s}`}}];function Um(e){if(!e)return"[output-index] extractor";let t=e.match(/^Session:\s*([0-9a-f]{8})\b/im),s=e.match(/^Opening prompt:\s*([^\n]{1,140})/im),n=t?.[1]?.trim(),r=s?.[1]?.trim().replace(/\s+/g," "),o=r&&r.length>70?r.slice(0,67)+"\u2026":r;return n&&o?`[output-index] \xB7 ${n} \xB7 ${o}`:n?`[output-index] \xB7 ${n}`:o?`[output-index] \xB7 ${o}`:"[output-index] extractor"}function Ai(e,t){if(!e)return t;let s=e.indexOf("Messages:");if(s===-1)return t;let n=e.slice(s+9),r=/^\s*\d+\.\s*([^\n]+)/gm;for(let o of n.matchAll(r)){let i=o[1]?.trim()??"";if(!i)continue;let a=i.replace(/^<[^>]+>[\s\S]*?<\/[^>]+>\s*/g,"").replace(/^\[Pasted text[^\]]*\]\s*/i,"").trim();if(!a||/^<local-command-/.test(a))continue;let d=a.length>60?a.slice(0,57)+"\u2026":a;return`${t} \xB7 ${d}`}return t}function Bm(e){if(!e)return null;let t=e.match(/([\/\w.\-]+\.(?:md|markdown|txt|json|yaml|yml|toml|ts|tsx|js|jsx|py|sql))(?=[\s,;:)\]"'`]|$)/i);if(!t)return null;let s=t[1].split("/").filter(Boolean);return(s[s.length-1]??"").replace(/\.[^.]+$/,"")||null}var Hm=[{match:/^Score this person'?s relevance/i,prefix:"Score relevance",extract:(e,t)=>ys(t)},{match:/^You are a [^\n.]{1,60}co-pilot/i,prefix:"co-pilot",completeFromExtract:!0,extract:(e,t)=>{let n=e.match(/^You are a ([^\n.]{1,60}?)co-pilot/i)?.[1]?.trim(),r=n?`${n} co-pilot`:"co-pilot",o=ys(t);return o?`${r} \xB7 ${o}`:r}},{match:/^Return ONLY valid JSON/i,prefix:"JSON-only",extract:(e,t)=>ys(t)}];function ys(e){let t=/^\s*(Name|Company|Prospect|Author|Brand|Client)\s*:\s*([^\n]+)/gim,s;for(;(s=t.exec(e))!==null;){let n=s[2].trim();if(!n||/^(null|none|n\/a|—|-)$/i.test(n))continue;let r=n.replace(/\s+/g," ");return Zn(r)||r}return null}function qt(e){return e.slice(0,$m).trim()}var Wm=[/^deep research$/i,/^reference(?: spec)?$/i,/^canonical spec/i,/^product context/i,/^services?(?: overview)?$/i,/^overview$/i,/^(?:introduction|template|instructions|context)$/i],Xm=[/^(?:brand|brand brief|brand summary)$/i,/^(?:known facts?|facts)$/i,/^(?:client|prospect|customer|account)$/i,/^(?:topic|subject|brief|task)$/i,/^(?:about|for|re)$/i];function Qn(e){let t=e.trim();return t.length<3?!0:Wm.some(s=>s.test(t))}function Jm(e){let t=e.trim().replace(/\s*\([^)]*\)\s*$/,"").trim();return Xm.some(s=>s.test(t))}function tr(e){let t=Gm(e);return t===null?null:Zn(t)||t}function Gm(e){if(/^\s*Skill smoke test\b/i.test(e))return"smoke test";let t=/(^|\n)#{1,3}\s+([^\n]+?)\s+(?:—|–|-|:)\s+([^\n]{2,80})/g,s,n=[];for(;(s=t.exec(e))!==null;){let d=s[2].trim(),l=s[3].trim().replace(/\s+/g," ");if(!Qn(l)){if(Jm(d))return l;n.push(l)}}if(n.length>0)return n[0];let r=e.match(/\b(?:Topic|Subject|Brand|About|Client|For|Re)\s*:\s*([^\n]{2,80})/i);if(r?.[1]&&!Qn(r[1]))return r[1].trim().replace(/\s+/g," ");let o=/===\s*([^=\n]{2,120}?)\s*===/g;for(;(s=o.exec(e))!==null;){let d=s[1].trim().replace(/\.(md|txt|json)$/i,""),l=d.replace(/\s*\([^)]*\)\s*$/,"").trim();if(l&&!Qn(l)&&!/product context|reference/i.test(d))return l}let i=e.replace(/^Context for this run[^:]*:\s*/i,"");if(i!==e){let d=i.split(`
|
|
853
853
|
`).map(l=>l.trim()).find(l=>l.length>=4);if(d)return d.slice(0,60)}let a=e.split(`
|
|
854
|
-
`).map(d=>d.trim()).find(d=>d.length>=4);return a?a.slice(0,60):null}function
|
|
855
|
-
FROM sessions WHERE id = ?`).get(e);if(!o||s==="heuristic"&&o.auto_title_source==="agent"&&o.auto_title||o.auto_title===n&&o.auto_title_source===s)return;let i=
|
|
854
|
+
`).map(d=>d.trim()).find(d=>d.length>=4);return a?a.slice(0,60):null}function nr(e,t,s){let n=t.trim();if(!n)return;let r=_(),o=r.prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
|
|
855
|
+
FROM sessions WHERE id = ?`).get(e);if(!o||s==="heuristic"&&o.auto_title_source==="agent"&&o.auto_title||o.auto_title===n&&o.auto_title_source===s)return;let i=Pm(o.auto_title_history),a=new Date().toISOString();o.auto_title&&o.auto_title_source&&i.push({title:o.auto_title,source:o.auto_title_source,replaced_at:a}),r.prepare(`UPDATE sessions
|
|
856
856
|
SET auto_title = ?,
|
|
857
857
|
auto_title_source = ?,
|
|
858
858
|
auto_title_generated_at = ?,
|
|
859
859
|
auto_title_history = ?
|
|
860
|
-
WHERE id = ?`).run(n,s,Date.now(),JSON.stringify(i),e),
|
|
861
|
-
`;
|
|
862
|
-
`)}catch(r){console.error("[autoTitle] mirror write failed:",r)}}import{existsSync as
|
|
860
|
+
WHERE id = ?`).run(n,s,Date.now(),JSON.stringify(i),e),zm(e,n,s,a)}function Ym(){F(),Im(er)||vm(er,{recursive:!0})}function zm(e,t,s,n){try{Ym();let r=Oi(er,`${e}.txt`),o=`# Claude Recall auto-title \xB7 session ${e} \xB7 source ${s} \xB7 updated ${n}
|
|
861
|
+
`;Om(r,o+t+`
|
|
862
|
+
`)}catch(r){console.error("[autoTitle] mirror write failed:",r)}}import{existsSync as qm,mkdirSync as Dx,readFileSync as Km,writeFileSync as $x}from"node:fs";import{homedir as Vm}from"node:os";import{join as vi}from"node:path";import{z as rr}from"zod";function Zm(){return process.env.RECALL_HOME??vi(Vm(),".recall")}function Qm(){return vi(Zm(),"config.json")}var eg=rr.object({heuristicEnabled:rr.boolean().default(!0),agentEnabled:rr.boolean().default(!1)}),or={heuristicEnabled:!0,agentEnabled:!1};function tg(){let e=Qm();if(!qm(e))return{};try{return JSON.parse(Km(e,"utf8"))}catch(t){return console.error("[auto-title-config] failed to parse config.json, using defaults:",t),{}}}function ir(){let e=tg().autoTitle;if(!e)return{...or};let t=eg.safeParse({...or,...e});return t.success?t.data:{...or}}var ng=[/^<local-command-caveat>/,/^<command-name>/,/^<command-message>/,/^<system-reminder>/,/^<user-prompt-submit-hook>/,/^Caveat: The messages below were generated/i];function rg(e){let t=e.trim();return t?ng.some(s=>s.test(t)):!0}function og(e){let t=e;return t=t.replace(/^(?:<[^>]+>[\s\S]*?<\/[^>]+>\s*)+/,"").trim(),t=t.replace(/^<command-[^>]+>[^<]*<\/command-[^>]+>\s*/gm,"").trim(),!t||rg(t)?null:t}function ig(e,t,s){let n=Jn(t),r=s??n,o=s?sg(s)||s:bi(t),i=e.prepare("SELECT id, decoded_path FROM projects WHERE encoded_path = ?").get(t);if(i)return s&&i.decoded_path!==s&&e.prepare("UPDATE projects SET decoded_path = ?, name = ? WHERE id = ?").run(s,o,i.id),i.id;let a=e.prepare("INSERT INTO projects (encoded_path, decoded_path, name) VALUES (?, ?, ?)").run(t,r,o);return Number(a.lastInsertRowid)}async function ag(e,t,s){let n=e.prepare("SELECT id, file_mtime FROM sessions WHERE file_path = ? LIMIT 1").get(t.sessionFile);if(!s&&n&&n.file_mtime>=t.mtime)return{inserted:!1,sessionCount:0,messageCount:0};let r=new Map,o=null;for await(let f of qn(t.sessionFile)){let h=r.get(f.sessionId);if(h||(h={sessionId:f.sessionId,entries:[],earliestTimestamp:null,latestTimestamp:null,firstUserMessage:null,userCount:0,assistantCount:0,cwd:null,gitBranch:null,version:null},r.set(f.sessionId,h)),h.entries.push(f),f.timestamp&&((!h.earliestTimestamp||f.timestamp<h.earliestTimestamp)&&(h.earliestTimestamp=f.timestamp),(!h.latestTimestamp||f.timestamp>h.latestTimestamp)&&(h.latestTimestamp=f.timestamp)),f.role==="user"&&!f.isSidechain){if(h.userCount+=1,!h.firstUserMessage&&f.contentText){let E=og(f.contentText);E&&(h.firstUserMessage=K(we(E).redacted,200))}}else f.role==="assistant"&&!f.isSidechain&&(h.assistantCount+=1);!h.cwd&&f.cwd&&(h.cwd=f.cwd),!h.gitBranch&&f.gitBranch&&(h.gitBranch=f.gitBranch),!h.version&&f.version&&(h.version=f.version),!o&&f.cwd&&(o=f.cwd)}let i=ig(e,t.encodedProject,o),a=new Date().toISOString(),d=e.prepare(`
|
|
863
863
|
INSERT INTO sessions (
|
|
864
864
|
id, project_id, file_path, file_mtime,
|
|
865
865
|
started_at, ended_at, message_count,
|
|
@@ -894,30 +894,30 @@ ${o}
|
|
|
894
894
|
@is_sidechain, @content_text, @tool_names, @raw_json
|
|
895
895
|
)
|
|
896
896
|
ON CONFLICT(uuid) DO NOTHING
|
|
897
|
-
`),u=e.prepare("DELETE FROM messages WHERE session_id = ?"),p=0,m=0;if(e.transaction(()=>{for(let f of r.values()){d.run({id:f.sessionId,project_id:i,file_path:t.sessionFile,file_mtime:t.mtime,started_at:f.earliestTimestamp,ended_at:f.latestTimestamp,message_count:f.entries.length,user_message_count:f.userCount,assistant_message_count:f.assistantCount,first_user_message:f.firstUserMessage,cwd:f.cwd,git_branch:f.gitBranch,version:f.version,indexed_at:a}),u.run(f.sessionId);for(let h of f.entries){let{redacted:E}=we(h.contentText),{redacted:b}=we(h.raw);l.run({uuid:h.uuid,session_id:h.sessionId,parent_uuid:h.parentUuid,type:h.type,role:h.role,timestamp:h.timestamp,is_sidechain:h.isSidechain?1:0,content_text:E,tool_names:h.toolNames.join(","),raw_json:b}),m+=1}
|
|
897
|
+
`),u=e.prepare("DELETE FROM messages WHERE session_id = ?"),p=0,m=0;if(e.transaction(()=>{for(let f of r.values()){d.run({id:f.sessionId,project_id:i,file_path:t.sessionFile,file_mtime:t.mtime,started_at:f.earliestTimestamp,ended_at:f.latestTimestamp,message_count:f.entries.length,user_message_count:f.userCount,assistant_message_count:f.assistantCount,first_user_message:f.firstUserMessage,cwd:f.cwd,git_branch:f.gitBranch,version:f.version,indexed_at:a}),u.run(f.sessionId);for(let h of f.entries){let{redacted:E}=we(h.contentText),{redacted:b}=we(h.raw);l.run({uuid:h.uuid,session_id:h.sessionId,parent_uuid:h.parentUuid,type:h.type,role:h.role,timestamp:h.timestamp,is_sidechain:h.isSidechain?1:0,content_text:E,tool_names:h.toolNames.join(","),raw_json:b}),m+=1}Vn(e,f.sessionId,f.entries),zt(e,f.sessionId),p+=1}})(),ir().heuristicEnabled)for(let f of r.values()){let h=sr(f.firstUserMessage);h&&nr(f.sessionId,h,"heuristic")}return{inserted:!0,sessionCount:p,messageCount:m}}async function Ii(e){let t=_(),s=Si();console.log(c.dim(`Scanning ${s.length} JSONL session files under ~/.claude/projects/`));let n=0,r=0,o=0,i=0,a=Date.now();for(let l of s)try{let u=await ag(t,l,e.force??!1);u.inserted?(n+=1,o+=u.sessionCount,i+=u.messageCount,e.verbose&&console.log(c.dim(` + ${l.sessionFile.split("/").slice(-2).join("/")} (${u.messageCount} msgs)`))):r+=1}catch(u){console.error(c.err(` ! failed: ${l.sessionFile}`),u)}let d=((Date.now()-a)/1e3).toFixed(1);console.log(""),console.log(`${c.ok("indexed")}: ${c.bold(String(n))} files, ${c.bold(String(o))} sessions, ${c.bold(String(i))} messages ${c.dim(`in ${d}s`)}`),r>0&&console.log(c.dim(`skipped: ${r} unchanged files (use --force to reindex)`))}w();v();import cg from"cli-table3";function Mi(e){let t=_(),n={limit:Math.max(1,Math.min(500,parseInt(e.limit??"30",10)||30))},r="1=1";e.project&&(r+=" AND (p.name LIKE @proj OR p.decoded_path LIKE @proj OR p.encoded_path LIKE @proj)",n.proj=`%${e.project}%`),e.all||(r+=" AND s.message_count > 2");let o=t.prepare(`SELECT s.id, p.name AS project_name, s.started_at,
|
|
898
898
|
s.message_count, s.first_user_message, s.git_branch
|
|
899
899
|
FROM sessions s
|
|
900
900
|
JOIN projects p ON p.id = s.project_id
|
|
901
901
|
WHERE ${r}
|
|
902
902
|
ORDER BY COALESCE(s.started_at, '') DESC
|
|
903
|
-
LIMIT @limit`).all(n);if(o.length===0){console.log(c.dim("no sessions found. run `recall index` first."));return}let i=new
|
|
904
|
-
`,"utf-8"),
|
|
903
|
+
LIMIT @limit`).all(n);if(o.length===0){console.log(c.dim("no sessions found. run `recall index` first."));return}let i=new cg({head:[c.bold("id"),c.bold("project"),c.bold("when"),c.bold("msgs"),c.bold("opening prompt")],colWidths:[10,22,14,6,70],wordWrap:!0,style:{head:[],border:["grey"]}});for(let a of o)i.push([c.accent(X(a.id)),c.project(K(a.project_name,20)),c.dim(J(a.started_at)),String(a.message_count),K(a.first_user_message,200)]);console.log(i.toString()),console.log(c.dim(`showing ${o.length} session${o.length===1?"":"s"}. use \`recall show <id>\` to view one.`))}w();v();import{spawn as Eg}from"node:child_process";import{readFileSync as lg,writeFileSync as dg,mkdirSync as ug,chmodSync as pg}from"node:fs";import{join as Di}from"node:path";import{homedir as $i}from"node:os";function Pi(){return Di($i(),".recall","config.json")}function Fi(){try{return JSON.parse(lg(Pi(),"utf-8"))}catch{return{}}}function mg(e){let t=Pi();ug(Di($i(),".recall"),{recursive:!0}),dg(t,JSON.stringify(e,null,2)+`
|
|
904
|
+
`,"utf-8"),pg(t,384)}function ws(){let t=Fi().verification;return typeof t=="object"&&t!==null&&"enabled"in t?!!t.enabled:!1}function ar(e){let t=Fi();t.verification={...typeof t.verification=="object"&&t.verification!==null?t.verification:{},enabled:e},mg(t)}w();var gg=[/\btask\s+complete/i,/\bdone\b/i,/\bfinished\b/i,/\bimplemented\b/i,/\bcompleted\b/i,/\bshipped\b/i,/\ball\s+(?:tests?\s+)?pass/i,/\bsuccessfully\b/i],fg=1440*60*1e3;function _g(e){let t=_(),s=t.prepare(`SELECT content_text, tool_names FROM messages
|
|
905
905
|
WHERE session_id = ? AND role = 'assistant'
|
|
906
|
-
ORDER BY timestamp DESC LIMIT 5`).all(e),n=!1;for(let d of s)if(d.content_text&&
|
|
907
|
-
WHERE session_id = ?`).all(e),o={fileWrites:!1,testRuns:!1,commits:!1,buildSuccess:!1};for(let d of r){let l=d.tool_names??"",u=d.content_text??"";/\bWrite\b|\bEdit\b/.test(l)&&(o.fileWrites=!0),/\b(?:jest|pytest|vitest|mocha|test|spec)\b/i.test(u)&&/pass|ok|✓/i.test(u)&&(o.testRuns=!0),/\bgit\s+commit\b/i.test(u)&&(o.commits=!0),(/\bbuild\s+(?:succeeded|success|passed)\b/i.test(u)||/tsc.*(?:0 errors|no errors)/i.test(u))&&(o.buildSuccess=!0)}return n?{status:[o.fileWrites,o.testRuns,o.commits,o.buildSuccess].filter(Boolean).length>=2?"verified":"unverified",evidence:o,claimFound:n}:{status:"neutral",evidence:o,claimFound:n}}function
|
|
906
|
+
ORDER BY timestamp DESC LIMIT 5`).all(e),n=!1;for(let d of s)if(d.content_text&&gg.some(l=>l.test(d.content_text))){n=!0;break}let r=t.prepare(`SELECT content_text, tool_names FROM messages
|
|
907
|
+
WHERE session_id = ?`).all(e),o={fileWrites:!1,testRuns:!1,commits:!1,buildSuccess:!1};for(let d of r){let l=d.tool_names??"",u=d.content_text??"";/\bWrite\b|\bEdit\b/.test(l)&&(o.fileWrites=!0),/\b(?:jest|pytest|vitest|mocha|test|spec)\b/i.test(u)&&/pass|ok|✓/i.test(u)&&(o.testRuns=!0),/\bgit\s+commit\b/i.test(u)&&(o.commits=!0),(/\bbuild\s+(?:succeeded|success|passed)\b/i.test(u)||/tsc.*(?:0 errors|no errors)/i.test(u))&&(o.buildSuccess=!0)}return n?{status:[o.fileWrites,o.testRuns,o.commits,o.buildSuccess].filter(Boolean).length>=2?"verified":"unverified",evidence:o,claimFound:n}:{status:"neutral",evidence:o,claimFound:n}}function hg(e){let t=_g(e);return _().prepare("UPDATE sessions SET verification_status = ?, verification_computed_at = ? WHERE id = ?").run(t.status,Date.now(),e),t}function ji(e){let s=_().prepare("SELECT verification_status, verification_computed_at FROM sessions WHERE id = ?").get(e);if(s?.verification_status&&s.verification_computed_at&&Date.now()-s.verification_computed_at<fg){let n={fileWrites:!1,testRuns:!1,commits:!1,buildSuccess:!1};return{status:s.verification_status,evidence:n,claimFound:s.verification_status!=="neutral"}}return hg(e)}function bg(e,t){if(t.length>=32)return e.prepare("SELECT id FROM sessions WHERE id = ?").get(t)?.id??null;let s=e.prepare("SELECT id FROM sessions WHERE id LIKE ? LIMIT 2").all(t+"%");return s.length===1?s[0].id:(s.length===0||console.error(c.err(`ambiguous id prefix "${t}". be more specific.`)),null)}function Sg(e,t){let s=_(),n=s.prepare(`SELECT s.id, p.name AS project_name, p.decoded_path,
|
|
908
908
|
s.started_at, s.ended_at, s.message_count,
|
|
909
909
|
s.git_branch, s.version, s.cwd
|
|
910
910
|
FROM sessions s
|
|
911
911
|
JOIN projects p ON p.id = s.project_id
|
|
912
|
-
WHERE s.id = ?`).get(e);if(!n)return null;let r=[],o=c.dim("\u2500".repeat(78));if(r.push(""),r.push(c.bold(c.project(n.project_name))+c.dim(` ${n.decoded_path}`)),r.push(c.dim(`session ${n.id} \xB7 ${n.message_count} msgs \xB7 ${J(n.started_at)}`+(n.git_branch?` \xB7 branch: ${n.git_branch}`:""))),ws()){let d=
|
|
912
|
+
WHERE s.id = ?`).get(e);if(!n)return null;let r=[],o=c.dim("\u2500".repeat(78));if(r.push(""),r.push(c.bold(c.project(n.project_name))+c.dim(` ${n.decoded_path}`)),r.push(c.dim(`session ${n.id} \xB7 ${n.message_count} msgs \xB7 ${J(n.started_at)}`+(n.git_branch?` \xB7 branch: ${n.git_branch}`:""))),ws()){let d=ji(e);d.status==="verified"?r.push(c.ok("\u2713 verified")):d.status==="unverified"&&r.push(c.warn("\u26A0 unverified"))}r.push(o);let i=t.limit?Math.max(1,parseInt(t.limit,10)||9999):9999,a=s.prepare(`SELECT uuid, type, role, timestamp, is_sidechain, content_text, tool_names, raw_json
|
|
913
913
|
FROM messages
|
|
914
914
|
WHERE session_id = ?
|
|
915
915
|
ORDER BY COALESCE(timestamp, ''), rowid
|
|
916
916
|
LIMIT ?`).all(e,i);for(let d of a){if(t.raw){r.push(d.raw_json);continue}let l=d.is_sidechain===1?c.dim(" [subagent]"):"",u=d.role==="user"?c.user("\u25B8 user"):d.role==="assistant"?c.assistant("\u25B8 assistant"):c.dim(`\u25B8 ${d.type}`);if(r.push(`${u}${l} ${c.dim(d.timestamp??"")}`),d.tool_names&&d.tool_names.length>0&&r.push(c.tool(` tools: ${d.tool_names}`)),d.content_text&&d.content_text.trim()){let p=d.content_text.trim().split(`
|
|
917
917
|
`).map(m=>" "+m).join(`
|
|
918
918
|
`);r.push(p)}r.push("")}return r.push(o),r.push(c.dim(`end of session ${n.id}`)),r.join(`
|
|
919
|
-
`)}function
|
|
920
|
-
`).length>s}function
|
|
919
|
+
`)}function yg(e,t){if(!t||!process.stdout.isTTY)return!1;let s=process.stdout.rows||24;return e.split(`
|
|
920
|
+
`).length>s}function wg(e){let t=Eg("less",["-R","-F","-X"],{stdio:["pipe","inherit","inherit"]});t.stdin.on("error",()=>{}),t.stdin.write(e),t.stdin.end(),t.on("exit",()=>process.exit(0))}function Ui(e,t){let s=_(),n=bg(s,e);if(!n){e.length>=32&&console.error(c.err(`session not found: ${e}`)),process.exitCode=1;return}let r=Sg(n,t);if(r===null){console.error(c.err(`session metadata missing for ${n}`)),process.exitCode=1;return}let o=t.pager!==!1;yg(r,o)?wg(r):console.log(r)}w();v();de();P();import{existsSync as Qg,readFileSync as ef}from"node:fs";import{join as tf}from"node:path";hr();function Qi(e){let t=Zi(),s={...e};return t&&(s["x-recall-token"]=t),s}async function ze(e,t){let s=t?.headers??{};return fetch(e,{...t,headers:Qi(s)})}async function st(e,t,s,n){let r=s!==void 0?{"content-type":"application/json",...n}:{...n};return fetch(t,{method:e,headers:Qi(r),body:s!==void 0?JSON.stringify(s):void 0})}function ea(e){let t=e.split(/\s+/).filter(Boolean).map(s=>s.replace(/"/g,"")).filter(s=>s.length>0);return t.length===0?"":t.map(s=>`"${s}"`).join(" ")}async function sf(e,t){let s=tf(x,"daemon.port");Qg(s)||(console.error(c.err("Semantic search requires the daemon to be running.")),console.error(c.dim(" Start it with: recall start")),console.error(c.dim(' Then re-run: recall search "your query" --semantic')),process.exit(1));let n;try{n=ef(s,"utf8").trim(),/^\d+$/.test(n)||(console.error(c.err(`Daemon port file is corrupt at ${s}.`)),console.error(c.dim(" Restart the daemon: recall stop && recall start")),process.exit(1))}catch(p){console.error(c.err(`Could not read daemon port: ${p instanceof Error?p.message:String(p)}`)),process.exit(1)}let r=Math.max(1,Math.min(200,parseInt(t.limit??"20",10)||20)),o=ea(e);if(!o){console.log(c.dim("empty query"));return}let i=new URLSearchParams({q:o,mode:"semantic",limit:String(r)});t.project&&i.set("project",t.project);let a;try{a=await ze(`http://127.0.0.1:${n}/api/search?${i.toString()}`)}catch(p){console.error(c.err("Could not reach the daemon for semantic search.")),console.error(c.dim(` ${p instanceof Error?p.message:String(p)}`)),console.error(c.dim(" Is the daemon still running? Try: recall status")),process.exit(1)}if(!a.ok){console.error(c.err(`Semantic search failed: HTTP ${a.status}`));try{let p=await a.text();p&&console.error(c.dim(` ${p.slice(0,500)}`))}catch{}process.exit(1)}let d=await a.json(),l=d.hits??[];if(l.length===0){console.log(c.dim(`no matches for "${e}"`));return}let u=d.fusion==="rrf"?"semantic (BM25 + summary + vector, RRF-fused)":"semantic";console.log(c.dim(`${l.length} match${l.length===1?"":"es"} for "${e}" ${c.dim("\xB7 mode:")} ${u}`)),console.log("");for(let p of l){let m=(p.snippet??"").replace(/<<([\s\S]*?)>>/g,(E,b)=>Kn(b,b).replace(/\n/g," ")).replace(/\n/g," "),g=p.role==="user"?c.user("user"):p.role==="assistant"?c.assistant("asst"):c.dim("----"),f=p.lanes&&p.lanes.length>0?c.dim(`[${p.lanes.join("+")}]`):p.matched_via?c.dim(`[${p.matched_via}]`):"",h=p.project??"(unknown)";console.log(`${c.accent(X(p.session_id))} ${c.project(K(h,20))} ${c.dim(J(p.started_at))} ${g} ${f}`),console.log(` ${K(m,200)}`),console.log("")}console.log(c.dim("`recall show <id>` to read any session."))}async function ta(e,t){if(await Ne("Full-text search"),t.semantic){await sf(e,t);return}let s=_(),n=ea(e);if(!n){console.log(c.dim("empty query"));return}let r=Math.max(1,Math.min(200,parseInt(t.limit??"20",10)||20)),o={q:n,limit:r},i="";t.project&&(i=" AND (p.name LIKE @proj OR p.decoded_path LIKE @proj) ",o.proj=`%${t.project}%`);let a=s.prepare(`SELECT m.session_id AS session_id,
|
|
921
921
|
p.name AS project_name,
|
|
922
922
|
s.started_at AS started_at,
|
|
923
923
|
snippet(messages_fts, 0, '<<', '>>', '\u2026', 16) AS snippet,
|
|
@@ -930,13 +930,13 @@ ${o}
|
|
|
930
930
|
WHERE messages_fts MATCH @q
|
|
931
931
|
${i}
|
|
932
932
|
ORDER BY bm25(messages_fts)
|
|
933
|
-
LIMIT @limit`).all(o);if(a.length===0){console.log(c.dim(`no matches for "${e}"`));return}console.log(c.dim(`${a.length} match${a.length===1?"":"es"} for "${e}"`)),console.log("");for(let d of a){let l=d.snippet.replace(/<<([\s\S]*?)>>/g,(p,m)=>
|
|
933
|
+
LIMIT @limit`).all(o);if(a.length===0){console.log(c.dim(`no matches for "${e}"`));return}console.log(c.dim(`${a.length} match${a.length===1?"":"es"} for "${e}"`)),console.log("");for(let d of a){let l=d.snippet.replace(/<<([\s\S]*?)>>/g,(p,m)=>Kn(m,m).replace(/\n/g," ")).replace(/\n/g," "),u=d.role==="user"?c.user("user"):d.role==="assistant"?c.assistant("asst"):c.dim("----");console.log(`${c.accent(X(d.session_id))} ${c.project(K(d.project_name,20))} ${c.dim(J(d.started_at))} ${u}`),console.log(` ${K(l,200)}`),console.log("")}console.log(c.dim("`recall show <id>` to read any session."))}w();P();qe();v();import{statSync as cf,existsSync as lf}from"node:fs";function na(){if(console.log(""),console.log(c.bold("Claude Recall status")),console.log(c.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),!lf(se)){console.log(c.dim(" no database yet. run `recall index`."));return}let t=_().prepare(`SELECT
|
|
934
934
|
(SELECT COUNT(*) FROM projects) AS projects,
|
|
935
935
|
(SELECT COUNT(*) FROM sessions) AS sessions,
|
|
936
936
|
(SELECT COUNT(*) FROM messages) AS messages,
|
|
937
937
|
(SELECT MIN(started_at) FROM sessions WHERE started_at IS NOT NULL) AS earliest,
|
|
938
938
|
(SELECT MAX(started_at) FROM sessions WHERE started_at IS NOT NULL) AS latest,
|
|
939
|
-
(SELECT MAX(indexed_at) FROM sessions) AS last_indexed`).get(),n=(
|
|
939
|
+
(SELECT MAX(indexed_at) FROM sessions) AS last_indexed`).get(),n=(cf(se).size/1024/1024).toFixed(1);console.log(` db path ${c.dim(se)}`),console.log(` db size ${c.accent(n+" MB")}`),console.log(` projects ${c.accent(String(t.projects))}`),console.log(` sessions ${c.accent(String(t.sessions))}`),console.log(` messages ${c.accent(String(t.messages))}`),console.log(` earliest ${c.dim(t.earliest??"n/a")}`),console.log(` latest ${c.dim(t.latest??"n/a")} ${c.dim(J(t.latest))}`),console.log(` last index ${c.dim(t.last_indexed??"never")}`),console.log("");let r=Q();r?(console.log(` daemon ${c.ok("running")} pid ${r.pid} \xB7 http://127.0.0.1:${r.port}`),console.log(` started ${c.dim(r.startedAt)} ${c.dim(J(r.startedAt))}`)):console.log(` daemon ${c.dim("stopped")} (run \`recall start\` or \`recall open\`)`),console.log("")}w();v();import df from"cli-table3";function ra(){let t=_().prepare(`SELECT p.name,
|
|
940
940
|
p.decoded_path,
|
|
941
941
|
COUNT(s.id) AS session_count,
|
|
942
942
|
COALESCE(SUM(s.message_count), 0) AS message_count,
|
|
@@ -944,13 +944,13 @@ ${o}
|
|
|
944
944
|
FROM projects p
|
|
945
945
|
LEFT JOIN sessions s ON s.project_id = p.id
|
|
946
946
|
GROUP BY p.id
|
|
947
|
-
ORDER BY MAX(COALESCE(s.started_at, '')) DESC`).all();if(t.length===0){console.log(c.dim("no projects indexed yet."));return}let s=new
|
|
948
|
-
`)){let i=o.trim();if(!i||!i.includes("dist/mcp/server.js")||
|
|
949
|
-
`),""}}function
|
|
950
|
-
`)}),i=e.truncateWal??
|
|
947
|
+
ORDER BY MAX(COALESCE(s.started_at, '')) DESC`).all();if(t.length===0){console.log(c.dim("no projects indexed yet."));return}let s=new df({head:[c.bold("project"),c.bold("sessions"),c.bold("msgs"),c.bold("latest"),c.bold("path")],style:{head:[],border:["grey"]}});for(let n of t)s.push([c.project(K(n.name,30)),String(n.session_count),String(n.message_count),c.dim(J(n.latest)),c.dim(K(n.decoded_path,50))]);console.log(s.toString())}qe();qe();P();v();Ke();import{spawn as mf}from"node:child_process";import{openSync as gf}from"node:fs";import{join as ff}from"node:path";function _f(){return ff(ee(),"dist","daemon","entrypoint.js")}async function Os(){let e=Q();if(e){console.log(`${c.ok("already running")} pid ${e.pid} \xB7 http://127.0.0.1:${e.port}`);return}F();let t=gf(Ns,"a"),s=_f();mf(process.execPath,[s],{detached:!0,stdio:["ignore",t,t],env:{...process.env}}).unref();let r=Date.now();for(;Date.now()-r<5e3;){await new Promise(i=>setTimeout(i,150));let o=Q();if(o){console.log(`${c.ok("daemon started")} pid ${o.pid} \xB7 http://127.0.0.1:${o.port}`),console.log(c.dim(`logs: ${Ns}`));return}}console.error(c.err("daemon did not come up within 5s \u2014 check the log file")),console.error(c.dim(` ${Ns}`)),process.exitCode=1}qe();import*as la from"node:readline";import{execFileSync as aa}from"node:child_process";function Qt(e={}){let t=e.psOutput??hf(),s=e.isProcessAlive??Ef,n=e.getParentCommand??bf,r=[];for(let o of t.split(`
|
|
948
|
+
`)){let i=o.trim();if(!i||!i.includes("dist/mcp/server.js")||Sf(i))continue;let a=i.split(/\s+/);if(a.length<5)continue;let d=Number(a[0]),l=Number(a[1]),u=a[2],p=Number(a[3]);if(!Number.isFinite(d)||!Number.isFinite(l))continue;let m=l>1&&s(l);r.push({pid:d,ppid:l,parentAlive:m,etimeSeconds:yf(u),pcpu:Number.isFinite(p)?p:0,orphan:!m,parentCommand:m?n(l):null})}return r}function hf(){try{return aa("ps",["-eo","pid,ppid,etime,pcpu,command"],{encoding:"utf8",timeout:2e3,maxBuffer:5*1024*1024})}catch(e){let t=e instanceof Error?e.message:String(e);return process.stderr.write(`[mcp-processes] ps -eo failed: ${t}
|
|
949
|
+
`),""}}function Ef(e){if(!Number.isFinite(e)||e<=1)return!1;try{return process.kill(e,0),!0}catch{return!1}}function bf(e){if(!Number.isFinite(e)||e<=1)return null;try{let s=aa("ps",["-p",String(e),"-o","command="],{encoding:"utf8",timeout:1e3,maxBuffer:1048576}).trim();return s.length?s.slice(0,200):null}catch{return null}}function Sf(e){let t=e.split(/\s+/);if(t.length<5)return!1;let s=t[4]??"";return s.endsWith("/grep")||s==="grep"||s.endsWith("/awk")||s==="awk"||s.endsWith("/rg")||s==="rg"}function yf(e){if(!e)return 0;let t=0,s=e,n=e.indexOf("-");n>=0&&(t=ia(e.slice(0,n)),s=e.slice(n+1));let r=s.split(":").map(ia),o=0,i=0,a=0;return r.length===3?[o,i,a]=r:r.length===2?[i,a]=r:r.length===1&&(a=r[0]),t*86400+o*3600+i*60+a}function ia(e){let t=Number(e);return Number.isFinite(t)?t:0}var wf=50,Tf=60;function vs(e){return e.pcpu>=wf&&e.etimeSeconds>=Tf}w();function It(e){if(e<60)return`${e}s`;if(e<3600)return`${Math.floor(e/60)}m`;if(e<86400)return`${Math.floor(e/3600)}h`;let t=Math.floor(e/86400),s=Math.floor(e%86400/3600);return s>0?`${t}d ${s}h`:`${t}d`}var DC=5*6e4,ca=1073741824;var Rf=100;function Is(e,t="PASSIVE",s){let n=Date.now(),r=e.pragma(`wal_checkpoint(${t})`),o=xf(r),i={busy:o.busy,log:o.log,moved:o.checkpointed,mode:t,durationMs:Date.now()-n};return s&&(t==="PASSIVE"&&i.log>=Rf&&i.moved===0?s(`[wal-maintenance] PASSIVE checkpoint blocked: log=${i.log} frames pending, moved=0 (readers holding snapshots)`):i.moved>0&&s(`[wal-maintenance] ${t} checkpoint: log=${i.log} moved=${i.moved} busy=${i.busy} (${i.durationMs}ms)`)),i}function xf(e){let t=Array.isArray(e)?e[0]??{}:e??{};return{busy:wr(t.busy),log:wr(t.log),checkpointed:wr(t.checkpointed)}}function wr(e){return typeof e=="number"&&Number.isFinite(e)?e:typeof e=="bigint"?Number(e):0}var kf=2e3;async function Ms(e={}){let t=e.list??Qt,s=e.kill??Cf,n=e.confirm??Af,r=e.sleep??Lf,o=e.logger??(g=>{process.stdout.write(g+`
|
|
950
|
+
`)}),i=e.truncateWal??Nf,a=!!e.json,d=a||!!e.yes,l=!!e.all,u=t(),p=l?u:u.filter(g=>g.orphan),m={found:p.length,killed:[],failed:[],walTruncated:!1};if(p.length===0)return o(a?JSON.stringify(m):l?"No MCP children running.":"No orphaned MCP children to prune."),0;if(!a){o(l?`Found ${p.length} MCP child${p.length===1?"":"ren"} (will prune all):`:`Found ${p.length} orphaned MCP child${p.length===1?"":"ren"}:`);for(let g of p){let f=g.orphan?" (orphan)":"";o(` pid ${g.pid} ppid ${g.ppid} age ${It(g.etimeSeconds)}${f}`)}}if(!d&&!await n("Kill these processes? [y/N] "))return o("Aborted."),0;for(let g of p)try{s(g.pid,"SIGTERM"),m.killed.push(g.pid)}catch(f){m.failed.push({pid:g.pid,reason:Tr(f)})}await r(kf);for(let g of[...m.killed])try{s(g,0);try{s(g,"SIGKILL")}catch(f){m.killed=m.killed.filter(h=>h!==g),m.failed.push({pid:g,reason:Tr(f)})}}catch{}if(m.killed.length>0)try{i(),m.walTruncated=!0}catch(g){a||o(` (WAL truncate skipped: ${Tr(g)})`)}if(a)o(JSON.stringify(m));else{o(""),o(`Pruned ${m.killed.length}/${p.length}`+(m.failed.length>0?` -- ${m.failed.length} failed`:"")+(m.walTruncated?". WAL truncated.":"."));for(let g of m.failed)o(` pid ${g.pid}: ${g.reason}`)}return m.failed.length===0?0:1}function Cf(e,t){process.kill(e,t)}function Lf(e){return new Promise(t=>{setTimeout(t,e)})}function Nf(){Is(_(),"TRUNCATE")}function Af(e){return new Promise(t=>{let s=la.createInterface({input:process.stdin,output:process.stderr});s.question(e,n=>{s.close();let r=n.trim().toLowerCase();t(r==="y"||r==="yes")})})}function Tr(e){if(e instanceof Error){let t=e.code;return t?`${t} ${e.message}`:e.message}return String(e)}w();v();async function da(e={}){let t=e.logger??(u=>console.log(u)),s=e.logErr??(u=>console.error(u)),n=e.getRunningDaemon??Q,r=e.isProcessAlive??yr,o=e.clearDaemonInfo??Sr,i=e.sleep??(u=>new Promise(p=>setTimeout(p,u))),a=e.kill??((u,p)=>{process.kill(u,p)}),d=e.truncateWal??vf,l=await Of({log:t,logErr:s,getDaemon:n,isAlive:r,clearInfo:o,kill:a,sleep:i});return e.all&&(await Ms({all:!0,yes:!0,list:e.listMcpProcesses,kill:e.kill,sleep:e.sleep,logger:t,truncateWal:d}),d()),l?0:1}async function Of(e){let t=e.getDaemon();if(!t)return e.log(c.dim("no daemon running.")),e.clearInfo(),!0;try{e.kill(t.pid,"SIGTERM")}catch(s){return e.logErr(c.err(`failed to signal pid ${t.pid}: ${s.message}`)),!1}for(let s=0;s<50;s++)if(await e.sleep(100),!e.isAlive(t.pid))return e.clearInfo(),e.log(c.ok(`stopped daemon pid ${t.pid}`)),!0;e.log(c.dim(`pid ${t.pid} ignored SIGTERM after 5s -- escalating to SIGKILL`));try{e.kill(t.pid,"SIGKILL")}catch(s){return s.code==="ESRCH"?(e.clearInfo(),e.log(c.ok(`stopped daemon pid ${t.pid}`)),!0):(e.logErr(c.err(`failed to SIGKILL pid ${t.pid}: ${s.message}`)),!1)}for(let s=0;s<20;s++)if(await e.sleep(100),!e.isAlive(t.pid))return e.clearInfo(),e.log(c.ok(`stopped daemon pid ${t.pid} (forced)`)),!0;return e.logErr(c.err(`pid ${t.pid} survived SIGKILL -- kill manually: kill -9 ${t.pid}`)),!1}function vf(){try{Is(_(),"TRUNCATE")}catch(e){console.warn(c.dim(`WAL truncate skipped: ${e.message}`))}}qe();import{spawn as If}from"node:child_process";import{platform as Ds}from"node:os";v();function Mf(e){let t=Ds()==="darwin"?"open":Ds()==="win32"?"start":"xdg-open",s=Ds()==="win32"?["",e]:[e];If(t,s,{detached:!0,stdio:"ignore",shell:Ds()==="win32"}).unref()}async function ua(){let e=Q();if(!e&&(console.log(c.dim("daemon not running \u2014 starting it\u2026")),await Os(),e=Q(),!e)){console.error(c.err("could not start the daemon. run `recall start` manually and check logs.")),process.exitCode=1;return}let t=`http://127.0.0.1:${e.port}`;console.log(`${c.ok("opening")} ${t}`),Mf(t)}w();var Df=/<(local-command-caveat|local-command-stdout|command-name|command-message|command-args|system-reminder|user-prompt-submit-hook|task-notification)[\s\S]*?<\/\1>/gi,$f=/⚡ \*\*Tool call · `[^`]+`\*\*\s*\n+```json\n[\s\S]*?\n```/g,Pf=/\*\*Tool result\*\*\s*\n+```\n[\s\S]*?\n```/g;function Ff(e){return e.replace(Df,"").trim()}function jf(e){let t=e.replace($f,"[tool call]");return t=t.replace(Pf,"[tool result]"),t=t.replace(/_\(unknown block: thinking\)_/g,""),t=t.replace(/(?:\[tool call\]|\[tool result\])(?:\s*(?:\[tool call\]|\[tool result\]))+/g,"[tool activity]"),t=t.replace(/\n{3,}/g,`
|
|
951
951
|
|
|
952
|
-
`),t.trim()}function
|
|
953
|
-
`)}w();P();import{randomUUID as
|
|
952
|
+
`),t.trim()}function Uf(e){return e.role??e.type??"message"}function pa(e,t,s={}){let n=s.mode??"condensed",r=s.includeSidechain===!0,o=s.since?Date.parse(s.since):0,i=t.filter(u=>!(!r&&u.is_sidechain===1||o&&u.timestamp&&Date.parse(u.timestamp)<o)),a=[];s.prelude&&(a.push(s.prelude.trim()),a.push("")),a.push(`# Claude Recall, past session context (${n})`),a.push(""),a.push(`- **Project**: ${e.project_name}`),e.decoded_path&&a.push(`- **Path**: \`${e.decoded_path}\``),a.push(`- **Session ID**: \`${e.id}\``),e.started_at&&a.push(`- **Started**: ${e.started_at}`),e.ended_at&&a.push(`- **Ended**: ${e.ended_at}`),e.git_branch&&a.push(`- **Branch**: \`${e.git_branch}\``),a.push(`- **Messages**: ${i.length}`),a.push(""),a.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."),a.push(""),a.push("---"),a.push("");let d=0,l=0;for(let u of i){let p=u.content_text??"",m=Ff(p);n==="condensed"&&(m=jf(m));let g=m.length>0,f=!!u.tool_names&&u.tool_names.length>0;if(!g&&!f){l+=1;continue}let h=Uf(u),E=u.timestamp?` \`${u.timestamp}\``:"";a.push(`## ${h}${E}`),a.push(""),f&&n==="condensed"&&(a.push(`_tools used: ${u.tool_names}_`),a.push("")),g&&(a.push(m),a.push("")),d+=1}return a.push("---"),a.push(""),a.push(`_${d} messages included_`+(l?`, ${l} empty skipped`:"")+(r?"":", subagent/sidechain hidden")+"."),a.join(`
|
|
953
|
+
`)}w();P();import{randomUUID as ma}from"node:crypto";import{writeFileSync as ga,readFileSync as nL,existsSync as Bf,mkdirSync as Hf}from"node:fs";import{join as Rr}from"node:path";var $s=Rr(x,"threads"),Wf=Rr($s,"index.json");function fa(){F(),Bf($s)||Hf($s,{recursive:!0})}function _a(e,t,s){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:t.session_count,origin_count:t.origin_count,project:s?.project??null,project_count:s?.project_count??0,folder_id:e.folder_id??null}}function ha(e){let t=new Map;if(e.length===0)return t;let s=_(),n=e.map(()=>"?").join(","),r=s.prepare(`SELECT te.thread_id AS thread_id,
|
|
954
954
|
p.name AS project,
|
|
955
955
|
COUNT(*) AS n,
|
|
956
956
|
SUM(CASE WHEN te.role = 'origin' THEN 1 ELSE 0 END) AS origin_n
|
|
@@ -958,7 +958,7 @@ ${o}
|
|
|
958
958
|
LEFT JOIN sessions s ON s.id = te.session_id
|
|
959
959
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
960
960
|
WHERE te.thread_id IN (${n})
|
|
961
|
-
GROUP BY te.thread_id, p.name`).all(...e),o=new Map;for(let i of r){let a=o.get(i.thread_id);a||(a=[],o.set(i.thread_id,a)),a.push(i)}for(let[i,a]of o){let d=a.filter(p=>p.project!==null),l=d.length,u=null;d.length>0&&(u=[...d].sort((m,g)=>g.n-m.n||g.origin_n-m.origin_n||(m.project??"").localeCompare(g.project??""))[0].project),t.set(i,{project:u,project_count:l})}return t}function
|
|
961
|
+
GROUP BY te.thread_id, p.name`).all(...e),o=new Map;for(let i of r){let a=o.get(i.thread_id);a||(a=[],o.set(i.thread_id,a)),a.push(i)}for(let[i,a]of o){let d=a.filter(p=>p.project!==null),l=d.length,u=null;d.length>0&&(u=[...d].sort((m,g)=>g.n-m.n||g.origin_n-m.origin_n||(m.project??"").localeCompare(g.project??""))[0].project),t.set(i,{project:u,project_count:l})}return t}function Ea(e){let t=e.auto_title_source;return{thread_id:e.thread_id,session_id:e.session_id,parent_session_id:e.parent_session_id,role:e.role,confidence:e.confidence,source:e.source,added_at:e.added_at,alias:e.alias,auto_title:e.auto_title,auto_title_source:t==="agent"||t==="heuristic"?t:null,alias_source:e.alias?"manual":null,first_user_message:e.first_user_message,project:e.project}}function ba(e){let s=_().prepare(`SELECT NULLIF(sa.alias, '') AS alias,
|
|
962
962
|
s.auto_title AS auto_title,
|
|
963
963
|
s.auto_title_source AS auto_title_source,
|
|
964
964
|
s.first_user_message AS first_user_message,
|
|
@@ -966,11 +966,11 @@ ${o}
|
|
|
966
966
|
FROM (SELECT ? AS sid) q
|
|
967
967
|
LEFT JOIN sessions s ON s.id = q.sid
|
|
968
968
|
LEFT JOIN session_aliases sa ON sa.session_id = q.sid
|
|
969
|
-
LEFT JOIN projects p ON p.id = s.project_id`).get(e),n=s?.auto_title_source??null;return{alias:s?.alias??null,auto_title:s?.auto_title??null,auto_title_source:n==="agent"||n==="heuristic"?n:null,first_user_message:s?.first_user_message??null,project:s?.project??null}}function
|
|
969
|
+
LEFT JOIN projects p ON p.id = s.project_id`).get(e),n=s?.auto_title_source??null;return{alias:s?.alias??null,auto_title:s?.auto_title??null,auto_title_source:n==="agent"||n==="heuristic"?n:null,first_user_message:s?.first_user_message??null,project:s?.project??null}}function Sa(e){let s=_().prepare(`SELECT
|
|
970
970
|
COUNT(*) AS session_count,
|
|
971
971
|
SUM(CASE WHEN role = 'origin' THEN 1 ELSE 0 END) AS origin_count
|
|
972
|
-
FROM thread_edges WHERE thread_id = ?`).get(e);return{session_count:s?.session_count??0,origin_count:s?.origin_count??0}}function Ae(e){let t=Oe(e);t&&(
|
|
973
|
-
VALUES (?, ?, NULL, 'origin', 1.0, 'manual', ?)`).run(n,e.originSessionId,r),Ae(n);let o=Oe(n);if(!o)throw new Error("thread creation succeeded but read-back failed");return o}function
|
|
972
|
+
FROM thread_edges WHERE thread_id = ?`).get(e);return{session_count:s?.session_count??0,origin_count:s?.origin_count??0}}function Ae(e){let t=Oe(e);t&&(fa(),ga(Rr($s,`${e}.json`),JSON.stringify(t,null,2)),ya())}function ya(){fa();let e=xr({includeArchived:!0});ga(Wf,JSON.stringify({threads:e},null,2))}function wa(e){let t=e.name.trim();if(!t)throw new Error("thread name cannot be empty");let s=_(),n=ma(),r=new Date().toISOString();s.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, ?, ?)").run(n,t,e.summary?.trim()||null,r),e.originSessionId&&s.prepare(`INSERT INTO thread_edges (thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
973
|
+
VALUES (?, ?, NULL, 'origin', 1.0, 'manual', ?)`).run(n,e.originSessionId,r),Ae(n);let o=Oe(n);if(!o)throw new Error("thread creation succeeded but read-back failed");return o}function xr(e={}){let t=_(),s=e.includeArchived?"":"WHERE archived = 0",n=t.prepare(`SELECT * FROM threads ${s} ORDER BY created_at DESC`).all(),r=ha(n.map(o=>o.id));return n.map(o=>_a(o,Sa(o.id),r.get(o.id)))}function Oe(e){let t=_(),s=t.prepare("SELECT * FROM threads WHERE id = ?").get(e);if(!s)return null;let n=t.prepare(`SELECT e.*,
|
|
974
974
|
NULLIF(sa.alias, '') AS alias,
|
|
975
975
|
s.auto_title AS auto_title,
|
|
976
976
|
s.auto_title_source AS auto_title_source,
|
|
@@ -981,7 +981,7 @@ ${o}
|
|
|
981
981
|
LEFT JOIN session_aliases sa ON sa.session_id = e.session_id
|
|
982
982
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
983
983
|
WHERE e.thread_id = ?
|
|
984
|
-
ORDER BY e.added_at ASC`).all(e).map(
|
|
984
|
+
ORDER BY e.added_at ASC`).all(e).map(Ea),r=ha([e]).get(e);return{..._a(s,Sa(s.id),r),edges:n}}function Ta(e){let t=_();if(!t.prepare("SELECT * FROM threads WHERE id = ?").get(e.threadId))throw new Error(`thread ${e.threadId} not found`);let n=new Date().toISOString(),r=e.parentSessionId??null,o=e.role??(r?"child":"origin"),i=e.confidence??1,a=e.source??"manual";if(i<0||i>1)throw new Error("confidence must be 0..1");t.prepare(`INSERT INTO thread_edges
|
|
985
985
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
986
986
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
987
987
|
ON CONFLICT(thread_id, session_id) DO UPDATE SET
|
|
@@ -989,57 +989,57 @@ ${o}
|
|
|
989
989
|
role = excluded.role,
|
|
990
990
|
confidence = excluded.confidence,
|
|
991
991
|
source = excluded.source,
|
|
992
|
-
added_at = excluded.added_at`).run(e.threadId,e.sessionId,r,o,i,a,n),Ae(e.threadId);let d=
|
|
992
|
+
added_at = excluded.added_at`).run(e.threadId,e.sessionId,r,o,i,a,n),Ae(e.threadId);let d=ba(e.sessionId);return{thread_id:e.threadId,session_id:e.sessionId,parent_session_id:r,role:o,confidence:i,source:a,added_at:n,alias:d.alias,auto_title:d.auto_title,auto_title_source:d.auto_title_source,alias_source:d.alias?"manual":null,first_user_message:d.first_user_message,project:d.project}}function Ra(e,t){let n=_().prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e,t);return n.changes>0&&Ae(e),{removed:n.changes}}function xa(e,t,s){let n=_(),r=n.prepare("SELECT * FROM thread_edges WHERE thread_id = ? AND session_id = ?").get(e,t);if(!r)throw new Error("edge not found; add the session first");if(s!==null){if(s===t)throw new Error("cycle detected: session cannot be its own parent");let a=n.prepare("SELECT parent_session_id FROM thread_edges WHERE thread_id = ? AND session_id = ?"),d=s,l=new Set;for(;d!==null;){if(d===t)throw new Error(`cycle detected: setting parent of ${t} to ${s} would create a loop`);if(l.has(d))break;l.add(d),d=a.get(e,d)?.parent_session_id??null}}let o=s?"child":"origin";n.prepare(`UPDATE thread_edges
|
|
993
993
|
SET parent_session_id = ?, role = ?, added_at = ?
|
|
994
|
-
WHERE thread_id = ? AND session_id = ?`).run(s,o,new Date().toISOString(),e,t),Ae(e);let i=
|
|
994
|
+
WHERE thread_id = ? AND session_id = ?`).run(s,o,new Date().toISOString(),e,t),Ae(e);let i=ba(t);return Ea({...r,parent_session_id:s,role:o,added_at:new Date().toISOString(),alias:i.alias,auto_title:i.auto_title,auto_title_source:i.auto_title_source,first_user_message:i.first_user_message,project:i.project})}function ka(e,t){let s=t.trim();if(!s)throw new Error("name cannot be empty");_().prepare("UPDATE threads SET name = ? WHERE id = ?").run(s,e),Ae(e);let r=Oe(e);if(!r)throw new Error(`thread ${e} not found`);return r}function Ca(e){_().prepare("UPDATE threads SET closed_at = ? WHERE id = ?").run(new Date().toISOString(),e),Ae(e);let s=Oe(e);if(!s)throw new Error(`thread ${e} not found`);return s}function La(e){_().prepare("UPDATE threads SET closed_at = NULL WHERE id = ?").run(e),Ae(e);let s=Oe(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Na(e){_().prepare("UPDATE threads SET archived = 1 WHERE id = ?").run(e),Ae(e);let s=Oe(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Aa(e,t){if(e===t)throw new Error("cannot merge a thread into itself");let s=_(),n=new Date().toISOString();s.transaction(()=>{let o=s.prepare("SELECT * FROM thread_edges WHERE thread_id = ?").all(e);for(let i of o)s.prepare(`INSERT INTO thread_edges
|
|
995
995
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
996
996
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
997
997
|
ON CONFLICT(thread_id, session_id) DO UPDATE SET
|
|
998
998
|
parent_session_id = COALESCE(thread_edges.parent_session_id, excluded.parent_session_id),
|
|
999
999
|
role = CASE WHEN thread_edges.role = 'origin' OR excluded.role = 'origin' THEN 'origin' ELSE 'child' END,
|
|
1000
1000
|
confidence = MAX(thread_edges.confidence, excluded.confidence),
|
|
1001
|
-
source = thread_edges.source`).run(t,i.session_id,i.parent_session_id,i.role,i.confidence,i.source,n);s.prepare("DELETE FROM threads WHERE id = ?").run(e)})(),Ae(t),
|
|
1001
|
+
source = thread_edges.source`).run(t,i.session_id,i.parent_session_id,i.role,i.confidence,i.source,n);s.prepare("DELETE FROM threads WHERE id = ?").run(e)})(),Ae(t),ya();let r=Oe(t);if(!r)throw new Error("merge destination disappeared");return r}function Oa(e){if(e.sessionIds.length===0)throw new Error("no sessions to split off");let t=_(),s=new Date().toISOString(),n=ma();t.transaction(()=>{t.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, NULL, ?)").run(n,e.newThreadName.trim(),s);for(let o of e.sessionIds){let i=t.prepare("SELECT * FROM thread_edges WHERE thread_id = ? AND session_id = ?").get(e.threadId,o);i&&(t.prepare(`INSERT INTO thread_edges
|
|
1002
1002
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1003
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(n,o,i.parent_session_id,i.role,i.confidence,i.source,s),t.prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e.threadId,o))}})(),Ae(e.threadId),Ae(n);let r=Oe(n);if(!r)throw new Error("split destination disappeared");return r}function
|
|
1003
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(n,o,i.parent_session_id,i.role,i.confidence,i.source,s),t.prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e.threadId,o))}})(),Ae(e.threadId),Ae(n);let r=Oe(n);if(!r)throw new Error("split destination disappeared");return r}function va(e){let t=Oe(e);if(!t)return[];let s=t.edges.filter(r=>r.role==="origin").map(r=>r.session_id),n=t.edges.filter(r=>r.role==="child").map(r=>r.session_id);return[...s,...n]}v();de();w();P();import{writeFileSync as Xf}from"node:fs";import{join as Jf}from"node:path";var Gf=Jf(x,"recall-events.json");function kr(e,t,s,n="cli"){_().prepare(`
|
|
1004
1004
|
INSERT INTO recall_events (session_id, recalled_at, token_count, mode, caller)
|
|
1005
1005
|
VALUES (?, datetime('now'), ?, ?, ?)
|
|
1006
|
-
`).run(e,t,s,n)
|
|
1007
|
-
`,"utf-8")}function
|
|
1008
|
-
`),null)}function
|
|
1006
|
+
`).run(e,t,s,n),Yf()}function Yf(){F();let t=_().prepare("SELECT id, session_id, recalled_at, token_count, mode, caller FROM recall_events ORDER BY recalled_at DESC").all();Xf(Gf,JSON.stringify(t,null,2)+`
|
|
1007
|
+
`,"utf-8")}function zf(e,t){if(t.length>=32)return e.prepare("SELECT id FROM sessions WHERE id = ?").get(t)?.id??null;let s=e.prepare("SELECT id FROM sessions WHERE id LIKE ? LIMIT 2").all(t+"%");return s.length===1?s[0].id:(s.length===0||process.stderr.write(`ambiguous id prefix "${t}". be more specific.
|
|
1008
|
+
`),null)}function qf(e){if(!e)return null;let t=e.match(/^(\d+)\s*(s|m|h|d)$/i);if(t){let n=parseInt(t[1],10),r=t[2].toLowerCase(),o=r==="s"?1e3:r==="m"?6e4:r==="h"?36e5:864e5;return new Date(Date.now()-n*o).toISOString()}if(/^\d{4}-\d{2}-\d{2}$/.test(e))return`${e}T00:00:00.000Z`;let s=Date.parse(e);return Number.isNaN(s)?null:new Date(s).toISOString()}function Kf(e,t){if(t.length>=32)return e.prepare("SELECT id FROM threads WHERE id = ?").get(t)?.id??null;let s=e.prepare("SELECT id, name FROM threads WHERE id LIKE ? ORDER BY created_at DESC").all(t+"%");if(s.length===1)return s[0].id;if(s.length===0)return null;process.stderr.write(`ambiguous thread prefix "${t}" \u2014 candidates:
|
|
1009
1009
|
`);for(let n of s)process.stderr.write(` ${X(n.id)} ${n.name}
|
|
1010
|
-
`);return null}function
|
|
1010
|
+
`);return null}function Ia(e,t,s){let n=e.prepare(`SELECT s.id, p.name AS project_name, p.decoded_path,
|
|
1011
1011
|
s.started_at, s.ended_at, s.message_count, s.git_branch
|
|
1012
1012
|
FROM sessions s JOIN projects p ON p.id = s.project_id
|
|
1013
1013
|
WHERE s.id = ?`).get(t);if(!n)return process.stderr.write(`session metadata missing for ${t}
|
|
1014
1014
|
`),null;let r=e.prepare(`SELECT uuid, type, role, timestamp, is_sidechain, content_text, tool_names
|
|
1015
1015
|
FROM messages
|
|
1016
1016
|
WHERE session_id = ?
|
|
1017
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(t),o=s.full?"full":"condensed";return
|
|
1018
|
-
`),process.exitCode=1;return}let a=
|
|
1019
|
-
`),process.exitCode=1;return}let d=
|
|
1020
|
-
`),process.exitCode=1;return}for(let l=0;l<d.length;l+=1){let u=d[l],p=
|
|
1017
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(t),o=s.full?"full":"condensed";return pa(n,r,{mode:o,includeSidechain:s.subagents===!0,prelude:s.prelude??null,since:qf(s.since)})}async function Ma(e,t){await Ne("Context re-injection");let s=_();if(e.startsWith("thread:")){let i=e.slice(7).trim();if(!i){process.stderr.write(`thread: target requires an id or prefix
|
|
1018
|
+
`),process.exitCode=1;return}let a=Kf(s,i);if(!a){process.stderr.write(`thread not found: ${i}
|
|
1019
|
+
`),process.exitCode=1;return}let d=va(a);if(d.length===0){process.stderr.write(`thread ${i} has no linked sessions
|
|
1020
|
+
`),process.exitCode=1;return}for(let l=0;l<d.length;l+=1){let u=d[l],p=Ia(s,u,{...t,prelude:l===0?t.prelude:void 0});if(p===null){process.exitCode=1;continue}l>0&&process.stdout.write(`
|
|
1021
1021
|
---
|
|
1022
1022
|
|
|
1023
1023
|
`),process.stdout.write(p),p.endsWith(`
|
|
1024
1024
|
`)||process.stdout.write(`
|
|
1025
|
-
`),
|
|
1026
|
-
`),process.exitCode=1;return}let r=
|
|
1025
|
+
`),kr(u,Math.ceil(p.length/4),"thread","cli")}return}let n=zf(s,e);if(!n){e.length>=32&&process.stderr.write(`session not found: ${e}
|
|
1026
|
+
`),process.exitCode=1;return}let r=Ia(s,n,t);if(r===null){process.exitCode=1;return}process.stdout.write(r),r.endsWith(`
|
|
1027
1027
|
`)||process.stdout.write(`
|
|
1028
|
-
`);let o=t.since?"since":t.full?"full":"condensed";
|
|
1029
|
-
`)}de();Ke();import{join as
|
|
1028
|
+
`);let o=t.since?"since":t.full?"full":"condensed";kr(n,Math.ceil(r.length/4),o,"cli"),process.stderr.isTTY&&process.stderr.write(`\x1B[2mShare this recall? \u2192 recall share\x1B[0m
|
|
1029
|
+
`)}de();Ke();import{join as Vf}from"node:path";import{spawn as Zf}from"node:child_process";async function Da(e={}){await Ne("MCP server");let t=Vf(ee(),"dist","mcp-server.js"),s={...process.env};e.allowWrites&&(s.RECALL_MCP_ALLOW_WRITES="1");let n=Zf(process.execPath,[t],{stdio:"inherit",env:s});return new Promise((r,o)=>{n.on("error",o),n.on("exit",(i,a)=>{if(a){process.kill(process.pid,a);return}process.exitCode=i??0,r()})})}w();import{execSync as Cr}from"node:child_process";import{randomUUID as Qf}from"node:crypto";import e_ from"node:readline/promises";v();async function $a(e){if(e.list){t_();return}if(e.purge){s_(e.purge);return}let t=await n_();t||(process.stderr.write(c.err(`clipboard empty / nothing on stdin
|
|
1030
1030
|
`)),process.exit(1));let s=Yt(t);if(s.length>0&&!e.force){process.stderr.write(c.warn(`\u26A0 detected ${s.length} possible secret${s.length===1?"":"s"} in content:
|
|
1031
1031
|
`));for(let a of s.slice(0,8))process.stderr.write(` ${c.err(a.pattern)} ${c.dim("\u2192")} ${a.maskedPreview}
|
|
1032
1032
|
`);s.length>8&&process.stderr.write(c.dim(` \u2026 ${s.length-8} more
|
|
1033
1033
|
`)),e.dryRun&&(process.stderr.write(c.dim(`
|
|
1034
1034
|
(dry run \u2014 nothing archived)
|
|
1035
1035
|
`)),process.exit(1)),!process.stdin.isTTY&&!process.stderr.isTTY&&(process.stderr.write(c.err(`refusing to archive secret content in non-interactive mode. use --force to override.
|
|
1036
|
-
`)),e.pipe&&process.stdout.write(t),process.exit(1));let o=
|
|
1036
|
+
`)),e.pipe&&process.stdout.write(t),process.exit(1));let o=e_.createInterface({input:process.stdin,output:process.stderr}),i=await o.question(c.accent("archive anyway? [y/N] "));o.close(),i.trim().toLowerCase()!=="y"&&(process.stderr.write(c.dim(`cancelled \u2014 nothing archived.
|
|
1037
1037
|
`)),e.pipe&&process.stdout.write(t),process.exit(0))}e.dryRun&&(process.stderr.write(c.dim(`(dry run \u2014 would archive ${t.length.toLocaleString()} chars)
|
|
1038
|
-
`)),e.pipe&&process.stdout.write(t),process.exit(0));let n=
|
|
1038
|
+
`)),e.pipe&&process.stdout.write(t),process.exit(0));let n=Qf(),r=new Date().toISOString();_().prepare(`INSERT INTO paste_archives (id, created_at, content, size_bytes, source, label)
|
|
1039
1039
|
VALUES (?, ?, ?, ?, ?, ?)`).run(n,r,t,Buffer.byteLength(t,"utf8"),e.pipe?"cli-piped":"cli",e.label??null),process.stderr.write(c.ok(`\u2713 archived ${t.length.toLocaleString()} chars as ${n.slice(0,8)}
|
|
1040
1040
|
`)),e.label&&process.stderr.write(c.dim(` label: ${e.label}
|
|
1041
1041
|
`)),process.stderr.write(c.dim(` purge any time with: recall paste --purge ${n.slice(0,8)}
|
|
1042
|
-
`)),e.pipe&&process.stdout.write(t)}function
|
|
1042
|
+
`)),e.pipe&&process.stdout.write(t)}function t_(){let e=_().prepare(`SELECT id, created_at, size_bytes, source, label,
|
|
1043
1043
|
substr(replace(replace(content, char(10), '\u21B5'), char(13), ''), 1, 80) AS preview
|
|
1044
1044
|
FROM paste_archives
|
|
1045
1045
|
ORDER BY created_at DESC
|
|
@@ -1051,11 +1051,11 @@ ${c.bold(`${e.length} archived paste${e.length===1?"":"s"}`)}
|
|
|
1051
1051
|
show full content: recall paste --show <id>
|
|
1052
1052
|
`)+c.dim(`purge one (permanent): recall paste --purge <id>
|
|
1053
1053
|
|
|
1054
|
-
`))}function
|
|
1054
|
+
`))}function s_(e){e.length<8&&(process.stderr.write(c.err(`provide at least 8 characters of the paste id to purge.
|
|
1055
1055
|
`)),process.exit(1));let t=_(),s=t.prepare("SELECT id FROM paste_archives WHERE id = ? OR id LIKE ? LIMIT 2").all(e,`${e}%`);s.length===0&&(process.stderr.write(c.err(`no paste matches ${e}
|
|
1056
1056
|
`)),process.exit(1)),s.length>1&&(process.stderr.write(c.err(`prefix ${e} is ambiguous. be more specific.
|
|
1057
1057
|
`)),process.exit(1)),t.prepare("DELETE FROM paste_archives WHERE id = ?").run(s[0].id),process.stderr.write(c.ok(`\u2713 purged ${s[0].id.slice(0,8)} \u2014 content permanently destroyed
|
|
1058
|
-
`))}async function
|
|
1058
|
+
`))}async function n_(){if(!process.stdin.isTTY)return await r_();try{return Cr("pbpaste",{encoding:"utf8",maxBuffer:16*1024*1024})}catch{}try{return Cr("xclip -o -selection clipboard",{encoding:"utf8",maxBuffer:16*1024*1024})}catch{}try{return Cr("powershell.exe -command Get-Clipboard",{encoding:"utf8",maxBuffer:16*1024*1024})}catch{}throw new Error("Could not read clipboard. On macOS pbpaste should be built in; on Linux install xclip; on Windows pipe into this command instead.")}async function r_(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(t));return Buffer.concat(e).toString("utf8")}w();v();async function Pa(e){let t=_(),s=new Map,n=0,r=0,o=0,i=new Set,a=t.prepare(`SELECT uuid, session_id, content_text, raw_json
|
|
1059
1059
|
FROM messages
|
|
1060
1060
|
WHERE content_text IS NOT NULL`).all(),d=t.prepare("UPDATE messages SET content_text = ?, raw_json = ? WHERE uuid = ?"),l=(f,h)=>{let E=Yt(f);if(E.length===0)return!1;for(let b of E){let S=s.get(b.pattern)??{pattern:b.pattern,severity:b.severity,hits:0,sessions:new Set};S.hits+=1,S.sessions.add(h),s.set(b.pattern,S)}return!0};process.stdout.write(c.dim(`Scanning ${a.length.toLocaleString()} messages\u2026
|
|
1061
1061
|
`));for(let f of a)if(n+=1,(l(f.content_text??"",f.session_id)||l(f.raw_json??"",f.session_id))&&(r+=1,i.add(f.session_id),e.verbose&&process.stderr.write(c.dim(` hit in session ${f.session_id.slice(0,8)} message ${f.uuid.slice(0,8)}
|
|
@@ -1070,22 +1070,22 @@ show full content: recall paste --show <id>
|
|
|
1070
1070
|
`)}process.stdout.write(`
|
|
1071
1071
|
`),e.redact?(process.stdout.write(c.ok(`\u2713 redacted in place: ${o.toLocaleString()} messages, ${m.toLocaleString()} session previews.
|
|
1072
1072
|
`)),process.stdout.write(c.dim(` (The source JSONL files at ~/.claude/projects/ were not modified.)
|
|
1073
|
-
`))):process.stdout.write(c.dim(" Rerun with --redact to scrub these in place, or run `recall index --force`.\n"))}w();v();import{basename as
|
|
1073
|
+
`))):process.stdout.write(c.dim(" Rerun with --redact to scrub these in place, or run `recall index --force`.\n"))}w();v();import{basename as o_}from"node:path";async function ja(e){let t=_(),s=await i_(t,e.project);if(!s){console.error(c.err(`No project found${e.project?` matching "${e.project}"`:" for current cwd"}. Run \`recall projects\` to list available projects.`)),process.exitCode=1;return}let n=t.prepare(`SELECT s.id,
|
|
1074
1074
|
s.auto_title,
|
|
1075
1075
|
s.auto_title_source,
|
|
1076
1076
|
CASE WHEN sa.session_id IS NOT NULL THEN 1 ELSE 0 END AS has_alias
|
|
1077
1077
|
FROM sessions s
|
|
1078
1078
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1079
1079
|
WHERE s.project_id = ?
|
|
1080
|
-
ORDER BY s.started_at`).all(s.id);if(n.length===0){console.error(c.warn(`Project "${s.name}" has 0 sessions.`));return}let r={clean:0,"fixed_v0.16.1":0,agent:0,manual_alias:0,template_pending:0,programmatic:0,recursive_meta:0,low_signal:0},o=[];for(let u of n){let p={auto_title:u.auto_title,auto_title_source:u.auto_title_source??null,has_alias:u.has_alias===1},m=
|
|
1080
|
+
ORDER BY s.started_at`).all(s.id);if(n.length===0){console.error(c.warn(`Project "${s.name}" has 0 sessions.`));return}let r={clean:0,"fixed_v0.16.1":0,agent:0,manual_alias:0,template_pending:0,programmatic:0,recursive_meta:0,low_signal:0},o=[];for(let u of n){let p={auto_title:u.auto_title,auto_title_source:u.auto_title_source??null,has_alias:u.has_alias===1},m=Ni(p);r[m]+=1,o.push({id:u.id,quality:m})}if(!e.dryRun){let u=t.prepare(`UPDATE sessions
|
|
1081
1081
|
SET title_quality = ?,
|
|
1082
1082
|
title_quality_computed_at = ?
|
|
1083
|
-
WHERE id = ?`),p=Date.now();t.transaction(g=>{for(let f of g)u.run(f.quality,p,f.id)})(o)}if(e.json){console.log(JSON.stringify({project:s.name,project_id:s.id,total_sessions:n.length,dry_run:!!e.dryRun,counts:r},null,2));return}let i=n.length;console.log(""),console.log(c.project(`Title quality audit \u2014 project ${c.bold(s.name)}`)),s.decoded_path&&console.log(c.dim(` ${s.decoded_path}`)),console.log(c.dim(` ${i} sessions${e.dryRun?" (DRY RUN \u2014 no DB writes)":""}`)),console.log("");let a=["clean","fixed_v0.16.1","agent","manual_alias","template_pending","programmatic","recursive_meta","low_signal"],d=Math.max(...a.map(u=>u.length));for(let u of a){let p=r[u];if(p===0)continue;let m=(p/i*100).toFixed(1).padStart(5),g=
|
|
1083
|
+
WHERE id = ?`),p=Date.now();t.transaction(g=>{for(let f of g)u.run(f.quality,p,f.id)})(o)}if(e.json){console.log(JSON.stringify({project:s.name,project_id:s.id,total_sessions:n.length,dry_run:!!e.dryRun,counts:r},null,2));return}let i=n.length;console.log(""),console.log(c.project(`Title quality audit \u2014 project ${c.bold(s.name)}`)),s.decoded_path&&console.log(c.dim(` ${s.decoded_path}`)),console.log(c.dim(` ${i} sessions${e.dryRun?" (DRY RUN \u2014 no DB writes)":""}`)),console.log("");let a=["clean","fixed_v0.16.1","agent","manual_alias","template_pending","programmatic","recursive_meta","low_signal"],d=Math.max(...a.map(u=>u.length));for(let u of a){let p=r[u];if(p===0)continue;let m=(p/i*100).toFixed(1).padStart(5),g=a_(p,i);console.log(` ${u.padEnd(d)} ${c.bold(String(p).padStart(5))} ${m}% ${g}`)}console.log("");let l=r.template_pending+r.programmatic+r.recursive_meta+r.low_signal;if(l>0){let u=(l/i*100).toFixed(1);console.log(c.dim(` ${l} sessions (${u}%) eligible for cleanup phases L1/L3/L4.`))}else console.log(c.ok(" No cleanup-eligible sessions remain in this project. \u2713"));console.log("")}async function i_(e,t){if(t){let o=e.prepare("SELECT id, name, decoded_path FROM projects WHERE name = ?").get(t);if(o)return o;let i=e.prepare("SELECT id, name, decoded_path FROM projects WHERE name LIKE ? ORDER BY name LIMIT 1").get(`%${t}%`);return i||null}let s=process.cwd(),n=e.prepare("SELECT id, name, decoded_path FROM projects WHERE decoded_path = ?").get(s);if(n)return n;let r=e.prepare("SELECT id, name, decoded_path FROM projects WHERE name = ? LIMIT 1").get(o_(s));return r||null}var Fa=28;function a_(e,t){let s=Math.round(e/t*Fa);return c.dim("\u2588".repeat(s)+"\xB7".repeat(Fa-s))}Et();v();w();Xn();w();Et();import{readFileSync as p_,existsSync as Nr,statSync as m_,readdirSync as g_}from"node:fs";import{join as Fs}from"node:path";import{homedir as f_}from"node:os";var es=["vscode","cursor","windsurf"],__={vscode:"Code",cursor:"Cursor",windsurf:"Windsurf"};var h_=.7,E_=300*1e3;function b_(e){let t=e?.homeDir??f_(),s=e?.sources??es,n=[];for(let r of s){let o=Fs(t,"Library","Application Support",__[r],"User","workspaceStorage");Nr(o)&&n.push({source:r,root:o})}return n}function S_(e,t){let s=Fs(e,"workspace.json"),n=Fs(e,"state.vscdb");if(!Nr(s)||!Nr(n))return[];let r;try{let d=JSON.parse(p_(s,"utf8"));if(!d.folder||!d.folder.startsWith("file://"))return[];r=decodeURIComponent(d.folder.replace(/^file:\/\//,""))}catch{return[]}let o;try{o=m_(n).mtime.toISOString()}catch{return[]}let i;try{i=new bs(n,{readonly:!0})}catch{return[]}let a=[];try{let d=i.prepare("SELECT value FROM ItemTable WHERE key = 'terminal.integrated.bufferState' LIMIT 1").get(),l={};if(d?.value)try{l=JSON.parse(d.value)}catch{}let u=l.state??[];if(u.length===0)a.push({workspace_path:r,workspace_storage_dir:e,tab_name:null,cwd_hint:null,last_seen_at:o,source:t});else for(let p of u){let m=p.shellLaunchConfig??{},g=typeof m.name=="string"?m.name.trim():"";a.push({workspace_path:r,workspace_storage_dir:e,tab_name:g||null,cwd_hint:typeof m.cwd=="string"?m.cwd:null,last_seen_at:o,source:t})}}finally{i.close()}return a}function y_(e,t){let s=[],n=0;if(e.cwd&&t.workspace_path){let r=e.cwd,o=t.workspace_path;(r===o||r.startsWith(o+"/")||o.startsWith(r+"/"))&&(n+=.5,s.push("cwd_prefix"))}if(e.started_at&&t.last_seen_at){let r=Date.parse(e.started_at),o=Date.parse(t.last_seen_at);Number.isFinite(r)&&Number.isFinite(o)&&Math.abs(r-o)<=E_&&(n+=.4,s.push("time_window"))}return e.cwd&&t.cwd_hint&&e.cwd===t.cwd_hint&&(n+=.1,s.push("cwd_exact")),t.tab_name&&(n+=.2,s.push("has_name")),n>1&&(n=1),{score:n,matchedOn:s}}var w_=new Set(["zsh","bash","fish","sh","dash","ksh","tcsh","csh","pwsh","powershell","cmd","nu","node","deno","bun","python","python3","ruby","ts-node","tsx","go","cargo","java","php","irb","pry","iex","task","tasks","terminal","copilot","cascade","composer","claude","claude code"]);function T_(e){return w_.has(e.trim().toLowerCase())}function R_(e){let t=e.tab_name?.trim();return!t||T_(t)?null:t}function Ha(e){let t=e?.minScore??h_,s=e?.limit,n=_(),r=e?.projectId?" AND s.project_id = ?":"",o=e?.projectId?[e.projectId]:[],i=n.prepare(`SELECT s.id, p.decoded_path AS cwd, s.started_at
|
|
1084
1084
|
FROM sessions s
|
|
1085
1085
|
JOIN projects p ON p.id = s.project_id
|
|
1086
1086
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1087
|
-
WHERE (sa.alias IS NULL OR sa.alias = '')${r}`).all(...o),a=e?.sources??es,d=
|
|
1088
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(e?"1":"0")}catch(t){let s=t instanceof Error?t.message:String(t);console.error(`[semantic-config] failed to sync semantic_enabled: ${s}`)}}w();Be();import{existsSync as
|
|
1087
|
+
WHERE (sa.alias IS NULL OR sa.alias = '')${r}`).all(...o),a=e?.sources??es,d=b_({sources:a,homeDir:e?.homeDir}),l=[];for(let{source:p,root:m}of d){let g;try{g=g_(m)}catch{continue}for(let f of g){let h=Fs(m,f);l.push(...S_(h,p))}}if(l.length===0)return[];let u=[];for(let p of i){let m=null;for(let f of l){let{score:h,matchedOn:E}=y_(p,f);h<t||(!m||h>m.score)&&(m={entry:f,score:h,matched:E})}if(!m)continue;let g=R_(m.entry);g&&u.push({session_id:p.id,proposed_alias:g,score:m.score,evidence:{source:m.entry.source,workspace_path:m.entry.workspace_path,workspace_storage_dir:m.entry.workspace_storage_dir,tab_name:m.entry.tab_name,cwd_hint:m.entry.cwd_hint,last_seen_at:m.entry.last_seen_at,matched_on:m.matched}})}return u.sort((p,m)=>m.score-p.score||p.session_id.localeCompare(m.session_id)),typeof s=="number"&&s>=0&&u.length>s?u.slice(0,s):u}function Wa(e){let t=[],s=[];for(let n of e){let r=Ps(n.session_id);if(r&&r.trim()!==""){s.push(n);continue}t.push(n)}return{applicable:t,skipped:s}}import{basename as x_}from"node:path";async function Ja(e){let t=C_(e.source);if(!t){console.error(c.err("Invalid --source. Allowed: vscode | cursor | windsurf | all (default).")),process.exitCode=1;return}let s=L_(e.minScore,.7);if(s===null||s<0||s>1){console.error(c.err("Invalid --min-score. Must be a number in [0, 1].")),process.exitCode=1;return}let n=e.limit?parseInt(e.limit,10):void 0;if(n!==void 0&&(!Number.isFinite(n)||n<0)){console.error(c.err("Invalid --limit. Must be a non-negative integer.")),process.exitCode=1;return}let r=e.project?N_(e.project):void 0;if(e.project&&r===null){console.error(c.err(`No project found matching "${e.project}". Run \`recall projects\`.`)),process.exitCode=1;return}let o=Ha({projectId:r??void 0,sources:t,minScore:s,limit:n}),{applicable:i,skipped:a}=Wa(o);if(e.apply){let d=0;for(let l of i)try{Mt(l.session_id,l.proposed_alias),d+=1}catch(u){console.error(c.err(`apply failed for ${l.session_id.slice(0,8)}: ${u.message}`))}if(e.json){console.log(JSON.stringify({mode:"apply",sources:t,min_score:s,project_id:r??null,applied:d,skipped:a.length,proposals:i.map(Xa)},null,2));return}console.log(""),console.log(c.ok(`Applied ${d} alias backfill${d===1?"":"s"}`+(a.length?` \xB7 skipped ${a.length} (alias appeared since proposal)`:""))),console.log("");return}if(e.json){console.log(JSON.stringify({mode:"dry-run",sources:t,min_score:s,project_id:r??null,proposals:i.map(Xa),skipped_due_to_existing_alias:a.length},null,2));return}k_(i,t,s,a.length)}function k_(e,t,s,n){if(console.log(""),console.log(c.project(`recall import-vscode-state \xB7 sources=${t.join("+")} min-score=${s.toFixed(2)} \xB7 DRY RUN`)),console.log(c.dim(" Pass --apply to write proposals via setAlias().")),console.log(""),e.length===0){console.log(c.warn(" No proposals \u2014 either no matching workspaces or every candidate session already has an alias.")),n>0&&console.log(c.dim(` ${n} sessions were skipped because they already have aliases.`)),console.log("");return}for(let r of e){let o=c.bold(r.session_id.slice(0,8)),i=c.bold(r.score.toFixed(2)),a=c.dim(`[${r.evidence.source}]`),d=r.evidence.matched_on.join("+");console.log(` ${o} score=${i} ${a} matched=${d}`),console.log(` proposed alias: ${c.bold(r.proposed_alias)}`),console.log(` workspace: ${c.dim(K(r.evidence.workspace_path,70))}`),r.evidence.tab_name&&console.log(` tab name: ${r.evidence.tab_name}`),r.evidence.cwd_hint&&console.log(` cwd hint: ${c.dim(K(r.evidence.cwd_hint,70))}`),console.log(` last seen: ${c.dim(r.evidence.last_seen_at)}`),console.log("")}console.log(c.dim(` ${e.length} proposal${e.length===1?"":"s"}`+(n>0?` \xB7 ${n} skipped (existing alias)`:"")+" \xB7 pass --apply to write via setAlias()")),console.log("")}function Xa(e){return e}function C_(e){if(!e||e==="all")return es;let t=e.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean),s=[];for(let n of t)if(es.includes(n))s.push(n);else return null;return s.length>0?s:null}function L_(e,t){if(e===void 0)return t;let s=Number.parseFloat(e);return Number.isFinite(s)?s:null}function N_(e){let t=_(),s=t.prepare("SELECT id FROM projects WHERE name = ? LIMIT 1").get(e);if(s)return s.id;let n=t.prepare("SELECT id FROM projects WHERE name LIKE ? ORDER BY name LIMIT 1").get(`%${e}%`);if(n)return n.id;let r=x_(e);if(r&&r!==e){let o=t.prepare("SELECT id FROM projects WHERE name = ? LIMIT 1").get(r);if(o)return o.id}return null}w();import{existsSync as Ga,mkdirSync as A_,readFileSync as O_,writeFileSync as v_}from"node:fs";import{homedir as I_}from"node:os";import{join as Ya}from"node:path";import{z as je}from"zod";function za(){return process.env.RECALL_HOME??Ya(I_(),".recall")}function M_(){let e=za();Ga(e)||A_(e,{recursive:!0})}function qa(){return Ya(za(),"config.json")}var Ka=je.object({enabled:je.boolean().default(!1),model:je.string().optional(),ratePerMinute:je.number().int().min(1).max(600).default(30),lastProcessedSessionId:je.string().nullable().default(null),backfillPaused:je.boolean().default(!1),autoExtractEnabled:je.boolean().default(!1),autoExtractIntervalMinutes:je.number().int().min(5).max(720).default(60),autoExtractBatchSize:je.number().int().min(1).max(20).default(1),autoResumeWorker:je.boolean().default(!1)}),js={enabled:!1,ratePerMinute:30,lastProcessedSessionId:null,backfillPaused:!1,autoExtractEnabled:!1,autoExtractIntervalMinutes:60,autoExtractBatchSize:1,autoResumeWorker:!1};function Va(){let e=qa();if(!Ga(e))return{};try{return JSON.parse(O_(e,"utf8"))}catch(t){return console.error("[semantic-config] failed to parse config.json, using defaults:",t),{}}}function ve(){let e=Va().semantic;if(!e)return{...js};let t=Ka.safeParse({...js,...e});return t.success?t.data:{...js}}function Ue(e){M_();let t=Va(),s=Ka.parse({...js,...t.semantic??{},...e}),n={...t,semantic:s};return v_(qa(),JSON.stringify(n,null,2)),D_(s.enabled),s}function D_(e){try{_().prepare(`INSERT INTO app_settings(key, value) VALUES ('semantic_enabled', ?)
|
|
1088
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(e?"1":"0")}catch(t){let s=t instanceof Error?t.message:String(t);console.error(`[semantic-config] failed to sync semantic_enabled: ${s}`)}}w();Be();import{existsSync as sh,mkdirSync as nh,writeFileSync as rh}from"node:fs";import{homedir as oh}from"node:os";import{join as Or}from"node:path";var ih=1,ah=12e3,ch=3,lh=[];function dh(){return process.env.RECALL_HOME??Or(oh(),".recall")}function pc(){return Or(dh(),"semantic")}function uh(){let e=pc();sh(e)||nh(e,{recursive:!0})}function ph(e){let t=_(),s=t.prepare(`SELECT s.id, s.message_count, s.first_user_message,
|
|
1089
1089
|
p.name AS project,
|
|
1090
1090
|
NULLIF(sa.alias, '') AS alias
|
|
1091
1091
|
FROM sessions s
|
|
@@ -1094,10 +1094,10 @@ show full content: recall paste --show <id>
|
|
|
1094
1094
|
WHERE s.id = ?`).get(e);if(!s)return null;let n=t.prepare(`SELECT role, content_text
|
|
1095
1095
|
FROM messages
|
|
1096
1096
|
WHERE session_id = ? AND is_sidechain = 0
|
|
1097
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(e),r=[],o=0;for(let i of n){if(!i.content_text)continue;let a=i.role??"system",d=i.content_text.replace(/```[\s\S]*?```/g,"[code]").replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g,"").trim();if(!d)continue;let l=d.length>1500?d.slice(0,1500)+"\u2026":d,u=`${a}: ${l}`;if(o+u.length>
|
|
1097
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(e),r=[],o=0;for(let i of n){if(!i.content_text)continue;let a=i.role??"system",d=i.content_text.replace(/```[\s\S]*?```/g,"[code]").replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g,"").trim();if(!d)continue;let l=d.length>1500?d.slice(0,1500)+"\u2026":d,u=`${a}: ${l}`;if(o+u.length>ah)break;r.push(u),o+=u.length}return{id:s.id,alias:s.alias,project:s.project,firstUserMessage:s.first_user_message,excerpt:r.join(`
|
|
1098
1098
|
|
|
1099
|
-
`),messageCount:s.message_count}}function
|
|
1100
|
-
`)}function
|
|
1099
|
+
`),messageCount:s.message_count}}function mh(e){return["You are summarizing a Claude Code session for a local semantic-search index. The summary will be stored as plain text and matched against future natural-language queries.","",`Session: ${e.alias??e.id}`,`Project: ${e.project}`,e.firstUserMessage?`Opening prompt: ${e.firstUserMessage}`:"","","Transcript excerpt (truncated):","---",e.excerpt,"---","","Output a single JSON object on one line, with no Markdown fences and no commentary:",'{"summary": "<3 sentences describing what the user was trying to do, what was built or debugged, and the outcome>", "keywords": ["<concept>", "<technology>", "<problem>", ...]}',"","Constraints:","- summary: 3 sentences, plain prose, no bullet points",'- keywords: 10\u201315 lowercase tokens, multi-word entries hyphenated (e.g. "memory-leak"); no duplicates; no generic words like "code" or "session"',"- Output JSON only. Do not echo this prompt."].filter(Boolean).join(`
|
|
1100
|
+
`)}function gh(e){let t=e.trim();try{let o=JSON.parse(t);typeof o.result=="string"&&(t=o.result.trim())}catch{}t=t.replace(/^```(?:json)?\s*/i,"").replace(/```\s*$/i,"").trim();let s=t.indexOf("{"),n=t.lastIndexOf("}");if(s===-1||n===-1||n<=s)return null;let r=t.slice(s,n+1);try{let o=JSON.parse(r),i=typeof o.summary=="string"?o.summary.trim():"",d=(Array.isArray(o.keywords)?o.keywords:[]).filter(l=>typeof l=="string").map(l=>l.trim().toLowerCase()).filter(l=>l.length>0&&l.length<64);return!i||d.length===0?null:{summary:i,keywords:Array.from(new Set(d)).slice(0,20)}}catch{return null}}function fh(e){let t=_(),s=e.keywords.join(",");t.prepare(`INSERT INTO session_semantic
|
|
1101
1101
|
(session_id, summary, keywords, model, source_message_count, generated_at)
|
|
1102
1102
|
VALUES (@session_id, @summary, @keywords, @model, @source_message_count, @generated_at)
|
|
1103
1103
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
@@ -1105,23 +1105,23 @@ show full content: recall paste --show <id>
|
|
|
1105
1105
|
keywords = excluded.keywords,
|
|
1106
1106
|
model = excluded.model,
|
|
1107
1107
|
source_message_count = excluded.source_message_count,
|
|
1108
|
-
generated_at = excluded.generated_at`).run({session_id:e.sessionId,summary:e.summary,keywords:s,model:e.model,source_message_count:e.sourceMessageCount,generated_at:e.generatedAt}),
|
|
1108
|
+
generated_at = excluded.generated_at`).run({session_id:e.sessionId,summary:e.summary,keywords:s,model:e.model,source_message_count:e.sourceMessageCount,generated_at:e.generatedAt}),uh();let n=Or(pc(),`${e.sessionId}.json`);rh(n,JSON.stringify({version:ih,session_id:e.sessionId,summary:e.summary,keywords:e.keywords,model:e.model,source_message_count:e.sourceMessageCount,generated_at:e.generatedAt},null,2))}var Bs=null;function _h(){let t=ve().ratePerMinute,s=t/6e4;return(!Bs||Bs.capacity!==t)&&(Bs={tokens:t,capacity:t,refillPerMs:s,lastRefill:Date.now()}),Bs}function hh(e){let t=Date.now(),s=t-e.lastRefill;s>0&&(e.tokens=Math.min(e.capacity,e.tokens+s*e.refillPerMs),e.lastRefill=t)}async function Eh(e){for(;;){if(e?.aborted)throw new Error("aborted");let t=_h();if(hh(t),t.tokens>=1){t.tokens-=1;return}let s=1-t.tokens,n=Math.max(50,Math.ceil(s/t.refillPerMs));await new Promise(r=>setTimeout(r,Math.min(n,5e3)))}}async function bh(e,t={}){let s=ve();if(!s.enabled)return{sessionId:e,ok:!1,reason:"disabled"};if(!ne())return{sessionId:e,ok:!1,reason:"claude-cli-missing"};let n=ph(e);if(!n)return{sessionId:e,ok:!1,reason:"session-not-found"};if(n.messageCount<ch)return{sessionId:e,ok:!1,reason:"too-short"};if(!n.excerpt.trim())return{sessionId:e,ok:!1,reason:"empty-excerpt"};await Eh(t.signal);let r=mh(n),o=await bt(r,lh,{model:s.model});if(!o.success)return{sessionId:e,ok:!1,reason:`claude-cli-exit-${o.exitCode??"null"}`,model:s.model??null};let i=gh(o.stdout);return i?(fh({sessionId:n.id,summary:i.summary,keywords:i.keywords,model:s.model??null,sourceMessageCount:n.messageCount,generatedAt:new Date().toISOString()}),{sessionId:e,ok:!0,model:s.model??null}):{sessionId:e,ok:!1,reason:"parse-failed",model:s.model??null}}async function Hs(e={}){let t=ve();if(!t.enabled)return{total:0,processed:0,ok:0,failed:0,currentSessionId:null};let s=_(),r={limit:e.limit??1e3},o="s.message_count >= 3";e.force||(o+=" AND ss.session_id IS NULL"),typeof e.projectId=="number"&&(o+=" AND s.project_id = @projectId",r.projectId=e.projectId),t.lastProcessedSessionId&&e.force;let i=s.prepare(`SELECT s.id
|
|
1109
1109
|
FROM sessions s
|
|
1110
1110
|
LEFT JOIN session_semantic ss ON ss.session_id = s.id
|
|
1111
1111
|
WHERE ${o}
|
|
1112
1112
|
ORDER BY COALESCE(s.started_at, '') ASC, s.id ASC
|
|
1113
|
-
LIMIT @limit`).all(r),a={total:i.length,processed:0,ok:0,failed:0,currentSessionId:null};e.onProgress?.(a);for(let{id:d}of i){if(e.signal?.aborted||ve().backfillPaused)break;a.currentSessionId=d,e.onProgress?.({...a});try{(await
|
|
1113
|
+
LIMIT @limit`).all(r),a={total:i.length,processed:0,ok:0,failed:0,currentSessionId:null};e.onProgress?.(a);for(let{id:d}of i){if(e.signal?.aborted||ve().backfillPaused)break;a.currentSessionId=d,e.onProgress?.({...a});try{(await bh(d,{signal:e.signal})).ok?a.ok+=1:a.failed+=1}catch(u){a.failed+=1,console.error("[semantic.backfill] failed for",d,u)}a.processed+=1,Ue({lastProcessedSessionId:d}),e.onProgress?.({...a})}return a.currentSessionId=null,e.onProgress?.({...a}),a}function mc(){let e=ve(),t=_(),s=t.prepare("SELECT COUNT(*) AS n FROM sessions WHERE message_count >= 3").get().n,n=t.prepare("SELECT COUNT(*) AS n FROM session_semantic").get().n;return{enabled:e.enabled,claudeCliAvailable:ne(),ratePerMinute:e.ratePerMinute,model:e.model??null,totalSessions:s,processedSessions:n,pendingSessions:Math.max(0,s-n),lastProcessedSessionId:e.lastProcessedSessionId,backfillPaused:e.backfillPaused}}Be();Js();yt();w();yt();w();function Sc(e){return e.replace(/```json[\s\S]*?```/g,"[tool-call]").replace(/\{[\s\S]{200,}?\}/g,"[json-object]")}function Mh(e){let t=[],o=0;for(;o<e.length;){let i=[],a=0;for(;i.length<5&&o<e.length;){let l=e[o],u=Sc(l.content_text??"");if(a+u.length>2e3&&i.length>=3)break;i.push(l),a+=u.length,o++}if(i.length===0)break;let d=i.map(l=>{let u=l.role??"system",p=Sc(l.content_text??"");return`[${u}] ${p}`}).join(`
|
|
1114
1114
|
|
|
1115
|
-
`);t.push({messageUuids:i.map(l=>l.uuid),text:d}),o<e.length&&i.length>=3&&(o=Math.max(o-1,o-1))}return t}function
|
|
1116
|
-
`);let S=((Date.now()-h)/1e3).toFixed(1);console.log(`Done in ${S}s \u2014 processed=${b.processed} ok=${b.ok} failed=${b.failed}`);return}if(s==="install"){let{ensureTransformersInstalled:m}=await Promise.resolve().then(()=>(
|
|
1115
|
+
`);t.push({messageUuids:i.map(l=>l.uuid),text:d}),o<e.length&&i.length>=3&&(o=Math.max(o-1,o-1))}return t}function jr(e){let s=_().prepare("SELECT uuid, role, content_text FROM messages WHERE session_id = ? AND is_sidechain = 0 AND content_text IS NOT NULL ORDER BY rowid").all(e);return Mh(s)}var Dh=!1,$h=null;var Ph=new Set;function Fh(){return _().prepare("SELECT COUNT(*) AS n FROM chunk_queue").get().n}function yc(){return{running:Dh,queueDepth:Fh(),lastProcessedAt:$h,blacklistedCount:Ph.size}}w();de();async function Ac(e,t){let s=(e??"status").toLowerCase();if(s==="on"||s==="enable"){if(!ne()){console.error("claude CLI not found on PATH. Install Claude Code first, then re-run."),process.exitCode=1;return}let m={enabled:!0};t.rate&&(m.ratePerMinute=Number(t.rate)),t.model&&(m.model=t.model);let g=Ue(m);console.log("Semantic search: ENABLED"),console.log(` Rate: ${g.ratePerMinute}/min`),g.model&&console.log(` Model: ${g.model}`),console.log(""),console.log("Disclosure: enabling semantic search sends condensed session"),console.log("summaries to Claude via your local `claude` CLI using your existing"),console.log("plan. New sessions are summarized on close. Run `recall semantic backfill`"),console.log("to summarize the existing archive.");return}if(s==="off"||s==="disable"){Ue({enabled:!1}),console.log("Semantic search: DISABLED"),console.log("Existing summaries are kept in the database; the pipeline will not run.");return}if(s==="pause"){Ue({backfillPaused:!0}),console.log("Backfill paused. Resume with `recall semantic resume`.");return}if(s==="resume"){Ue({backfillPaused:!1}),console.log("Backfill resumed.");return}if(s==="backfill"){let m=ve();if(!m.enabled){console.error("Semantic search is disabled. Run `recall semantic on` first."),process.exitCode=1;return}m.backfillPaused&&Ue({backfillPaused:!1});let g=t.limit?Math.max(1,Number(t.limit)):1e3,f=!!t.force;console.log(`Backfilling up to ${g} sessions${f?" (force)":""}\u2026`);let h=Date.now(),E=0,b=await Hs({limit:g,force:f,onProgress:R=>{if(R.processed===E)return;E=R.processed;let T=R.total>0?` (${Math.round(R.processed/R.total*100)}%)`:"";process.stdout.write(`\r ${R.processed}/${R.total}${T} ok=${R.ok} failed=${R.failed} `)}});process.stdout.write(`
|
|
1116
|
+
`);let S=((Date.now()-h)/1e3).toFixed(1);console.log(`Done in ${S}s \u2014 processed=${b.processed} ok=${b.ok} failed=${b.failed}`);return}if(s==="install"){let{ensureTransformersInstalled:m}=await Promise.resolve().then(()=>(Nc(),Lc)),g=await m();if(!g.ok){console.error("Failed to install @huggingface/transformers:"),console.error(` ${g.error}`),console.error("Vector search not enabled. Other features unaffected."),process.exitCode=1;return}g.action==="installed"&&console.log("Installed @huggingface/transformers."),St()?console.log("Model already installed."):(console.log("Downloading bge-base-en-v1.5 (~110MB)..."),await Mr((f,h,E)=>{let b=E>0?Math.round(h/E*100):0;process.stdout.write(`\r ${f}: ${b}% `)}),process.stdout.write(`
|
|
1117
1117
|
`)),console.log("Loading embedder...");try{await nt(),console.log("Done. Vector search is now active.")}catch(f){console.log("Model files installed."),console.log(` Embedder load skipped: ${f instanceof Error?f.message.split(`
|
|
1118
|
-
`)[0]:String(f)}`),console.log(" Vector features remain available; runtime load happens on first use.")}return}if(s==="uninstall"){
|
|
1118
|
+
`)[0]:String(f)}`),console.log(" Vector features remain available; runtime load happens on first use.")}return}if(s==="uninstall"){Dr(),console.log("Model removed. Vector search will fall back to keyword search.");return}if(s==="auto-extract"){let m=t._autoExtractAction;if(m==="on"||m==="enable"){let g=Ue({autoExtractEnabled:!0});console.log("Auto-extract: ENABLED"),console.log(` Cadence: 1 batch every ${g.autoExtractIntervalMinutes} minutes`),console.log(` Batch size: ${g.autoExtractBatchSize} session(s) per tick`),console.log(" Honors your 5-hour Claude plan window \u2014 at 60min/1, that is"),console.log(" ~5 extractions per window, leaving 95%+ of the rate budget free."),console.log(" The daemon will start nibbling through un-extracted sessions on"),console.log(" the next tick (within 15 minutes). Status: `recall semantic status`.");return}if(m==="off"||m==="disable"){Ue({autoExtractEnabled:!1}),console.log("Auto-extract: DISABLED. The daemon will stop running extract-outputs.");return}console.error("Usage: recall semantic auto-extract <on|off>"),process.exitCode=1;return}if(s==="reindex"){if(await Ne("Vector reindex"),!Ze().loaded){if(!St()){console.error("Model not installed. Run `recall semantic install` first."),process.exitCode=1;return}await nt()}let m=_(),g=m.prepare(`
|
|
1119
1119
|
SELECT s.id FROM sessions s
|
|
1120
1120
|
WHERE s.message_count >= 3
|
|
1121
1121
|
AND NOT EXISTS (SELECT 1 FROM chunk_meta cm WHERE cm.session_id = s.id)
|
|
1122
|
-
`).all(),f=Number(process.env.RECALL_REINDEX_MAX_CHUNKS??"0"),h=f>0?` (cap ${f} chunks/session)`:" (no cap)";console.log(`Reindexing ${g.length} sessions (skipping already-indexed)${h}...`);let E=0;for(let{id:b}of g){let S
|
|
1122
|
+
`).all(),f=Number(process.env.RECALL_REINDEX_MAX_CHUNKS??"0"),h=f>0?` (cap ${f} chunks/session)`:" (no cap)";console.log(`Reindexing ${g.length} sessions (skipping already-indexed)${h}...`);let E=0;for(let{id:b}of g){let S=jr(b),R=f>0?S.slice(0,f):S;if(R.length===0){E++;continue}let T=R.map($=>$.text),L=await $t(T);m.prepare("DELETE FROM vec_chunks WHERE rowid IN (SELECT rowid FROM chunk_meta WHERE session_id = ?)").run(b),m.prepare("DELETE FROM chunk_meta WHERE session_id = ?").run(b);let D=m.prepare(`INSERT INTO chunk_meta(session_id, message_uuids, text, embedding_model_id, embedding_dim, stale, generated_at)
|
|
1123
1123
|
VALUES (?, ?, ?, 'bge-base-en-v1.5', 768, 0, datetime('now'))`),A=m.prepare("INSERT INTO vec_chunks(rowid, embedding) VALUES (?, ?)");for(let $=0;$<R.length;$++){let U=D.run(b,JSON.stringify(R[$].messageUuids),R[$].text),y=Buffer.from(L[$].buffer,L[$].byteOffset,L[$].byteLength);A.run(BigInt(U.lastInsertRowid),y)}E++,E%10===0&&process.stdout.write(`\r ${E}/${g.length} `)}process.stdout.write(`
|
|
1124
|
-
`),console.log(`Reindexed ${E} sessions.`);return}if(s==="verify-spawn"){if(!ne()){console.error("claude CLI not found on PATH. Install Claude Code first, then re-run."),process.exitCode=1;return}let{spawnClaudePrompt:m}=await Promise.resolve().then(()=>(Be(),Us)),{homedir:g}=await import("node:os"),{join:f}=await import("node:path"),{readdirSync:h}=await import("node:fs"),E=f(g(),".claude","projects"),b=()=>{let $=new Set;try{for(let U of h(E,{withFileTypes:!0})){if(!U.isDirectory())continue;let y=f(E,U.name);for(let B of h(y,{withFileTypes:!0}))B.isFile()&&B.name.endsWith(".jsonl")&&$.add(f(y,B.name))}}catch{}return $};console.log("Snapshotting JSONL files in ~/.claude/projects/ ...");let S=b();console.log(` Before: ${S.size} JSONL file(s)`),console.log('Spawning a tiny `claude -p --no-session-persistence "ok"`...');let R=Date.now(),T=await m("Reply with the single word: ok",[],{}),L=((Date.now()-R)/1e3).toFixed(1);if(console.log(` CLI exit: ${T.exitCode}, ${L}s`),!T.success){console.error(` stderr: ${T.stderr.slice(-500)}`),console.error("FAIL: claude CLI exited non-zero. Cannot validate spawn behavior."),process.exitCode=1;return}console.log("Snapshotting again...");let D=b();console.log(` After: ${D.size} JSONL file(s)`);let A=[];for(let $ of D)S.has($)||A.push($);if(A.length===0){console.log(""),console.log("PASS: --no-session-persistence is honored on this claude CLI version."),console.log("It is safe to re-enable Tier-1 features (autoTitle.agentEnabled,"),console.log("semantic.enabled, semantic.autoExtractEnabled) in ~/.recall/config.json.");return}console.error(""),console.error(`FAIL: ${A.length} new JSONL file(s) appeared despite the flag.`),console.error("Tier-1 features WILL produce phantom sessions if re-enabled.");for(let $ of A.slice(0,5))console.error(` - ${$}`);A.length>5&&console.error(` ... and ${A.length-5} more`),console.error(""),console.error("Possible causes:"),console.error(" 1. Your `claude` CLI is older than the version that added the flag."),console.error(" 2. The flag is in --help but not actually wired up in your version."),console.error("Mitigation: keep autoTitle.agentEnabled=false until claude is upgraded."),process.exitCode=1;return}let n=
|
|
1124
|
+
`),console.log(`Reindexed ${E} sessions.`);return}if(s==="verify-spawn"){if(!ne()){console.error("claude CLI not found on PATH. Install Claude Code first, then re-run."),process.exitCode=1;return}let{spawnClaudePrompt:m}=await Promise.resolve().then(()=>(Be(),Us)),{homedir:g}=await import("node:os"),{join:f}=await import("node:path"),{readdirSync:h}=await import("node:fs"),E=f(g(),".claude","projects"),b=()=>{let $=new Set;try{for(let U of h(E,{withFileTypes:!0})){if(!U.isDirectory())continue;let y=f(E,U.name);for(let B of h(y,{withFileTypes:!0}))B.isFile()&&B.name.endsWith(".jsonl")&&$.add(f(y,B.name))}}catch{}return $};console.log("Snapshotting JSONL files in ~/.claude/projects/ ...");let S=b();console.log(` Before: ${S.size} JSONL file(s)`),console.log('Spawning a tiny `claude -p --no-session-persistence "ok"`...');let R=Date.now(),T=await m("Reply with the single word: ok",[],{}),L=((Date.now()-R)/1e3).toFixed(1);if(console.log(` CLI exit: ${T.exitCode}, ${L}s`),!T.success){console.error(` stderr: ${T.stderr.slice(-500)}`),console.error("FAIL: claude CLI exited non-zero. Cannot validate spawn behavior."),process.exitCode=1;return}console.log("Snapshotting again...");let D=b();console.log(` After: ${D.size} JSONL file(s)`);let A=[];for(let $ of D)S.has($)||A.push($);if(A.length===0){console.log(""),console.log("PASS: --no-session-persistence is honored on this claude CLI version."),console.log("It is safe to re-enable Tier-1 features (autoTitle.agentEnabled,"),console.log("semantic.enabled, semantic.autoExtractEnabled) in ~/.recall/config.json.");return}console.error(""),console.error(`FAIL: ${A.length} new JSONL file(s) appeared despite the flag.`),console.error("Tier-1 features WILL produce phantom sessions if re-enabled.");for(let $ of A.slice(0,5))console.error(` - ${$}`);A.length>5&&console.error(` ... and ${A.length-5} more`),console.error(""),console.error("Possible causes:"),console.error(" 1. Your `claude` CLI is older than the version that added the flag."),console.error(" 2. The flag is in --help but not actually wired up in your version."),console.error("Mitigation: keep autoTitle.agentEnabled=false until claude is upgraded."),process.exitCode=1;return}let n=mc(),r=St(),o=Ze(),i=yc(),a=ve(),d=0;try{d=_().prepare("SELECT COUNT(*) AS n FROM vec_chunks").get()?.n??0}catch{}let l=r||d>0;function u(){console.log("--- Tier 1 (inference \u2014 costs plan tokens) ---"),console.log(`Semantic search: ${n.enabled?"ENABLED":"disabled"}`),console.log(` claude CLI: ${n.claudeCliAvailable?"available":"NOT FOUND"}`),console.log(` Rate: ${n.ratePerMinute}/min`),console.log(` Model: ${n.model??"claude default"}`),console.log(` Sessions: ${n.processedSessions}/${n.totalSessions} summarized`),console.log(` Pending: ${n.pendingSessions}`),n.lastProcessedSessionId&&console.log(` Cursor: ${n.lastProcessedSessionId}`),n.backfillPaused&&console.log(" Backfill: PAUSED (run `recall semantic resume`)")}function p(){console.log("--- Tier 2 (vectors \u2014 free, local, zero tokens) ---"),console.log("Vector tier (Pro):"),console.log(` Model: ${r?"installed":"not installed"}`),console.log(` Embedder: ${o.loaded?"loaded":"not loaded"} (${o.modelId}, ${o.dim}d)`),console.log(` Worker: ${i.running?"running":"stopped"} (queue: ${i.queueDepth})`),console.log(` Chunks: ${d} indexed`)}l?(p(),console.log(""),u()):(u(),console.log(""),p()),console.log(""),console.log("Auto-extract:"),console.log(` Enabled: ${a.autoExtractEnabled?"YES":"no"}`),a.autoExtractEnabled?console.log(` Cadence: 1 batch every ${a.autoExtractIntervalMinutes} min \xD7 ${a.autoExtractBatchSize} session(s)`):console.log(" Run `recall semantic auto-extract on` to populate Patterns / Galaxy automatically."),process.env.RECALL_RRF_K&&console.log(` RRF k: ${process.env.RECALL_RRF_K}`)}w();w();w();yt();var rt=400,Vs=768,Kh="bge-base-en-v1.5";function Vh(e){return Buffer.from(e.buffer,e.byteOffset,e.byteLength)}function Zh(e){if(!e.message_uuid)throw new Error("message_uuid is required");if(!e.session_id)throw new Error("session_id is required");if(!(e.embedding instanceof Float32Array))throw new Error("embedding must be a Float32Array");if(e.embedding.length!==Vs)throw new Error(`embedding dim mismatch: got ${e.embedding.length}, expected ${Vs}`);let t=e.embedding_model_id??Kh,s=new Date().toISOString();return _().prepare(`INSERT INTO message_embeddings
|
|
1125
1125
|
(message_uuid, session_id, embedding,
|
|
1126
1126
|
embedding_model_id, embedding_dim, text_length, generated_at)
|
|
1127
1127
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
@@ -1131,7 +1131,7 @@ show full content: recall paste --show <id>
|
|
|
1131
1131
|
embedding_model_id = excluded.embedding_model_id,
|
|
1132
1132
|
embedding_dim = excluded.embedding_dim,
|
|
1133
1133
|
text_length = excluded.text_length,
|
|
1134
|
-
generated_at = excluded.generated_at`).run(e.message_uuid,e.session_id,
|
|
1134
|
+
generated_at = excluded.generated_at`).run(e.message_uuid,e.session_id,Vh(e.embedding),t,Vs,e.text_length,s),{message_uuid:e.message_uuid,session_id:e.session_id,embedding_model_id:t,embedding_dim:Vs,text_length:e.text_length,generated_at:s}}function Qh(e,t={}){let s=_(),n=t.force?`m.session_id = ? AND m.is_sidechain = 0
|
|
1135
1135
|
AND m.content_text IS NOT NULL
|
|
1136
1136
|
AND length(m.content_text) > ?`:`m.session_id = ? AND m.is_sidechain = 0
|
|
1137
1137
|
AND m.content_text IS NOT NULL
|
|
@@ -1141,7 +1141,7 @@ show full content: recall paste --show <id>
|
|
|
1141
1141
|
LEFT JOIN message_embeddings me ON me.message_uuid = m.uuid
|
|
1142
1142
|
WHERE ${n}
|
|
1143
1143
|
ORDER BY m.timestamp ASC, m.rowid ASC
|
|
1144
|
-
LIMIT ?`).all(e,rt,r)}var
|
|
1144
|
+
LIMIT ?`).all(e,rt,r)}var Oc=null;async function eE(){return Oc||(Ze().loaded||await nt(),$t)}async function tE(e,t={}){let s={embedded:0,skipped:0,failed:0,failures:[]},n=Qh(e,{limit:t.limit,force:t.force});if(n.length===0)return s;let r=await eE(),o=n.map(a=>a.content_text),i;try{if(t.signal?.aborted)return s;i=await r(o)}catch(a){return s.failed=n.length,s.failures=n.map(d=>({uuid:d.uuid,error:a instanceof Error?a.message:String(a)})),s}if(i.length!==n.length)return s.failed=n.length,s.failures=n.map(a=>({uuid:a.uuid,error:`embedder returned ${i.length} vectors for ${n.length} inputs`})),s;for(let a=0;a<n.length&&!t.signal?.aborted;a++)try{Zh({message_uuid:n[a].uuid,session_id:n[a].session_id,embedding:i[a],text_length:o[a].length}),s.embedded+=1}catch(d){s.failed+=1,s.failures.push({uuid:n[a].uuid,error:d instanceof Error?d.message:String(d)})}return s.skipped=0,s}async function vc(e={}){let t=_(),s=[],n="s.message_count >= 1";typeof e.projectId=="number"&&(n+=" AND s.project_id = ?",s.push(e.projectId));let r=t.prepare(`SELECT DISTINCT s.id AS id
|
|
1145
1145
|
FROM sessions s
|
|
1146
1146
|
JOIN messages m ON m.session_id = s.id
|
|
1147
1147
|
LEFT JOIN message_embeddings me ON me.message_uuid = m.uuid
|
|
@@ -1151,7 +1151,7 @@ show full content: recall paste --show <id>
|
|
|
1151
1151
|
AND length(m.content_text) > ?
|
|
1152
1152
|
AND me.message_uuid IS NULL
|
|
1153
1153
|
ORDER BY s.started_at ASC
|
|
1154
|
-
LIMIT ?`).all(...s,rt,e.limitSessions??1e3),o={total_sessions:r.length,processed_sessions:0,embedded_messages:0,failed_messages:0,current_session_id:null};e.onProgress?.({...o});for(let{id:i}of r){if(e.signal?.aborted)break;o.current_session_id=i,e.onProgress?.({...o});let a=await
|
|
1154
|
+
LIMIT ?`).all(...s,rt,e.limitSessions??1e3),o={total_sessions:r.length,processed_sessions:0,embedded_messages:0,failed_messages:0,current_session_id:null};e.onProgress?.({...o});for(let{id:i}of r){if(e.signal?.aborted)break;o.current_session_id=i,e.onProgress?.({...o});let a=await tE(i,{limit:e.limitMessagesPerSession,signal:e.signal});o.embedded_messages+=a.embedded,o.failed_messages+=a.failed,o.processed_sessions+=1,e.onProgress?.({...o})}return o.current_session_id=null,e.onProgress?.({...o}),o}function Zs(e,t){return t===0?0:Math.round(e/t*1e3)/10}function Ee(e){return _().prepare("SELECT id, name FROM projects WHERE name = ? LIMIT 1").get(e)??null}function Ic(e={}){let t=_(),s=null;if(typeof e.projectId=="number"){let S=t.prepare("SELECT id, name FROM projects WHERE id = ?").get(e.projectId);S&&(s=S)}else e.projectName&&(s=Ee(e.projectName));let n=s?" WHERE s.project_id = ?":"",r=s?[s.id]:[],o=t.prepare(`SELECT
|
|
1155
1155
|
COUNT(*) AS total,
|
|
1156
1156
|
SUM(CASE WHEN ss.session_id IS NOT NULL THEN 1 ELSE 0 END) AS with_semantic,
|
|
1157
1157
|
SUM(CASE WHEN cm_count.session_id IS NOT NULL THEN 1 ELSE 0 END) AS with_chunks,
|
|
@@ -1178,8 +1178,8 @@ show full content: recall paste --show <id>
|
|
|
1178
1178
|
FROM chunk_meta cm
|
|
1179
1179
|
${f}`).get(...r),E=s?" JOIN sessions s ON s.id = cq.session_id WHERE s.project_id = ?":"",b=t.prepare(`SELECT COUNT(*) AS n
|
|
1180
1180
|
FROM chunk_queue cq
|
|
1181
|
-
${E}`).get(...r);return{project:s,sessions:{total:i,with_semantic:a,with_semantic_pct:
|
|
1182
|
-
`);let d=((Date.now()-o)/1e3).toFixed(1);e.json?console.log(JSON.stringify({...a,elapsed_seconds:Number(d)},null,2)):console.log(`Done in ${d}s \u2014 processed=${a.processed} ok=${a.ok} failed=${a.failed}`)}async function
|
|
1181
|
+
${E}`).get(...r);return{project:s,sessions:{total:i,with_semantic:a,with_semantic_pct:Zs(a,i),with_chunks:d,with_chunks_pct:Zs(d,i),with_message_embeddings:l,with_message_embeddings_pct:Zs(l,i)},messages:{total:p.total??0,eligible:p.eligible??0,embedded:g.embedded,embedded_pct:Zs(g.embedded,p.eligible??0),threshold_chars:rt},chunks:{chunk_meta_rows:h.n,chunk_queue_pending:b.n},generated_at:new Date().toISOString()}}function Mc(e){let s=e.sessions.with_semantic_pct,n=e.sessions.total-e.sessions.with_semantic,r=e.sessions.total>0&&s>=95,o=r?`${s}% session_semantic coverage (${e.sessions.with_semantic} of ${e.sessions.total})`:e.sessions.total===0?"no sessions in scope":`${s}% session_semantic coverage (need \u226595%); ${n} sessions missing summaries`;return{passes:r,required_pct:95,actual_pct:s,missing_sessions:n,reason:o}}de();Js();yt();async function Dc(e,t){let s=(e??"audit").toLowerCase();if(s==="audit")return sE(t);if(s==="backfill-summaries")return rE(t);if(s==="backfill-messages")return oE(t);console.error(`Unknown embeddings action: ${e}. Supported: audit | backfill-summaries | backfill-messages`),process.exitCode=1}function Wr(e){if(!e)return null;let t=Ee(e);return t||"not-found"}async function sE(e){let t=Wr(e.project);if(t==="not-found"){console.error(`Project "${e.project}" not found.`),process.exitCode=1;return}let s=Ic({projectId:t?t.id:void 0}),n=Mc(s);if(e.json){console.log(JSON.stringify({report:s,verdict:n},null,2));return}nE(s,n),n.passes||(process.exitCode=1)}function nE(e,t){let s=e.project?`project "${e.project.name}"`:"all projects";console.log(`Embeddings coverage \u2014 ${s}`),console.log(""),console.log(` Sessions: ${e.sessions.total} total`),console.log(` session_semantic: ${e.sessions.with_semantic}/${e.sessions.total} (${e.sessions.with_semantic_pct}%)`),console.log(` chunk_meta (any): ${e.sessions.with_chunks}/${e.sessions.total} (${e.sessions.with_chunks_pct}%)`),console.log(` message_embeddings: ${e.sessions.with_message_embeddings}/${e.sessions.total} (${e.sessions.with_message_embeddings_pct}%)`),console.log(""),console.log(` Messages: ${e.messages.total} total`),console.log(` eligible (>${e.messages.threshold_chars} chars): ${e.messages.eligible}`),console.log(` embedded: ${e.messages.embedded}${e.messages.eligible>0?` (${e.messages.embedded_pct}% of eligible)`:""}`),console.log(""),console.log(" Chunks (Pro tier):"),console.log(` chunk_meta rows: ${e.chunks.chunk_meta_rows}`),console.log(` chunk_queue pending: ${e.chunks.chunk_queue_pending}`),e.chunks.chunk_queue_pending>1e3&&e.chunks.chunk_meta_rows===0&&(console.log(""),console.log(" \u26A0 chunk_queue is backed up but no chunks have been processed."),console.log(" Activate Pro and run `recall semantic install` to drain the queue.")),console.log(""),t.passes?console.log(` \u2705 Acceptance: ${t.reason}`):(console.log(` \u274C Acceptance: ${t.reason}`),console.log(` Run \`recall embeddings backfill-summaries${e.project?` --project ${e.project.name}`:""}\` to close the gap.`))}async function rE(e){if(!ve().enabled){console.error("Semantic search is disabled. Run `recall semantic on` first."),process.exitCode=1;return}let s=Wr(e.project);if(s==="not-found"){console.error(`Project "${e.project}" not found.`),process.exitCode=1;return}let n=e.limit?Math.max(1,Number(e.limit)):1e3,r=s?`project "${s.name}"`:"all projects";console.log(`Backfilling session_semantic \u2014 ${r} \u2014 up to ${n} sessions\u2026`);let o=Date.now(),i=0,a=await Hs({limit:n,projectId:s?s.id:void 0,onProgress:l=>{if(e.json||l.processed===i)return;i=l.processed;let u=l.total>0?` (${Math.round(l.processed/l.total*100)}%)`:"";process.stdout.write(`\r ${l.processed}/${l.total}${u} ok=${l.ok} failed=${l.failed} `)}});e.json||process.stdout.write(`
|
|
1182
|
+
`);let d=((Date.now()-o)/1e3).toFixed(1);e.json?console.log(JSON.stringify({...a,elapsed_seconds:Number(d)},null,2)):console.log(`Done in ${d}s \u2014 processed=${a.processed} ok=${a.ok} failed=${a.failed}`)}async function oE(e){if(await Ne("Per-message embeddings"),!St()){console.error("Model not installed. Run `recall semantic install` first."),process.exitCode=1;return}Ze().loaded||await nt();let t=Wr(e.project);if(t==="not-found"){console.error(`Project "${e.project}" not found.`),process.exitCode=1;return}let s=e.limit?Math.max(1,Number(e.limit)):200,n=t?`project "${t.name}"`:"all projects";console.log(`Embedding messages > ${rt} chars \u2014 ${n} \u2014 up to ${s} sessions\u2026`);let r=Date.now(),o=0,i=await vc({projectId:t?t.id:void 0,limitSessions:s,onProgress:m=>{if(e.json||m.processed_sessions===o)return;o=m.processed_sessions;let g=m.total_sessions>0?` (${Math.round(m.processed_sessions/m.total_sessions*100)}%)`:"";process.stdout.write(`\r ${m.processed_sessions}/${m.total_sessions} sessions${g} embedded=${m.embedded_messages} failed=${m.failed_messages} `)}});e.json||process.stdout.write(`
|
|
1183
1183
|
`);let a=((Date.now()-r)/1e3).toFixed(1);e.json?console.log(JSON.stringify({...i,elapsed_seconds:Number(a)},null,2)):console.log(`Done in ${a}s \u2014 sessions=${i.processed_sessions}/${i.total_sessions} embedded=${i.embedded_messages} failed=${i.failed_messages}`);let d=_(),l=t?" AND s.project_id = ?":"",u=t?[t.id]:[],p=d.prepare(`SELECT COUNT(DISTINCT s.id) AS n
|
|
1184
1184
|
FROM sessions s
|
|
1185
1185
|
JOIN messages m ON m.session_id = s.id
|
|
@@ -1188,7 +1188,7 @@ show full content: recall paste --show <id>
|
|
|
1188
1188
|
AND m.content_text IS NOT NULL
|
|
1189
1189
|
AND length(m.content_text) > ?
|
|
1190
1190
|
AND me.message_uuid IS NULL
|
|
1191
|
-
${l}`).get(rt,...u);p.n>0&&console.log(` ${p.n} session(s) still have unembedded eligible messages. Re-run to continue.`)}w();import{createHash as
|
|
1191
|
+
${l}`).get(rt,...u);p.n>0&&console.log(` ${p.n} session(s) still have unembedded eligible messages. Re-run to continue.`)}w();import{createHash as pE}from"node:crypto";Be();w();P();import{writeFileSync as iE,readFileSync as jA,existsSync as aE,mkdirSync as cE,readdirSync as UA}from"node:fs";import{join as $c}from"node:path";var Xr=$c(x,"output-index");function lE(){F(),aE(Xr)||cE(Xr,{recursive:!0})}function rs(e){if(!e)return[];try{let t=JSON.parse(e);return Array.isArray(t)?t:[]}catch{return[]}}function dE(e){if(!e)return null;try{return JSON.parse(e)}catch{return e}}function Pc(e){return{session_id:e.session_id,files_written:rs(e.files_written),brands_mentioned:rs(e.brands_mentioned),terms_introduced:rs(e.terms_introduced),plan_ids_referenced:rs(e.plan_ids_referenced),bug_signatures:rs(e.bug_signatures),raw_extraction:dE(e.raw_extraction),extracted_at:e.extracted_at,extractor_version:e.extractor_version}}function Fc(e){if(!e.session_id)throw new Error("session_id is required");let t=_(),s=new Date().toISOString(),n=JSON.stringify(e.files_written??[]),r=JSON.stringify(e.brands_mentioned??[]),o=JSON.stringify(e.terms_introduced??[]),i=JSON.stringify(e.plan_ids_referenced??[]),a=JSON.stringify(e.bug_signatures??[]),d=e.raw_extraction===void 0?null:JSON.stringify(e.raw_extraction),l=Math.max(1,Math.floor(e.extractor_version??1));t.prepare(`INSERT INTO session_output_index
|
|
1192
1192
|
(session_id, files_written, brands_mentioned, terms_introduced,
|
|
1193
1193
|
plan_ids_referenced, bug_signatures, raw_extraction,
|
|
1194
1194
|
extracted_at, extractor_version)
|
|
@@ -1201,7 +1201,7 @@ show full content: recall paste --show <id>
|
|
|
1201
1201
|
bug_signatures = excluded.bug_signatures,
|
|
1202
1202
|
raw_extraction = excluded.raw_extraction,
|
|
1203
1203
|
extracted_at = excluded.extracted_at,
|
|
1204
|
-
extractor_version = excluded.extractor_version`).run(e.session_id,n,r,o,i,a,d,s,l);let u=t.prepare("SELECT * FROM session_output_index WHERE session_id = ?").get(e.session_id);if(!u)throw new Error("setOutputIndex succeeded but read-back failed");let p=
|
|
1204
|
+
extractor_version = excluded.extractor_version`).run(e.session_id,n,r,o,i,a,d,s,l);let u=t.prepare("SELECT * FROM session_output_index WHERE session_id = ?").get(e.session_id);if(!u)throw new Error("setOutputIndex succeeded but read-back failed");let p=Pc(u);return uE(e.session_id),p}function os(e){let s=_().prepare("SELECT * FROM session_output_index WHERE session_id = ?").get(e);return s?Pc(s):null}function uE(e){try{lE();let t=os(e);if(!t)return;let s=$c(Xr,`${e}.json`),n={schema:"claude-recall.session-output-index.v1",backed_up_at:new Date().toISOString(),...t};iE(s,JSON.stringify(n,null,2))}catch(t){console.error("[output-index] backup failed:",t)}}var is=1,Yr="claude-haiku-4-5-20251001",mE=3,gE=32e3,jc=2e3,fE=30,_E=30,hE=30,EE=30;function bE(e){let s=_().prepare(`SELECT s.id,
|
|
1205
1205
|
NULLIF(sa.alias, '') AS alias,
|
|
1206
1206
|
s.auto_title,
|
|
1207
1207
|
s.auto_title_source,
|
|
@@ -1212,15 +1212,15 @@ show full content: recall paste --show <id>
|
|
|
1212
1212
|
FROM sessions s
|
|
1213
1213
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1214
1214
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1215
|
-
WHERE s.id = ?`).get(e);return s?{...s,alias_source:s.alias?"manual":null}:null}function
|
|
1215
|
+
WHERE s.id = ?`).get(e);return s?{...s,alias_source:s.alias?"manual":null}:null}function Uc(e,t={}){if(e.message_count<mE)return{eligible:!1,reason:"too-short"};let s=e.title_quality;if(s==="programmatic"||s==="recursive_meta")return{eligible:!1,reason:"low-signal-title"};if(e.auto_title_source==="agent"&&!e.alias)return{eligible:!1,reason:"agent-titled-no-override"};if(!t.force){let n=os(e.id);if(n&&n.extractor_version>=is)return{eligible:!1,reason:"already-extracted"}}return{eligible:!0}}function SE(e){let t=bE(e);if(!t)return null;let n=_().prepare(`SELECT role, content_text
|
|
1216
1216
|
FROM messages
|
|
1217
1217
|
WHERE session_id = ?
|
|
1218
1218
|
AND is_sidechain = 0
|
|
1219
1219
|
AND content_text IS NOT NULL
|
|
1220
|
-
ORDER BY COALESCE(timestamp, ''), rowid`).all(e),r=[],o=0;for(let i of n){let a=i.role??"system",d=i.content_text.trim();if(!d)continue;let l=d.length>
|
|
1220
|
+
ORDER BY COALESCE(timestamp, ''), rowid`).all(e),r=[],o=0;for(let i of n){let a=i.role??"system",d=i.content_text.trim();if(!d)continue;let l=d.length>jc?d.slice(0,jc)+"\u2026":d,u=`${a}: ${l}`;if(o+u.length>gE)break;r.push(u),o+=u.length}return{meta:t,excerpt:r.join(`
|
|
1221
1221
|
|
|
1222
|
-
`)}}function
|
|
1223
|
-
`)}function
|
|
1222
|
+
`)}}function yE(e){let{meta:t}=e;return["You are extracting a structured Output Index from a Claude Code session for a local knowledge graph.","","Read the transcript and produce a single JSON object with EXACTLY these fields. Output JSON only \u2014 no Markdown fences, no commentary, no explanation.","","Fields (every field MUST appear; use [] for empty):","- files_written: array of strings. Relative or absolute file paths the assistant created, edited, or extensively discussed (Write/Edit tool calls or paths quoted verbatim).",'- brands_mentioned: array of strings. Distinct proper-noun company / product / brand names mentioned. Examples of valid: "Glaser Group", "Apollo", "TikTok", "Cloudflare R2". NOT generic terms like "the company" or "users".','- terms_introduced: array of objects { "term": "<lowercase phrase>", "freq": <int> }. Distinctive multi-word noun phrases the session introduced or extensively discussed. Skip generic words. Cap at 30 entries.','- plan_ids_referenced: array of strings. Planning identifiers like "v0.18.A", "Phase D", "L3", "MASTER-PLAN-cognitive-graph". Empty array if none.','- bug_signatures: array of objects { "error_type": "<class or null>", "snippet": "<first line of the error>", "file": "<path or null>" }. Errors actually encountered in the session (not hypothetical). The error_type is the exception class (e.g. "TypeError", "ReferenceError") or null if unspecified.',"","Hard constraints:","- Do NOT fabricate. If a field has no concrete content from the transcript, output an empty array.","- Output JSON ONLY. No backticks, no markdown, no preamble.","- terms_introduced \u2264 30 entries; brands_mentioned \u2264 30 distinct entries; plan_ids_referenced \u2264 30; bug_signatures \u2264 30.","",`Session: ${t.alias??t.id.slice(0,8)}`,`Project: ${t.project??"unknown"}`,t.first_user_message?`Opening prompt: ${t.first_user_message.slice(0,500)}`:"","","Transcript excerpt (may be truncated):","---",e.excerpt,"---"].filter(Boolean).join(`
|
|
1223
|
+
`)}function wE(e){return Array.isArray(e)&&e.every(t=>typeof t=="string")}function Jr(e,t,s=!1){if(!Array.isArray(e))return[];let n=new Set,r=[];for(let o of e){if(typeof o!="string")continue;let i=s?o.trim().toLowerCase():o.trim();if(!(!i||i.length>256)&&!n.has(i)&&(n.add(i),r.push(i),r.length>=t))break}return r}function TE(e,t){if(!Array.isArray(e))return[];let s=new Set,n=[];for(let r of e){if(!r||typeof r!="object")continue;let o=r.term,i=r.freq;if(typeof o!="string")continue;let a=o.trim().toLowerCase();if(!a||a.length>128||s.has(a))continue;s.add(a);let d=typeof i=="number"&&Number.isFinite(i)&&i>0?Math.floor(i):1;if(n.push({term:a,frequency:d}),n.length>=t)break}return n}function RE(e,t){if(!Array.isArray(e))return[];let s=[];for(let n of e){if(!n||typeof n!="object")continue;let r=n.error_type,o=n.snippet,i=n.file,a=typeof r=="string"&&r.trim().length>0?r.trim().slice(0,64):"unknown",d=typeof o=="string"?o.trim().replace(/\s+/g," ").slice(0,256):"";if(!d)continue;let l=typeof i=="string"&&i.trim().length>0?i.trim().slice(0,256):null,u=pE("sha256").update(`${a}::${d}`).digest("hex").slice(0,12);if(s.push({error_type:a,message_hash:u,snippet:d,file:l}),s.length>=t)break}return s}function xE(e){let t=e.trim();try{let m=JSON.parse(t);typeof m.result=="string"&&(t=m.result.trim())}catch{}t=t.replace(/^```(?:json)?\s*/i,"").replace(/```\s*$/i,"").trim();let s=t.indexOf("{"),n=t.lastIndexOf("}");if(s===-1||n===-1||n<=s)return null;let r=t.slice(s,n+1),o;try{o=JSON.parse(r)}catch{return null}let i=Jr(o.files_written,200),a=Jr(o.brands_mentioned,_E),d=TE(o.terms_introduced,fE),l=Jr(o.plan_ids_referenced,hE),u=RE(o.bug_signatures,EE);return i.length===0&&a.length===0&&d.length===0&&l.length===0&&u.length===0&&!wE(o.files_written)?null:{files_written:i,brands_mentioned:a,terms_introduced:d,plan_ids_referenced:l,bug_signatures:u}}var Gr=null;async function kE(e,t){return Gr?Gr(e,t):bt(e,[],{model:t})}async function CE(e,t={}){if(t.signal?.aborted)return{session_id:e,ok:!1,failed:"aborted"};let s=SE(e);if(!s)return{session_id:e,ok:!1,skipped:"session-not-found"};let n=Uc(s.meta,{force:t.force});if(!n.eligible)return{session_id:e,ok:!1,skipped:n.reason};if(!Gr&&!ne())return{session_id:e,ok:!1,failed:"claude-cli-missing"};let r=yE(s),o=t.model??Yr,i=await kE(r,o);if(!i.success){let u=(i.stderr||i.stdout||"").slice(0,400),p=u?we(u).redacted:void 0;return{session_id:e,ok:!1,failed:"claude-cli-error",exit_code:i.exitCode,stderr_excerpt:p}}let a=xE(i.stdout);if(!a){let u=i.stdout.slice(0,400),p=u?we(u).redacted:void 0;return{session_id:e,ok:!1,failed:"parse-failed",exit_code:i.exitCode,stderr_excerpt:p}}let d=LE(i.stdout),l=Fc({session_id:e,files_written:a.files_written,brands_mentioned:a.brands_mentioned,terms_introduced:a.terms_introduced,plan_ids_referenced:a.plan_ids_referenced,bug_signatures:a.bug_signatures,raw_extraction:{model:o,usage:d,raw_response_excerpt:i.stdout.slice(0,4e3)},extractor_version:is});return{session_id:e,ok:!0,index:l,usage:d}}function LE(e){try{let t=JSON.parse(e.trim());if(t&&typeof t=="object"&&t.usage){let s=t.usage,n={};return typeof s.input_tokens=="number"&&(n.input_tokens=s.input_tokens),typeof s.output_tokens=="number"&&(n.output_tokens=s.output_tokens),n}}catch{}return null}function NE(e={}){let t=_(),s=[],n=[];typeof e.projectId=="number"&&(s.push("s.project_id = ?"),n.push(e.projectId));let r=Math.max(1,Math.min(1e4,e.limit??1e3)),o=s.length?`WHERE ${s.join(" AND ")}`:"",i=t.prepare(`SELECT s.id,
|
|
1224
1224
|
NULLIF(sa.alias, '') AS alias,
|
|
1225
1225
|
s.auto_title,
|
|
1226
1226
|
s.auto_title_source,
|
|
@@ -1232,13 +1232,13 @@ show full content: recall paste --show <id>
|
|
|
1232
1232
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1233
1233
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1234
1234
|
${o}
|
|
1235
|
-
ORDER BY COALESCE(s.started_at, ''), s.id`).all(...n),a=[],d=new Map;for(let l of i){let u={...l,alias_source:l.alias?"manual":null},p=
|
|
1236
|
-
`);let d=Number(((Date.now()-r)/1e3).toFixed(1)),l=i.total>0?Math.round(i.ok/i.total*1e3)/10:0;if(e.json){let u=a.filter(p=>!p.ok&&p.failed).map(p=>({session_id:p.session_id,failed:p.failed,exit_code:p.exit_code??null}));console.log(JSON.stringify({project:t.name,extractor_version:is,model:n,progress:i,acceptance_pct:l,failures:u.slice(0,20),elapsed_seconds:d},null,2))}else console.log(`Done in ${d}s \u2014 ok=${i.ok}/${i.total} (${l}%) failed=${i.failed} skipped=${i.skipped}`),console.log(`Token spend: input=${i.total_input_tokens} output=${i.total_output_tokens}`),i.total>0&&l<90?(console.log(` \u26A0 Phase D acceptance bar is 90% non-empty Output Index; actual ${l}%. Inspect failures and re-run.`),process.exitCode=1):i.total===0&&console.log(" \u24D8 No eligible sessions (all sessions were skipped or already extracted).")}w();as();var
|
|
1235
|
+
ORDER BY COALESCE(s.started_at, ''), s.id`).all(...n),a=[],d=new Map;for(let l of i){let u={...l,alias_source:l.alias?"manual":null},p=Uc(u,{force:e.force});if(!p.eligible){let m=p.reason??"session-not-found";d.set(m,(d.get(m)??0)+1);continue}if(a.push(u),a.length>=r)break}return{eligible:a,skipped:d}}async function Bc(e={}){let t=NE({projectId:e.projectId,limit:e.limit,force:e.force}),s={total:t.eligible.length,processed:0,ok:0,failed:0,skipped:0,current_session_id:null,total_input_tokens:0,total_output_tokens:0};for(let r of t.skipped.values())s.skipped+=r;e.onProgress?.({...s});let n=[];for(let r of t.eligible){if(e.signal?.aborted)break;s.current_session_id=r.id,e.onProgress?.({...s});let o=await CE(r.id,{model:e.model,force:e.force,signal:e.signal});n.push(o),e.onResult?.(o),o.ok?s.ok+=1:o.skipped?s.skipped+=1:s.failed+=1,o.usage?.input_tokens&&(s.total_input_tokens+=o.usage.input_tokens),o.usage?.output_tokens&&(s.total_output_tokens+=o.usage.output_tokens),s.processed+=1,e.onProgress?.({...s})}return s.current_session_id=null,e.onProgress?.({...s}),{progress:s,results:n}}Be();async function Hc(e){if(!e.project){console.error("--project <name> is required."),process.exitCode=1;return}let t=Ee(e.project);if(!t){console.error(`Project "${e.project}" not found.`),process.exitCode=1;return}if(!ne()){console.error("claude CLI not found on PATH. Install Claude Code first, then re-run."),process.exitCode=1;return}let s=e.limit?Math.max(1,Number(e.limit)):200,n=e.model??Yr;e.json||console.log(`Extracting Output Index \u2014 project "${t.name}" \u2014 extractor v${is} \u2014 model ${n} \u2014 up to ${s} sessions\u2026`);let r=Date.now(),o=-1,{progress:i,results:a}=await Bc({projectId:t.id,limit:s,force:e.force,model:n,onProgress:u=>{if(e.json||u.processed===o)return;o=u.processed;let p=u.total>0?` (${Math.round(u.processed/u.total*100)}%)`:"";process.stdout.write(`\r ${u.processed}/${u.total}${p} ok=${u.ok} failed=${u.failed} skipped=${u.skipped} in=${u.total_input_tokens} out=${u.total_output_tokens} `)}});e.json||process.stdout.write(`
|
|
1236
|
+
`);let d=Number(((Date.now()-r)/1e3).toFixed(1)),l=i.total>0?Math.round(i.ok/i.total*1e3)/10:0;if(e.json){let u=a.filter(p=>!p.ok&&p.failed).map(p=>({session_id:p.session_id,failed:p.failed,exit_code:p.exit_code??null}));console.log(JSON.stringify({project:t.name,extractor_version:is,model:n,progress:i,acceptance_pct:l,failures:u.slice(0,20),elapsed_seconds:d},null,2))}else console.log(`Done in ${d}s \u2014 ok=${i.ok}/${i.total} (${l}%) failed=${i.failed} skipped=${i.skipped}`),console.log(`Token spend: input=${i.total_input_tokens} output=${i.total_output_tokens}`),i.total>0&&l<90?(console.log(` \u26A0 Phase D acceptance bar is 90% non-empty Output Index; actual ${l}%. Inspect failures and re-run.`),process.exitCode=1):i.total===0&&console.log(" \u24D8 No eligible sessions (all sessions were skipped or already extracted).")}w();as();var sn={citation:"same-project",similar:"same-project",skill_track:"same-project",bug_pattern:"cross-project",wiki_link:"cross-project",temporal_proximity:"same-project"},BE=2,HE=.25,WE=5,XE=60,JE=25;function tn(e){return e.trim().toLowerCase()}function GE(e){let t=new Set;for(let s of e.files_written)t.add(`file:${tn(s)}`);for(let s of e.brands_mentioned)t.add(`brand:${tn(s)}`);for(let s of e.terms_introduced)t.add(`term:${tn(s)}`);for(let s of e.plan_ids_referenced)t.add(`plan:${tn(s)}`);return t}function YE(e){if(!Number.isFinite(e)||e<0)return 1;let t=Math.exp(-e/XE);return Math.max(.2,t)}function zE(e,t){if(!e||!t)return 0;let s=Date.parse(e),n=Date.parse(t);return!Number.isFinite(s)||!Number.isFinite(n)?0:Math.abs(n-s)/(1e3*60*60*24)}function qE(e){let t=os(e);return t?{session_id:t.session_id,files_written:t.files_written,brands_mentioned:t.brands_mentioned,terms_introduced:t.terms_introduced.map(s=>s.term),plan_ids_referenced:t.plan_ids_referenced}:null}function KE(e){return _().prepare(`SELECT s.id AS id, s.project_id AS project_id, s.started_at AS started_at
|
|
1237
1237
|
FROM sessions s
|
|
1238
1238
|
JOIN session_output_index oi ON oi.session_id = s.id
|
|
1239
1239
|
WHERE s.project_id = ?
|
|
1240
|
-
ORDER BY COALESCE(s.started_at, ''), s.id`).all(e)}function
|
|
1241
|
-
`);let o=Number(((Date.now()-s)/1e3).toFixed(1));e.json?console.log(JSON.stringify({project:t.name,progress:r.progress,suggestion_count:r.suggestion_ids.length,sample_suggestion_ids:r.suggestion_ids.slice(0,25),elapsed_seconds:o},null,2)):(console.log(`Done in ${o}s \u2014 sessions=${r.progress.processed_sessions}/${r.progress.total_sessions} suggestions=${r.progress.suggestions_created}`),r.progress.total_sessions===0?console.log(` \u24D8 No sessions had a populated Output Index. Run \`recall extract-outputs --project ${t.name}\` first.`):r.progress.suggestions_created===0?console.log(" \u24D8 No suggestions met the confidence threshold. This is expected on small corpora; the Output Index needs more shared vocabulary across sessions for citations to surface."):console.log(" Review the queue at GET /api/links/suggestions?status=pending (Phase F UI ships the queue review)."))}import{existsSync as
|
|
1240
|
+
ORDER BY COALESCE(s.started_at, ''), s.id`).all(e)}function VE(e,t){let s=new Map,n=new Map,r=new Map;for(let o of e){r.set(o.id,o.started_at);let i=t.get(o.id);if(!i)continue;let a=GE(i);if(a.size!==0){n.set(o.id,a);for(let d of a){let l=s.get(d);l?l.push(o.id):s.set(d,[o.id])}}}return{posting:s,vocab:n,startedAt:r}}function ZE(e,t){let s=t.vocab.get(e),n=t.startedAt.get(e)??null;if(!s||!n)return[];let r=new Map;for(let i of s){let a=t.posting.get(i);if(a)for(let d of a){if(d===e)continue;let l=t.startedAt.get(d);if(!l||l>=n)continue;let u=r.get(d);u?u.push(i):r.set(d,[i])}}let o=[];for(let[i,a]of r){let d=a.length;if(d<BE)continue;let l=t.startedAt.get(i)??null,u=zE(n,l),p=YE(u),m=Math.min(1,d/WE*p);if(m<HE)continue;let g=a.slice(0,12);o.push({target_session_id:i,matched_terms:g,overlap:d,days_apart:Math.round(u*10)/10,recency:Math.round(p*1e3)/1e3,confidence:Math.round(m*1e3)/1e3})}return o.sort((i,a)=>a.confidence-i.confidence),o.slice(0,JE)}async function Vc(e){if(sn.citation!=="same-project")throw new Error("citation policy unexpectedly not same-project \u2014 refusing to run inference");let t=KE(e.projectId),s=new Map;for(let i of t){let a=qE(i.id);a&&s.set(i.id,a)}let n=VE(t,s),r={total_sessions:t.length,processed_sessions:0,suggestions_created:0,suggestions_skipped_existing:0,current_session_id:null};e.onProgress?.({...r});let o=[];for(let i of t){if(e.signal?.aborted)break;r.current_session_id=i.id,e.onProgress?.({...r});let a=ZE(i.id,n);for(let d of a)try{let l=ot({source_session_id:i.id,target_session_id:d.target_session_id,link_type:"citation",confidence:d.confidence,evidence:{matched_terms:d.matched_terms,overlap_count:d.overlap,recency:d.recency,days_apart:d.days_apart},inferred_by:"L2"});o.push(l.id),r.suggestions_created+=1}catch(l){console.error("[citation-inference] createSuggestion failed:",l)}r.processed_sessions+=1,e.onProgress?.({...r})}return r.current_session_id=null,e.onProgress?.({...r}),{progress:r,suggestion_ids:o}}async function Zc(e){if(!e.project){console.error("--project <name> is required."),process.exitCode=1;return}let t=Ee(e.project);if(!t){console.error(`Project "${e.project}" not found.`),process.exitCode=1;return}e.json||console.log(`Inferring citation suggestions \u2014 project "${t.name}"\u2026`);let s=Date.now(),n=-1,r=await Vc({projectId:t.id,onProgress:i=>{if(e.json||i.processed_sessions===n)return;n=i.processed_sessions;let a=i.total_sessions>0?` (${Math.round(i.processed_sessions/i.total_sessions*100)}%)`:"";process.stdout.write(`\r ${i.processed_sessions}/${i.total_sessions} sessions${a} suggestions=${i.suggestions_created} `)}});e.json||process.stdout.write(`
|
|
1241
|
+
`);let o=Number(((Date.now()-s)/1e3).toFixed(1));e.json?console.log(JSON.stringify({project:t.name,progress:r.progress,suggestion_count:r.suggestion_ids.length,sample_suggestion_ids:r.suggestion_ids.slice(0,25),elapsed_seconds:o},null,2)):(console.log(`Done in ${o}s \u2014 sessions=${r.progress.processed_sessions}/${r.progress.total_sessions} suggestions=${r.progress.suggestions_created}`),r.progress.total_sessions===0?console.log(` \u24D8 No sessions had a populated Output Index. Run \`recall extract-outputs --project ${t.name}\` first.`):r.progress.suggestions_created===0?console.log(" \u24D8 No suggestions met the confidence threshold. This is expected on small corpora; the Output Index needs more shared vocabulary across sessions for citations to surface."):console.log(" Review the queue at GET /api/links/suggestions?status=pending (Phase F UI ships the queue review)."))}import{existsSync as pb,readFileSync as mb}from"node:fs";import{join as gb}from"node:path";w();as();var QE=/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/gi,eb=[/\bv\d+\.\d+(?:\.[A-Za-z]+)?\b/g,/\bPhase\s+[A-Ha-h]\b/g,/\bL\d+\b/g,/MASTER-PLAN-[A-Za-z-]+/g],tb=.95,sb=.85,nb=.7,Qr=50,rb=50;function ob(e){if(!e)return[];let t=new Set,s=[],n=e.match(QE);if(!n)return s;for(let r of n){let o=r.toLowerCase();if(!t.has(o)&&(t.add(o),s.push(o),s.length>=Qr))break}return s}function ib(e){if(!e)return[];let t=new Set,s=[];for(let n of eb){n.lastIndex=0;let r=e.match(n);if(r){for(let o of r){let i=o.trim().toLowerCase().replace(/\s+/g," ");if(!(!i||i.length>64)&&!t.has(i)&&(t.add(i),s.push(i),s.length>=Qr))break}if(s.length>=Qr)break}}return s}function eo(e){let t=_();return typeof e=="number"?t.prepare("SELECT id, project_id FROM sessions WHERE project_id = ?").all(e):t.prepare("SELECT id, project_id FROM sessions").all()}function Qc(e){let t=_();return typeof e=="number"?t.prepare(`SELECT m.uuid AS message_uuid, m.session_id, m.content_text,
|
|
1242
1242
|
s.project_id
|
|
1243
1243
|
FROM messages m
|
|
1244
1244
|
JOIN sessions s ON s.id = m.session_id
|
|
@@ -1251,12 +1251,12 @@ show full content: recall paste --show <id>
|
|
|
1251
1251
|
JOIN sessions s ON s.id = m.session_id
|
|
1252
1252
|
WHERE m.is_sidechain = 0
|
|
1253
1253
|
AND m.content_text IS NOT NULL
|
|
1254
|
-
AND length(m.content_text) > 0`).all()}function
|
|
1254
|
+
AND length(m.content_text) > 0`).all()}function ab(e){let t=_(),s=typeof e=="number"?"WHERE s.project_id = ?":"",n=typeof e=="number"?[e]:[];return t.prepare(`SELECT oi.session_id AS session_id,
|
|
1255
1255
|
s.project_id AS project_id,
|
|
1256
1256
|
oi.plan_ids_referenced AS plan_ids_json
|
|
1257
1257
|
FROM session_output_index oi
|
|
1258
1258
|
JOIN sessions s ON s.id = oi.session_id
|
|
1259
|
-
${s}`).all(...n)}function
|
|
1259
|
+
${s}`).all(...n)}function cb(e){if(!e)return[];try{let t=JSON.parse(e);if(Array.isArray(t))return t.filter(s=>typeof s=="string")}catch{}return[]}function lb(e={}){if(sn.citation!=="same-project")throw new Error("citation policy unexpectedly not same-project");let t=eo(e.projectId),s=new Set(t.map(o=>o.id)),n=new Map;for(let o of t)n.set(o.id,o.project_id);let r=0;for(let o of Qc(e.projectId)){if(e.signal?.aborted)break;let i=ob(o.content_text);if(i.length===0)continue;let a=n.get(o.session_id);if(a!==void 0)for(let d of i){if(d===o.session_id)continue;let l=n.get(d);if(!(l===void 0&&!s.has(d))&&!(l!==void 0&&a!==void 0&&l!==a))try{ot({source_session_id:o.session_id,target_session_id:d,link_type:"citation",confidence:tb,evidence:{matched_uuid:d,source_message_uuid:o.message_uuid,scanner:"uuid-ref"},inferred_by:"L1"}),r+=1}catch{}}}return{created:r}}function db(e={}){let t=ab(e.projectId);if(t.length===0)return{created:0};let s=new Map;for(let a of t){let d=cb(a.plan_ids_json);for(let l of d){let u=l.trim().toLowerCase();if(!u)continue;let p=s.get(u);p?p.push({id:a.session_id,project_id:a.project_id}):s.set(u,[{id:a.session_id,project_id:a.project_id}])}}if(s.size===0)return{created:0};let n=eo(e.projectId),r=new Map;for(let a of n)r.set(a.id,a.project_id);let o=0,i=new Map;for(let a of Qc(e.projectId)){if(e.signal?.aborted)break;let d=ib(a.content_text);if(d.length===0)continue;let l=r.get(a.session_id);if(l!==void 0)for(let u of d){let p=s.get(u);if(p)for(let m of p){if(m.id===a.session_id||m.project_id!==l)continue;let g=i.get(a.session_id);g||(g=new Map,i.set(a.session_id,g));let f=g.get(m.id);f||(f=new Set,g.set(m.id,f)),f.add(u)}}}for(let[a,d]of i)for(let[l,u]of d)try{ot({source_session_id:a,target_session_id:l,link_type:"citation",confidence:sb,evidence:{matched_plan_ids:Array.from(u).slice(0,12),scanner:"plan-ref"},inferred_by:"L1"}),o+=1}catch{}return{created:o}}function ub(e){if(sn.skill_track!=="same-project")throw new Error("skill_track policy unexpectedly not same-project");if(!e.registry||!Array.isArray(e.registry.terminals))return{created:0};let t=eo(e.projectId),s=new Map,n=new Map;if(t.length>0){let i=_(),a=t.map(u=>u.id),d=a.map(()=>"?").join(","),l=i.prepare(`SELECT id, started_at FROM sessions WHERE id IN (${d})`).all(...a);for(let u of l)n.set(u.id,u.started_at)}for(let i of t)s.set(i.id,i.project_id);let r=new Map;for(let i of e.registry.terminals){let a=e.registry.sessions_by_pid[String(i.shell_pid)]??[];if(a.length===0)continue;let d=`${i.cwd??"<no-cwd>"}::${i.tab_name}`,l=r.get(d);l||(l=new Set,r.set(d,l));for(let u of a)l.add(u)}let o=0;for(let[,i]of r){if(i.size<2)continue;let a=new Map;for(let d of i){let l=s.get(d);if(l===void 0||typeof e.projectId=="number"&&l!==e.projectId)continue;let u=a.get(l);u||(u=[],a.set(l,u)),u.push(d)}for(let[,d]of a){if(d.length<2)continue;d.sort((u,p)=>{let m=n.get(u)??"",g=n.get(p)??"";return m<g?-1:m>g?1:u<p?-1:1});let l=0;for(let u=1;u<d.length&&!e.signal?.aborted;u++)for(let p=0;p<u&&!(l>=rb);p++)try{ot({source_session_id:d[u],target_session_id:d[p],link_type:"skill_track",confidence:nb,evidence:{shared_tab_signature:!0,bucket_size:d.length,scanner:"tab-carry"},inferred_by:"L1"}),o+=1,l+=1}catch{}}}return{created:o}}function el(e={}){let t=lb(e);if(e.signal?.aborted)return{uuid_refs_created:t.created,plan_refs_created:0,tab_carry_created:0,total:t.created};let s=db(e);if(e.signal?.aborted)return{uuid_refs_created:t.created,plan_refs_created:s.created,tab_carry_created:0,total:t.created+s.created};let n=e.registry?ub({projectId:e.projectId,signal:e.signal,registry:e.registry}):{created:0};return{uuid_refs_created:t.created,plan_refs_created:s.created,tab_carry_created:n.created,total:t.created+s.created+n.created}}P();function fb(){let e=gb(x,"terminals.json");if(pb(e))try{let t=mb(e,"utf8"),s=JSON.parse(t);if(!Array.isArray(s.terminals))return;let n=s.terminals.filter(o=>typeof o.shell_pid=="number"&&typeof o.tab_name=="string").map(o=>({shell_pid:o.shell_pid,tab_name:o.tab_name,cwd:o.cwd??null})),r=s.sessions_by_pid??{};return{terminals:n,sessions_by_pid:r}}catch{return}}async function tl(e){let t=null;if(e.project&&(t=Ee(e.project),!t)){console.error(`Project "${e.project}" not found.`),process.exitCode=1;return}let s=fb(),n=t?`project "${t.name}"`:"all projects";e.json||console.log(`L1 deterministic inference \u2014 ${n} \u2014 `+(s?`(tab registry has ${s.terminals.length} entries)`:"(no tab registry; tab-carry scanner skipped)"));let r=Date.now(),o=el({projectId:t?.id,registry:s}),i=Number(((Date.now()-r)/1e3).toFixed(2));e.json?console.log(JSON.stringify({project:t?.name??null,...o,elapsed_seconds:i},null,2)):(console.log(`Done in ${i}s \u2014 uuid_refs=${o.uuid_refs_created} plan_refs=${o.plan_refs_created} tab_carry=${o.tab_carry_created} (total=${o.total})`),o.total===0?console.log(" \u24D8 Zero new suggestions. This is expected when L1 has already run against this corpus or when the project has no inter-session references. (Tombstones from prior reject decisions also keep re-runs idempotent.)"):console.log(" Review the queue at GET /api/links/suggestions?status=pending (or the web UI at #view=suggestions)."))}w();Be();as();function to(e){if(!e||e.length===0)return 0;let t=1,s=0;for(let n of e){if(!Number.isFinite(n))continue;let r=Math.max(0,Math.min(1,n));if(t*=1-r,s+=1,t<=1e-9)return 1}return s===0?0:Math.round((1-t)*1e4)/1e4}var no="claude-haiku-4-5-20251001",nn=.4,ro=.95,sl=30,nl=240,_b=new Set(["CITATION","SIMILAR","SKILL_TRACK","UNRELATED"]),hb={CITATION:"citation",SIMILAR:"similar",SKILL_TRACK:"skill_track"};function il(e){return _().prepare(`SELECT s.id,
|
|
1260
1260
|
s.source_session_id,
|
|
1261
1261
|
s.target_session_id,
|
|
1262
1262
|
s.link_type,
|
|
@@ -1266,7 +1266,7 @@ show full content: recall paste --show <id>
|
|
|
1266
1266
|
FROM session_link_suggestions s
|
|
1267
1267
|
JOIN sessions src ON src.id = s.source_session_id
|
|
1268
1268
|
WHERE s.status = 'pending'
|
|
1269
|
-
AND src.project_id = ?`).all(e)}function
|
|
1269
|
+
AND src.project_id = ?`).all(e)}function Eb(e){if(e.length===0)return new Map;let t=_(),s=e.map(()=>"?").join(","),n=t.prepare(`SELECT s.id,
|
|
1270
1270
|
NULLIF(sa.alias, '') AS alias,
|
|
1271
1271
|
s.auto_title,
|
|
1272
1272
|
s.first_user_message,
|
|
@@ -1275,18 +1275,18 @@ show full content: recall paste --show <id>
|
|
|
1275
1275
|
FROM sessions s
|
|
1276
1276
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1277
1277
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1278
|
-
WHERE s.id IN (${s})`).all(...e),r=new Map;for(let o of n)r.set(o.id,o);return r}function
|
|
1279
|
-
`)}var
|
|
1280
|
-
`);let l=Number(((Date.now()-a)/1e3).toFixed(1));if(e.json)console.log(JSON.stringify({project:t.name,model:o,pre_filter_threshold:s,auto_promote:i,auto_promote_threshold:i?n:null,...d,elapsed_seconds:l},null,2));else{if(console.log(`Done in ${l}s \u2014 pairs=${d.candidates_after_filter}/${d.candidates_total} classified=${d.classified} suggestions=${d.suggestions_created} promoted=${d.links_promoted} failures=${d.failures.length}`),console.log(`Token spend: input=${d.total_input_tokens} output=${d.total_output_tokens}`),d.failures.length>0)for(let u of d.failures.slice(0,3))console.log(` \u26A0 batch ${u.batch_index}: ${u.error}`);d.suggestions_created>0?console.log(" Review the new L4 suggestions at #view=suggestions (filter by inferred_by=L4)."):d.candidates_after_filter===0&&console.log(` \u24D8 No pairs cleared the pre-filter (combined conf \u2265 ${s}). Lower --min-conf to widen the batch, or run more L1/L2/L3 inference first.`)}}w();import{createHash as
|
|
1278
|
+
WHERE s.id IN (${s})`).all(...e),r=new Map;for(let o of n)r.set(o.id,o);return r}function rl(e,t){if(!e)return t.slice(0,8);let s=e.first_user_message?e.first_user_message.slice(0,80):null;return e.alias??e.auto_title??s??t.slice(0,8)}function ol(e){try{return JSON.parse(e)}catch{return e}}function bb(e){let t=e.minConfidence??nn,s=Math.max(1,Math.min(500,e.limit??100)),n=il(e.projectId);if(n.length===0)return[];let r=new Map,o=new Map;for(let l of n){let u=`${l.source_session_id}|${l.target_session_id}|${l.link_type}`,p=o.get(u);p||(p={source_session_id:l.source_session_id,target_session_id:l.target_session_id,link_type:l.link_type,layers:[]},o.set(u,p));let m=p.layers.find(g=>g.inferred_by===l.inferred_by);m?(m.suggestion_ids.push(l.id),l.confidence>m.confidence&&(m.confidence=l.confidence,m.evidence=ol(l.evidence))):p.layers.push({inferred_by:l.inferred_by,link_type:l.link_type,confidence:l.confidence,evidence:ol(l.evidence),suggestion_ids:[l.id]})}let i=new Set;for(let l of o.values())i.add(l.source_session_id),i.add(l.target_session_id);let a=Eb(Array.from(i)),d=[];for(let l of o.values()){let u=to(l.layers.map(g=>g.confidence));if(u<t)continue;let p=a.get(l.source_session_id),m=a.get(l.target_session_id);d.push({source_session_id:l.source_session_id,target_session_id:l.target_session_id,primary_link_type:l.link_type,combined_confidence:u,layers:l.layers,source_title:rl(p,l.source_session_id),target_title:rl(m,l.target_session_id),source_project:p?.project??null})}return d.sort((l,u)=>u.combined_confidence-l.combined_confidence),d.slice(0,s)}function Sb(e){let t;try{t=JSON.stringify(e)}catch{t=String(e)}return t.length>nl&&(t=t.slice(0,nl)+"\u2026"),t}function yb(e){let t=["You are classifying candidate session-pair relationships for a developer knowledge graph.","","Categories:","- CITATION: the source session explicitly references content/decisions from the target (a previous session).","- SIMILAR: same subject matter, but neither cites the other directly.","- SKILL_TRACK: same person doing the same kind of work \u2014 same skill, different tasks.","- UNRELATED: shared evidence is coincidental; no real relationship.","","Use UNRELATED with low confidence when evidence is weak or contradictory.","Output a SINGLE JSON object on one line, no markdown fences, no commentary:",'{"classifications":[{"i":0,"label":"CITATION","conf":0.85,"why":"<<=25 words>"},...]}',"",`Pairs (${e.length}):`];return e.forEach((s,n)=>{t.push(""),t.push(`[${n}] B="${(s.source_title??"").slice(0,80)}" [${s.source_session_id.slice(0,8)}] -> A="${(s.target_title??"").slice(0,80)}" [${s.target_session_id.slice(0,8)}]`),t.push(` primary_type: ${s.primary_link_type}`),t.push(` combined_conf: ${s.combined_confidence.toFixed(3)}`);for(let r of s.layers)t.push(` ${r.inferred_by} ${r.link_type} conf=${r.confidence.toFixed(2)} ev=${Sb(r.evidence)}`)}),t.join(`
|
|
1279
|
+
`)}var so=null;async function wb(e,t){return so?so(e,t):bt(e,[],{model:t})}function Tb(e){try{let t=JSON.parse(e.trim());return{input_tokens:t?.usage?.input_tokens??0,output_tokens:t?.usage?.output_tokens??0}}catch{return{input_tokens:0,output_tokens:0}}}function Rb(e){let t=e.trim();try{let i=JSON.parse(t);typeof i.result=="string"&&(t=i.result.trim())}catch{}t=t.replace(/^```(?:json)?\s*/i,"").replace(/```\s*$/i,"").trim();let s=t.indexOf("{"),n=t.lastIndexOf("}");if(s===-1||n===-1||n<=s)return null;let r;try{r=JSON.parse(t.slice(s,n+1))}catch{return null}if(!Array.isArray(r.classifications))return null;let o=[];for(let i of r.classifications){if(!i||typeof i!="object")continue;let a=i,d=typeof a.i=="number"?a.i:typeof a.pair_index=="number"?a.pair_index:NaN;if(!Number.isInteger(d)||d<0)continue;let l=typeof a.label=="string"?a.label.toUpperCase():"";if(!_b.has(l))continue;let u=typeof a.conf=="number"?a.conf:typeof a.confidence=="number"?a.confidence:NaN;if(!Number.isFinite(u))continue;let p=Math.max(0,Math.min(1,u)),m=typeof a.why=="string"?a.why.trim().slice(0,200):typeof a.reason=="string"?a.reason.trim().slice(0,200):"";o.push({pair_index:d,label:l,confidence:p,reason:m})}return o}async function al(e){let t={candidates_total:0,candidates_after_filter:0,classified:0,suggestions_created:0,links_promoted:0,total_input_tokens:0,total_output_tokens:0,failures:[]};if(!so&&!ne())return t.failures.push({batch_index:-1,error:"claude CLI not available on PATH"}),t;let s=bb({projectId:e.projectId,minConfidence:e.minConfidence??nn,limit:e.limit??100});t.candidates_after_filter=s.length;let n=il(e.projectId),r=new Set;for(let a of n)r.add(`${a.source_session_id}|${a.target_session_id}|${a.link_type}`);if(t.candidates_total=r.size,s.length===0)return t;let o=e.model??no,i=e.autoPromoteThreshold??ro;for(let a=0,d=0;a<s.length&&!e.signal?.aborted;a+=sl,d+=1){let l=s.slice(a,a+sl);e.onBatchStart?.({batch:d,pairs:l.length});let u=yb(l),p=await wb(u,o);if(!p.success){t.failures.push({batch_index:d,error:`claude CLI exited ${p.exitCode}: ${p.stderr.slice(0,200)}`});continue}let m=Rb(p.stdout);if(!m){t.failures.push({batch_index:d,error:"parse-failed"});continue}let g=Tb(p.stdout);t.total_input_tokens+=g.input_tokens,t.total_output_tokens+=g.output_tokens;for(let f of m){if(f.pair_index>=l.length)continue;let h=l[f.pair_index];if(t.classified+=1,f.label==="UNRELATED")continue;let E=hb[f.label];if(E)try{let b=ot({source_session_id:h.source_session_id,target_session_id:h.target_session_id,link_type:E,confidence:f.confidence,evidence:{l4_label:f.label,l4_reason:f.reason,primary_type_l1_l2_l3:h.primary_link_type,combined_pre_l4:h.combined_confidence,scanner:"l4-llm",model:o},inferred_by:"L4"});if(t.suggestions_created+=1,e.autoPromote){let S=h.layers.map(T=>T.confidence);if(S.push(f.confidence),to(S)>=i)try{qc(b.id,"approved",{source:"auto"}),t.links_promoted+=1}catch{}}}catch{}}}return t}Be();var xb=1e3;function kb(e){let t=e.minConf?Math.max(0,Math.min(1,Number(e.minConf))):nn,s=e.autoPromoteThreshold!==void 0?Math.max(0,Math.min(1,Number(e.autoPromoteThreshold))):ro,n=e.limit?Math.max(1,Number(e.limit)):xb,r=e.model??no,o=!!e.autoPromote||e.autoPromoteThreshold!==void 0;return{minConfidence:t,autoPromoteThreshold:s,limit:n,model:r,autoPromote:o}}async function cl(e){if(!e.project){console.error("--project <name> is required."),process.exitCode=1;return}let t=Ee(e.project);if(!t){console.error(`Project "${e.project}" not found.`),process.exitCode=1;return}if(!ne()){console.error("claude CLI not found on PATH. Install Claude Code first, then re-run."),process.exitCode=1;return}let{minConfidence:s,autoPromoteThreshold:n,limit:r,model:o,autoPromote:i}=kb(e);e.json||console.log(`L4 inference \u2014 project "${t.name}" \u2014 model ${o} \u2014 pre-filter conf \u2265 ${s} \u2014 limit ${r} \u2014 auto-promote ${i?`ON (\u2265 ${n})`:"OFF"}`);let a=Date.now(),d=await al({projectId:t.id,minConfidence:s,limit:r,autoPromote:i,autoPromoteThreshold:n,model:o,onBatchStart:u=>{e.json||process.stdout.write(`\r batch ${u.batch} (${u.pairs} pairs)\u2026 `)}});e.json||process.stdout.write(`
|
|
1280
|
+
`);let l=Number(((Date.now()-a)/1e3).toFixed(1));if(e.json)console.log(JSON.stringify({project:t.name,model:o,pre_filter_threshold:s,auto_promote:i,auto_promote_threshold:i?n:null,...d,elapsed_seconds:l},null,2));else{if(console.log(`Done in ${l}s \u2014 pairs=${d.candidates_after_filter}/${d.candidates_total} classified=${d.classified} suggestions=${d.suggestions_created} promoted=${d.links_promoted} failures=${d.failures.length}`),console.log(`Token spend: input=${d.total_input_tokens} output=${d.total_output_tokens}`),d.failures.length>0)for(let u of d.failures.slice(0,3))console.log(` \u26A0 batch ${u.batch_index}: ${u.error}`);d.suggestions_created>0?console.log(" Review the new L4 suggestions at #view=suggestions (filter by inferred_by=L4)."):d.candidates_after_filter===0&&console.log(` \u24D8 No pairs cleared the pre-filter (combined conf \u2265 ${s}). Lower --min-conf to widen the batch, or run more L1/L2/L3 inference first.`)}}w();import{createHash as Mb}from"node:crypto";w();P();import{writeFileSync as Cb,readFileSync as $O,existsSync as Lb,mkdirSync as Nb,readdirSync as PO,unlinkSync as FO}from"node:fs";import{join as ll}from"node:path";import{randomUUID as Ab}from"node:crypto";var oo=ll(x,"bug-patterns");function Ob(){F(),Lb(oo)||Nb(oo,{recursive:!0})}function rn(e){return{id:e.id,signature_hash:e.signature_hash,example_message:e.example_message,occurrence_count:e.occurrence_count,first_seen_at:e.first_seen_at,last_seen_at:e.last_seen_at,resolved_in_session_id:e.resolved_in_session_id,fix_summary:e.fix_summary}}function vb(e){return{cluster_id:e.cluster_id,session_id:e.session_id,matched_at:e.matched_at}}function dl(e){if(!e.signature_hash)throw new Error("signature_hash is required");if(!e.example_message)throw new Error("example_message is required");if(!Array.isArray(e.member_session_ids)||e.member_session_ids.length===0)throw new Error("at least one member_session_id is required");let t=_(),s=new Date().toISOString(),n=e.id??Ab(),r=e.first_seen_at??s,o=e.last_seen_at??s,i=Array.from(new Set(e.member_session_ids));t.transaction(()=>{t.prepare(`INSERT INTO bug_pattern_clusters
|
|
1281
1281
|
(id, signature_hash, example_message, occurrence_count,
|
|
1282
1282
|
first_seen_at, last_seen_at, resolved_in_session_id, fix_summary)
|
|
1283
1283
|
VALUES (?, ?, ?, ?, ?, ?, NULL, NULL)`).run(n,e.signature_hash,e.example_message,i.length,r,o);let d=t.prepare(`INSERT INTO bug_pattern_members (cluster_id, session_id, matched_at)
|
|
1284
1284
|
VALUES (?, ?, ?)
|
|
1285
|
-
ON CONFLICT(cluster_id, session_id) DO NOTHING`);for(let l of i)d.run(n,l,s)})();let a=
|
|
1285
|
+
ON CONFLICT(cluster_id, session_id) DO NOTHING`);for(let l of i)d.run(n,l,s)})();let a=ul(n);if(!a)throw new Error("createCluster succeeded but read-back failed");return gl(n),a}function ul(e){let t=_(),s=t.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);if(!s)return null;let n=t.prepare("SELECT * FROM bug_pattern_members WHERE cluster_id = ? ORDER BY matched_at ASC, session_id ASC").all(e);return{cluster:rn(s),members:n.map(vb)}}function pl(e,t){if(!e)throw new Error("clusterId is required");if(!Array.isArray(t)||t.length===0)throw new Error("sessionIds must be a non-empty array");let s=_();if(!s.prepare("SELECT 1 FROM bug_pattern_clusters WHERE id = ?").get(e))throw new Error(`cluster ${e} not found`);let r=new Date().toISOString(),o=0;s.transaction(()=>{let a=s.prepare(`INSERT INTO bug_pattern_members (cluster_id, session_id, matched_at)
|
|
1286
1286
|
VALUES (?, ?, ?)
|
|
1287
1287
|
ON CONFLICT(cluster_id, session_id) DO NOTHING`);for(let d of Array.from(new Set(t)))a.run(e,d,r).changes>0&&(o+=1);if(o>0){let d=s.prepare("SELECT COUNT(*) AS n FROM bug_pattern_members WHERE cluster_id = ?").get(e).n;s.prepare(`UPDATE bug_pattern_clusters
|
|
1288
1288
|
SET occurrence_count = ?, last_seen_at = ?
|
|
1289
|
-
WHERE id = ?`).run(d,r,e)}})(),o>0&&
|
|
1289
|
+
WHERE id = ?`).run(d,r,e)}})(),o>0&&gl(e);let i=s.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);return{cluster:rn(i),added:o}}function Ib(e){let t=e.first_user_message?e.first_user_message.slice(0,80):null,s=e.alias??e.auto_title??t??e.session_id.slice(0,8);return{cluster_id:e.cluster_id,session_id:e.session_id,matched_at:e.matched_at,title:s,alias:e.alias,auto_title:e.auto_title,project:e.project,started_at:e.started_at}}function ml(e){let t=_(),s=t.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);if(!s)return null;let n=t.prepare(`SELECT m.cluster_id, m.session_id, m.matched_at,
|
|
1290
1290
|
NULLIF(sa.alias, '') AS alias,
|
|
1291
1291
|
s.auto_title,
|
|
1292
1292
|
s.first_user_message,
|
|
@@ -1297,37 +1297,37 @@ show full content: recall paste --show <id>
|
|
|
1297
1297
|
LEFT JOIN session_aliases sa ON sa.session_id = m.session_id
|
|
1298
1298
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1299
1299
|
WHERE m.cluster_id = ?
|
|
1300
|
-
ORDER BY COALESCE(s.started_at, ''), m.matched_at, m.session_id`).all(e);return{cluster:
|
|
1300
|
+
ORDER BY COALESCE(s.started_at, ''), m.matched_at, m.session_id`).all(e);return{cluster:rn(s),members:n.map(Ib)}}function gl(e){try{Ob();let t=ul(e);if(!t)return;let s=ll(oo,`${e}.json`),n={schema:"claude-recall.bug-pattern-cluster.v1",cluster:t.cluster,members:t.members,backed_up_at:new Date().toISOString()};Cb(s,JSON.stringify(n,null,2))}catch(t){console.error("[bug-patterns] backup failed:",t)}}function fl(e){return e?_().prepare(`SELECT * FROM bug_pattern_clusters
|
|
1301
1301
|
WHERE signature_hash = ?
|
|
1302
|
-
ORDER BY last_seen_at DESC, id ASC`).all(e).map(
|
|
1302
|
+
ORDER BY last_seen_at DESC, id ASC`).all(e).map(rn):[]}function _l(e){if(!e)return new Set;let s=_().prepare(`SELECT DISTINCT m.session_id
|
|
1303
1303
|
FROM bug_pattern_members m
|
|
1304
1304
|
JOIN bug_pattern_clusters c ON c.id = m.cluster_id
|
|
1305
|
-
WHERE c.signature_hash = ?`).all(e);return new Set(s.map(n=>n.session_id))}var
|
|
1305
|
+
WHERE c.signature_hash = ?`).all(e);return new Set(s.map(n=>n.session_id))}var Db=/\b0x[0-9a-fA-F]+\b/g,$b=/\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b/g,Pb=/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?\b/g,Fb=/:\d+:\d+/g,jb=/\bline\s+\d+\b/gi,Ub=/\bcolumn\s+\d+\b/gi,Bb=/\b(?:pid|PID|process(?:\s+id)?)\s*[:=]?\s*\d+\b/gi,Hb=/\b(?:port|:)\s*[:=]?\s*\d{2,5}\b/gi,Wb=/\b\d{4,}\b/g,Xb=/(['"`])[^'"`\n]{1,128}\1/g;function Jb(e){if(!e)return"";let t=String(e);return t=t.replace(Db,"<hex>"),t=t.replace($b,"<uuid>"),t=t.replace(Pb,"<ts>"),t=t.replace(Fb,":<line>:<col>"),t=t.replace(jb,"line <n>"),t=t.replace(Ub,"column <n>"),t=t.replace(Bb,"pid <n>"),t=t.replace(Hb,"port <n>"),t=t.replace(Wb,"<num>"),t=t.replace(Xb,"<arg>"),t=t.replace(/\s+/g," ").trim(),t.toLowerCase()}function Gb(e){let t=(e.error_type??"unknown").toLowerCase().trim(),s=Jb(e.snippet??e.message_hash??""),n=`${t}|${s}`;return Mb("sha256").update(n).digest("hex").slice(0,16)}function Yb(e){let t=_(),s=["oi.bug_signatures IS NOT NULL"],n=[];e&&(s.push("p.name = ?"),n.push(e));let r=`WHERE ${s.join(" AND ")}`,o=t.prepare(`SELECT oi.session_id AS session_id,
|
|
1306
1306
|
p.name AS project,
|
|
1307
1307
|
s.started_at AS started_at,
|
|
1308
1308
|
oi.bug_signatures AS bug_signatures
|
|
1309
1309
|
FROM session_output_index oi
|
|
1310
1310
|
LEFT JOIN sessions s ON s.id = oi.session_id
|
|
1311
1311
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1312
|
-
${r}`).all(...n),i=[];for(let a of o){if(!a.bug_signatures)continue;let d=[];try{let l=JSON.parse(a.bug_signatures);Array.isArray(l)&&(d=l)}catch{continue}for(let l of d){if(!l||typeof l!="object"||!(l.snippet??"").trim())continue;let p=
|
|
1312
|
+
${r}`).all(...n),i=[];for(let a of o){if(!a.bug_signatures)continue;let d=[];try{let l=JSON.parse(a.bug_signatures);Array.isArray(l)&&(d=l)}catch{continue}for(let l of d){if(!l||typeof l!="object"||!(l.snippet??"").trim())continue;let p=Gb(l);i.push({session_id:a.session_id,project:a.project,started_at:a.started_at,signature:l,fingerprint:p})}}return i}function zb(e){let t=new Map;for(let n of e){let r=t.get(n.fingerprint);r||(r=[],t.set(n.fingerprint,r)),r.push(n)}let s=[];for(let[n,r]of t){let o=new Set,i=[];for(let u of r)o.has(u.session_id)||(o.add(u.session_id),i.push(u));let a=[...i].sort((u,p)=>{let m=u.started_at??"",g=p.started_at??"";return m&&g?m<g?-1:m>g?1:0:m?-1:g?1:0}),d=a.find(u=>u.started_at),l=[...a].reverse().find(u=>u.started_at);s.push({fingerprint:n,example_message:i[0].signature.snippet??i[0].signature.message_hash??"",members:i,first_seen_at:d?.started_at??new Date().toISOString(),last_seen_at:l?.started_at??new Date().toISOString()})}return s}function qb(e,t){if(e.length!==t.length)return 0;let s=0,n=0,r=0;for(let i=0;i<e.length;i++)s+=e[i]*t[i],n+=e[i]*e[i],r+=t[i]*t[i];let o=Math.sqrt(n)*Math.sqrt(r);return o>0?s/o:0}function Kb(e){let{records:t,vectors:s,epsilon:n,minPts:r}=e,o=t.length;if(o===0)return[];let i=[];for(let u=0;u<o;u++){let p=[];for(let m=0;m<o;m++){if(u===m)continue;1-qb(s[u],s[m])<=n&&p.push(m)}i.push(p)}let a=new Array(o).fill(!1),d=new Array(o).fill(-1),l=[];for(let u=0;u<o;u++){if(a[u])continue;a[u]=!0;let p=i[u];if(p.length<r)continue;let m=l.length;l.push({members:[t[u]]}),d[u]=m;let g=[...p];for(;g.length>0;){let f=g.shift();if(!a[f]&&(a[f]=!0,i[f].length>=r))for(let h of i[f])(!a[h]||d[h]===-1)&&g.push(h);d[f]===-1&&(d[f]=m,l[m].members.push(t[f]))}}return l}async function Vb(e,t,s,n){if(e.length===0)return[];let r=e.map(d=>{let l=d.signature.snippet??d.signature.message_hash??"";return`${d.signature.error_type??""}: ${l}`.trim()}),o=await t(r);if(o.length!==e.length)throw new Error(`embedder returned ${o.length} vectors for ${e.length} inputs`);let i=Kb({records:e,vectors:o,epsilon:s,minPts:n}),a=[];for(let d of i){if(d.members.length===0)continue;let l=new Set,u=[];for(let E of d.members)l.has(E.session_id)||(l.add(E.session_id),u.push(E));if(u.length===0)continue;let m=`sem:${[...u.map(E=>E.fingerprint)].sort()[0]}`,g=[...u].sort((E,b)=>{let S=E.started_at??"",R=b.started_at??"";return S<R?-1:S>R?1:0}),f=g.find(E=>E.started_at),h=[...g].reverse().find(E=>E.started_at);a.push({fingerprint:m,example_message:u[0].signature.snippet??u[0].signature.message_hash??"",members:u,first_seen_at:f?.started_at??new Date().toISOString(),last_seen_at:h?.started_at??new Date().toISOString()})}return a}function hl(e,t){let s={clusters_created:0,clusters_merged:0,members_added:0,cluster_ids:[]};for(let n of e){if(n.members.length<t)continue;let r=fl(n.fingerprint),o=_l(n.fingerprint),i=n.members.map(l=>l.session_id).filter(l=>!o.has(l));if(r.length===0){let l=dl({signature_hash:n.fingerprint,example_message:n.example_message.slice(0,256),member_session_ids:n.members.map(u=>u.session_id),first_seen_at:n.first_seen_at,last_seen_at:n.last_seen_at});s.clusters_created+=1,s.members_added+=l.members.length,s.cluster_ids.push(l.cluster.id);continue}if(i.length===0){s.cluster_ids.push(r[0].id);continue}let a=r[0],d=pl(a.id,i);d.added>0&&(s.clusters_merged+=1,s.members_added+=d.added),s.cluster_ids.push(a.id)}return s}async function El(e={}){let t=Math.max(2,Math.floor(e.minClusterSize??3)),s=Math.max(1,Math.min(5e3,Math.floor(e.limit??1e3))),n=e.semanticEpsilon??.15,r=Math.max(1,Math.floor(e.semanticMinPts??1)),o=Yb(e.project),i=new Set(o.map(S=>S.session_id)),a=zb(o),d=[],l=!1;if(e.semantic){let S=e.embedder??null;if(!S)try{S=await eS()}catch(R){let L=(R instanceof Error?R.message:String(R)).split(`
|
|
1313
1313
|
`)[0];console.warn(`[bug-pattern] --semantic requested but the embedder is unavailable: ${L}
|
|
1314
|
-
Falling back to exact-match-only clustering. Run \`recall semantic install\` to enable the semantic pass.`),l=!0}if(S){let R=[];for(let T of a)T.members.length===1&&R.push(T.members[0]);R.length>=2&&(d=await
|
|
1314
|
+
Falling back to exact-match-only clustering. Run \`recall semantic install\` to enable the semantic pass.`),l=!0}if(S){let R=[];for(let T of a)T.members.length===1&&R.push(T.members[0]);R.length>=2&&(d=await Vb(R,S,n,r))}}let u=a.filter(S=>S.members.length>=t),p=d.filter(S=>S.members.length>=t),m=hl(u,t),g=hl(p,t),f=[...m.cluster_ids,...g.cluster_ids],h=Array.from(new Set(f)),E=[];if(h.length>0){let S=_(),R=h.map(()=>"?").join(","),T=S.prepare(`SELECT * FROM bug_pattern_clusters
|
|
1315
1315
|
WHERE id IN (${R})
|
|
1316
1316
|
ORDER BY occurrence_count DESC, last_seen_at DESC
|
|
1317
|
-
LIMIT ?`).all(...h,s);for(let L of T)E.push({id:L.id,signature_hash:L.signature_hash,example_message:L.example_message,occurrence_count:L.occurrence_count,first_seen_at:L.first_seen_at,last_seen_at:L.last_seen_at,resolved_in_session_id:L.resolved_in_session_id,fix_summary:L.fix_summary})}return{progress:{total_sessions:i.size,total_signatures:o.length,exact_match_groups:u.length,semantic_groups:p.length,clusters_created:m.clusters_created+g.clusters_created,clusters_merged:m.clusters_merged+g.clusters_merged,members_added:m.members_added+g.members_added,semantic_skipped:l},clusters:E}}var
|
|
1317
|
+
LIMIT ?`).all(...h,s);for(let L of T)E.push({id:L.id,signature_hash:L.signature_hash,example_message:L.example_message,occurrence_count:L.occurrence_count,first_seen_at:L.first_seen_at,last_seen_at:L.last_seen_at,resolved_in_session_id:L.resolved_in_session_id,fix_summary:L.fix_summary})}return{progress:{total_sessions:i.size,total_signatures:o.length,exact_match_groups:u.length,semantic_groups:p.length,clusters_created:m.clusters_created+g.clusters_created,clusters_merged:m.clusters_merged+g.clusters_merged,members_added:m.members_added+g.members_added,semantic_skipped:l},clusters:E}}var Zb=async()=>{let{embed:e,loadEmbedder:t,getEmbedderStatus:s}=await Promise.resolve().then(()=>(yt(),Fr));return s().loaded||await t(),e},Qb=Zb;async function eS(){return Qb()}function tS(e,t){let s=ml(e);if(!s)return null;let n=s.cluster,r=s.members.slice(0,t).map(o=>({session_id:o.session_id,title:o.title,project:o.project,started_at:o.started_at}));return{id:n.id,signature_hash:n.signature_hash,example_message:n.example_message,occurrence_count:n.occurrence_count,first_seen_at:n.first_seen_at,last_seen_at:n.last_seen_at,status:n.resolved_in_session_id?"resolved":"open",resolved_in_session_id:n.resolved_in_session_id,sample_members:r}}async function bl(e){let t=e.minClusterSize?Math.max(2,Math.floor(Number(e.minClusterSize))):3;if(!Number.isFinite(t)){console.error("--min-cluster-size must be a positive number"),process.exitCode=1;return}let s=e.limit?Math.max(1,Math.floor(Number(e.limit))):100;if(!Number.isFinite(s)){console.error("--limit must be a positive number"),process.exitCode=1;return}let n;if(e.project){let l=Ee(e.project);if(!l){console.error(`Project "${e.project}" not found.`),process.exitCode=1;return}n=l.name}if(!e.json){let l=n?`project "${n}"`:"all projects",u=e.semantic?"exact + semantic":"exact";console.log(`Inferring bug patterns \u2014 ${l}, min-cluster-size=${t}, ${u} passes\u2026`)}let r={project:n,minClusterSize:t,semantic:!!e.semantic,limit:s},o=Date.now(),i;try{i=await El(r)}catch(l){console.error(l instanceof Error?l.message:String(l)),process.exitCode=1;return}let a=Number(((Date.now()-o)/1e3).toFixed(1)),d=[];for(let l of i.clusters.slice(0,s)){let u=tS(l.id,3);u&&d.push(u)}if(e.json){console.log(JSON.stringify({project:n??null,progress:i.progress,elapsed_seconds:a,clusters:d},null,2));return}if(console.log(`Done in ${a}s \u2014 sessions=${i.progress.total_sessions} signatures=${i.progress.total_signatures} clusters: created=${i.progress.clusters_created} merged=${i.progress.clusters_merged} members_added=${i.progress.members_added}`),i.progress.semantic_skipped&&console.log(" \u24D8 semantic pass skipped \u2014 embedder unavailable. Run `recall semantic install` to enable."),d.length===0){i.progress.total_signatures===0?console.log(" \u24D8 No bug signatures in the corpus yet. Run `recall extract-outputs --project <name>` first to populate session_output_index."):console.log(` \u24D8 No clusters at min-cluster-size=${t}. Try \`--semantic\` to merge near-duplicate snippets, or lower \`--min-cluster-size 2\` to surface pairs.`);return}console.log(`
|
|
1318
1318
|
Top ${d.length} cluster(s):`);for(let l of d){let u=l.status==="resolved"?"\u2713 resolved":"open",p=l.example_message.slice(0,80);console.log(`
|
|
1319
1319
|
[${u}] occurs=${l.occurrence_count} hash=${l.signature_hash.slice(0,8)}
|
|
1320
1320
|
${p}
|
|
1321
|
-
first=${l.first_seen_at} last=${l.last_seen_at}`);for(let m of l.sample_members){let g=m.project?`[${m.project}] `:"";console.log(` \u2022 ${g}${m.title} (${m.session_id.slice(0,8)})`)}}console.log("\n Review at /graph/patterns in the web UI, or `recall neighborhood <session-id>` for an agent-facing bundle.")}w();import{z as
|
|
1321
|
+
first=${l.first_seen_at} last=${l.last_seen_at}`);for(let m of l.sample_members){let g=m.project?`[${m.project}] `:"";console.log(` \u2022 ${g}${m.title} (${m.session_id.slice(0,8)})`)}}console.log("\n Review at /graph/patterns in the web UI, or `recall neighborhood <session-id>` for an agent-facing bundle.")}w();import{z as sv}from"zod";w();function Sl(e){let t=e.trim();if(t.length<4)return null;let s=_();if(t.length>=32)return s.prepare("SELECT id FROM sessions WHERE id = ?").get(t)?.id??null;let n=s.prepare("SELECT id FROM sessions WHERE id LIKE ? LIMIT 2").all(`${t}%`);return n.length===1?n[0].id:null}io();var Nl=new Set(["citation","similar","skill_track","bug_pattern","wiki_link","temporal_proximity"]),Al=new Set(["pagerank","embedding-rerank","hybrid"]);function _S(e){if(!e)return;let t=e.split(",").map(s=>s.trim()).filter(Boolean);for(let s of t)if(!Nl.has(s))throw new Error(`invalid --edge-types value: '${s}'. Valid: ${Array.from(Nl).join(", ")}`);return t}function hS(e){if(!e)return"hybrid";if(!Al.has(e))throw new Error(`invalid --scoring value: '${e}'. Valid: ${Array.from(Al).join(", ")}`);return e}async function Ol(e,t){let s=Sl(e);if(!s){console.error(`session not found or prefix ambiguous: ${e}`),process.exitCode=1;return}let n,r;try{n=hS(t.scoring),r=_S(t.edgeTypes)}catch(d){console.error(d instanceof Error?d.message:String(d)),process.exitCode=1;return}let o=t.budget?Math.max(100,Number(t.budget)):4e3;if(!Number.isFinite(o)){console.error("--budget must be a positive number"),process.exitCode=1;return}let i=t.maxDepth?Math.max(1,Number(t.maxDepth)):2;if(!Number.isFinite(i)){console.error("--max-depth must be a positive number"),process.exitCode=1;return}let a;try{a=cn(s,{budget:o,maxDepth:i,scoring:n,edgeTypes:r,includeWikiLinks:t.wikiLinks!==!1,includeSuggestions:!!t.includeSuggestions})}catch(d){console.error(d instanceof Error?d.message:String(d)),process.exitCode=1;return}if(t.json){process.stdout.write(JSON.stringify(a,null,2)+`
|
|
1322
1322
|
`);return}process.stdout.write(a.bundle),a.truncated.length>0&&console.error(`
|
|
1323
|
-
[truncated ${a.truncated.length} refs to fit ${o}-token budget; ${a.budgetUsed} used / ${a.budgetRemaining} remaining]`)}w();v();var
|
|
1323
|
+
[truncated ${a.truncated.length} refs to fit ${o}-token budget; ${a.budgetUsed} used / ${a.budgetRemaining} remaining]`)}w();v();var ES=[[/opus[-_ ]?4[-_. ]?7/i,{label:"Opus 4.7",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/opus[-_ ]?4[-_. ]?6/i,{label:"Opus 4.6",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/sonnet[-_ ]?4[-_. ]?6/i,{label:"Sonnet 4.6",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/sonnet[-_ ]?4[-_. ]?5/i,{label:"Sonnet 4.5",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/haiku[-_ ]?4[-_. ]?5/i,{label:"Haiku 4.5",inputCentsPerMtok:100,outputCentsPerMtok:500,cacheCreateCentsPerMtok:125,cacheReadCentsPerMtok:10}],[/opus[-_ ]?4(?!.*[5-9])/i,{label:"Opus 4",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/sonnet[-_ ]?4(?!.*[5-9])/i,{label:"Sonnet 4",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/3[-_. ]?7[-_ ]?sonnet|sonnet[-_ ]?3[-_. ]?7/i,{label:"Sonnet 3.7",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/3[-_. ]?5[-_ ]?sonnet|sonnet[-_ ]?3[-_. ]?5/i,{label:"Sonnet 3.5",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/3[-_. ]?5[-_ ]?haiku|haiku[-_ ]?3[-_. ]?5/i,{label:"Haiku 3.5",inputCentsPerMtok:80,outputCentsPerMtok:400,cacheCreateCentsPerMtok:100,cacheReadCentsPerMtok:8}],[/3[-_ ]?opus|opus[-_ ]?3/i,{label:"Opus 3",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/3[-_ ]?haiku|haiku(?!.*3[-_. ]?5)/i,{label:"Haiku 3",inputCentsPerMtok:25,outputCentsPerMtok:125,cacheCreateCentsPerMtok:30,cacheReadCentsPerMtok:3}],[/opus/i,{label:"Opus",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/sonnet/i,{label:"Sonnet",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/haiku/i,{label:"Haiku",inputCentsPerMtok:100,outputCentsPerMtok:500,cacheCreateCentsPerMtok:125,cacheReadCentsPerMtok:10}]],vl={label:"unknown",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30};function it(e){if(!e)return vl;for(let[t,s]of ES)if(t.test(e))return s;return vl}function Ie(e,t){if(e.byModel&&Object.keys(e.byModel).length>0){let i={input:0,output:0,cacheCreate:0,cacheRead:0},a=0;for(let[l,u]of Object.entries(e.byModel)){let p=it(l);i.input+=u.inputTokens/1e6*p.inputCentsPerMtok,i.output+=u.outputTokens/1e6*p.outputCentsPerMtok,i.cacheCreate+=u.cacheCreateTokens/1e6*p.cacheCreateCentsPerMtok,i.cacheRead+=u.cacheReadTokens/1e6*p.cacheReadCentsPerMtok,a+=u.inputTokens+u.outputTokens+u.cacheCreateTokens+u.cacheReadTokens}let d=i.input+i.output+i.cacheCreate+i.cacheRead;return{cents:d,dollars:d/100,totalTokens:a,parts:i}}let s=it(t),n={input:e.inputTokens/1e6*s.inputCentsPerMtok,output:e.outputTokens/1e6*s.outputCentsPerMtok,cacheCreate:e.cacheCreateTokens/1e6*s.cacheCreateCentsPerMtok,cacheRead:e.cacheReadTokens/1e6*s.cacheReadCentsPerMtok},r=n.input+n.output+n.cacheCreate+n.cacheRead,o=e.inputTokens+e.outputTokens+e.cacheCreateTokens+e.cacheReadTokens;return{cents:r,dollars:r/100,totalTokens:o,parts:n}}function re(e){let t=e/100;return t===0?"$0.00":t<.01?"<$0.01":t<1?`$${t.toFixed(2)}`:t<100?`$${t.toFixed(2)}`:t<1e4?`$${t.toFixed(0)}`:`$${(t/1e3).toFixed(1)}k`}function ue(e){return!Number.isFinite(e)||e<0?"0":e<1e3?String(Math.round(e)):e<1e6?`${(e/1e3).toFixed(1)}k`:e<1e9?`${(e/1e6).toFixed(2)}M`:e<1e12?`${(e/1e9).toFixed(2)}B`:`${(e/1e12).toFixed(2)}T`}w();w();var bS=500,ln=new Set;function Il(){return ln.size}function SS(){return`
|
|
1324
1324
|
SELECT m.uuid, m.session_id, m.timestamp, m.raw_json
|
|
1325
1325
|
FROM messages m
|
|
1326
1326
|
LEFT JOIN message_usage mu ON mu.message_uuid = m.uuid
|
|
1327
1327
|
WHERE m.role = 'assistant' AND mu.message_uuid IS NULL
|
|
1328
1328
|
AND m.uuid NOT IN (SELECT value FROM json_each(?))
|
|
1329
1329
|
LIMIT ?
|
|
1330
|
-
`}function
|
|
1330
|
+
`}function yS(e,t){let s=t.limit??Number.MAX_SAFE_INTEGER,n=Math.max(1,t.chunkSize??bS),r=e.prepare(SS()),o=e.prepare(`
|
|
1331
1331
|
INSERT INTO message_usage (
|
|
1332
1332
|
message_uuid, session_id, model,
|
|
1333
1333
|
input_tokens, output_tokens, cache_create_tokens, cache_read_tokens,
|
|
@@ -1337,7 +1337,7 @@ Top ${d.length} cluster(s):`);for(let l of d){let u=l.status==="resolved"?"\u271
|
|
|
1337
1337
|
@input, @output, @cc, @cr, @ts
|
|
1338
1338
|
)
|
|
1339
1339
|
ON CONFLICT(message_uuid) DO NOTHING
|
|
1340
|
-
`),i=0,a=0,d=new Set;for(;i<s;){let l=Math.min(n,s-i),u=JSON.stringify([...
|
|
1340
|
+
`),i=0,a=0,d=new Set;for(;i<s;){let l=Math.min(n,s-i),u=JSON.stringify([...ln]),p=r.all(u,l);if(p.length===0)break;let m=new Set;if(e.transaction(()=>{for(let f of p){let h;try{h=JSON.parse(f.raw_json)}catch{ln.add(f.uuid);continue}let E=zn(h.message);if(!E){ln.add(f.uuid);continue}o.run({uuid:f.uuid,session_id:f.session_id,model:h.message?.model??null,input:E.inputTokens,output:E.outputTokens,cc:E.cacheCreateTokens,cr:E.cacheReadTokens,ts:f.timestamp}),a+=1,m.add(f.session_id)}for(let f of m)zt(e,f),d.add(f)})(),i+=p.length,t.onProgress?.({scanned:i,inserted:a,sessionsTouched:d.size,done:p.length<l}),p.length<l)break}return{scanned:i,inserted:a,sessionsTouched:d.size,done:!0}}function Ml(e={}){return yS(_(),e)}function ao(e){let t=new Map;for(let n of e){let r=n.model??null,o=t.get(r)??{inputTokens:0,outputTokens:0,cacheCreateTokens:0,cacheReadTokens:0,messageCount:0};o.inputTokens+=n.input_tokens,o.outputTokens+=n.output_tokens,o.cacheCreateTokens+=n.cache_create_tokens,o.cacheReadTokens+=n.cache_read_tokens,o.messageCount+=n.n,t.set(r,o)}let s=[];for(let[n,r]of t.entries()){let o=Ie({inputTokens:r.inputTokens,outputTokens:r.outputTokens,cacheCreateTokens:r.cacheCreateTokens,cacheReadTokens:r.cacheReadTokens},n);s.push({model:n,modelLabel:it(n).label,inputTokens:r.inputTokens,outputTokens:r.outputTokens,cacheCreateTokens:r.cacheCreateTokens,cacheReadTokens:r.cacheReadTokens,messageCount:r.messageCount,cost:o})}return s.sort((n,r)=>r.cost.cents-n.cost.cents)}function co(e){let t={};for(let s of e)t[s.model??"__unknown__"]={inputTokens:s.inputTokens,outputTokens:s.outputTokens,cacheCreateTokens:s.cacheCreateTokens,cacheReadTokens:s.cacheReadTokens};return{byModel:t}}function Dl(e){let t=_(),s=t.prepare(`SELECT s.id, p.name AS project, s.started_at, s.ended_at,
|
|
1341
1341
|
s.message_count,
|
|
1342
1342
|
s.total_input_tokens, s.total_output_tokens,
|
|
1343
1343
|
s.total_cache_create_tokens, s.total_cache_read_tokens,
|
|
@@ -1352,7 +1352,7 @@ Top ${d.length} cluster(s):`);for(let l of d){let u=l.status==="resolved"?"\u271
|
|
|
1352
1352
|
COUNT(*) AS n
|
|
1353
1353
|
FROM message_usage
|
|
1354
1354
|
WHERE session_id = ?
|
|
1355
|
-
GROUP BY model`).all(e),r=
|
|
1355
|
+
GROUP BY model`).all(e),r=ao(n),o=s.total_input_tokens??0,i=s.total_output_tokens??0,a=s.total_cache_create_tokens??0,d=s.total_cache_read_tokens??0,l=Ie({inputTokens:o,outputTokens:i,cacheCreateTokens:a,cacheReadTokens:d,...co(r)},s.primary_model);return{sessionId:s.id,project:s.project,startedAt:s.started_at,endedAt:s.ended_at,messageCount:s.message_count,primaryModel:s.primary_model,primaryModelLabel:it(s.primary_model).label,inputTokens:o,outputTokens:i,cacheCreateTokens:a,cacheReadTokens:d,totalTokens:l.totalTokens,cost:l,byModel:r,display:{dollars:re(l.cents),tokens:ue(l.totalTokens),model:it(s.primary_model).label}}}function $l(e){let t=_(),s=t.prepare("SELECT id, name FROM projects WHERE name = ?").get(e);if(!s)return null;let n=t.prepare(`SELECT mu.model,
|
|
1356
1356
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
1357
1357
|
COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
|
|
1358
1358
|
COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
|
|
@@ -1361,12 +1361,12 @@ Top ${d.length} cluster(s):`);for(let l of d){let u=l.status==="resolved"?"\u271
|
|
|
1361
1361
|
FROM message_usage mu
|
|
1362
1362
|
JOIN sessions s ON s.id = mu.session_id
|
|
1363
1363
|
WHERE s.project_id = ?
|
|
1364
|
-
GROUP BY mu.model`).all(s.id),r=
|
|
1364
|
+
GROUP BY mu.model`).all(s.id),r=ao(n),o=t.prepare(`SELECT COALESCE(SUM(total_input_tokens), 0) AS input_tokens,
|
|
1365
1365
|
COALESCE(SUM(total_output_tokens), 0) AS output_tokens,
|
|
1366
1366
|
COALESCE(SUM(total_cache_create_tokens), 0) AS cache_create_tokens,
|
|
1367
1367
|
COALESCE(SUM(total_cache_read_tokens), 0) AS cache_read_tokens,
|
|
1368
1368
|
COUNT(*) AS session_count
|
|
1369
|
-
FROM sessions WHERE project_id = ?`).get(s.id),i=Ie({inputTokens:o.input_tokens,outputTokens:o.output_tokens,cacheCreateTokens:o.cache_create_tokens,cacheReadTokens:o.cache_read_tokens,...
|
|
1369
|
+
FROM sessions WHERE project_id = ?`).get(s.id),i=Ie({inputTokens:o.input_tokens,outputTokens:o.output_tokens,cacheCreateTokens:o.cache_create_tokens,cacheReadTokens:o.cache_read_tokens,...co(r)},null),d=t.prepare(`SELECT s.id, sa.alias, s.started_at,
|
|
1370
1370
|
s.total_input_tokens, s.total_output_tokens,
|
|
1371
1371
|
s.total_cache_create_tokens, s.total_cache_read_tokens,
|
|
1372
1372
|
s.primary_model
|
|
@@ -1377,7 +1377,7 @@ Top ${d.length} cluster(s):`);for(let l of d){let u=l.status==="resolved"?"\u271
|
|
|
1377
1377
|
+ COALESCE(s.total_output_tokens,0)
|
|
1378
1378
|
+ COALESCE(s.total_cache_create_tokens,0)
|
|
1379
1379
|
+ COALESCE(s.total_cache_read_tokens,0)) DESC
|
|
1380
|
-
LIMIT 10`).all(s.id).map(l=>{let u=Ie({inputTokens:l.total_input_tokens??0,outputTokens:l.total_output_tokens??0,cacheCreateTokens:l.total_cache_create_tokens??0,cacheReadTokens:l.total_cache_read_tokens??0},l.primary_model);return{sessionId:l.id,alias:l.alias,startedAt:l.started_at,totalTokens:u.totalTokens,cost:u}});return{project:s.name,sessionCount:o.session_count,inputTokens:o.input_tokens,outputTokens:o.output_tokens,cacheCreateTokens:o.cache_create_tokens,cacheReadTokens:o.cache_read_tokens,totalTokens:i.totalTokens,cost:i,byModel:r,topSessions:d,display:{dollars:re(i.cents),tokens:ue(i.totalTokens)}}}function
|
|
1380
|
+
LIMIT 10`).all(s.id).map(l=>{let u=Ie({inputTokens:l.total_input_tokens??0,outputTokens:l.total_output_tokens??0,cacheCreateTokens:l.total_cache_create_tokens??0,cacheReadTokens:l.total_cache_read_tokens??0},l.primary_model);return{sessionId:l.id,alias:l.alias,startedAt:l.started_at,totalTokens:u.totalTokens,cost:u}});return{project:s.name,sessionCount:o.session_count,inputTokens:o.input_tokens,outputTokens:o.output_tokens,cacheCreateTokens:o.cache_create_tokens,cacheReadTokens:o.cache_read_tokens,totalTokens:i.totalTokens,cost:i,byModel:r,topSessions:d,display:{dollars:re(i.cents),tokens:ue(i.totalTokens)}}}function Pl(e="all"){let t=_(),s=e==="7d"?new Date(Date.now()-7*864e5).toISOString():e==="30d"?new Date(Date.now()-30*864e5).toISOString():null,n=s?"WHERE mu.timestamp >= @since OR (mu.timestamp IS NULL AND s.started_at >= @since)":"",r=s?"WHERE m.timestamp >= @since OR (m.timestamp IS NULL AND s2.started_at >= @since)":"",o=s?{since:s}:{},i=y=>s?t.prepare(y).get(o):t.prepare(y).get(),a=y=>s?t.prepare(y).all(o):t.prepare(y).all(),d=a(`SELECT mu.model,
|
|
1381
1381
|
COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
|
|
1382
1382
|
COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
|
|
1383
1383
|
COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
|
|
@@ -1386,7 +1386,7 @@ Top ${d.length} cluster(s):`);for(let l of d){let u=l.status==="resolved"?"\u271
|
|
|
1386
1386
|
FROM message_usage mu
|
|
1387
1387
|
JOIN sessions s ON s.id = mu.session_id
|
|
1388
1388
|
${n}
|
|
1389
|
-
GROUP BY mu.model`),l=
|
|
1389
|
+
GROUP BY mu.model`),l=ao(d),u=0,p=0,m=0,g=0;for(let y of l)u+=y.inputTokens,p+=y.outputTokens,m+=y.cacheCreateTokens,g+=y.cacheReadTokens;let f=Ie({inputTokens:u,outputTokens:p,cacheCreateTokens:m,cacheReadTokens:g,...co(l)},null),h=s?i(`SELECT
|
|
1390
1390
|
(SELECT COUNT(DISTINCT m.session_id)
|
|
1391
1391
|
FROM messages m
|
|
1392
1392
|
JOIN sessions s2 ON s2.id = m.session_id
|
|
@@ -1440,14 +1440,14 @@ Top ${d.length} cluster(s):`);for(let l of d){let u=l.status==="resolved"?"\u271
|
|
|
1440
1440
|
${n}
|
|
1441
1441
|
GROUP BY p.id, mu.model`),D=new Map;for(let y of L){let B=y.project_id??"__none__",M=D.get(B);M||(M={project:y.project??"(no project)",sessionIds:new Set,sessionsApprox:0,byModel:{}},D.set(B,M)),y.sessions>M.sessionsApprox&&(M.sessionsApprox=y.sessions),M.byModel[y.model??"__unknown__"]={inputTokens:y.input_tokens,outputTokens:y.output_tokens,cacheCreateTokens:y.cache_create_tokens,cacheReadTokens:y.cache_read_tokens}}let A=[...D.values()].map(y=>{let B=0,M=0,V=0,Ge=0;for(let _t of Object.values(y.byModel))B+=_t.inputTokens,M+=_t.outputTokens,V+=_t.cacheCreateTokens,Ge+=_t.cacheReadTokens;let Fe=Ie({inputTokens:B,outputTokens:M,cacheCreateTokens:V,cacheReadTokens:Ge,byModel:y.byModel},null);return{project:y.project,sessions:y.sessionsApprox,totalTokens:Fe.totalTokens,cost:Fe}});A.sort((y,B)=>B.totalTokens-y.totalTokens);let $=A.slice(0,20),U=t.prepare(`SELECT
|
|
1442
1442
|
(SELECT COUNT(*) FROM messages WHERE role='assistant') AS assistant_messages,
|
|
1443
|
-
(SELECT COUNT(*) FROM message_usage) AS messages_with_usage`).get();return{range:e,totalSessions:h.total_sessions,sessionsWithUsage:h.sessions_with_usage,inputTokens:u,outputTokens:p,cacheCreateTokens:m,cacheReadTokens:g,totalTokens:f.totalTokens,cost:f,daily:S,byModel:l,topSessions:T,topRepos:$,backfill:{assistantMessages:U.assistant_messages,messagesWithUsage:U.messages_with_usage,pending:Math.max(0,U.assistant_messages-U.messages_with_usage),unrecoverable:Math.min(
|
|
1444
|
-
`)){if(!a)continue;let[d,l,...u]=a.split(" ");!d||i.has(d)||(i.add(d),o.push({commit_sha:d,committed_at:l??null,subject:u.join(" ")||null}))}return o}function
|
|
1445
|
-
FROM sessions WHERE id = ?`).get(e)??null}function
|
|
1443
|
+
(SELECT COUNT(*) FROM message_usage) AS messages_with_usage`).get();return{range:e,totalSessions:h.total_sessions,sessionsWithUsage:h.sessions_with_usage,inputTokens:u,outputTokens:p,cacheCreateTokens:m,cacheReadTokens:g,totalTokens:f.totalTokens,cost:f,daily:S,byModel:l,topSessions:T,topRepos:$,backfill:{assistantMessages:U.assistant_messages,messagesWithUsage:U.messages_with_usage,pending:Math.max(0,U.assistant_messages-U.messages_with_usage),unrecoverable:Math.min(Il(),Math.max(0,U.assistant_messages-U.messages_with_usage))},display:{dollars:re(f.cents),tokens:ue(f.totalTokens)}}}var Qe=e=>e.toLocaleString();function wS(e){return _().prepare("SELECT id FROM sessions WHERE id = ? OR id LIKE ? LIMIT 1").get(e,`${e}%`)?.id??null}async function Fl(e,t){if(t.backfill){let r=t.limit?Math.max(1,Number(t.limit)):void 0;console.log(c.dim("backfilling per-message usage from raw_json\u2026"));let o=Ml({limit:r});if(console.log(`${c.ok("backfill done")}: scanned ${c.bold(String(o.scanned))}, inserted ${c.bold(String(o.inserted))}, rolled-up ${c.bold(String(o.sessionsTouched))} sessions`),!e&&!t.project)return}if(e){let r=wS(e);if(!r){console.error(c.err(`no session matches '${e}'`)),process.exit(1);return}let o=Dl(r);if(!o){console.error(c.err("session has no usage data yet \u2014 try --backfill")),process.exit(1);return}if(t.json){console.log(JSON.stringify(o,null,2));return}if(console.log(""),console.log(c.bold("session cost")),console.log(c.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(` id ${c.dim(o.sessionId)}`),console.log(` project ${c.accent(o.project??"\u2014")}`),console.log(` started ${c.dim(o.startedAt??"n/a")}`),console.log(` messages ${c.accent(Qe(o.messageCount))}`),console.log(` model ${c.accent(o.primaryModelLabel)} ${c.dim(o.primaryModel??"")}`),console.log(""),console.log(` input ${Qe(o.inputTokens).padStart(12)}`),console.log(` output ${Qe(o.outputTokens).padStart(12)}`),console.log(` cache write ${Qe(o.cacheCreateTokens).padStart(12)}`),console.log(` cache read ${Qe(o.cacheReadTokens).padStart(12)}`),console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),console.log(` total tokens ${c.bold(Qe(o.totalTokens).padStart(12))}`),console.log(` estimated cost ${c.accent(re(o.cost.cents).padStart(12))}`),o.byModel.length>1){console.log(""),console.log(c.dim(" by model:"));for(let i of o.byModel)console.log(` ${i.modelLabel.padEnd(14)} ${ue(i.inputTokens+i.outputTokens+i.cacheCreateTokens+i.cacheReadTokens).padStart(10)} ${c.accent(re(i.cost.cents).padStart(10))}`)}console.log("");return}if(t.project){let r=$l(t.project);if(!r){console.error(c.err(`project '${t.project}' not found`)),process.exit(1);return}if(t.json){console.log(JSON.stringify(r,null,2));return}if(console.log(""),console.log(c.bold(`project \xB7 ${r.project}`)),console.log(c.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(` sessions ${c.accent(Qe(r.sessionCount))}`),console.log(` total tokens ${c.accent(ue(r.totalTokens).padStart(12))}`),console.log(` estimated cost ${c.accent(re(r.cost.cents).padStart(12))}`),r.byModel.length>0){console.log(""),console.log(c.dim(" by model:"));for(let o of r.byModel)console.log(` ${o.modelLabel.padEnd(14)} ${ue(o.inputTokens+o.outputTokens+o.cacheCreateTokens+o.cacheReadTokens).padStart(10)} ${c.accent(re(o.cost.cents).padStart(10))}`)}if(r.topSessions.length>0){console.log(""),console.log(c.dim(" top sessions:"));for(let o of r.topSessions){let i=o.alias??o.sessionId.slice(0,8);console.log(` ${i.padEnd(22)} ${ue(o.totalTokens).padStart(10)} ${c.accent(re(o.cost.cents).padStart(10))}`)}}console.log("");return}let s=t.days==="7"?"7d":t.days==="30"?"30d":"all",n=Pl(s);if(t.json){console.log(JSON.stringify(n,null,2));return}if(console.log(""),console.log(c.bold(`overview \xB7 ${s}`)),console.log(c.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(` sessions ${c.accent(Qe(n.totalSessions))} ${c.dim(`(${n.sessionsWithUsage} w/ usage)`)}`),console.log(` total tokens ${c.accent(ue(n.totalTokens).padStart(12))}`),console.log(` estimated cost ${c.accent(re(n.cost.cents).padStart(12))}`),n.backfill.pending>0&&console.log(c.dim(` (${Qe(n.backfill.pending)} assistant messages pending \u2014 run \`recall stats --backfill\`)`)),n.byModel.length>0){console.log(""),console.log(c.dim(" by model:"));for(let r of n.byModel)console.log(` ${r.modelLabel.padEnd(14)} ${ue(r.inputTokens+r.outputTokens+r.cacheCreateTokens+r.cacheReadTokens).padStart(10)} ${c.accent(re(r.cost.cents).padStart(10))}`)}if(n.topSessions.length>0){console.log(""),console.log(c.dim(" top sessions:"));for(let r of n.topSessions){let o=r.alias??r.sessionId.slice(0,8);console.log(` ${o.padEnd(22)} ${(r.project??"").slice(0,20).padEnd(22)} ${ue(r.totalTokens).padStart(10)} ${c.accent(re(r.cost.cents).padStart(10))}`)}}console.log("")}v();w();w();import{execFile as TS}from"node:child_process";import{promisify as RS}from"node:util";import{stat as xS}from"node:fs/promises";var jl=RS(TS),Ul=1e4,kS="%H%x09%aI%x09%s";async function CS(e){try{let{stdout:t}=await jl("git",["rev-parse","--is-inside-work-tree"],{cwd:e,timeout:Ul});return t.trim()==="true"}catch{return!1}}async function LS(e,t,s){let n=["--no-pager","log","--all","--no-color","--since",t,"--until",s,`--pretty=format:${kS}`],{stdout:r}=await jl("git",n,{cwd:e,timeout:Ul,maxBuffer:8*1024*1024}),o=[],i=new Set;for(let a of r.split(`
|
|
1444
|
+
`)){if(!a)continue;let[d,l,...u]=a.split(" ");!d||i.has(d)||(i.add(d),o.push({commit_sha:d,committed_at:l??null,subject:u.join(" ")||null}))}return o}function NS(e){return _().prepare(`SELECT id, cwd, started_at, ended_at
|
|
1445
|
+
FROM sessions WHERE id = ?`).get(e)??null}function AS(e,t,s){if(s.length===0)return 0;let n=_(),r=new Date().toISOString(),o=n.prepare(`INSERT OR IGNORE INTO session_commits
|
|
1446
1446
|
(session_id, commit_sha, committed_at, subject, cwd_snapshot, correlated_at)
|
|
1447
|
-
VALUES (?, ?, ?, ?, ?, ?)`),i=0;return n.transaction(d=>{for(let l of d)o.run(e,l.commit_sha,l.committed_at,l.subject,t,r).changes>0&&(i+=1)})(s),i}async function
|
|
1447
|
+
VALUES (?, ?, ?, ?, ?, ?)`),i=0;return n.transaction(d=>{for(let l of d)o.run(e,l.commit_sha,l.committed_at,l.subject,t,r).changes>0&&(i+=1)})(s),i}async function lo(e){let t=NS(e);if(!t)return{sessionId:e,status:"error",commitsFound:0,commitsInserted:0,error:"session not found"};if(!t.cwd)return{sessionId:e,status:"no-cwd",commitsFound:0,commitsInserted:0};if(!t.started_at||!t.ended_at)return{sessionId:e,status:"no-window",commitsFound:0,commitsInserted:0};let s=t.started_at,n=t.ended_at===t.started_at?new Date(Date.parse(t.ended_at)+1e3).toISOString():t.ended_at;try{if(!(await xS(t.cwd)).isDirectory())return{sessionId:e,status:"cwd-missing",commitsFound:0,commitsInserted:0}}catch{return{sessionId:e,status:"cwd-missing",commitsFound:0,commitsInserted:0}}if(!await CS(t.cwd))return{sessionId:e,status:"not-a-repo",commitsFound:0,commitsInserted:0};try{let o=await LS(t.cwd,s,n),i=AS(e,t.cwd,o);return{sessionId:e,status:"ok",commitsFound:o.length,commitsInserted:i}}catch(o){return{sessionId:e,status:"error",commitsFound:0,commitsInserted:0,error:o.message}}}async function Bl(e={}){let t=_(),s=Math.max(1,Math.min(1e5,e.limit??1e4)),n=t.prepare(`SELECT id FROM sessions
|
|
1448
1448
|
WHERE cwd IS NOT NULL AND started_at IS NOT NULL AND ended_at IS NOT NULL
|
|
1449
1449
|
ORDER BY COALESCE(ended_at, started_at) DESC
|
|
1450
|
-
LIMIT ?`).all(s),r={total:n.length,ok:0,skipped:0,errors:0,commitsInserted:0,results:[]},o=0;for(let i of n){let a=await
|
|
1450
|
+
LIMIT ?`).all(s),r={total:n.length,ok:0,skipped:0,errors:0,commitsInserted:0,results:[]},o=0;for(let i of n){let a=await lo(i.id);r.results.push(a),a.status==="ok"?(r.ok+=1,r.commitsInserted+=a.commitsInserted):a.status==="error"?r.errors+=1:r.skipped+=1,o+=1,e.onProgress?.(o,n.length)}return r}function dn(e){let t=_(),s=e.trim();if(!/^[0-9a-fA-F]{4,40}$/.test(s))return[];let n=`${s.toLowerCase()}%`;return t.prepare(`SELECT sc.session_id AS sessionId,
|
|
1451
1451
|
NULLIF(sa.alias, '') AS alias,
|
|
1452
1452
|
p.name AS project,
|
|
1453
1453
|
s.started_at AS startedAt,
|
|
@@ -1461,33 +1461,33 @@ Top ${d.length} cluster(s):`);for(let l of d){let u=l.status==="resolved"?"\u271
|
|
|
1461
1461
|
LEFT JOIN session_aliases sa ON sa.session_id = sc.session_id
|
|
1462
1462
|
WHERE lower(sc.commit_sha) = lower(?)
|
|
1463
1463
|
OR lower(sc.commit_sha) LIKE ?
|
|
1464
|
-
ORDER BY COALESCE(sc.committed_at, s.started_at, '') DESC`).all(s,n)}function
|
|
1465
|
-
`),t.json){console.log(JSON.stringify(n,null,2));return}console.log(""),console.log(`${c.ok("correlated")}: ${c.bold(String(n.ok))} sessions \xB7 ${c.bold(String(n.commitsInserted))} new commits`),console.log(c.dim(` skipped ${n.skipped} (no cwd / non-repo / cwd missing), errors ${n.errors}`)),console.log("")}v();w();Et();import{existsSync as
|
|
1464
|
+
ORDER BY COALESCE(sc.committed_at, s.started_at, '') DESC`).all(s,n)}function OS(e){return _().prepare("SELECT id FROM sessions WHERE id = ? OR id LIKE ? LIMIT 1").get(e,`${e}%`)?.id??null}function vS(e){let t=e.sessionId.slice(0,8);switch(e.status){case"ok":console.log(`${c.ok("\u2713")} ${c.dim(t)} ${c.bold(String(e.commitsFound))} commits (${e.commitsInserted} new)`);break;case"no-cwd":console.log(`${c.dim("\xB7")} ${c.dim(t)} no cwd recorded \u2014 skipped`);break;case"not-a-repo":console.log(`${c.dim("\xB7")} ${c.dim(t)} cwd is not a git worktree \u2014 skipped`);break;case"cwd-missing":console.log(`${c.dim("\xB7")} ${c.dim(t)} cwd no longer exists on disk \u2014 skipped`);break;case"no-window":console.log(`${c.dim("\xB7")} ${c.dim(t)} session has no time window yet \u2014 skipped`);break;case"error":console.log(`${c.err("\u2717")} ${c.dim(t)} ${e.error??"unknown error"}`);break}}async function Hl(e,t){if(e){let r=OS(e);if(!r){console.error(c.err(`no session matches '${e}'`)),process.exit(1);return}let o=await lo(r);if(t.json){console.log(JSON.stringify(o,null,2));return}vS(o);return}let s=t.limit?Math.max(1,Number(t.limit)):void 0;console.log(c.dim(`correlating sessions to git commits (limit ${s??"default"})\u2026`));let n=await Bl({limit:s,onProgress:(r,o)=>{(r%25===0||r===o)&&process.stderr.write(`\r${c.dim(` ${r}/${o}`)}`)}});if(process.stderr.write(`
|
|
1465
|
+
`),t.json){console.log(JSON.stringify(n,null,2));return}console.log(""),console.log(`${c.ok("correlated")}: ${c.bold(String(n.ok))} sessions \xB7 ${c.bold(String(n.commitsInserted))} new commits`),console.log(c.dim(` skipped ${n.skipped} (no cwd / non-repo / cwd missing), errors ${n.errors}`)),console.log("")}v();w();Et();import{existsSync as _o,readFileSync as ho}from"node:fs";P();import{existsSync as IS,readFileSync as MS,writeFileSync as DS}from"node:fs";import{join as $S}from"node:path";import{z as pe}from"zod";var uo=$S(x,"terminals.json"),Wl=1440*60*1e3,PS=3e4,FS=6e4,jS=pe.object({shell_pid:pe.number(),tab_name:pe.string(),cwd:pe.string().nullable().optional(),opened_at:pe.string(),last_seen_at:pe.string()}),US=pe.object({schema:pe.string().optional(),saved_at:pe.string().optional(),terminals:pe.array(jS).max(500).default([]),sessions_by_pid:pe.record(pe.string(),pe.array(pe.string()).max(50)).optional().default({})}),Xl=/^[⠀-⣿✳\s]+/,Jl=/^\d+(\.\d+){1,3}$/;function mo(e){let t=e.trim();return!!(!t||Xl.test(t)||Jl.test(t))}function Gl(e){let t=e.trim();if(!t||Jl.test(t))return null;let s=t.replace(Xl,"").trim();return s.length>0?s:null}function po(e,t){if(!mo(e))return e;let s=Gl(e);return s||(t&&!mo(t)?t:e)}function BS(e){let t=e.now-e.withinMs,s=e.pending.filter(n=>{let r=Date.parse(n.started_at);return Number.isFinite(r)&&r>=t});if(s.length===0)return{kind:"none"};if(e.shellPid!=null){let n=s.find(r=>r.shell_pid===e.shellPid);if(n)return{kind:"pid-match",entry:n}}if(e.cwd){let n=e.cwd.replace(/\/+$/,""),r=s.filter(o=>o.cwd&&o.cwd.replace(/\/+$/,"")===n);if(r.length===1)return{kind:"singleton-cwd",entry:r[0]};if(r.length>=2)return{kind:"ambiguous",candidates:r}}return{kind:"none"}}var go=class{entries=new Map;origins=new Map;sessionsByPid=new Map;pendingClaudeStarts=[];deferredLinks=new Map;pidOwnership=new Map;loaded=!1;ensureLoaded(){if(!this.loaded&&(this.loaded=!0,!!IS(uo)))try{let t=MS(uo,"utf8"),s=JSON.parse(t),n=US.safeParse(s);if(!n.success){console.warn("[terminal-registry] terminals.json failed validation, starting with empty registry:",n.error.issues);return}let r=n.data;for(let o of r.terminals)this.entries.set(o.shell_pid,{shell_pid:o.shell_pid,tab_name:o.tab_name,cwd:o.cwd??null,opened_at:o.opened_at,last_seen_at:o.last_seen_at});for(let[o,i]of Object.entries(r.sessions_by_pid??{})){let a=Number(o);if(!Number.isFinite(a))continue;let d=new Set;for(let l of i)l.length>0&&d.add(l);d.size>0&&this.sessionsByPid.set(a,d)}this.gc()}catch{}}save(){try{F();let t={schema:"claude-recall.terminals.v1",saved_at:new Date().toISOString(),terminals:Array.from(this.entries.values()),sessions_by_pid:Object.fromEntries(Array.from(this.sessionsByPid.entries()).map(([s,n])=>[String(s),Array.from(n)]))};DS(uo,JSON.stringify(t,null,2))}catch{}}upsert(t){this.ensureLoaded();let s=new Date().toISOString(),n=this.entries.get(t.shell_pid),r=po(t.tab_name,n?.tab_name),o=n?.opened_at??t.opened_at,i={...t,tab_name:r,opened_at:o,last_seen_at:s};return this.entries.set(t.shell_pid,i),this.gc(),this.save(),i}rename(t,s){this.ensureLoaded();let n=this.entries.get(t);if(!n)return null;let r=po(s,n.tab_name),o={...n,tab_name:r,last_seen_at:new Date().toISOString()};return this.entries.set(t,o),this.save(),o}remove(t){this.ensureLoaded();let s=this.entries.delete(t),n=this.sessionsByPid.delete(t);return this.outputTails.delete(t),this.pidOwnership.delete(t),(s||n)&&this.save(),s}claimPidOwnership(t,s,n=Date.now()){if(this.ensureLoaded(),!s)return"anonymous";let r=this.pidOwnership.get(t);return r?r.instance_id===s?(r.last_claim_at=n,"refreshed"):n-r.last_claim_at>FS?(this.pidOwnership.set(t,{instance_id:s,last_claim_at:n}),"claimed"):"rejected":(this.pidOwnership.set(t,{instance_id:s,last_claim_at:n}),"claimed")}pidOwnershipSnapshot(){return this.ensureLoaded(),Array.from(this.pidOwnership.entries()).map(([t,s])=>({shell_pid:t,instance_id:s.instance_id,last_claim_at:s.last_claim_at}))}sync(t){this.ensureLoaded();let s=new Date().toISOString(),n=0,r=0;for(let o of t){let i=this.entries.get(o.shell_pid),a=po(o.tab_name,i?.tab_name),d=i?.opened_at??o.opened_at;this.entries.set(o.shell_pid,{...o,tab_name:a,opened_at:d,last_seen_at:s}),i?(i.tab_name!==a||i.cwd!==o.cwd)&&r++:n++}return this.gc(),this.save(),{added:n,updated:r,removed:0}}get(t){return this.ensureLoaded(),this.entries.get(t)??null}all(){return this.ensureLoaded(),this.gc(),Array.from(this.entries.values())}size(){return this.ensureLoaded(),this.entries.size}linkSession(t,s){this.ensureLoaded();let n=this.sessionsByPid.get(s);n||(n=new Set,this.sessionsByPid.set(s,n)),n.has(t)||(n.add(t),this.save())}sessionsFor(t){return this.ensureLoaded(),Array.from(this.sessionsByPid.get(t)??[])}isSessionAutoLinked(t){this.ensureLoaded();for(let s of this.sessionsByPid.values())if(s.has(t))return!0;return!1}unlinkSession(t){this.ensureLoaded();let s=!1;for(let[n,r]of this.sessionsByPid)r.delete(t)&&(s=!0,r.size===0&&this.sessionsByPid.delete(n));return s&&this.save(),s}pushPending(t){this.ensureLoaded(),this.gcPending(),this.pendingClaudeStarts.push({...t})}takePendingMatched(t){this.ensureLoaded(),this.gcPending();let s=BS({pending:this.pendingClaudeStarts,shellPid:t.shellPid,cwd:t.cwd,withinMs:t.withinMs,now:Date.now()});if(s.kind==="pid-match"||s.kind==="singleton-cwd"){let n=this.pendingClaudeStarts.indexOf(s.entry);n>=0&&this.pendingClaudeStarts.splice(n,1)}return s}pendingSize(){return this.ensureLoaded(),this.gcPending(),this.pendingClaudeStarts.length}deferSessionLink(t,s,n,r){this.ensureLoaded(),this.gcDeferredLinks(),this.deferredLinks.set(t,{parent_shell_pid:s,queued_at:Date.now(),cwd:n,git_branch:r})}allDeferredLinks(){return this.ensureLoaded(),this.gcDeferredLinks(),Array.from(this.deferredLinks.entries()).map(([t,s])=>({session_id:t,...s}))}resolveDeferredLink(t){return this.deferredLinks.delete(t)}deferredLinkSize(){return this.ensureLoaded(),this.gcDeferredLinks(),this.deferredLinks.size}gcDeferredLinks(){let t=Date.now()-9e4;for(let[s,n]of this.deferredLinks)n.queued_at<t&&this.deferredLinks.delete(s)}gcPending(){let t=Date.now()-PS;this.pendingClaudeStarts.length!==0&&(this.pendingClaudeStarts=this.pendingClaudeStarts.filter(s=>{let n=Date.parse(s.started_at);return Number.isFinite(n)&&n>=t}))}outputTails=new Map;setOutputTail(t,s,n){this.ensureLoaded(),this.outputTails.set(t,{text:s,captured_at:n})}getOutputTail(t){return this.ensureLoaded(),this.outputTails.get(t)??null}allOutputTails(){return this.ensureLoaded(),new Map(this.outputTails)}removeOutputTail(t){return this.outputTails.delete(t)}setOrigin(t,s){this.ensureLoaded(),this.origins.set(t,s),this.gcOrigins()}getOrigin(t){return this.ensureLoaded(),this.origins.get(t)??null}removeOrigin(t){return this.ensureLoaded(),this.origins.delete(t)}allOrigins(){return this.ensureLoaded(),this.gcOrigins(),new Map(this.origins)}originSize(){return this.ensureLoaded(),this.origins.size}gc(){let t=Date.now()-Wl;for(let[s,n]of this.entries){let r=Date.parse(n.last_seen_at);!Number.isNaN(r)&&r<t&&this.entries.delete(s)}}gcOrigins(){let t=Date.now()-Wl;for(let[s,n]of this.origins)n.detectedAt<t&&this.origins.delete(s)}reapStaleLinks(t=10080*60*1e3){this.ensureLoaded();let n=Date.now()-t,r=0,o=0;for(let[i,a]of this.sessionsByPid){let d=this.entries.get(i);if(d){let l=Date.parse(d.last_seen_at);if(Number.isFinite(l)&&l>=n)continue}o+=a.size,this.sessionsByPid.delete(i),d&&(this.entries.delete(i),r++)}return(r||o)&&this.save(),{pruned_pids:r,pruned_sessions:o}}gcDeadPids(){this.ensureLoaded();let t=0,s=0;for(let n of[...this.entries.keys()]){let r=!0;try{process.kill(n,0)}catch(i){i.code==="ESRCH"&&(r=!1)}if(r)continue;this.entries.delete(n),t++;let o=this.sessionsByPid.get(n);o&&(s+=o.size,this.sessionsByPid.delete(n)),this.outputTails.delete(n),this.pidOwnership.delete(n)}return(t||s)&&this.save(),{pruned_pids:t,pruned_sessions:s}}},fo=new go;P();function WS(){let e=`${x}/daemon.port`;if(!_o(e))return null;try{let t=ho(e,"utf8").trim();return/^\d+$/.test(t)?t:null}catch{return null}}async function XS(e,t){try{return(await st("POST",`http://127.0.0.1:${e}/api/sessions/${encodeURIComponent(t)}/relink`,{clear:!0})).ok}catch{return!1}}function JS(e,t){let s=_(),n=t?" AND p.name = ?":"",r=t?[t]:[],o=s.prepare(`SELECT s.id, p.name AS project_name, s.cwd, s.started_at, sa.alias
|
|
1466
1466
|
FROM sessions s
|
|
1467
1467
|
JOIN projects p ON p.id = s.project_id
|
|
1468
1468
|
JOIN session_aliases sa ON sa.session_id = s.id
|
|
1469
1469
|
WHERE s.cwd IS NOT NULL
|
|
1470
1470
|
AND s.started_at IS NOT NULL
|
|
1471
1471
|
AND sa.alias IS NOT NULL
|
|
1472
|
-
AND sa.alias <> ''${n}`).all(...r),i=new Map;for(let l of o){let u=l.cwd.replace(/\/+$/,""),p=i.get(u);p||(p=[],i.set(u,p)),p.push(l)}let a=e*1e3,d=new Map;for(let[l,u]of i){let p=new Map;for(let m of u){let g=p.get(m.alias);g||(g=[],p.set(m.alias,g)),g.push(m)}for(let m of p.values())if(!(m.length<2))for(let g of m){let f=new Set;for(let h of m)h.id!==g.id&&f.add(h.id);d.set(g.id,{row:g,cwdKey:l,reason:"duplicate-alias",siblingIds:f})}}for(let[l,u]of i){if(u.length<2)continue;u.sort((g,f)=>Date.parse(g.started_at)-Date.parse(f.started_at));let p=[],m=()=>{if(p.length>=2)for(let g of p){if(d.has(g.id))continue;let f=new Set;for(let h of p)h.id!==g.id&&f.add(h.id);d.set(g.id,{row:g,cwdKey:l,reason:"cwd-collision",siblingIds:f})}p=[]};for(let g of u){if(p.length===0){p.push(g);continue}let f=p[p.length-1];Date.parse(g.started_at)-Date.parse(f.started_at)<=a||m(),p.push(g)}m()}return Array.from(d.values()).map(l=>({session_id:l.row.id,project_name:l.row.project_name,cwd:l.cwdKey,started_at:l.row.started_at,alias:l.row.alias,sibling_ids:Array.from(l.siblingIds),reason:l.reason}))}function
|
|
1472
|
+
AND sa.alias <> ''${n}`).all(...r),i=new Map;for(let l of o){let u=l.cwd.replace(/\/+$/,""),p=i.get(u);p||(p=[],i.set(u,p)),p.push(l)}let a=e*1e3,d=new Map;for(let[l,u]of i){let p=new Map;for(let m of u){let g=p.get(m.alias);g||(g=[],p.set(m.alias,g)),g.push(m)}for(let m of p.values())if(!(m.length<2))for(let g of m){let f=new Set;for(let h of m)h.id!==g.id&&f.add(h.id);d.set(g.id,{row:g,cwdKey:l,reason:"duplicate-alias",siblingIds:f})}}for(let[l,u]of i){if(u.length<2)continue;u.sort((g,f)=>Date.parse(g.started_at)-Date.parse(f.started_at));let p=[],m=()=>{if(p.length>=2)for(let g of p){if(d.has(g.id))continue;let f=new Set;for(let h of p)h.id!==g.id&&f.add(h.id);d.set(g.id,{row:g,cwdKey:l,reason:"cwd-collision",siblingIds:f})}p=[]};for(let g of u){if(p.length===0){p.push(g);continue}let f=p[p.length-1];Date.parse(g.started_at)-Date.parse(f.started_at)<=a||m(),p.push(g)}m()}return Array.from(d.values()).map(l=>({session_id:l.row.id,project_name:l.row.project_name,cwd:l.cwdKey,started_at:l.row.started_at,alias:l.row.alias,sibling_ids:Array.from(l.siblingIds),reason:l.reason}))}function GS(e){let t=[];try{let s=`${x}/terminals.json`;if(!_o(s))return t;let n=JSON.parse(ho(s,"utf8")),r=n.sessions_by_pid??{},o=new Map;for(let a of n.terminals??[])o.set(a.shell_pid,a);let i=_();for(let[a,d]of Object.entries(r)){let l=Number(a);if(!Number.isFinite(l))continue;let u=o.get(l)??null;for(let p of d){let m=i.prepare(`SELECT s.id, p.name AS project_name, s.cwd, s.started_at,
|
|
1473
1473
|
NULLIF(sa.alias, '') AS alias
|
|
1474
1474
|
FROM sessions s
|
|
1475
1475
|
JOIN projects p ON p.id = s.project_id
|
|
1476
1476
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1477
|
-
WHERE s.id = ?`).get(p);m&&(m.alias||e&&m.project_name!==e||t.push({session_id:m.id,project_name:m.project_name,cwd:(m.cwd??"").replace(/\/+$/,""),started_at:m.started_at??"",alias:"",sibling_ids:[],reason:"orphan-link",stale_terminal:u}))}}}catch{}return t}function
|
|
1477
|
+
WHERE s.id = ?`).get(p);m&&(m.alias||e&&m.project_name!==e||t.push({session_id:m.id,project_name:m.project_name,cwd:(m.cwd??"").replace(/\/+$/,""),started_at:m.started_at??"",alias:"",sibling_ids:[],reason:"orphan-link",stale_terminal:u}))}}}catch{}return t}function YS(e){let t=[],s=`${x}/terminals.json`;if(!_o(s))return t;let n;try{n=JSON.parse(ho(s,"utf8"))}catch{return t}let r=new Map;for(let l of n.terminals??[]){if(!l.tab_name)continue;let u=r.get(l.tab_name);u||(u=[],r.set(l.tab_name,u)),u.push(l)}if(r.size===0)return t;let o=_(),i=e?" AND p.name = ?":"",a=e?[e]:[],d=o.prepare(`SELECT s.id, p.name AS project_name, s.cwd, s.started_at, sa.alias
|
|
1478
1478
|
FROM sessions s
|
|
1479
1479
|
JOIN projects p ON p.id = s.project_id
|
|
1480
1480
|
JOIN session_aliases sa ON sa.session_id = s.id
|
|
1481
1481
|
WHERE s.started_at IS NOT NULL
|
|
1482
1482
|
AND sa.alias IS NOT NULL
|
|
1483
|
-
AND sa.alias <> ''${i}`).all(...a);for(let l of d){let u=r.get(l.alias);if(!u||u.length===0)continue;let p=Date.parse(l.started_at);if(!Number.isFinite(p))continue;let m=!1,g=null,f=1/0;for(let h of u){let E=Date.parse(h.opened_at);if(Number.isFinite(E)){if(E-p<=6e4){m=!0;break}E<f&&(f=E,g=h)}}m||!g||t.push({session_id:l.id,project_name:l.project_name,cwd:l.cwd.replace(/\/+$/,""),started_at:l.started_at,alias:l.alias,sibling_ids:[],reason:"temporal-anomaly",postdating_terminal:g})}return t}function
|
|
1483
|
+
AND sa.alias <> ''${i}`).all(...a);for(let l of d){let u=r.get(l.alias);if(!u||u.length===0)continue;let p=Date.parse(l.started_at);if(!Number.isFinite(p))continue;let m=!1,g=null,f=1/0;for(let h of u){let E=Date.parse(h.opened_at);if(Number.isFinite(E)){if(E-p<=6e4){m=!0;break}E<f&&(f=E,g=h)}}m||!g||t.push({session_id:l.id,project_name:l.project_name,cwd:l.cwd.replace(/\/+$/,""),started_at:l.started_at,alias:l.alias,sibling_ids:[],reason:"temporal-anomaly",postdating_terminal:g})}return t}function zS(e){if(e.length===0){console.log(c.ok("No suspicious correlations found."));return}console.log(c.warn(`Found ${e.length} session${e.length===1?"":"s"} with likely-bad correlator aliases:`)),console.log();let t=new Map;for(let s of e){let n=t.get(s.cwd);n||(n=[],t.set(s.cwd,n)),n.push(s)}for(let[s,n]of t){console.log(c.dim(` cwd: ${s}`)),n.sort((r,o)=>Date.parse(r.started_at)-Date.parse(o.started_at));for(let r of n){let o=r.reason==="duplicate-alias"?c.err("[dup-alias]"):r.reason==="orphan-link"?c.warn("[orphan-link]"):r.reason==="temporal-anomaly"?c.err("[temporal]"):c.warn("[cwd-collision]"),i=r.reason==="orphan-link"&&r.stale_terminal?`${c.dim("still pointing at pid")} ${r.stale_terminal.shell_pid} ${c.dim("=")} ${c.bold(`"${r.stale_terminal.tab_name}"`)}`:r.reason==="temporal-anomaly"&&r.postdating_terminal?`${c.bold(`"${r.alias}"`)} ${c.dim("\u2190 terminal opened")} ${r.postdating_terminal.opened_at.replace("T"," ").slice(0,19)} ${c.dim("(after session)")}`:c.bold(`"${r.alias}"`);console.log(` ${o} ${c.dim(r.session_id.slice(0,8))} ${c.dim(r.started_at.replace("T"," ").slice(0,19))} \u2192 ${i}`)}console.log()}}async function Yl(e){let t=e.window?Math.max(5,Math.min(600,Number(e.window)||60)):60,s=e.project?.trim()||void 0,n=JS(t,s),r=GS(s),o=YS(s),i=new Set,a=[];for(let p of[...n,...r,...o])i.has(p.session_id)||(i.add(p.session_id),a.push(p));if(e.json){console.log(JSON.stringify({window_sec:t,suspects:a},null,2));return}if(zS(a),!e.fix){a.length>0&&console.log(c.dim("Re-run with --fix to clear these aliases. Heuristic titles will display instead, and you can manually relink via the UI's \u{1F517} picker."));return}let d=WS(),l=0,u=0;for(let p of a){if(d){await XS(d,p.session_id)?l++:(u++,console.error(c.err(`failed to clear ${p.session_id.slice(0,8)} via daemon`)));continue}try{Ua(p.session_id),fo.unlinkSession(p.session_id),l++}catch(m){u++,console.error(c.err(`failed to clear ${p.session_id.slice(0,8)}: ${m.message}`))}}console.log(c.ok(`Cleared ${l} suspect alias${l===1?"":"es"}.`)),u>0&&console.log(c.warn(`${u} clears failed; see errors above.`)),d||console.log(c.dim("Daemon was not running, so changes were applied directly to state files.")),console.log(c.dim("Open an affected session in the UI and use the \u{1F517} picker to pin the correct terminal."))}v();w();P();import{existsSync as Ql,readFileSync as sy,statSync as So,statfsSync as ny}from"node:fs";import{join as ed}from"node:path";import*as td from"node:http";P();w();import{watch as PI}from"chokidar";import{readdirSync as ey,statSync as jI}from"node:fs";import{basename as XI,join as ty}from"node:path";import{execFile as VS}from"node:child_process";import{promisify as ZS}from"node:util";Et();w();import{execFile as qS}from"node:child_process";import{promisify as KS}from"node:util";var Qv=KS(qS);var cI=ZS(VS);var lI=3600*1e3;w();P();import{basename as SI,join as Eo}from"node:path";w();P();import{join as QS}from"node:path";var gI=QS(x,"collections.json");var zl=Eo(x,"auto-rules"),RI=Eo(zl,"rules.json"),xI=Eo(zl,"suggestions.json");Et();function ql(e){let t=e.split(/[/\\]/),s=t.findIndex(n=>n==="projects");return s===-1||s+1>=t.length?null:t[s+1]??null}function Kl(e){return e.replace(/\\/g,"/").includes("/subagents/")}function*bo(e){let t;try{t=ey(e,{withFileTypes:!0,encoding:"utf8"})}catch{return}for(let s of t){if(s.isSymbolicLink())continue;let n=ty(e,s.name);s.isDirectory()?yield*bo(n):s.isFile()&&s.name.endsWith(".jsonl")&&(yield n)}}var ry=["VS Code","VS Code Insiders","Cursor","Windsurf","Warp","iTerm","Terminal","WezTerm","Windows Terminal","Kitty","Alacritty"],oy=new RegExp(`^(${ry.map(e=>e.replace(/ /g,"\\s")).join("|")})\\s\xB7\\s`);function iy(e){let t=[];for(let s of e)if(s.alias){if(oy.test(s.alias)){t.push({session_id:s.session_id,alias:s.alias,violation:"fabricated-origin-label",cwd:s.cwd});continue}if(s.cwd){let n=s.cwd.replace(/\/+$/,"").split("/").pop();n&&s.alias.startsWith(`${n} \xB7 `)&&t.push({session_id:s.session_id,alias:s.alias,violation:"fabricated-cwd-branch",cwd:s.cwd})}}return t}var sd=5*6e4,yo=24,nd=.5;function ay(){let e=ed(x,"daemon.port");if(!Ql(e))return null;try{let t=sy(e,"utf8").trim();if(t.startsWith("{")){let n=JSON.parse(t);return typeof n.port=="number"?n.port:null}let s=Number.parseInt(t,10);return Number.isFinite(s)&&s>0&&s<65536?s:null}catch{return null}}function cy(e,t,s=1500){return new Promise(n=>{let r=td.request({host:"127.0.0.1",port:e,path:t,method:"GET",timeout:s,headers:{host:"127.0.0.1","user-agent":"recall-doctor"}},o=>{let i=[];o.on("data",a=>i.push(Buffer.isBuffer(a)?a:Buffer.from(a))),o.on("end",()=>{if(!o.statusCode||o.statusCode<200||o.statusCode>=300){n(null);return}try{n(JSON.parse(Buffer.concat(i).toString("utf8")))}catch{n(null)}})});r.on("error",()=>n(null)),r.on("timeout",()=>{r.destroy(),n(null)}),r.end()})}function ly(){let e=ed(x,"terminals.json");if(!Ql(e))return{exists:!1,mtimeMs:null,ageSeconds:null};try{let t=So(e),s=Math.floor((Date.now()-t.mtimeMs)/1e3);return{exists:!0,mtimeMs:t.mtimeMs,ageSeconds:s}}catch{return{exists:!1,mtimeMs:null,ageSeconds:null}}}function dy(){let e=new Date(Date.now()-yo*36e5).toISOString();try{let t=_().prepare(`SELECT
|
|
1484
1484
|
COUNT(*) AS total,
|
|
1485
1485
|
SUM(CASE WHEN sa.alias IS NULL OR sa.alias = '' THEN 1 ELSE 0 END) AS without_alias,
|
|
1486
1486
|
SUM(CASE WHEN (sa.alias IS NULL OR sa.alias = '')
|
|
1487
1487
|
AND s.auto_title_source = 'heuristic' THEN 1 ELSE 0 END) AS heuristic_only
|
|
1488
1488
|
FROM sessions s
|
|
1489
1489
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1490
|
-
WHERE s.started_at >= ?`).get(e),s=t.total??0,n=t.without_alias??0,r=t.heuristic_only??0,o=s>0?r/s:0;return{total:s,withoutAlias:n,heuristicOnly:r,fractionHeuristic:o}}catch{return{total:0,withoutAlias:0,heuristicOnly:0,fractionHeuristic:0}}}async function
|
|
1490
|
+
WHERE s.started_at >= ?`).get(e),s=t.total??0,n=t.without_alias??0,r=t.heuristic_only??0,o=s>0?r/s:0;return{total:s,withoutAlias:n,heuristicOnly:r,fractionHeuristic:o}}catch{return{total:0,withoutAlias:0,heuristicOnly:0,fractionHeuristic:0}}}async function uy(){let e=ay(),t=ly(),s=dy(),n=!1,r=null,o=null,i=null,a=null,d=null;if(e){let p=await cy(e,"/api/health");if(p){n=!0,r=typeof p.uptimeSeconds=="number"?p.uptimeSeconds:null,o=typeof p.version=="string"?p.version:null,i=typeof p.pipeline?.silentTerminalRejections=="number"?p.pipeline.silentTerminalRejections:null,a=p.pipeline?.lastTerminalSyncAt??null;let m=p.pipeline?.autoExtract;m&&(d={circuitBroken:m.circuitBroken===!0,reason:m.reason??null,brokenAt:typeof m.brokenAt=="number"?m.brokenAt:null,consecutiveZeroTokenRuns:typeof m.consecutiveZeroTokenRuns=="number"?m.consecutiveZeroTokenRuns:0})}}let l=[];if(n||l.push("Daemon not reachable on 127.0.0.1 \u2014 start it with `recall start` before further diagnosis."),i!==null&&i>0&&l.push(`Daemon rejected ${i.toLocaleString()} /api/terminal/* request(s) without a valid X-Recall-Token. The editor extension is outdated relative to this daemon \u2014 tab names are not flowing through. Reinstall: \`code --install-extension extensions/vscode/clauderecall-vscode-*.vsix\`, then reload the extension host (Cmd/Ctrl+Shift+P \u2192 "Developer: Restart Extension Host").`),n&&r!==null&&r>30)if(!a)l.push(`Daemon has been running ${Math.round(r/60)} min but has not yet seen a single successful /api/terminal/sync. Either no editor extension is installed/active, or every attempt is being rejected (see counter above).`);else{let p=Date.now()-Date.parse(a);Number.isFinite(p)&&p>sd&&l.push(`Last successful /api/terminal/sync was ${Math.round(p/6e4)} min ago \u2014 extension may have crashed, been disabled, or is failing auth. Run \`recall doctor\` again after restarting the extension host.`)}return!n&&t.exists&&t.ageSeconds!==null&&t.ageSeconds>24*3600&&l.push(`~/.recall/terminals.json is ${Math.round(t.ageSeconds/3600)}h old \u2014 pipeline has not produced fresh data recently. Likely root cause is the same as the rejection counter would show; start the daemon and re-run.`),d?.circuitBroken&&l.push(`Auto-extract circuit breaker tripped \u2014 ${d.reason??"reason unknown"}. Run \`recall semantic auto-extract off\` then \`... on\` to reset, or restart the daemon.`),s.total>=3&&s.fractionHeuristic>=nd&&l.push(`${s.heuristicOnly}/${s.total} sessions in the last ${yo}h (${Math.round(s.fractionHeuristic*100)}%) fell back to the heuristic first-message title. A healthy pipeline rate is < 20%. Either the extension is not syncing tab names, or the correlator cannot disambiguate. Reinstall the extension and verify the rejection counter drops to 0.`),{state:n?l.length>0?"degraded":"ok":"down",flags:l,daemon:{running:n,port:e,uptimeSeconds:r,version:o},runtime:{silentTerminalRejections:i,lastTerminalSyncAt:a,autoExtract:d},terminalsJson:t,recentSessions:s}}function We(e){return e<1024?`${e} B`:e<1024**2?`${(e/1024).toFixed(1)} KB`:e<1024**3?`${(e/1024**2).toFixed(1)} MB`:`${(e/1024**3).toFixed(2)} GB`}function Vl(e){try{return So(e).size}catch{return 0}}function Zl(e){try{return _().prepare(`SELECT COUNT(*) AS n FROM ${e}_data WHERE block = 1`).get().n}catch{return 0}}function py(){try{return _().prepare(`SELECT p.name AS project,
|
|
1491
1491
|
COALESCE(NULLIF(sa.alias, ''), s.auto_title, substr(s.first_user_message, 1, 60)) AS label,
|
|
1492
1492
|
COUNT(*) AS count,
|
|
1493
1493
|
MAX(CASE WHEN sa.alias IS NOT NULL AND sa.alias != '' THEN 1 ELSE 0 END) AS any_aliased
|
|
@@ -1499,27 +1499,27 @@ Top ${d.length} cluster(s):`);for(let l of d){let u=l.status==="resolved"?"\u271
|
|
|
1499
1499
|
GROUP BY p.name, label
|
|
1500
1500
|
HAVING count >= 2
|
|
1501
1501
|
ORDER BY count DESC, label ASC
|
|
1502
|
-
LIMIT 10`).all().map(e=>{let t=e;return{project:t.project,label:t.label,count:t.count,anyAliased:t.any_aliased===1}})}catch{return[]}}function
|
|
1502
|
+
LIMIT 10`).all().map(e=>{let t=e;return{project:t.project,label:t.label,count:t.count,anyAliased:t.any_aliased===1}})}catch{return[]}}function my(e){let t=_(),s=t.pragma("page_size",{simple:!0})||4096,n=t.pragma("page_count",{simple:!0})||0,r=t.pragma("freelist_count",{simple:!0})||0;e?.("Checking database integrity");let o="ok";try{let A=t.pragma("quick_check").map($=>$.quick_check);o=A.length===1&&A[0]==="ok"?"ok":A.join("; ")}catch(D){o=`check failed: ${D.message}`}let i=Vl(se),a=Vl(`${se}-wal`),d=0,l=0;try{let D=ny(x);d=Number(D.bavail)*Number(D.bsize),l=Number(D.blocks)*Number(D.bsize)}catch{}e?.("Counting rows");let u=t.prepare(`SELECT
|
|
1503
1503
|
(SELECT COUNT(*) FROM projects) AS projects,
|
|
1504
1504
|
(SELECT COUNT(*) FROM sessions) AS sessions,
|
|
1505
1505
|
(SELECT COUNT(*) FROM messages) AS messages,
|
|
1506
|
-
(SELECT COUNT(*) FROM message_usage) AS message_usage`).get(),p=0;try{p=t.prepare("SELECT COUNT(*) AS n FROM vec_chunks").get().n}catch{}e?.("Measuring FTS fragmentation");let m=[];d>0&&d<1*1024**3&&m.push(`Disk free is ${We(d)} \u2014 heavy operations (synthesis, extract-outputs, vector ingest) may fail.`);let g=100*1024**2,f=a>=
|
|
1506
|
+
(SELECT COUNT(*) FROM message_usage) AS message_usage`).get(),p=0;try{p=t.prepare("SELECT COUNT(*) AS n FROM vec_chunks").get().n}catch{}e?.("Measuring FTS fragmentation");let m=[];d>0&&d<1*1024**3&&m.push(`Disk free is ${We(d)} \u2014 heavy operations (synthesis, extract-outputs, vector ingest) may fail.`);let g=100*1024**2,f=a>=ca?"error":a>=g?"warn":"ok";f==="error"?m.push(`WAL is ${We(a)} \u2014 readers are pinning the checkpoint frontier. Run \`recall mcp-prune\` to release stuck MCP children, then \`recall optimize\` to truncate.`):f==="warn"&&m.push(`WAL is ${We(a)} \u2014 run \`recall optimize\` to truncate it.`);let h=Qt(),E=h.filter(D=>D.orphan);E.length>0&&m.push(`${E.length} orphaned MCP child${E.length===1?"":"ren"} (pid${E.length===1?"":"s"}: ${E.map(D=>D.pid).join(", ")}). Each holds a SQLite read connection. Run \`recall mcp-prune\` to clean up.`);let b=h.filter(vs);b.length>0&&m.push(`${b.length} MCP child${b.length===1?"":"ren"} burning CPU (pid${b.length===1?"":"s"}: ${b.map(D=>D.pid).join(", ")}). Likely runaway vec0 kNN. Run \`recall stop --all\` or \`recall mcp-prune --all\`.`),r>n*.2&&n>1e3&&m.push(`${r.toLocaleString()} free pages (${(r/n*100).toFixed(0)}% of file) \u2014 \`recall optimize --vacuum\` will reclaim them.`);let S=Zl("messages_fts"),R=Zl("sessions_fts");S>16&&m.push(`messages_fts has ${S} segments \u2014 \`recall optimize\` will merge them.`);let T=0;try{T=t.prepare("SELECT COUNT(*) AS n FROM chunk_queue").get().n}catch{}let L=!1;try{L=t.prepare("SELECT value FROM app_settings WHERE key = 'semantic_enabled'").get()?.value==="1"}catch{}return!L&&T>0?m.push(`${T.toLocaleString()} rows in chunk_queue with semantic disabled \u2014 schema gate is stale; restart daemon (v0.67+) to migrate.`):T>1e5&&m.push(`chunk_queue has ${T.toLocaleString()} pending rows \u2014 embedder is behind. \`recall semantic backfill\` to drain manually.`),{db:{sizeBytes:i,walSizeBytes:a,pageCount:n,pageSize:s,freelistCount:r,freelistBytes:r*s,integrity:o},disk:{freeBytes:d,totalBytes:l},fts:{messages:{fragments:S},sessions:{fragments:R}},vectors:{rows:p},rows:{projects:u.projects,sessions:u.sessions,messages:u.messages,messageUsage:u.message_usage},chunkQueue:{size:T,semanticEnabled:L},wal:{sizeBytes:a,level:f},mcpProcesses:h,warnings:m}}function gy(e){if(!e)return{stage:()=>{},done:()=>{}};let t=!!process.stderr.isTTY,s="",n=0,r=()=>{if(!s)return;let o=Date.now()-n,i=o<1e3?`${o}ms`:`${(o/1e3).toFixed(1)}s`;t?process.stderr.write(`\r\x1B[2K ${c.ok("\u2713")} ${s} ${c.dim(`(${i})`)}
|
|
1507
1507
|
`):process.stderr.write(` \u2713 ${s} (${i})
|
|
1508
1508
|
`),s=""};return{stage(o){r(),s=o,n=Date.now(),t?process.stderr.write(` ${c.dim("\u2026")} ${s}`):process.stderr.write(` \u2026 ${s}
|
|
1509
|
-
`)},done:r}}function
|
|
1509
|
+
`)},done:r}}function fy(e){if(console.log(c.dim("\u2014 MCP processes \u2014")),e.length===0){console.log(c.ok(" \u2713 no MCP children running"));return}let t=e.filter(o=>o.orphan),s=e.reduce((o,i)=>Math.max(o,i.etimeSeconds),0),n=e.length-t.length;if(console.log(` ${e.length} total, ${n} with live parent, ${t.length===0?c.ok("0 orphaned"):c.err(`${t.length} orphaned`)} (oldest ${It(s)})`),t.length>0){console.log(c.dim(" Orphans hold a SQLite read connection and can stall WAL checkpoints. Run `recall mcp-prune` to kill them."));for(let o of t.slice(0,5))console.log(` ${c.dim("\u2022")} pid ${o.pid} age ${It(o.etimeSeconds)} ppid=${o.ppid} (gone)`)}let r=e.filter(vs);if(r.length>0){console.log(c.warn(` ! ${r.length} MCP child${r.length===1?"":"ren"} burning CPU (likely runaway vec0 kNN -- see 2026-05-15 incident).`));for(let o of r){let i=o.parentCommand?`parent ${o.parentCommand}`:`ppid ${o.ppid}`;console.log(c.dim(` pid ${o.pid} ${o.pcpu.toFixed(0)}%cpu ${It(o.etimeSeconds)} elapsed (${i})`))}console.log(c.dim(" Run `recall stop --all` for one-command recovery, or `recall mcp-prune --all` to keep the daemon alive."))}}function _y(){let e=_(),t=e.prepare("SELECT encoded_path FROM projects").all(),s=new Set(t.map(l=>l.encoded_path));if(s.size===0)return{status:"ok",staleCount:0,scanned:0,sampleFiles:[],message:"Ingest freshness: ok (no projects indexed yet \u2014 nothing to monitor)."};let n=e.prepare("SELECT file_mtime FROM sessions WHERE file_path = ? LIMIT 1"),r=0,o=0,i=[];for(let l of bo(Lt)){if(Kl(l))continue;let u=ql(l);if(!u||!s.has(u))continue;r+=1;let p;try{p=So(l).mtimeMs}catch{continue}let m=n.get(l);(!m||m.file_mtime<p)&&(o+=1,i.length<5&&i.push(l))}if(o===0)return{status:"ok",staleCount:0,scanned:r,sampleFiles:[],message:`Ingest freshness: ok (scanned ${r} JSONL${r===1?"":"s"} in ${s.size} known project${s.size===1?"":"s"}, none newer than the index).`};let a=o>10?"fail":"warn",d=i.slice(0,3).map(l=>l.split(/[/\\]/).pop()??l).join(", ");return{status:a,staleCount:o,scanned:r,sampleFiles:i,message:`Ingest freshness: ${a} \u2014 ${o} JSONL${o===1?"":" files"} newer than the index (sample: ${d}). Run \`recall index\` to force a reindex, or restart the daemon if this is persistent.`}}async function rd(e={}){let t=gy(!e.json);t.stage("Scanning session aliases");let s=_().prepare(`SELECT sa.session_id AS session_id, sa.alias AS alias, s.cwd AS cwd
|
|
1510
1510
|
FROM session_aliases sa
|
|
1511
1511
|
LEFT JOIN sessions s ON s.id = sa.session_id
|
|
1512
|
-
WHERE sa.alias IS NOT NULL AND sa.alias != ''`).all(),n=
|
|
1513
|
-
`);let u=o.state==="degraded",p=a.status==="fail";return n.length===0&&r.db.integrity==="ok"&&!u&&!p?0:1}console.log(c.dim("\u2014 System health \u2014")),console.log(` Database ${We(r.db.sizeBytes)} (${r.rows.messages.toLocaleString()} messages across ${r.rows.sessions.toLocaleString()} sessions, ${r.rows.projects.toLocaleString()} projects)`);{let u=r.wal,p=u.level==="error"?c.err(We(u.sizeBytes)):u.level==="warn"?c.warn(We(u.sizeBytes)):c.ok(We(u.sizeBytes)),m=u.level==="error"?" (readers are pinning the checkpoint frontier \u2014 see warnings below)":u.level==="warn"?" (above 100 MB \u2014 daemon will WARN until it drops)":" (daemon checkpoints PASSIVE every 60s, RESTART above 5 GB)";console.log(` WAL ${p}${m}`)}console.log(` Free pages ${r.db.freelistCount.toLocaleString()} (${We(r.db.freelistBytes)} reclaimable via VACUUM)`);{let u=r.fts.messages.fragments,p=r.fts.sessions.fragments,m=u===0&&p===0;console.log(` FTS segments messages=${u}, sessions=${p}`+(m?" (merged \u2014 search uses the index)":" (lower is faster \u2014 `recall optimize` merges them)"))}if(r.chunkQueue.size>0){let p=r.chunkQueue.size>1e5?c.warn(r.chunkQueue.size.toLocaleString()):r.chunkQueue.size.toLocaleString();console.log(` Embed queue ${p}`+(r.chunkQueue.semanticEnabled?" (worker is enabled; should drain over time)":" (semantic disabled \u2014 should be 0; if not, schema migration may be stale)"))}if(console.log(` Vector rows ${r.vectors.rows.toLocaleString()}`),r.disk.totalBytes>0){let u=r.disk.freeBytes/r.disk.totalBytes*100;console.log(` Disk free ${We(r.disk.freeBytes)} of ${We(r.disk.totalBytes)} (${u.toFixed(1)}%)`)}if(console.log(` Integrity ${r.db.integrity==="ok"?c.ok("ok"):c.err(r.db.integrity)}`),r.warnings.length>0){console.log("");for(let u of r.warnings)console.log(` ${c.warn("!")} ${u}`)}if(console.log(""),
|
|
1512
|
+
WHERE sa.alias IS NOT NULL AND sa.alias != ''`).all(),n=iy(s),r=my(t.stage);t.stage("Probing daemon");let o=await uy();t.stage("Detecting label collisions");let i=py();t.stage("Checking ingest freshness");let a=_y();if(t.done(),e.json){process.stdout.write(JSON.stringify({scanned:s.length,violations:n.length,items:n,health:r,pipeline:o,labelCollisions:i,ingestFreshness:a},null,2)),process.stdout.write(`
|
|
1513
|
+
`);let u=o.state==="degraded",p=a.status==="fail";return n.length===0&&r.db.integrity==="ok"&&!u&&!p?0:1}console.log(c.dim("\u2014 System health \u2014")),console.log(` Database ${We(r.db.sizeBytes)} (${r.rows.messages.toLocaleString()} messages across ${r.rows.sessions.toLocaleString()} sessions, ${r.rows.projects.toLocaleString()} projects)`);{let u=r.wal,p=u.level==="error"?c.err(We(u.sizeBytes)):u.level==="warn"?c.warn(We(u.sizeBytes)):c.ok(We(u.sizeBytes)),m=u.level==="error"?" (readers are pinning the checkpoint frontier \u2014 see warnings below)":u.level==="warn"?" (above 100 MB \u2014 daemon will WARN until it drops)":" (daemon checkpoints PASSIVE every 60s, RESTART above 5 GB)";console.log(` WAL ${p}${m}`)}console.log(` Free pages ${r.db.freelistCount.toLocaleString()} (${We(r.db.freelistBytes)} reclaimable via VACUUM)`);{let u=r.fts.messages.fragments,p=r.fts.sessions.fragments,m=u===0&&p===0;console.log(` FTS segments messages=${u}, sessions=${p}`+(m?" (merged \u2014 search uses the index)":" (lower is faster \u2014 `recall optimize` merges them)"))}if(r.chunkQueue.size>0){let p=r.chunkQueue.size>1e5?c.warn(r.chunkQueue.size.toLocaleString()):r.chunkQueue.size.toLocaleString();console.log(` Embed queue ${p}`+(r.chunkQueue.semanticEnabled?" (worker is enabled; should drain over time)":" (semantic disabled \u2014 should be 0; if not, schema migration may be stale)"))}if(console.log(` Vector rows ${r.vectors.rows.toLocaleString()}`),r.disk.totalBytes>0){let u=r.disk.freeBytes/r.disk.totalBytes*100;console.log(` Disk free ${We(r.disk.freeBytes)} of ${We(r.disk.totalBytes)} (${u.toFixed(1)}%)`)}if(console.log(` Integrity ${r.db.integrity==="ok"?c.ok("ok"):c.err(r.db.integrity)}`),r.warnings.length>0){console.log("");for(let u of r.warnings)console.log(` ${c.warn("!")} ${u}`)}if(console.log(""),fy(r.mcpProcesses),console.log(""),console.log(c.dim("\u2014 Ingest freshness \u2014")),a.status==="ok")console.log(c.ok(` \u2713 ${a.scanned.toLocaleString()} JSONL${a.scanned===1?"":"s"} scanned, none newer than the index`));else if(a.status==="warn"){console.log(` ${c.warn(`${a.staleCount} stale file${a.staleCount===1?"":"s"}`)} newer than the index \u2014 live watcher may be missing events`);for(let u of a.sampleFiles.slice(0,5))console.log(` ${c.dim("\u2022")} ${u}`);console.log(c.dim(" Run `recall index` to force a reindex, or restart the daemon if persistent."))}else{console.log(` ${c.err(`${a.staleCount} stale file${a.staleCount===1?"":"s"}`)} newer than the index \u2014 significant ingest gap`);for(let u of a.sampleFiles.slice(0,5))console.log(` ${c.dim("\u2022")} ${u}`);console.log(c.err(" Restart the daemon (recall stop && recall start) \u2014 the live watcher has fallen behind."))}if(console.log(""),console.log(c.dim("\u2014 Pipeline health (tab-name \u2192 session alias) \u2014")),o.daemon.running?console.log(` Daemon ${c.ok("running")} (port ${o.daemon.port}, version ${o.daemon.version??"?"}, up ${o.daemon.uptimeSeconds!==null?Math.round(o.daemon.uptimeSeconds/60)+" min":"?"})`):console.log(` Daemon ${c.warn("not reachable")}`),o.runtime.silentTerminalRejections!==null){let u=o.runtime.silentTerminalRejections;console.log(` Auth rejections ${u===0?c.ok("0"):c.err(u.toLocaleString())} (extension /api/terminal/* requests denied without a valid X-Recall-Token)`)}if(o.runtime.autoExtract){let u=o.runtime.autoExtract;u.circuitBroken?console.log(` Auto-extract ${c.err("circuit broken")} (${u.reason??"unknown"} \u2014 toggle off/on to reset)`):u.consecutiveZeroTokenRuns>0&&console.log(` Auto-extract ${c.warn(`${u.consecutiveZeroTokenRuns} zero-token run(s)`)} (breaker trips at 3)`)}if(o.runtime.lastTerminalSyncAt!==null){let u=Date.now()-Date.parse(o.runtime.lastTerminalSyncAt),p=Number.isFinite(u)?Math.round(u/6e4):null,m=p!==null&&u>sd;console.log(` Last ext sync ${m?c.warn(`${p} min ago`):c.ok(p===0?"just now":`${p} min ago`)} (most recent successful POST /api/terminal/sync)`)}else o.daemon.running&&console.log(` Last ext sync ${c.warn("never")} (no extension has called /api/terminal/sync since the daemon started)`);if(o.terminalsJson.exists&&o.terminalsJson.ageSeconds!==null){let u=Math.round(o.terminalsJson.ageSeconds/3600),p=o.terminalsJson.ageSeconds>24*3600;console.log(` terminals.json ${p?c.warn(`${u}h old`):c.ok(`${u}h old`)} (persisted registry mtime \u2014 fresh means extensions are connecting)`)}let d=o.recentSessions;if(d.total>0){let u=Math.round(d.fractionHeuristic*100),p=d.fractionHeuristic>=nd&&d.total>=3;console.log(` Recent titles ${p?c.err(`${u}% heuristic`):c.ok(`${u}% heuristic`)} (${d.heuristicOnly}/${d.total} sessions in last ${yo}h fell back to first-message title)`)}if(o.flags.length>0){console.log("");for(let u of o.flags)console.log(` ${c.warn("!")} ${u}`)}if(console.log(""),console.log(c.dim("\u2014 Label collisions (last 7d) \u2014")),i.length===0)console.log(c.ok(" \u2713 no session-list label collisions in the last week"));else{let u=i.filter(p=>!p.anyAliased);console.log(` ${c.warn(`${i.length} group(s)`)} of 2+ sessions in the same project share an identical display label \u2014 N parallel runs ended up with the same heuristic title.`);for(let p of i.slice(0,5)){let m=p.anyAliased?c.dim("partial alias"):c.warn("NO alias"),g=p.label.length>60?`${p.label.slice(0,57)}\u2026`:p.label;console.log(` ${c.dim(`${p.count}\xD7`)} ${g} ${m} ${c.dim(`(${p.project})`)}`)}i.length>5&&console.log(c.dim(` \u2026 and ${i.length-5} more (--json for full list)`)),console.log(""),console.log(c.dim(" The display layer auto-disambiguates these (HH:MM / msgs / UUID suffix), so customers\n never see two identical rows. To fix at the source \u2014 when spawning `claude -p` from\n a script or Captain-Code subordinate, prepend each prompt with:")),console.log(c.dim(" <!-- claude-recall-alias: T2.1 Run-Prospects -->")),console.log(c.dim(` The watcher will read the header, alias the session, and strip the marker from the
|
|
1514
1514
|
displayed first user message. See docs/HANDOFF.md \u2192 "Alias header convention".`)),u.length>0&&(console.log(""),console.log(c.warn(` ${u.length} of these groups have NO alias on any row \u2014 strongest candidates
|
|
1515
|
-
for the header-alias fix above.`)))}if(console.log(""),console.log(c.dim("\u2014 Tab-name invariant \u2014")),n.length===0)return console.log(c.ok(` \u2713 holds across ${s.length.toLocaleString()} aliased session${s.length===1?"":"s"}`)),console.log(c.dim(" No fabricated origin labels (`VS Code \xB7 cwd \xB7 branch`) found. No deprecated cwd-branch synthesis found.")),r.db.integrity==="ok"&&a.status!=="fail"?0:1;console.log(c.err(`\u2717 ${n.length} invariant violation${n.length===1?"":"s"} found across ${s.length.toLocaleString()} aliased sessions`)),console.log("");let l=new Map;for(let u of n){let p=l.get(u.violation)??[];p.push(u),l.set(u.violation,p)}for(let[u,p]of l){console.log(c.warn(` ${u} (${p.length})`));for(let m of p.slice(0,10))console.log(` ${m.session_id.slice(0,8)} ${c.dim("\u2192")} ${JSON.stringify(m.alias)}`);p.length>10&&console.log(c.dim(` \u2026 and ${p.length-10} more (rerun with --json for the full list)`)),console.log("")}return console.log(c.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}v();w();P();import{existsSync as
|
|
1515
|
+
for the header-alias fix above.`)))}if(console.log(""),console.log(c.dim("\u2014 Tab-name invariant \u2014")),n.length===0)return console.log(c.ok(` \u2713 holds across ${s.length.toLocaleString()} aliased session${s.length===1?"":"s"}`)),console.log(c.dim(" No fabricated origin labels (`VS Code \xB7 cwd \xB7 branch`) found. No deprecated cwd-branch synthesis found.")),r.db.integrity==="ok"&&a.status!=="fail"?0:1;console.log(c.err(`\u2717 ${n.length} invariant violation${n.length===1?"":"s"} found across ${s.length.toLocaleString()} aliased sessions`)),console.log("");let l=new Map;for(let u of n){let p=l.get(u.violation)??[];p.push(u),l.set(u.violation,p)}for(let[u,p]of l){console.log(c.warn(` ${u} (${p.length})`));for(let m of p.slice(0,10))console.log(` ${m.session_id.slice(0,8)} ${c.dim("\u2192")} ${JSON.stringify(m.alias)}`);p.length>10&&console.log(c.dim(` \u2026 and ${p.length-10} more (rerun with --json for the full list)`)),console.log("")}return console.log(c.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}v();w();P();import{existsSync as hy,readFileSync as Ey}from"node:fs";import{join as by}from"node:path";function Sy(){let e=by(x,"daemon.pid");if(!hy(e))return!1;try{let t=parseInt(Ey(e,"utf-8").trim(),10);return!Number.isFinite(t)||t<=0?!1:(process.kill(t,0),!0)}catch{return!1}}async function cs(e,t){let s=Date.now();try{return t(),{step:e,ok:!0,durationMs:Date.now()-s}}catch(n){return{step:e,ok:!1,durationMs:Date.now()-s,error:n.message}}}async function od(e={}){let t=_(),s=[];if(e.vacuum&&Sy())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)+`
|
|
1516
1516
|
`),2):(console.error(c.err("\u2717 VACUUM requires the daemon to be stopped. Run `recall stop` first, then re-run with --vacuum.")),2);s.push(await cs("wal_checkpoint(TRUNCATE)",()=>{t.pragma("wal_checkpoint(TRUNCATE)")})),s.push(await cs("messages_fts optimize",()=>{t.exec("INSERT INTO messages_fts(messages_fts) VALUES('optimize');")})),s.push(await cs("sessions_fts optimize",()=>{t.exec("INSERT INTO sessions_fts(sessions_fts) VALUES('optimize');")})),s.push(await cs("PRAGMA optimize",()=>{t.exec("PRAGMA optimize")})),e.vacuum&&s.push(await cs("VACUUM",()=>{t.exec("VACUUM")}));let n=s.filter(r=>!r.ok);if(e.json)return process.stdout.write(JSON.stringify({ok:n.length===0,steps:s,vacuum:!!e.vacuum},null,2)+`
|
|
1517
|
-
`),n.length===0?0:1;for(let r of s){let o=r.ok?c.ok("\u2713"):c.err("\u2717"),i=`${r.durationMs} ms`;console.log(` ${o} ${r.step.padEnd(28)} ${c.dim(i)}`),r.error&&console.log(` ${c.err(r.error)}`)}return n.length===0?(console.log(""),console.log(c.ok("All maintenance passes completed.")),e.vacuum||console.log(c.dim(" Run `recall optimize --vacuum` (with the daemon stopped) to reclaim disk pages from deleted rows.")),0):(console.log(""),console.log(c.warn(`${n.length} step(s) failed \u2014 review the errors above.`)),1)}v();w();P();import{copyFileSync as
|
|
1518
|
-
`),2):(console.error(c.err(`\u2717 ${m}`)),2)}let t=
|
|
1517
|
+
`),n.length===0?0:1;for(let r of s){let o=r.ok?c.ok("\u2713"):c.err("\u2717"),i=`${r.durationMs} ms`;console.log(` ${o} ${r.step.padEnd(28)} ${c.dim(i)}`),r.error&&console.log(` ${c.err(r.error)}`)}return n.length===0?(console.log(""),console.log(c.ok("All maintenance passes completed.")),e.vacuum||console.log(c.dim(" Run `recall optimize --vacuum` (with the daemon stopped) to reclaim disk pages from deleted rows.")),0):(console.log(""),console.log(c.warn(`${n.length} step(s) failed \u2014 review the errors above.`)),1)}v();w();P();import{copyFileSync as yy,existsSync as wy,readFileSync as Ty}from"node:fs";import{join as id}from"node:path";function Ry(){let e=id(x,"daemon.pid");if(!wy(e))return!1;try{let t=parseInt(Ty(e,"utf-8").trim(),10);return!Number.isFinite(t)||t<=0?!1:(process.kill(t,0),!0)}catch{return!1}}function xy(){let e=[];for(let t of Nt){let r=t.source.replace(/^\^/,"").replace(/\\d\+/g,"").match(/^([^.*+?{[(\\]+)/)?.[1]??"";r.length>=8&&e.push(r.replace(/'/g,"''"))}return e.length===0?"0":e.map(t=>`first_user_message LIKE '${t}%'`).join(" OR ")}async function ad(e={}){if(Ry()&&!e.dryRun){let m="Daemon is running. Run `recall stop`, then `recall purge-phantoms`, then `recall start`. The purge needs an exclusive write lock and the daemon would race it.";return e.json?(process.stdout.write(JSON.stringify({ok:!1,reason:"daemon-running",message:m},null,2)+`
|
|
1518
|
+
`),2):(console.error(c.err(`\u2717 ${m}`)),2)}let t=xy();if(t==="0"){let m="No phantom patterns are registered \u2014 nothing to purge.";return e.json?(process.stdout.write(JSON.stringify({ok:!0,reason:"no-patterns",message:m})+`
|
|
1519
1519
|
`),0):(console.log(c.warn(m)),0)}let s=_(),n=s.prepare(`SELECT COUNT(*) AS n FROM sessions WHERE ${t}`).get().n;if(n===0){let m="No phantom sessions found. Your DB is clean.";return e.json?(process.stdout.write(JSON.stringify({ok:!0,sessionsBefore:0,sessionsAfter:0,messagesDeleted:0,aliasesDeleted:0,patternsApplied:Nt.length,vacuumHint:!1,dryRun:!!e.dryRun},null,2)+`
|
|
1520
1520
|
`),0):(console.log(c.ok(`\u2713 ${m}`)),0)}let r=s.prepare(`SELECT COUNT(*) AS n FROM messages WHERE session_id IN (SELECT id FROM sessions WHERE ${t})`).get().n,o=s.prepare(`SELECT COUNT(*) AS n FROM session_aliases WHERE session_id IN (SELECT id FROM sessions WHERE ${t})`).get().n;if(e.dryRun){let m={ok:!0,sessionsBefore:n,sessionsAfter:n,messagesDeleted:r,aliasesDeleted:o,patternsApplied:Nt.length,vacuumHint:!1,dryRun:!0};return e.json?(process.stdout.write(JSON.stringify(m,null,2)+`
|
|
1521
|
-
`),0):(console.log(c.warn("\u2014 dry run, no changes \u2014")),console.log(` Phantoms found: ${c.dim(String(n))} sessions`),console.log(` Messages cascaded: ${c.dim(String(r))}`),console.log(` Aliases cascaded: ${c.dim(String(o))}`),console.log(c.dim(" Re-run without --dry-run to actually delete.")),0)}let i=
|
|
1522
|
-
`),p.ok?0:1):(console.log(c.ok(`\u2713 Purged ${n} phantom session(s).`)),console.log(` Cascaded ${r} message(s) and ${o} alias row(s).`),console.log(` Backup: ${c.dim(d)}`),console.log(` Patterns applied: ${c.dim(String(Nt.length))}`),console.log(""),console.log(u===0?c.ok("All known phantom patterns are clear."):c.warn(`${u} phantom row(s) remain after purge \u2014 investigate.`)),p.vacuumHint&&(console.log(""),console.log(c.dim(" Reclaim the disk pages with `recall optimize --vacuum` (daemon must stay stopped)."))),console.log(c.dim(" Restart with `recall start` when ready.")),p.ok?0:1)}v();w();P();w();import{existsSync as
|
|
1521
|
+
`),0):(console.log(c.warn("\u2014 dry run, no changes \u2014")),console.log(` Phantoms found: ${c.dim(String(n))} sessions`),console.log(` Messages cascaded: ${c.dim(String(r))}`),console.log(` Aliases cascaded: ${c.dim(String(o))}`),console.log(c.dim(" Re-run without --dry-run to actually delete.")),0)}let i=id(x,"db.sqlite"),a=new Date().toISOString().replace(/[-:T]/g,"").replace(/\..+$/,"").slice(0,15),d=`${i}.pre-phantom-purge-${a}`;yy(i,d),s.pragma("foreign_keys = ON"),s.transaction(()=>{s.exec(`DELETE FROM sessions WHERE ${t}`)})();let u=s.prepare(`SELECT COUNT(*) AS n FROM sessions WHERE ${t}`).get().n,p={ok:u===0,backupPath:d,sessionsBefore:n,sessionsAfter:u,messagesDeleted:r,aliasesDeleted:o,patternsApplied:Nt.length,vacuumHint:n>100,dryRun:!1};return e.json?(process.stdout.write(JSON.stringify(p,null,2)+`
|
|
1522
|
+
`),p.ok?0:1):(console.log(c.ok(`\u2713 Purged ${n} phantom session(s).`)),console.log(` Cascaded ${r} message(s) and ${o} alias row(s).`),console.log(` Backup: ${c.dim(d)}`),console.log(` Patterns applied: ${c.dim(String(Nt.length))}`),console.log(""),console.log(u===0?c.ok("All known phantom patterns are clear."):c.warn(`${u} phantom row(s) remain after purge \u2014 investigate.`)),p.vacuumHint&&(console.log(""),console.log(c.dim(" Reclaim the disk pages with `recall optimize --vacuum` (daemon must stay stopped)."))),console.log(c.dim(" Restart with `recall start` when ready.")),p.ok?0:1)}v();w();P();w();import{existsSync as ky}from"node:fs";import{join as Cy}from"node:path";var un=Cy(x,"archive.sqlite");var cd=!1;function ld(){if(cd&&ky(un))return;F();let e=_(),t=un.replace(/'/g,"''");e.exec(`ATTACH DATABASE '${t}' AS archive`);try{e.exec(`
|
|
1523
1523
|
CREATE TABLE IF NOT EXISTS archive.messages_archive (
|
|
1524
1524
|
uuid TEXT PRIMARY KEY,
|
|
1525
1525
|
session_id TEXT NOT NULL,
|
|
@@ -1534,7 +1534,7 @@ Top ${d.length} cluster(s):`);for(let l of d){let u=l.status==="resolved"?"\u271
|
|
|
1534
1534
|
archived_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1535
1535
|
);
|
|
1536
1536
|
CREATE INDEX IF NOT EXISTS archive.idx_messages_archive_session ON messages_archive(session_id);
|
|
1537
|
-
`)}finally{e.exec("DETACH DATABASE archive")}
|
|
1537
|
+
`)}finally{e.exec("DETACH DATABASE archive")}cd=!0}import{existsSync as dd,mkdirSync as Ly,readFileSync as Ny,writeFileSync as Ay}from"node:fs";import{homedir as Oy}from"node:os";import{join as ud}from"node:path";import{z as pn}from"zod";function pd(){return process.env.RECALL_HOME??ud(Oy(),".recall")}function vy(){let e=pd();dd(e)||Ly(e,{recursive:!0})}function md(){return ud(pd(),"config.json")}var gd=pn.object({autoArchiveEnabled:pn.boolean().default(!1),autoArchiveAfterDays:pn.number().int().min(7).max(3650).default(90),lastRunAt:pn.string().nullable().default(null)}),mn={autoArchiveEnabled:!1,autoArchiveAfterDays:90,lastRunAt:null};function fd(){let e=md();if(!dd(e))return{};try{return JSON.parse(Ny(e,"utf8"))}catch(t){let s=t instanceof Error?t.message:String(t);return console.error(`[retention-config] failed to parse config.json: ${s}`),{}}}function _d(){let e=fd().retention;if(!e)return{...mn};let t=gd.safeParse({...mn,...e});return t.success?t.data:{...mn}}function wo(e){vy();let t=fd(),s=gd.parse({...mn,...t.retention??{},...e}),n={...t,retention:s};return Ay(md(),JSON.stringify(n,null,2)),s}function To(e){ld();let t=_(),s=un.replace(/'/g,"''");t.exec(`ATTACH DATABASE '${s}' AS archive`);try{return e(t)}finally{t.exec("DETACH DATABASE archive")}}function Iy(){return To(e=>{let t=e.prepare(`SELECT
|
|
1538
1538
|
SUM(CASE WHEN archive_status = 'archived' THEN 1 ELSE 0 END) AS archived,
|
|
1539
1539
|
SUM(CASE WHEN archive_status != 'archived' THEN 1 ELSE 0 END) AS live
|
|
1540
1540
|
FROM sessions`).get(),s=e.prepare("SELECT COUNT(*) AS n FROM messages").get().n,n=e.prepare(`SELECT
|
|
@@ -1543,15 +1543,15 @@ Top ${d.length} cluster(s):`);for(let l of d){let u=l.status==="resolved"?"\u271
|
|
|
1543
1543
|
SELECT MAX(timestamp) AS t FROM main.messages_archive WHERE timestamp IS NOT NULL
|
|
1544
1544
|
UNION ALL
|
|
1545
1545
|
SELECT MAX(timestamp) AS t FROM archive.messages_archive WHERE timestamp IS NOT NULL
|
|
1546
|
-
)`).get();return{liveSessions:t.live??0,archivedSessions:t.archived??0,liveMessages:s,archivedMessages:n,oldestLiveTimestamp:r.t,newestArchivedTimestamp:o.t}})}function
|
|
1546
|
+
)`).get();return{liveSessions:t.live??0,archivedSessions:t.archived??0,liveMessages:s,archivedMessages:n,oldestLiveTimestamp:r.t,newestArchivedTimestamp:o.t}})}function hd(e){return e?e.slice(0,10):"\u2014"}function My(){let e=Iy();return console.log(c.dim("\u2014 Archive state \u2014")),console.log(` Live sessions ${e.liveSessions.toLocaleString()}`),console.log(` Archived sessions ${e.archivedSessions.toLocaleString()}`),console.log(` Live messages ${e.liveMessages.toLocaleString()}`),console.log(` Archived messages ${e.archivedMessages.toLocaleString()}`),console.log(` Oldest live ${hd(e.oldestLiveTimestamp)}`),console.log(` Newest archived ${hd(e.newestArchivedTimestamp)}`),console.log(""),console.log(c.dim(" recall archive run --before YYYY-MM-DD")),console.log(c.dim(" recall archive restore <session-id>")),0}function Dy(e){if(!/^\d{4}-\d{2}-\d{2}$/.test(e.before))return console.error("--before must be YYYY-MM-DD"),1;let s=_().prepare(`SELECT s.id, s.ended_at, s.message_count
|
|
1547
1547
|
FROM sessions s
|
|
1548
1548
|
WHERE s.archive_status != 'archived'
|
|
1549
|
-
AND COALESCE(s.ended_at, s.started_at) < ?`).all(`${e.before}T00:00:00.000Z`);if(s.length===0)return console.log(`No sessions to archive (none older than ${e.before}).`),0;let n=s.reduce((a,d)=>a+(d.message_count??0),0);if(console.log(`${s.length.toLocaleString()} session(s), ${n.toLocaleString()} message(s) eligible.`),e.dryRun)return console.log(c.dim("Dry run \u2014 no rows moved. Re-run without --dry-run to apply.")),0;let r=s.map(a=>a.id),o=Date.now(),i=
|
|
1549
|
+
AND COALESCE(s.ended_at, s.started_at) < ?`).all(`${e.before}T00:00:00.000Z`);if(s.length===0)return console.log(`No sessions to archive (none older than ${e.before}).`),0;let n=s.reduce((a,d)=>a+(d.message_count??0),0);if(console.log(`${s.length.toLocaleString()} session(s), ${n.toLocaleString()} message(s) eligible.`),e.dryRun)return console.log(c.dim("Dry run \u2014 no rows moved. Re-run without --dry-run to apply.")),0;let r=s.map(a=>a.id),o=Date.now(),i=To(a=>a.transaction(l=>{let u=0,p=a.prepare(`INSERT OR IGNORE INTO archive.messages_archive
|
|
1550
1550
|
(uuid, session_id, parent_uuid, type, role, timestamp,
|
|
1551
1551
|
is_sidechain, content_text, tool_names, raw_json, archived_at)
|
|
1552
1552
|
SELECT uuid, session_id, parent_uuid, type, role, timestamp,
|
|
1553
1553
|
is_sidechain, content_text, tool_names, raw_json, datetime('now')
|
|
1554
|
-
FROM messages WHERE session_id = ?`),m=a.prepare("DELETE FROM messages WHERE session_id = ?"),g=a.prepare("UPDATE sessions SET archive_status = 'archived', archived_at = datetime('now') WHERE id = ?");for(let f of l){p.run(f);let h=m.run(f);u+=Number(h.changes??0),g.run(f)}return u})(r));return console.log(`Archived ${s.length.toLocaleString()} session(s), moved ${i.toLocaleString()} message(s) in ${Date.now()-o}ms.`),console.log(c.dim(" Run `recall optimize --vacuum` (with the daemon stopped) to reclaim disk pages from the moved rows.")),0}function
|
|
1554
|
+
FROM messages WHERE session_id = ?`),m=a.prepare("DELETE FROM messages WHERE session_id = ?"),g=a.prepare("UPDATE sessions SET archive_status = 'archived', archived_at = datetime('now') WHERE id = ?");for(let f of l){p.run(f);let h=m.run(f);u+=Number(h.changes??0),g.run(f)}return u})(r));return console.log(`Archived ${s.length.toLocaleString()} session(s), moved ${i.toLocaleString()} message(s) in ${Date.now()-o}ms.`),console.log(c.dim(" Run `recall optimize --vacuum` (with the daemon stopped) to reclaim disk pages from the moved rows.")),0}function $y(e){if(!e)return console.error("Usage: recall archive restore <session-id>"),1;let s=_().prepare("SELECT id, archive_status FROM sessions WHERE id = ?").get(e);if(!s)return console.error(`Session ${e} not found.`),1;if(s.archive_status!=="archived")return console.error(`Session ${e} is not archived (status=${s.archive_status}).`),1;let n=To(r=>r.transaction(()=>{let i=r.prepare(`INSERT OR IGNORE INTO messages
|
|
1555
1555
|
(uuid, session_id, parent_uuid, type, role, timestamp,
|
|
1556
1556
|
is_sidechain, content_text, tool_names, raw_json)
|
|
1557
1557
|
SELECT uuid, session_id, parent_uuid, type, role, timestamp,
|
|
@@ -1561,33 +1561,33 @@ Top ${d.length} cluster(s):`);for(let l of d){let u=l.status==="resolved"?"\u271
|
|
|
1561
1561
|
is_sidechain, content_text, tool_names, raw_json)
|
|
1562
1562
|
SELECT uuid, session_id, parent_uuid, type, role, timestamp,
|
|
1563
1563
|
is_sidechain, content_text, tool_names, raw_json
|
|
1564
|
-
FROM archive.messages_archive WHERE session_id = ?`),d=r.prepare("DELETE FROM main.messages_archive WHERE session_id = ?"),l=r.prepare("DELETE FROM archive.messages_archive WHERE session_id = ?"),u=Number(i.run(e).changes??0),p=Number(a.run(e).changes??0);return d.run(e),l.run(e),r.prepare("UPDATE sessions SET archive_status = 'live', archived_at = NULL WHERE id = ?").run(e),u+p})());return console.log(`Restored ${n.toLocaleString()} message(s) for session ${e}.`),0}function
|
|
1564
|
+
FROM archive.messages_archive WHERE session_id = ?`),d=r.prepare("DELETE FROM main.messages_archive WHERE session_id = ?"),l=r.prepare("DELETE FROM archive.messages_archive WHERE session_id = ?"),u=Number(i.run(e).changes??0),p=Number(a.run(e).changes??0);return d.run(e),l.run(e),r.prepare("UPDATE sessions SET archive_status = 'live', archived_at = NULL WHERE id = ?").run(e),u+p})());return console.log(`Restored ${n.toLocaleString()} message(s) for session ${e}.`),0}function Py(){let e=_d();return console.log(c.dim("\u2014 Auto-archive \u2014")),console.log(` Enabled ${e.autoArchiveEnabled?c.ok("YES"):"no"}`),console.log(` After ${e.autoArchiveAfterDays} days`),console.log(` Last run ${e.lastRunAt??"\u2014"}`),0}function Fy(e){if(e===void 0||!Number.isFinite(e))return console.error("Usage: recall archive auto on --after <days>"),1;let t=Math.floor(e);if(t<7||t>3650)return console.error("--after must be between 7 and 3650 days"),1;let s=wo({autoArchiveEnabled:!0,autoArchiveAfterDays:t});return console.log(`Auto-archive: ENABLED (after ${s.autoArchiveAfterDays} days).`),console.log(c.dim(" The daemon will run a daily archive pass on the next tick.")),0}function jy(){return wo({autoArchiveEnabled:!1}),console.log("Auto-archive: DISABLED. Existing archived sessions stay archived."),0}async function Ed(e){let t=e._action??"list";if(t==="list"||t==="stats")return My();if(t==="run")return e.before?Dy({before:e.before,dryRun:e.dryRun===!0}):(console.error("Usage: recall archive run --before YYYY-MM-DD [--dry-run]"),1);if(t==="restore")return $y(e._sessionId??"");if(t==="auto"){let s=e._subAction??"status";return s==="status"?Py():s==="on"||s==="enable"?Fy(e.after):s==="off"||s==="disable"?jy():(console.error("Usage: recall archive auto <status|on|off> [--after <days>]"),1)}return console.error(`Usage: recall archive <list|run|restore|auto> [args]
|
|
1565
1565
|
list \u2014 show archive counts
|
|
1566
1566
|
run --before YYYY-MM-DD [--dry-run] \u2014 move sessions older than DATE
|
|
1567
1567
|
restore <session-id> \u2014 pull a session back from archive
|
|
1568
|
-
auto <status|on|off> [--after N] \u2014 daemon auto-archives sessions older than N days`),1}v();w();P();import{existsSync as
|
|
1568
|
+
auto <status|on|off> [--after N] \u2014 daemon auto-archives sessions older than N days`),1}v();w();P();import{existsSync as Uy,readFileSync as By}from"node:fs";function Hy(){let e=`${x}/daemon.port`;if(!Uy(e))return null;try{let t=By(e,"utf8").trim();return/^\d+$/.test(t)?t:null}catch{return null}}function Wy(e){let t=_();if(e.length>=32){let n=t.prepare("SELECT id FROM sessions WHERE id = ?").get(e);return n?n.id:(process.stderr.write(`session not found: ${e}
|
|
1569
1569
|
`),null)}let s=t.prepare("SELECT id FROM sessions WHERE id LIKE ? LIMIT 2").all(e+"%");return s.length===1?s[0].id:s.length===0?(process.stderr.write(`no session matches prefix "${e}"
|
|
1570
1570
|
`),null):(process.stderr.write(`ambiguous session prefix "${e}". be more specific.
|
|
1571
|
-
`),null)}async function
|
|
1572
|
-
`),process.exitCode=1;return}let o=
|
|
1571
|
+
`),null)}async function bd(e,t,s){let n=Wy(e);if(!n){process.exitCode=1;return}let r=t.trim();if(!r){process.stderr.write(`name cannot be empty
|
|
1572
|
+
`),process.exitCode=1;return}let o=Hy();if(!o){process.stderr.write("daemon is not running. start it with `recall start` so the alias write is durable.\n"),process.exitCode=1;return}let i;try{i=await st("PUT",`http://127.0.0.1:${o}/api/sessions/${encodeURIComponent(n)}/alias`,{alias:r,pin:s.pin===!0})}catch(d){process.stderr.write(`failed to reach daemon at 127.0.0.1:${o}: ${d.message}
|
|
1573
1573
|
`),process.exitCode=1;return}if(!i.ok){let d="";try{d=await i.text()}catch{}process.stderr.write(`daemon rejected alias write (HTTP ${i.status}): ${d}
|
|
1574
|
-
`),process.exitCode=1;return}let a={session_id:n,alias:r};if(s.json){console.log(JSON.stringify(a));return}console.log(`${c.ok("renamed")} ${c.dim(n.slice(0,8))} \u2192 ${c.bold(`"${r}"`)}`)}v();w();var
|
|
1574
|
+
`),process.exitCode=1;return}let a={session_id:n,alias:r};if(s.json){console.log(JSON.stringify(a));return}console.log(`${c.ok("renamed")} ${c.dim(n.slice(0,8))} \u2192 ${c.bold(`"${r}"`)}`)}v();w();var wd=90;async function Xy(){try{let e=`${process.env.HOME}/.recall/daemon.port`,t=await import("node:fs");if(!t.existsSync(e))return null;let s=t.readFileSync(e,"utf8").trim(),n=await ze(`http://127.0.0.1:${s}/api/terminal/registry`);return n.ok?await n.json():null}catch{return null}}function Jy(){let e=Date.now()/1e3-wd;return _().prepare(`SELECT s.id, s.cwd, s.file_mtime, NULLIF(sa.alias, '') AS alias
|
|
1575
1575
|
FROM sessions s
|
|
1576
1576
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1577
1577
|
WHERE s.cwd IS NOT NULL AND s.file_mtime > ?
|
|
1578
|
-
ORDER BY s.cwd, s.file_mtime DESC`).all(e)}var
|
|
1578
|
+
ORDER BY s.cwd, s.file_mtime DESC`).all(e)}var Gy=new Set(["zsh","bash","fish","sh","dash","ksh","tcsh","csh","pwsh","powershell","cmd","nu"]);function Sd(e){let t=e.trim().toLowerCase().replace(/^[-/]+/,"").replace(/^.*\//,"").replace(/\s*\(\d+\)\s*$/,"").trim();return!t||Gy.has(t)}var Yy=/^[⠀-⣿✳\s]+/,zy=/^\d+(\.\d+){1,3}$/;function yd(e){let t=e.trim();return!!(!t||Yy.test(t)||zy.test(t))}async function Td(e){let t=await Xy(),s=Jy();if(e.json){console.log(JSON.stringify({registry:t,active:s},null,2));return}if(console.log(),console.log(c.bold("TERMINAL REGISTRY")),console.log(c.dim("(what the daemon thinks each VS Code terminal is named)")),console.log(),!t){console.log(c.err(" Could not reach daemon. Is recall start running?"));return}let n=new Map;for(let o of t.terminals){let i=o.cwd??"(no cwd)",a=n.get(i);a||(a=[],n.set(i,a)),a.push(o)}for(let[o,i]of n){console.log(c.dim(` cwd: ${o}`));for(let a of i){let d=Sd(a.tab_name),l=yd(a.tab_name),p=!d&&!l?c.ok("[usable]"):d?c.warn("[generic-shell]"):c.warn("[claude-auto]");console.log(` ${p} pid ${a.shell_pid} ${c.bold(`"${a.tab_name}"`)}`)}console.log()}if(console.log(c.bold(`ACTIVE SESSIONS (mtime within last ${wd}s)`)),console.log(c.dim("(sessions whose JSONL is being actively written)")),console.log(),s.length===0){console.log(c.dim(" (none)")),console.log();return}let r=new Map;for(let o of s){let i=r.get(o.cwd);i||(i=[],r.set(o.cwd,i)),i.push(o)}for(let[o,i]of r){console.log(c.dim(` cwd: ${o}`));for(let a of i)console.log(` ${c.dim(a.id.slice(0,8))} ${c.bold(a.alias??"(no alias)")} ${c.dim(`mtime ${new Date(a.file_mtime*1e3).toISOString().slice(11,19)}`)}`);console.log()}console.log(c.bold("LAYER 5 SWEEP DECISIONS")),console.log(c.dim("(what the live correlator would do for each cwd right now)")),console.log();for(let[o,i]of r){let d=(n.get(o)??[]).filter(l=>!Sd(l.tab_name)&&!yd(l.tab_name));d.length===0?console.log(` ${c.warn("skip")} ${c.dim(o)} ${c.dim("\u2014 no usable terminal name (all generic or claude-auto)")}`):d.length>1?console.log(` ${c.warn("refuse")} ${c.dim(o)} ${c.dim(`\u2014 ${d.length} usable terminals, ambiguous`)}`):i.length>1?console.log(` ${c.warn("refuse")} ${c.dim(o)} ${c.dim(`\u2014 ${i.length} active sessions, ambiguous`)}`):console.log(` ${c.ok("link")} ${c.dim(o)} ${c.dim("\u2192")} ${c.dim(i[0].id.slice(0,8))} ${c.dim("=")} ${c.bold(`"${d[0].tab_name}"`)}`)}console.log()}v();w();P();import{existsSync as qy,readFileSync as Ky}from"node:fs";function Vy(){let e=`${x}/daemon.port`;if(!qy(e))return null;try{let t=Ky(e,"utf8").trim();return/^\d+$/.test(t)?t:null}catch{return null}}async function Zy(e){try{let t=await ze(`http://127.0.0.1:${e}/api/terminal/registry`);return t.ok?(await t.json()).terminals??[]:[]}catch{return[]}}async function Qy(e,t,s){try{return(await st("POST",`http://127.0.0.1:${e}/api/sessions/${encodeURIComponent(t)}/relink`,{shell_pid:s})).ok}catch{return!1}}function ew(e){try{let t=JSON.parse(e);if(!Array.isArray(t))return null;for(let s=t.length-1;s>=0;s--){let n=t[s];if(n&&typeof n.alias=="string"&&n.alias!=="")return n.alias}return null}catch{return null}}function tw(e){let t=new Map;for(let i of e){if(!i.cwd)continue;let a=i.cwd.replace(/\/+$/,""),d=t.get(a);d||(d=new Map,t.set(a,d)),d.set(i.tab_name,i.shell_pid)}let s=new Map;for(let i of e){if(!i.cwd)continue;let d=`${i.cwd.replace(/\/+$/,"")}::${i.tab_name}`,l=s.get(d);l||(l=new Set,s.set(d,l)),l.add(String(i.shell_pid))}let r=_().prepare(`SELECT sa.session_id, s.cwd, sa.previous_aliases
|
|
1579
1579
|
FROM session_aliases sa
|
|
1580
1580
|
JOIN sessions s ON s.id = sa.session_id
|
|
1581
|
-
WHERE sa.alias = '' AND sa.previous_aliases != '[]' AND s.cwd IS NOT NULL`).all(),o=[];for(let i of r){let a=
|
|
1581
|
+
WHERE sa.alias = '' AND sa.previous_aliases != '[]' AND s.cwd IS NOT NULL`).all(),o=[];for(let i of r){let a=ew(i.previous_aliases);if(!a)continue;let d=i.cwd.replace(/\/+$/,""),u=t.get(d)?.get(a);if(!u)continue;let p=s.get(`${d}::${a}`);p&&p.size>1||o.push({session_id:i.session_id,cwd:d,alias_to_restore:a,matching_pid:u})}return o}async function Rd(e){let t=Vy();t||(console.error(c.err("Daemon is not running. Run `recall start` first.")),process.exit(1));let s=await Zy(t);s.length===0&&(console.error(c.err("Registry is empty. Open VS Code with the extension to populate it.")),process.exit(1));let n=tw(s);if(e.json){console.log(JSON.stringify({candidates:n},null,2));return}if(n.length===0){console.log(c.dim("No restorable sessions found.")),console.log(c.dim("A session is restorable when its previous alias still matches a currently-open terminal in the same cwd, with no name collisions."));return}console.log(c.bold(`Found ${n.length} session${n.length===1?"":"s"} that can be cleanly restored:`)),console.log();let r=new Map;for(let a of n){let d=r.get(a.cwd);d||(d=[],r.set(a.cwd,d)),d.push(a)}for(let[a,d]of r){console.log(c.dim(` cwd: ${a}`));for(let l of d)console.log(` ${c.dim(l.session_id.slice(0,8))} ${c.dim("\u2192 pid")} ${l.matching_pid} ${c.dim("=")} ${c.bold(`"${l.alias_to_restore}"`)}`);console.log()}if(!e.apply){console.log(c.dim("Re-run with --apply to restore these aliases."));return}let o=0,i=0;for(let a of n)await Qy(t,a.session_id,a.matching_pid)?o++:(i++,console.error(c.err(`failed to restore ${a.session_id.slice(0,8)}`)));console.log(c.ok(`Restored ${o} alias${o===1?"":"es"}.${i>0?` ${i} failed.`:""}`)),console.log(c.dim("Sessions whose previous alias did not cleanly match a current terminal still need manual relink via the \u{1F517} picker."))}v();async function xd(e,t){let s=e.trim();if(!/^[0-9a-fA-F]{4,40}$/.test(s)){console.error(c.err(`not a valid commit SHA: '${e}'`)),process.exit(1);return}let n=dn(s);if(t.json){console.log(JSON.stringify(n,null,2));return}if(n.length===0){console.log(""),console.log(c.dim(`no correlated sessions for ${s}`)),console.log(c.dim(" (if you haven't yet, run `recall correlate` to backfill)")),console.log("");return}console.log(""),console.log(`${c.bold("commit")} ${c.accent(n[0].commitSha.slice(0,12))} ${c.dim(n[0].committedAt??"")}`),n[0].subject&&console.log(` ${n[0].subject}`),console.log(""),console.log(c.dim(`authored during ${n.length} session${n.length===1?"":"s"}:`));for(let r of n){let o=r.alias??r.sessionId.slice(0,8);console.log(` ${c.accent(o.padEnd(24))} ${c.dim((r.project??"").slice(0,24).padEnd(26))} ${c.dim(r.startedAt??"")}`),console.log(` ${c.dim(`recall show ${r.sessionId}`)}`)}console.log("")}v();w();import{execFile as sw}from"node:child_process";import{promisify as nw}from"node:util";import{stat as rw}from"node:fs/promises";var ow=nw(sw),iw=60,aw=7,cw=7,lw=5e3;function dw(){let e=_(),t=s=>{try{return!!e.prepare(s).get()}catch{return!1}};return{semantic:t("SELECT 1 FROM session_semantic LIMIT 1"),cost:t(`SELECT 1 FROM sessions
|
|
1582
1582
|
WHERE (COALESCE(total_input_tokens,0)
|
|
1583
1583
|
+ COALESCE(total_output_tokens,0)
|
|
1584
1584
|
+ COALESCE(total_cache_create_tokens,0)
|
|
1585
1585
|
+ COALESCE(total_cache_read_tokens,0)) > 0
|
|
1586
|
-
LIMIT 1`),git:t("SELECT 1 FROM session_commits LIMIT 1")}}function
|
|
1586
|
+
LIMIT 1`),git:t("SELECT 1 FROM session_commits LIMIT 1")}}function Ro(e){if(!e)return[];let t=new Set,s=[];for(let n of e.split(",")){let r=n.trim().toLowerCase();!r||t.has(r)||(t.add(r),s.push(r))}return s}function kd(e){return{sessionId:e.session_id,project:e.project,alias:e.alias,startedAt:e.started_at,endedAt:e.ended_at,firstUserMessage:e.first_user_message}}function uw(){let e=_(),t=e.prepare(`SELECT ss.keywords
|
|
1587
1587
|
FROM session_semantic ss
|
|
1588
1588
|
JOIN sessions s ON s.id = ss.session_id
|
|
1589
1589
|
WHERE s.started_at IS NOT NULL
|
|
1590
|
-
AND julianday('now') - julianday(s.started_at) <= @windowDays`).all({windowDays:
|
|
1590
|
+
AND julianday('now') - julianday(s.started_at) <= @windowDays`).all({windowDays:aw});if(t.length===0)return null;let s=new Set;for(let o of t)for(let i of Ro(o.keywords))s.add(i);if(s.size===0)return null;let n=e.prepare(`SELECT ss.session_id AS session_id,
|
|
1591
1591
|
ss.summary AS summary,
|
|
1592
1592
|
ss.keywords AS keywords,
|
|
1593
1593
|
p.name AS project,
|
|
@@ -1603,7 +1603,7 @@ Top ${d.length} cluster(s):`);for(let l of d){let u=l.status==="resolved"?"\u271
|
|
|
1603
1603
|
WHERE s.started_at IS NOT NULL
|
|
1604
1604
|
AND s.message_count > 2
|
|
1605
1605
|
AND julianday('now') - julianday(s.started_at) >= @ageDays
|
|
1606
|
-
ORDER BY s.started_at ASC`).all({ageDays:
|
|
1606
|
+
ORDER BY s.started_at ASC`).all({ageDays:iw});if(n.length===0)return null;let r=null;for(let o of n){let a=Ro(o.keywords).filter(d=>s.has(d));a.length!==0&&(!r||a.length>r.overlap.length)&&(r={row:o,overlap:a})}return r?{...kd(r.row),summary:r.row.summary,keywords:Ro(r.row.keywords),matchedKeywords:r.overlap,daysAgo:Math.max(0,Math.round(r.row.days_old))}:null}function pw(){let t=_().prepare(`SELECT s.id AS session_id,
|
|
1607
1607
|
p.name AS project,
|
|
1608
1608
|
NULLIF(sa.alias, '') AS alias,
|
|
1609
1609
|
s.started_at AS started_at,
|
|
@@ -1622,54 +1622,54 @@ Top ${d.length} cluster(s):`);for(let l of d){let u=l.status==="resolved"?"\u271
|
|
|
1622
1622
|
AND (COALESCE(s.total_input_tokens, 0)
|
|
1623
1623
|
+ COALESCE(s.total_output_tokens, 0)
|
|
1624
1624
|
+ COALESCE(s.total_cache_create_tokens, 0)
|
|
1625
|
-
+ COALESCE(s.total_cache_read_tokens, 0)) > 0`).all({windowDays:
|
|
1625
|
+
+ COALESCE(s.total_cache_read_tokens, 0)) > 0`).all({windowDays:cw});if(t.length===0)return null;let s=null;for(let n of t){let r=Ie({inputTokens:n.input_tokens,outputTokens:n.output_tokens,cacheCreateTokens:n.cache_create_tokens,cacheReadTokens:n.cache_read_tokens},n.primary_model);r.cents<=0||(!s||r.cents>s.cents)&&(s={row:n,cents:r.cents,totalTokens:r.totalTokens})}return s?{...kd(s.row),totalTokens:s.totalTokens,costCents:s.cents,costDisplay:re(s.cents),tokensDisplay:ue(s.totalTokens),primaryModel:s.row.primary_model,primaryModelLabel:it(s.row.primary_model).label}:null}async function mw(e){try{if(!(await rw(e)).isDirectory())return null}catch{return null}try{let{stdout:t}=await ow("git",["rev-parse","HEAD"],{cwd:e,timeout:lw}),s=t.trim();return/^[0-9a-f]{40}$/.test(s)?s:null}catch{return null}}async function gw(){let e=_(),t=e.prepare(`SELECT s.id AS id, s.cwd AS cwd
|
|
1626
1626
|
FROM sessions s
|
|
1627
1627
|
WHERE s.cwd IS NOT NULL AND s.started_at IS NOT NULL
|
|
1628
1628
|
ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
|
|
1629
|
-
LIMIT 1`).get();if(!t?.cwd)return null;let s=await
|
|
1629
|
+
LIMIT 1`).get();if(!t?.cwd)return null;let s=await mw(t.cwd);if(!s)return null;let n=dn(s);if(n.length===0)return null;let r=n[0],o=e.prepare(`SELECT s.first_user_message AS first_user_message, s.ended_at AS ended_at
|
|
1630
1630
|
FROM sessions s
|
|
1631
|
-
WHERE s.id = ?`).get(r.sessionId);return{sessionId:r.sessionId,project:r.project,alias:r.alias,startedAt:r.startedAt,endedAt:o?.ended_at??r.endedAt,firstUserMessage:o?.first_user_message??null,commitSha:r.commitSha,shortSha:r.commitSha.slice(0,7),subject:r.subject,committedAt:r.committedAt,cwd:t.cwd}}async function
|
|
1631
|
+
WHERE s.id = ?`).get(r.sessionId);return{sessionId:r.sessionId,project:r.project,alias:r.alias,startedAt:r.startedAt,endedAt:o?.ended_at??r.endedAt,firstUserMessage:o?.first_user_message??null,commitSha:r.commitSha,shortSha:r.commitSha.slice(0,7),subject:r.subject,committedAt:r.committedAt,cwd:t.cwd}}async function Cd(){let e=dw(),t=e.semantic?Promise.resolve().then(()=>{try{return uw()}catch(a){return console.error("[discover.rediscovered]",a),null}}):Promise.resolve(null),s=e.cost?Promise.resolve().then(()=>{try{return pw()}catch(a){return console.error("[discover.expensive]",a),null}}):Promise.resolve(null),n=e.git?gw().catch(a=>(console.error("[discover.authored]",a),null)):Promise.resolve(null),[r,o,i]=await Promise.all([t,s,n]);return{rediscovered:r,expensive:o,authored:i,availability:e,generatedAt:new Date().toISOString()}}function ko(e,t=60){if(!e)return"";let s=e.replace(/\s+/g," ").trim();return s.length<=t?s:s.slice(0,t-1)+"\u2026"}function xo(e){return e.alias??(e.firstUserMessage?ko(e.firstUserMessage,60):null)??e.sessionId.slice(0,8)}function fw(e){let t=e.rediscovered||e.expensive||e.authored;if(console.log(""),console.log(c.bold("today \xB7 for you")),console.log(c.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),!t){let s=[];e.availability.semantic||s.push("v0.11 semantic (`recall semantic on`)"),e.availability.cost||s.push("v0.10a cost (`recall stats --backfill`)"),e.availability.git||s.push("v0.10b git (`recall correlate`)"),console.log(c.dim(" no picks today.")),s.length>0?console.log(c.dim(" enable: "+s.join(", "))):console.log(c.dim(" data sources are live but nothing matched \u2014 try again tomorrow.")),console.log("");return}if(e.rediscovered){let s=e.rediscovered;console.log(""),console.log(` ${c.tool("\u2726 rediscovered")} ${c.dim(`(${s.daysAgo}d ago)`)}`),console.log(` ${c.bold(xo(s))} ${c.dim(s.project?`\xB7 ${s.project}`:"")}`),console.log(` ${c.dim(s.sessionId.slice(0,8))}`),s.matchedKeywords.length>0&&console.log(" "+c.dim("matched: ")+s.matchedKeywords.slice(0,6).map(n=>c.tool(`#${n}`)).join(" ")),s.summary&&console.log(" "+c.dim(ko(s.summary,100)))}if(e.expensive){let s=e.expensive;console.log(""),console.log(` ${c.accent("$ most expensive \xB7 7d")} ${c.bold(s.costDisplay)} ${c.dim(`(${s.tokensDisplay} \xB7 ${s.primaryModelLabel})`)}`),console.log(` ${c.bold(xo(s))} ${c.dim(s.project?`\xB7 ${s.project}`:"")}`),console.log(` ${c.dim(s.sessionId.slice(0,8))} ${c.dim(s.startedAt?J(s.startedAt):"")}`)}if(e.authored){let s=e.authored;console.log(""),console.log(` ${c.ok("\u2387 authored current HEAD")} ${c.bold(s.shortSha)} ${c.dim(s.committedAt?`(${J(s.committedAt)})`:"")}`),console.log(` ${c.bold(xo(s))} ${c.dim(s.project?`\xB7 ${s.project}`:"")}`),s.subject&&console.log(` ${c.dim(ko(s.subject,80))}`),console.log(` ${c.dim(s.sessionId.slice(0,8))} ${c.dim("cwd: "+s.cwd)}`)}console.log("")}async function Ld(e){let t=await Cd();if(e.json){console.log(JSON.stringify(t,null,2));return}fw(t)}v();Ke();import{spawnSync as vd}from"node:child_process";import{readdirSync as _w}from"node:fs";import{join as hw,resolve as Ew}from"node:path";var Co=["code","cursor","code-insiders","windsurf"],Nd=[{id:"code",label:"VS Code"},{id:"cursor",label:"Cursor"},{id:"code-insiders",label:"VS Code Insiders"},{id:"windsurf",label:"Windsurf"}],bw="https://marketplace.visualstudio.com/items?itemName=clauderecallhq.clauderecall-vscode";function Sw(){return hw(ee(),"extensions","vscode")}function yw(){let e=Sw(),t;try{t=_w(e)}catch{return null}let s=t.filter(n=>/^(clauderecall|claude-recall)-vscode-.*\.vsix$/i.test(n));return s.length===0?null:(s.sort((n,r)=>r.localeCompare(n,void 0,{numeric:!0})),Ew(e,s[0]))}function Ad(e){let t=vd(e,["--version"],{stdio:"ignore",shell:process.platform==="win32"});return t.error?!1:t.status===0}function ww(e,t){let s=vd(e,["--install-extension",t],{stdio:"pipe",encoding:"utf8",shell:process.platform==="win32"});return s.error?{ok:!1,message:s.error.message}:s.status!==0?{ok:!1,message:(s.stderr??"").trim()||`exit code ${s.status??"null"}`}:{ok:!0,message:""}}function Od(){process.stderr.write(c.err(`no bundled .vsix found at extensions/vscode/(clauderecall|claude-recall)-vscode-*.vsix
|
|
1632
1632
|
`)),process.stderr.write(c.dim(`see PUBLISHING.md for how to build the extension, or run:
|
|
1633
1633
|
`)),process.stderr.write(c.dim(` (cd extensions/vscode && npm install && npm run build && npm run package)
|
|
1634
|
-
`))}function
|
|
1635
|
-
`)),process.stderr.write(c.dim(`valid values: ${
|
|
1636
|
-
`)),process.exitCode=1;return}let t=
|
|
1637
|
-
`);return}if(!t){
|
|
1634
|
+
`))}function Tw(e){return Co.includes(e)}async function Id(e){if(e.editor!==void 0&&!Tw(e.editor)){process.stderr.write(c.err(`unknown --editor target: ${e.editor}
|
|
1635
|
+
`)),process.stderr.write(c.dim(`valid values: ${Co.join(", ")}
|
|
1636
|
+
`)),process.exitCode=1;return}let t=yw();if(e.printPath){if(!t){Od(),process.exitCode=1;return}process.stdout.write(t+`
|
|
1637
|
+
`);return}if(!t){Od(),process.exitCode=1;return}let s;if(e.editor){let r=e.editor;if(!Ad(r)){process.stderr.write(c.err(`editor CLI not found on PATH: ${r}
|
|
1638
1638
|
`)),process.stderr.write(c.dim(`install the editor shell integration, or omit --editor to install into every detected editor.
|
|
1639
1639
|
`)),process.stderr.write(c.dim(`see PUBLISHING.md for manual install instructions.
|
|
1640
|
-
`)),process.exitCode=1;return}s=[
|
|
1641
|
-
`)),process.stderr.write(c.dim(`looked for: ${
|
|
1640
|
+
`)),process.exitCode=1;return}s=[Nd.find(i=>i.id===r)??{id:r,label:r}]}else s=Nd.filter(r=>Ad(r.id));if(s.length===0){process.stderr.write(c.err(`no supported editor CLI detected on PATH.
|
|
1641
|
+
`)),process.stderr.write(c.dim(`looked for: ${Co.join(", ")}.
|
|
1642
1642
|
`)),process.stderr.write(c.dim(`install the editor shell integration and try again.
|
|
1643
1643
|
`)),process.stderr.write(c.dim(`see PUBLISHING.md for manual install instructions.
|
|
1644
1644
|
`)),process.exitCode=1;return}process.stderr.write(c.dim(`bundled .vsix: ${t}
|
|
1645
1645
|
|
|
1646
|
-
`));let n=!1;for(let r of s){let{ok:o,message:i}=
|
|
1646
|
+
`));let n=!1;for(let r of s){let{ok:o,message:i}=ww(r.id,t);o?process.stderr.write(c.ok(`\u2713 installed into ${r.label} (${r.id})
|
|
1647
1647
|
`)):(n=!0,process.stderr.write(c.err(`\u2717 ${r.label} (${r.id}): ${i}
|
|
1648
1648
|
`)))}process.stderr.write(`
|
|
1649
1649
|
`),process.stderr.write(c.bold(`next steps:
|
|
1650
1650
|
`)),process.stderr.write(` 1. reload the editor window: Cmd/Ctrl+Shift+P then pick "Developer: Reload Window"
|
|
1651
1651
|
`),process.stderr.write(` 2. enable the extension: open settings and set "claude-recall.autoAlias" to true
|
|
1652
1652
|
`),process.stderr.write(`
|
|
1653
|
-
`),process.stderr.write(c.dim(`marketplace install: ${
|
|
1654
|
-
`)),n&&(process.exitCode=1)}
|
|
1653
|
+
`),process.stderr.write(c.dim(`marketplace install: ${bw}
|
|
1654
|
+
`)),n&&(process.exitCode=1)}Oo();v();function vo(e){return e>=70?c.ok:e>=40?c.warn:c.err}function Dd(e,t=20){let s=Math.round(e/100*t);return"\u2588".repeat(s)+"\u2591".repeat(t-s)}function Rw(e){let t=vo(e.score);process.stdout.write(`
|
|
1655
1655
|
${c.bold(e.projectName)} ${t(String(e.score)+"/100")}
|
|
1656
|
-
`);let s=e.breakdown,n=[["Sessions",s.sessionCount.score*100,`${s.sessionCount.raw} sessions`],["Recency",s.recency.score*100,`${s.recency.daysSinceLastSession}d ago`],["Depth",s.fragmentation.score*100,`avg ${s.fragmentation.avgMessages} msgs`],["Search",s.searchCoverage.score*100,`${Math.round(s.searchCoverage.ratio*100)}% covered`],["Tags",s.tagCoverage.score*100,`${Math.round(s.tagCoverage.ratio*100)}% tagged`]];for(let[r,o,i]of n){let a=
|
|
1657
|
-
`)}}function
|
|
1656
|
+
`);let s=e.breakdown,n=[["Sessions",s.sessionCount.score*100,`${s.sessionCount.raw} sessions`],["Recency",s.recency.score*100,`${s.recency.daysSinceLastSession}d ago`],["Depth",s.fragmentation.score*100,`avg ${s.fragmentation.avgMessages} msgs`],["Search",s.searchCoverage.score*100,`${Math.round(s.searchCoverage.ratio*100)}% covered`],["Tags",s.tagCoverage.score*100,`${Math.round(s.tagCoverage.ratio*100)}% tagged`]];for(let[r,o,i]of n){let a=vo(o);process.stdout.write(` ${r.padEnd(10)} ${a(Dd(o,16))} ${String(Math.round(o)).padStart(3)}% ${c.dim(i)}
|
|
1657
|
+
`)}}function $d(e,t){if(e){let r=No(e);if(!r){process.stderr.write(c.err(`project "${e}" not found
|
|
1658
1658
|
`)),process.exitCode=1;return}if(t.json){process.stdout.write(JSON.stringify(r,null,2)+`
|
|
1659
|
-
`);return}
|
|
1659
|
+
`);return}Rw(r);return}let s=Ao();if(s.length===0){process.stdout.write("No projects found. Run `recall index` first.\n");return}if(t.json){process.stdout.write(JSON.stringify(s,null,2)+`
|
|
1660
1660
|
`);return}let n=[...s].sort((r,o)=>r.score-o.score);process.stdout.write(c.bold("Memory Health Scores")+` (worst first)
|
|
1661
1661
|
|
|
1662
|
-
`);for(let r of n){let o=
|
|
1662
|
+
`);for(let r of n){let o=vo(r.score);process.stdout.write(` ${o(Dd(r.score,12))} ${String(r.score).padStart(3)}/100 ${r.projectName}
|
|
1663
1663
|
`)}process.stdout.write(`
|
|
1664
|
-
`)}v();function
|
|
1665
|
-
`);return}if(e==="off"){
|
|
1664
|
+
`)}v();function Pd(e){if(e==="on"){ar(!0),process.stdout.write(c.ok("Verification badges enabled.")+`
|
|
1665
|
+
`);return}if(e==="off"){ar(!1),process.stdout.write(`Verification badges disabled.
|
|
1666
1666
|
`);return}let t=ws();process.stdout.write(`Verification badges: ${t?c.ok("ON"):"OFF"}
|
|
1667
1667
|
`),process.stdout.write(`
|
|
1668
1668
|
Toggle with: recall verify on | off
|
|
1669
|
-
`)}v();Vt();xs();At();ks();import{hostname as
|
|
1669
|
+
`)}v();Vt();xs();At();ks();import{hostname as eT}from"node:os";import{randomBytes as tT}from"node:crypto";qe();de();P();w();Ke();import{existsSync as xw}from"node:fs";import kw from"node:readline";import{createRequire as Cw}from"node:module";import{Chalk as Lw}from"chalk";var Nw=Cw(import.meta.url),Aw=Nw(`${ee()}/package.json`).version,Bd="#f97316",Ow="#8b9098",vw="#10b981",Iw="#f59e0b",Ft=new Lw({level:process.env.NO_COLOR?0:3}),be=Ft.hex(Bd),oe=Ft.hex(Bd).bold,I=Ft.hex(Ow),wt=Ft.hex(vw),gn=Ft.hex(Iw),Hd=Ft.bold,Wd=[" \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557","\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D","\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 ","\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D ","\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557"," \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D","\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 ","\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 ","\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 ","\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 ","\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557","\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"],Mw=Wd[0]?.length??49,Fd="Never lose a Claude Code session again.",Dw="CLAUDE RECALL";function $w(){if(!xw(se))return{sessions:0,projects:0};try{let t=_().prepare(`SELECT
|
|
1670
1670
|
(SELECT COUNT(*) FROM sessions) AS sessions,
|
|
1671
|
-
(SELECT COUNT(*) FROM projects) AS projects`).get();return{sessions:t.sessions,projects:t.projects}}catch{return{sessions:0,projects:0}}}function kw(){let e=process.stdout.columns;return typeof e!="number"||e<=0?!1:e<Tw+2}function Id(){if(kw()){console.log(`${oe(Rw)} ${I("\xB7")} ${I(Cd)}`);return}for(let e of vd)console.log(oe(e));console.log(I(Cd))}async function Cw(){return{daemon:Q(),counts:xw(),license:await Te()}}function Lw(e){let t=[];if(t.push(` ${I("Version:")} ${be(`v${bw}`)} ${I("\xB7 CLI \xB7 @clauderecallhq/cli")}`),e.daemon){let s=`http://127.0.0.1:${e.daemon.port}`;t.push(` ${I("Daemon:")} ${wt("running")} on ${s} ${I(`(pid ${e.daemon.pid})`)}`)}else t.push(` ${I("Daemon:")} ${un("stopped")}`);if(e.counts.sessions===0)t.push(` ${I("Sessions:")} ${un("none indexed yet")}`);else{let s=e.counts.projects===1?"project":"projects";t.push(` ${I("Sessions:")} ${be(String(e.counts.sessions))} indexed across ${be(String(e.counts.projects))} ${s}`)}return e.license.tier==="pro"?t.push(` ${I("License:")} ${wt("Pro")}`):t.push(` ${I("License:")} Free`),t}function Nw(e){let t=be(">");if(e.counts.sessions===0)return`${t} Run ${oe("recall index")} to scan ~/.claude/projects/`;if(!e.daemon)return`${t} Run ${oe("recall start")} to launch the local daemon`;if(e.license.tier==="free")return`${t} Run ${oe("recall upgrade")} to unlock Pro features`;let s=`http://127.0.0.1:${e.daemon.port}`;return`${t} Open ${be(s)} in your browser ${I("\xB7")} or run ${oe("recall --help")} for all commands`}var pn=[{title:"Setup",commands:[{name:"index",description:"Scan your Claude sessions and build the searchable database"},{name:"status",description:"Database size, session counts, and daemon state"},{name:"projects",description:"List every project with how many sessions are in each"},{name:"install-extension",description:"Install the VS Code / Cursor / Windsurf extension"},{name:"doctor",description:"Health check: DB size, WAL, FTS fragmentation, integrity, disk space, alias invariant"},{name:"optimize",description:"Maintenance: WAL checkpoint, FTS5 merge, planner stats. Add --vacuum to reclaim free pages"}]},{title:"Browse",commands:[{name:"tui",description:"Browse and search sessions in an interactive terminal UI"},{name:"list",description:"List your sessions, newest first"},{name:"show <id>",description:"Print a full session transcript"},{name:"search <query>",description:"Full-text search across every message"},{name:"similar <id>",description:"Find sessions about the same topic (Pro)"},{name:"semantic [action]",description:'Manage semantic search (defaults to "status")'},{name:"name <id> <name>",description:"Rename a session (or its terminal tab)"}]},{title:"Pipe to Claude",commands:[{name:"context <id>",description:"Pipe a past session as markdown into a fresh `claude` chat"},{name:"neighborhood <id>",description:"Bundle a session with its parents, children, and citations for richer context"}]},{title:"Threads",commands:[{name:"threads sync",description:"Capture sessions running in this repo right now into a thread"},{name:"threads sync --preflight",description:"Preview what `threads sync` would do without writing"},{name:"threads scan",description:"Auto-detect parent-child links across past sessions"},{name:"threads list",description:"List threads, newest first"},{name:"threads show <id>",description:"Show a thread's header and session tree"},{name:"threads new <name>",description:"Create a new thread"},{name:"threads link <id>",description:"Link a session into a thread"},{name:"threads unlink <id>",description:"Remove a session from a thread"},{name:"threads set-parent <id>",description:"Change a session's parent within a thread"},{name:"threads rename <id>",description:"Rename a thread"},{name:"threads close <id>",description:"Mark a thread as closed"},{name:"threads reopen <id>",description:"Reopen a closed thread"},{name:"threads archive <id>",description:"Archive a thread"},{name:"threads merge <id>",description:"Merge one thread into another"},{name:"threads split <id>",description:"Split sessions out into a new thread"}]},{title:"Inference",commands:[{name:"infer outputs",description:"Extract code, file, and error references from sessions in one project"},{name:"infer citations",description:"Find sessions that cite the same files or errors"},{name:"infer l1",description:"Fast deterministic link inference. No LLM cost"},{name:"infer links",description:"LLM-powered link classification (Pro). Optional --auto-promote"},{name:"infer bug-patterns",description:"Cluster sessions that hit the same bug"},{name:"embeddings [action]",description:'Audit embedding coverage (defaults to "audit")'}]},{title:"Analytics",commands:[{name:"stats [id]",description:"Token + dollar usage. No args = overview, pass a session for details"},{name:"health [project]",description:"Score how well-organized each project's sessions are"},{name:"digest",description:"Today's rediscovery picks and standouts"},{name:"correlate [id]",description:"Link sessions to the git commits they wrote"},{name:"blame <sha>",description:"Find which session(s) wrote the code in a commit"}]},{title:"Daemon",commands:[{name:"start",description:"Start the background daemon (live tab tracking + web UI)"},{name:"stop",description:"Stop the background daemon"},{name:"open",description:"Open the local web UI in your browser"}]},{title:"Sharing",commands:[{name:"share [id]",description:"Save a session as a shareable PNG card"},{name:"wrapped [month]",description:"Save a monthly recap as a PNG card"}]},{title:"Diagnostics",commands:[{name:"correlator audit",description:"Find sessions with bad terminal-name aliases (--fix to clear)"},{name:"correlator debug",description:"Show how the daemon is matching open terminals to sessions"},{name:"correlator restore",description:"Restore aliases that `correlator audit --fix` cleared"},{name:"titles [action]",description:'Audit session titles in the current project (defaults to "audit")'},{name:"import-vscode-state",description:"Backfill missing tab names from your editor's workspace state"},{name:"audit-secrets",description:"Scan the database for leaked secrets"}]},{title:"Integrations",commands:[{name:"mcp",description:"Expose Recall as an MCP server (for Claude Desktop / Claude Code)"},{name:"paste",description:"Save clipboard content (or stdin) into Recall"}]},{title:"Pro & License",commands:[{name:"upgrade",description:"Open the Pro pricing page"},{name:"activate <key>",description:"Activate your Pro license (run once after purchase)"},{name:"license [action]",description:"Show the current license. Use `license deactivate` to remove it"},{name:"verify [action]",description:'Toggle verification badges in the UI (defaults to "status")'}]},{title:"Feedback",commands:[{name:"feedback",description:"Send a 1-5 rating to the team. Pass --score for non-interactive use"}]}],Aw=pn.reduce((e,t)=>e+t.commands.length,0),Ow=new Set([...pn.flatMap(e=>e.commands.map(t=>t.name.split(/\s+/)[0])),"thread","correlator-audit","correlator-debug","correlator-restore","extract-outputs","infer-citations","infer-l1","infer-links","infer-bug-patterns"]);function Ld(){let e=pn.flatMap(s=>s.commands.map(n=>`recall ${n.name}`)),t=Math.max(...e.map(s=>s.length));console.log(),console.log(`${Od("COMMANDS")} ${I(`\xB7 ${Aw} total \xB7 q to quit \xB7 recall --help for full details`)}`),console.log(` ${I("All commands run as subcommands of")} ${oe("recall")}${I(". There is no separate")} ${be("threads")}${I(",")} ${be("thread")}${I(", or")} ${be("titles")} ${I("binary.")}`);for(let s of pn){console.log(),console.log(` ${oe(s.title.toUpperCase())}`);for(let n of s.commands){let r=`recall ${n.name}`.padEnd(t," ");console.log(` ${be(r)} ${I(n.description)}`)}}console.log()}function vw(){return!!process.stdin.isTTY&&!!process.stdout.isTTY}async function Iw(){if(!vw())return;let e=fw.createInterface({input:process.stdin,output:process.stdout}),t=`${I("Type")} ${oe("/")} ${I("for commands, or press")} ${oe("Enter")} ${I("to exit")} ${be("\u203A")} `;return new Promise(s=>{let n=()=>{e.question(t,r=>{let o=r.trim();if(o==="/"||o==="/help"||o==="help"||o==="?"){Ld(),n();return}if(o===""||o==="q"||o==="quit"||o==="exit"){e.close();return}let i=o.startsWith("/")?o.slice(1).trimStart():o,a=i.toLowerCase();if((a==="recall"||a.startsWith("recall "))&&(i=i.slice(6).trimStart()),i===""){Ld(),n();return}let d=(i.split(/\s+/)[0]??"").toLowerCase();Ow.has(d)?console.log(` ${I("Run")} ${oe(`recall ${i}`)} ${I("in your shell to invoke that command.")}`):console.log(` ${I(`No command matches "${o}". Type`)} ${oe("/")} ${I("to browse, or")} ${oe("recall --help")} ${I("for the full list.")}`),n()})};e.on("close",()=>{console.log(),s()}),n()})}async function Md(){let e=await Cw();console.log(),Id(),console.log();for(let t of Lw(e))console.log(t);console.log(),console.log(Nw(e)),console.log(),await Iw()}var Mw=[{name:"Unlimited search",detail:"full history, no caps"},{name:"Context for Claude",detail:"recall past sessions on demand"},{name:"MCP integration",detail:"native tool access from any client"},{name:"Semantic search",detail:"find by meaning, not just keywords"},{name:"Advanced UI",detail:"tags, aliases, notes, exports"}];function Dw(){return process.env.NO_COLOR||process.env.CI||process.env.RECALL_NO_ANIMATION?!1:!!process.stdout.isTTY}function Co(e){return new Promise(t=>setTimeout(t,e))}function $w(e){let s=(e.split("@")[0]??"").split(/[._+\-]/)[0]??"";return s?s.charAt(0).toUpperCase()+s.slice(1).toLowerCase():""}function Nd(){process.stdout.write("\r\x1B[2K")}async function mn(e){let t=Dw();console.log(),Id(),console.log(),t&&(process.stdout.write(` ${I("Verifying license...")}`),await Co(260),Nd()),console.log(` ${wt("\u2713")} ${I("License verified")}`),console.log(` ${wt("\u2713")} ${I("Tier:")} ${oe("Pro")} ${I("\xB7 Lifetime")}`),console.log(` ${wt("\u2713")} ${I("Key:")} ${be(e.key_short)}`),console.log(` ${wt("\u2713")} ${I("Email:")} ${e.email}`),e.test_mode&&console.log(` ${un("!")} ${un("Test mode:")} this license was issued in test mode.`),console.log(),console.log(` ${Od("Unlocking Pro features")}`),console.log();for(let r of Mw)t&&(process.stdout.write(` ${I("\xB7")} ${I(r.name)}`),await Co(110),Nd()),console.log(` ${wt("\u2713")} ${r.name} ${I(r.detail)}`);t&&await Co(160),console.log();let s=$w(e.email),n=s?`Welcome aboard, ${s}.`:"Welcome aboard.";console.log(` ${oe(n)}`),console.log(` ${I("Run")} ${be("recall")} ${I("to open the dashboard, or")} ${be("recall help")} ${I("for the command list.")}`),console.log()}v();Vt();xs();At();ks();import{hostname as Pw}from"node:os";import{randomBytes as Fw}from"node:crypto";import{createInterface as jw}from"node:readline/promises";async function Uw(e){let t=e.fetchFn??fetch,s=`${e.apiBaseUrl}/api/trial/cli-redeem`,n=await t(s,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({promo_code:e.promoCode,email:e.email,machine_fingerprint:e.fingerprint,instance_name:e.instanceName,referral_source:"cli"})}),r;try{r=await n.json()}catch{throw new Error(`server returned non-JSON (status ${n.status})`)}return{status:n.status,body:r}}async function Bw(e={}){let t=jw({input:e.stdin??process.stdin,output:e.stdout??process.stdout});try{return(await t.question(c.bold("Email for your 7-day trial: "))).trim().toLowerCase()}finally{t.close()}}function Hw(e){if(e.length<5||e.length>256||!e.includes("@"))return!1;let[t,s]=e.split("@");return!(!t||!s||!s.includes(".")||/\s/.test(e))}async function gn(e,t={}){let s=e.trim().toUpperCase();if(s.length<3&&(console.error(c.warn(`Promo code "${e}" is too short.`)),process.exit(1)),!t.skipExistingProCheck){let{getLicenseStatus:u}=await Promise.resolve().then(()=>(de(),Zt)),p=await u();if(p.tier==="pro"){console.log(),console.log(c.bold("You are already on Pro \u2014 no need to redeem a trial.")),console.log(` ${c.dim("Key:")} ${p.key_short}`),console.log(` ${c.dim("Email:")} ${p.customer_email}`),console.log(),console.log(c.dim("If you wanted to switch accounts, run `recall license deactivate` first.")),console.log();return}}console.log(),console.log(`${c.ok("Redeeming")} promo code ${c.bold(s)} for a 7-day Pro trial.`),console.log(c.dim("You will get an email with your license key for backup.")),console.log(),!t.promptFn&&!process.stdin.isTTY&&(console.error(c.warn("Trial activation needs an interactive terminal so you can type your email.")),console.error(c.dim("Re-run `recall activate "+s+"` in a terminal (not piped, not CI).")),process.exit(1));let r=await(t.promptFn??Bw)();Hw(r)||(console.error(c.warn(`"${r}" doesn't look like an email address.`)),process.exit(1));let o=t.instanceName??`${Pw()}-${Fw(4).toString("hex")}`,i=t.fingerprint??vt(),a=t.apiBaseUrl??tt(),d;try{d=await Uw({promoCode:s,email:r,fingerprint:i,instanceName:o,apiBaseUrl:a,...t.fetchFn?{fetchFn:t.fetchFn}:{}})}catch(u){let p=u instanceof Error?u.message:"unknown error";console.error(c.warn(`Could not reach the trial server at ${a}: ${p}`)),console.error(c.dim("If you are offline, try again when you have a connection.")),process.exit(1)}if(d.status!==200||!d.body.license_jwt||!d.body.license_key){let u=d.body.error??`trial activation failed (status ${d.status})`;console.error(c.warn(`Trial refused: ${u}`)),process.exit(1)}let l=await Ot(d.body.license_jwt);(!l.valid||!l.claims)&&(console.error(c.warn(`Server returned a JWT that fails local verification: ${l.reason??"unknown"}`)),console.error(c.dim("This usually means the CLI is older than the server. Try `npm i -g @clauderecallhq/cli`.")),process.exit(1)),Ts({license_jwt:d.body.license_jwt,license_key:d.body.license_key,key_short:d.body.key_short??l.claims.key_short,customer_email:d.body.customer_email??l.claims.email,activated_at:new Date().toISOString(),tier:"pro",test_mode:!!l.claims.test_mode}),t.skipSuccessDashboard||await mn({...l.claims,key_short:d.body.key_short??l.claims.key_short,email:d.body.customer_email??l.claims.email})}function Jw(e){return/^recall-pro-/i.test(e)}async function Dd(e){let t=e.trim();if(t.length<3&&(console.error(c.warn("License key or promo code looks too short.")),console.error(c.dim("Paste the full key from your purchase email, or a promo code like RECALL7DAY.")),process.exit(1)),!Jw(t)){await gn(t);return}t.length<8&&(console.error(c.warn("License key looks too short. Paste the full key from your purchase email.")),process.exit(1));let s=`${Ww()}-${Xw(4).toString("hex")}`,n=`${tt()}/api/license/activate`,r;try{r=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({license_key:t,instance_name:s,machine_fingerprint:vt()})})}catch(a){let d=a instanceof Error?a.message:"unknown error";console.error(c.warn(`Could not reach activation server at ${n}: ${d}`)),console.error(c.dim("If you are offline, try again when you have a connection.")),process.exit(1)}let o;try{o=await r.json()}catch{console.error(c.warn(`Activation server returned invalid JSON (status ${r.status}).`)),process.exit(1)}if(r.status!==200||!o.license_jwt){let a=o.error??`activation failed (status ${r.status})`;console.error(c.warn(`Activation refused: ${a}`)),process.exit(1)}let i=await Ot(o.license_jwt);(!i.valid||!i.claims)&&(console.error(c.warn(`Server returned a JWT that fails local verification: ${i.reason??"unknown"}`)),console.error(c.dim("This usually means the CLI is older than the server, or the public key was rotated.")),process.exit(1)),Ts({license_jwt:o.license_jwt,license_key:t,key_short:o.key_short??i.claims.key_short,customer_email:o.customer_email??i.claims.email,activated_at:new Date().toISOString(),tier:"pro",test_mode:!!i.claims.test_mode}),await mn({...i.claims,key_short:o.key_short??i.claims.key_short,email:o.customer_email??i.claims.email})}v();import{spawn as Gw}from"node:child_process";import{platform as fn}from"node:os";var $d="https://clauderecall.com/pricing";function Yw(e){let t=fn()==="darwin"?"open":fn()==="win32"?"start":"xdg-open",s=fn()==="win32"?["",e]:[e];Gw(t,s,{detached:!0,stdio:"ignore",shell:fn()==="win32"}).unref()}async function Pd(){let{getLicenseStatus:e}=await Promise.resolve().then(()=>(de(),Zt)),t=await e();if(t.tier==="pro"){console.log(),console.log(c.bold("Already on Pro.")),console.log(` ${c.dim("Key:")} ${t.key_short}`),console.log(` ${c.dim("Email:")} ${t.customer_email}`),console.log();return}console.log(),console.log(`${c.ok("Opening")} ${$d}`),console.log(c.dim("After purchase, run: recall activate <license-key>")),console.log(),Yw($d)}v();import{spawn as zw}from"node:child_process";import{platform as _n}from"node:os";var Fd="https://clauderecall.com/pricing?trial=1&utm_source=cli&utm_medium=recall_trial";function qw(e){let t=_n()==="darwin"?"open":_n()==="win32"?"start":"xdg-open",s=_n()==="win32"?["",e]:[e];zw(t,s,{detached:!0,stdio:"ignore",shell:_n()==="win32"}).unref()}async function jd(e){if(e&&e.trim().length>0){await gn(e);return}let{getLicenseStatus:t}=await Promise.resolve().then(()=>(de(),Zt)),s=await t();if(s.tier==="pro"){console.log(),console.log(c.bold("You are already on Pro.")),console.log(` ${c.dim("Key:")} ${s.key_short}`),console.log(` ${c.dim("Email:")} ${s.customer_email}`),console.log();return}console.log(),console.log(`${c.ok("Opening")} ${Fd}`),console.log(c.dim("Enter your email to get a 7-day Pro trial link. No card.")),console.log(c.dim("After you click the verification email, run: recall activate <key>")),console.log(),console.log(c.dim("Tip: if you have a promo code, run `recall trial <CODE>` to skip the browser.")),console.log(),qw(Fd)}v();Vt();import{existsSync as Ud,mkdirSync as Kw,readFileSync as Vw,writeFileSync as Zw}from"node:fs";import{homedir as Qw}from"node:os";import{join as Bd}from"node:path";import{randomBytes as eT}from"node:crypto";var No=Bd(Qw(),".recall"),En=Bd(No,"telemetry.json"),Lo={decided_at:null,decision:null,last_ping_month:null,nonce_month:null,nonce:null,last_error:null};function Tt(){if(!Ud(En))return{...Lo};try{let e=Vw(En,"utf8"),t=JSON.parse(e);return{...Lo,...t}}catch{return{...Lo}}}function hn(e){Ud(No)||Kw(No,{recursive:!0}),Zw(En,JSON.stringify(e,null,2)+`
|
|
1672
|
-
`,{mode:384})}function
|
|
1671
|
+
(SELECT COUNT(*) FROM projects) AS projects`).get();return{sessions:t.sessions,projects:t.projects}}catch{return{sessions:0,projects:0}}}function Pw(){let e=process.stdout.columns;return typeof e!="number"||e<=0?!1:e<Mw+2}function Xd(){if(Pw()){console.log(`${oe(Dw)} ${I("\xB7")} ${I(Fd)}`);return}for(let e of Wd)console.log(oe(e));console.log(I(Fd))}async function Fw(){return{daemon:Q(),counts:$w(),license:await Te()}}function jw(e){let t=[];if(t.push(` ${I("Version:")} ${be(`v${Aw}`)} ${I("\xB7 CLI \xB7 @clauderecallhq/cli")}`),e.daemon){let s=`http://127.0.0.1:${e.daemon.port}`;t.push(` ${I("Daemon:")} ${wt("running")} on ${s} ${I(`(pid ${e.daemon.pid})`)}`)}else t.push(` ${I("Daemon:")} ${gn("stopped")}`);if(e.counts.sessions===0)t.push(` ${I("Sessions:")} ${gn("none indexed yet")}`);else{let s=e.counts.projects===1?"project":"projects";t.push(` ${I("Sessions:")} ${be(String(e.counts.sessions))} indexed across ${be(String(e.counts.projects))} ${s}`)}return e.license.tier==="pro"?t.push(` ${I("License:")} ${wt("Pro")}`):t.push(` ${I("License:")} Free`),t}function Uw(e){let t=be(">");if(e.counts.sessions===0)return`${t} Run ${oe("recall index")} to scan ~/.claude/projects/`;if(!e.daemon)return`${t} Run ${oe("recall start")} to launch the local daemon`;if(e.license.tier==="free")return`${t} Run ${oe("recall upgrade")} to unlock Pro features`;let s=`http://127.0.0.1:${e.daemon.port}`;return`${t} Open ${be(s)} in your browser ${I("\xB7")} or run ${oe("recall --help")} for all commands`}var fn=[{title:"Setup",commands:[{name:"index",description:"Scan your Claude sessions and build the searchable database"},{name:"status",description:"Database size, session counts, and daemon state"},{name:"projects",description:"List every project with how many sessions are in each"},{name:"install-extension",description:"Install the VS Code / Cursor / Windsurf extension"},{name:"doctor",description:"Health check: DB size, WAL, FTS fragmentation, integrity, disk space, alias invariant"},{name:"optimize",description:"Maintenance: WAL checkpoint, FTS5 merge, planner stats. Add --vacuum to reclaim free pages"}]},{title:"Browse",commands:[{name:"tui",description:"Browse and search sessions in an interactive terminal UI"},{name:"list",description:"List your sessions, newest first"},{name:"show <id>",description:"Print a full session transcript"},{name:"search <query>",description:"Full-text search across every message"},{name:"similar <id>",description:"Find sessions about the same topic (Pro)"},{name:"semantic [action]",description:'Manage semantic search (defaults to "status")'},{name:"name <id> <name>",description:"Rename a session (or its terminal tab)"}]},{title:"Pipe to Claude",commands:[{name:"context <id>",description:"Pipe a past session as markdown into a fresh `claude` chat"},{name:"neighborhood <id>",description:"Bundle a session with its parents, children, and citations for richer context"}]},{title:"Threads",commands:[{name:"threads sync",description:"Capture sessions running in this repo right now into a thread"},{name:"threads sync --preflight",description:"Preview what `threads sync` would do without writing"},{name:"threads scan",description:"Auto-detect parent-child links across past sessions"},{name:"threads list",description:"List threads, newest first"},{name:"threads show <id>",description:"Show a thread's header and session tree"},{name:"threads new <name>",description:"Create a new thread"},{name:"threads link <id>",description:"Link a session into a thread"},{name:"threads unlink <id>",description:"Remove a session from a thread"},{name:"threads set-parent <id>",description:"Change a session's parent within a thread"},{name:"threads rename <id>",description:"Rename a thread"},{name:"threads close <id>",description:"Mark a thread as closed"},{name:"threads reopen <id>",description:"Reopen a closed thread"},{name:"threads archive <id>",description:"Archive a thread"},{name:"threads merge <id>",description:"Merge one thread into another"},{name:"threads split <id>",description:"Split sessions out into a new thread"}]},{title:"Inference",commands:[{name:"infer outputs",description:"Extract code, file, and error references from sessions in one project"},{name:"infer citations",description:"Find sessions that cite the same files or errors"},{name:"infer l1",description:"Fast deterministic link inference. No LLM cost"},{name:"infer links",description:"LLM-powered link classification (Pro). Optional --auto-promote"},{name:"infer bug-patterns",description:"Cluster sessions that hit the same bug"},{name:"embeddings [action]",description:'Audit embedding coverage (defaults to "audit")'}]},{title:"Analytics",commands:[{name:"stats [id]",description:"Token + dollar usage. No args = overview, pass a session for details"},{name:"health [project]",description:"Score how well-organized each project's sessions are"},{name:"digest",description:"Today's rediscovery picks and standouts"},{name:"correlate [id]",description:"Link sessions to the git commits they wrote"},{name:"blame <sha>",description:"Find which session(s) wrote the code in a commit"}]},{title:"Daemon",commands:[{name:"start",description:"Start the background daemon (live tab tracking + web UI)"},{name:"stop",description:"Stop the background daemon"},{name:"open",description:"Open the local web UI in your browser"}]},{title:"Sharing",commands:[{name:"share [id]",description:"Save a session as a shareable PNG card"},{name:"wrapped [month]",description:"Save a monthly recap as a PNG card"}]},{title:"Diagnostics",commands:[{name:"correlator audit",description:"Find sessions with bad terminal-name aliases (--fix to clear)"},{name:"correlator debug",description:"Show how the daemon is matching open terminals to sessions"},{name:"correlator restore",description:"Restore aliases that `correlator audit --fix` cleared"},{name:"titles [action]",description:'Audit session titles in the current project (defaults to "audit")'},{name:"import-vscode-state",description:"Backfill missing tab names from your editor's workspace state"},{name:"audit-secrets",description:"Scan the database for leaked secrets"}]},{title:"Integrations",commands:[{name:"mcp",description:"Expose Recall as an MCP server (for Claude Desktop / Claude Code)"},{name:"paste",description:"Save clipboard content (or stdin) into Recall"}]},{title:"Pro & License",commands:[{name:"upgrade",description:"Open the Pro pricing page"},{name:"activate <key>",description:"Activate your Pro license (run once after purchase)"},{name:"license [action]",description:"Show the current license. Use `license deactivate` to remove it"},{name:"verify [action]",description:'Toggle verification badges in the UI (defaults to "status")'}]},{title:"Feedback",commands:[{name:"feedback",description:"Send a 1-5 rating to the team. Pass --score for non-interactive use"}]}],Bw=fn.reduce((e,t)=>e+t.commands.length,0),Hw=new Set([...fn.flatMap(e=>e.commands.map(t=>t.name.split(/\s+/)[0])),"thread","correlator-audit","correlator-debug","correlator-restore","extract-outputs","infer-citations","infer-l1","infer-links","infer-bug-patterns"]);function jd(){let e=fn.flatMap(s=>s.commands.map(n=>`recall ${n.name}`)),t=Math.max(...e.map(s=>s.length));console.log(),console.log(`${Hd("COMMANDS")} ${I(`\xB7 ${Bw} total \xB7 q to quit \xB7 recall --help for full details`)}`),console.log(` ${I("All commands run as subcommands of")} ${oe("recall")}${I(". There is no separate")} ${be("threads")}${I(",")} ${be("thread")}${I(", or")} ${be("titles")} ${I("binary.")}`);for(let s of fn){console.log(),console.log(` ${oe(s.title.toUpperCase())}`);for(let n of s.commands){let r=`recall ${n.name}`.padEnd(t," ");console.log(` ${be(r)} ${I(n.description)}`)}}console.log()}function Ww(){return!!process.stdin.isTTY&&!!process.stdout.isTTY}async function Xw(){if(!Ww())return;let e=kw.createInterface({input:process.stdin,output:process.stdout}),t=`${I("Type")} ${oe("/")} ${I("for commands, or press")} ${oe("Enter")} ${I("to exit")} ${be("\u203A")} `;return new Promise(s=>{let n=()=>{e.question(t,r=>{let o=r.trim();if(o==="/"||o==="/help"||o==="help"||o==="?"){jd(),n();return}if(o===""||o==="q"||o==="quit"||o==="exit"){e.close();return}let i=o.startsWith("/")?o.slice(1).trimStart():o,a=i.toLowerCase();if((a==="recall"||a.startsWith("recall "))&&(i=i.slice(6).trimStart()),i===""){jd(),n();return}let d=(i.split(/\s+/)[0]??"").toLowerCase();Hw.has(d)?console.log(` ${I("Run")} ${oe(`recall ${i}`)} ${I("in your shell to invoke that command.")}`):console.log(` ${I(`No command matches "${o}". Type`)} ${oe("/")} ${I("to browse, or")} ${oe("recall --help")} ${I("for the full list.")}`),n()})};e.on("close",()=>{console.log(),s()}),n()})}async function Jd(){let e=await Fw();console.log(),Xd(),console.log();for(let t of jw(e))console.log(t);console.log(),console.log(Uw(e)),console.log(),await Xw()}var Jw=[{name:"Unlimited search",detail:"full history, no caps"},{name:"Context for Claude",detail:"recall past sessions on demand"},{name:"MCP integration",detail:"native tool access from any client"},{name:"Semantic search",detail:"find by meaning, not just keywords"},{name:"Advanced UI",detail:"tags, aliases, notes, exports"}];function Gw(){return process.env.NO_COLOR||process.env.CI||process.env.RECALL_NO_ANIMATION?!1:!!process.stdout.isTTY}function Io(e){return new Promise(t=>setTimeout(t,e))}function Yw(e){let s=(e.split("@")[0]??"").split(/[._+\-]/)[0]??"";return s?s.charAt(0).toUpperCase()+s.slice(1).toLowerCase():""}function Ud(){process.stdout.write("\r\x1B[2K")}async function _n(e){let t=Gw();console.log(),Xd(),console.log(),t&&(process.stdout.write(` ${I("Verifying license...")}`),await Io(260),Ud()),console.log(` ${wt("\u2713")} ${I("License verified")}`),console.log(` ${wt("\u2713")} ${I("Tier:")} ${oe("Pro")} ${I("\xB7 Lifetime")}`),console.log(` ${wt("\u2713")} ${I("Key:")} ${be(e.key_short)}`),console.log(` ${wt("\u2713")} ${I("Email:")} ${e.email}`),e.test_mode&&console.log(` ${gn("!")} ${gn("Test mode:")} this license was issued in test mode.`),console.log(),console.log(` ${Hd("Unlocking Pro features")}`),console.log();for(let r of Jw)t&&(process.stdout.write(` ${I("\xB7")} ${I(r.name)}`),await Io(110),Ud()),console.log(` ${wt("\u2713")} ${r.name} ${I(r.detail)}`);t&&await Io(160),console.log();let s=Yw(e.email),n=s?`Welcome aboard, ${s}.`:"Welcome aboard.";console.log(` ${oe(n)}`),console.log(` ${I("Run")} ${be("recall")} ${I("to open the dashboard, or")} ${be("recall help")} ${I("for the command list.")}`),console.log()}v();Vt();xs();At();ks();import{hostname as zw}from"node:os";import{randomBytes as qw}from"node:crypto";import{createInterface as Kw}from"node:readline/promises";async function Vw(e){let t=e.fetchFn??fetch,s=`${e.apiBaseUrl}/api/trial/cli-redeem`,n=await t(s,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({promo_code:e.promoCode,email:e.email,machine_fingerprint:e.fingerprint,instance_name:e.instanceName,referral_source:"cli"})}),r;try{r=await n.json()}catch{throw new Error(`server returned non-JSON (status ${n.status})`)}return{status:n.status,body:r}}async function Zw(e={}){let t=Kw({input:e.stdin??process.stdin,output:e.stdout??process.stdout});try{return(await t.question(c.bold("Email for your 7-day trial: "))).trim().toLowerCase()}finally{t.close()}}function Qw(e){if(e.length<5||e.length>256||!e.includes("@"))return!1;let[t,s]=e.split("@");return!(!t||!s||!s.includes(".")||/\s/.test(e))}async function hn(e,t={}){let s=e.trim().toUpperCase();if(s.length<3&&(console.error(c.warn(`Promo code "${e}" is too short.`)),process.exit(1)),!t.skipExistingProCheck){let{getLicenseStatus:u}=await Promise.resolve().then(()=>(de(),Zt)),p=await u();if(p.tier==="pro"){console.log(),console.log(c.bold("You are already on Pro \u2014 no need to redeem a trial.")),console.log(` ${c.dim("Key:")} ${p.key_short}`),console.log(` ${c.dim("Email:")} ${p.customer_email}`),console.log(),console.log(c.dim("If you wanted to switch accounts, run `recall license deactivate` first.")),console.log();return}}console.log(),console.log(`${c.ok("Redeeming")} promo code ${c.bold(s)} for a 7-day Pro trial.`),console.log(c.dim("You will get an email with your license key for backup.")),console.log(),!t.promptFn&&!process.stdin.isTTY&&(console.error(c.warn("Trial activation needs an interactive terminal so you can type your email.")),console.error(c.dim("Re-run `recall activate "+s+"` in a terminal (not piped, not CI).")),process.exit(1));let r=await(t.promptFn??Zw)();Qw(r)||(console.error(c.warn(`"${r}" doesn't look like an email address.`)),process.exit(1));let o=t.instanceName??`${zw()}-${qw(4).toString("hex")}`,i=t.fingerprint??vt(),a=t.apiBaseUrl??tt(),d;try{d=await Vw({promoCode:s,email:r,fingerprint:i,instanceName:o,apiBaseUrl:a,...t.fetchFn?{fetchFn:t.fetchFn}:{}})}catch(u){let p=u instanceof Error?u.message:"unknown error";console.error(c.warn(`Could not reach the trial server at ${a}: ${p}`)),console.error(c.dim("If you are offline, try again when you have a connection.")),process.exit(1)}if(d.status!==200||!d.body.license_jwt||!d.body.license_key){let u=d.body.error??`trial activation failed (status ${d.status})`;console.error(c.warn(`Trial refused: ${u}`)),process.exit(1)}let l=await Ot(d.body.license_jwt);(!l.valid||!l.claims)&&(console.error(c.warn(`Server returned a JWT that fails local verification: ${l.reason??"unknown"}`)),console.error(c.dim("This usually means the CLI is older than the server. Try `npm i -g @clauderecallhq/cli`.")),process.exit(1)),Ts({license_jwt:d.body.license_jwt,license_key:d.body.license_key,key_short:d.body.key_short??l.claims.key_short,customer_email:d.body.customer_email??l.claims.email,activated_at:new Date().toISOString(),tier:"pro",test_mode:!!l.claims.test_mode}),t.skipSuccessDashboard||await _n({...l.claims,key_short:d.body.key_short??l.claims.key_short,email:d.body.customer_email??l.claims.email})}function sT(e){return/^recall-pro-/i.test(e)}async function Gd(e){let t=e.trim();if(t.length<3&&(console.error(c.warn("License key or promo code looks too short.")),console.error(c.dim("Paste the full key from your purchase email, or a promo code like RECALL7DAY.")),process.exit(1)),!sT(t)){await hn(t);return}t.length<8&&(console.error(c.warn("License key looks too short. Paste the full key from your purchase email.")),process.exit(1));let s=`${eT()}-${tT(4).toString("hex")}`,n=`${tt()}/api/license/activate`,r;try{r=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({license_key:t,instance_name:s,machine_fingerprint:vt()})})}catch(a){let d=a instanceof Error?a.message:"unknown error";console.error(c.warn(`Could not reach activation server at ${n}: ${d}`)),console.error(c.dim("If you are offline, try again when you have a connection.")),process.exit(1)}let o;try{o=await r.json()}catch{console.error(c.warn(`Activation server returned invalid JSON (status ${r.status}).`)),process.exit(1)}if(r.status!==200||!o.license_jwt){let a=o.error??`activation failed (status ${r.status})`;console.error(c.warn(`Activation refused: ${a}`)),process.exit(1)}let i=await Ot(o.license_jwt);(!i.valid||!i.claims)&&(console.error(c.warn(`Server returned a JWT that fails local verification: ${i.reason??"unknown"}`)),console.error(c.dim("This usually means the CLI is older than the server, or the public key was rotated.")),process.exit(1)),Ts({license_jwt:o.license_jwt,license_key:t,key_short:o.key_short??i.claims.key_short,customer_email:o.customer_email??i.claims.email,activated_at:new Date().toISOString(),tier:"pro",test_mode:!!i.claims.test_mode}),await _n({...i.claims,key_short:o.key_short??i.claims.key_short,email:o.customer_email??i.claims.email})}v();import{spawn as nT}from"node:child_process";import{platform as En}from"node:os";var Yd="https://clauderecall.com/pricing";function rT(e){let t=En()==="darwin"?"open":En()==="win32"?"start":"xdg-open",s=En()==="win32"?["",e]:[e];nT(t,s,{detached:!0,stdio:"ignore",shell:En()==="win32"}).unref()}async function zd(){let{getLicenseStatus:e}=await Promise.resolve().then(()=>(de(),Zt)),t=await e();if(t.tier==="pro"){console.log(),console.log(c.bold("Already on Pro.")),console.log(` ${c.dim("Key:")} ${t.key_short}`),console.log(` ${c.dim("Email:")} ${t.customer_email}`),console.log();return}console.log(),console.log(`${c.ok("Opening")} ${Yd}`),console.log(c.dim("After purchase, run: recall activate <license-key>")),console.log(),rT(Yd)}v();import{spawn as oT}from"node:child_process";import{platform as bn}from"node:os";var qd="https://clauderecall.com/pricing?trial=1&utm_source=cli&utm_medium=recall_trial";function iT(e){let t=bn()==="darwin"?"open":bn()==="win32"?"start":"xdg-open",s=bn()==="win32"?["",e]:[e];oT(t,s,{detached:!0,stdio:"ignore",shell:bn()==="win32"}).unref()}async function Kd(e){if(e&&e.trim().length>0){await hn(e);return}let{getLicenseStatus:t}=await Promise.resolve().then(()=>(de(),Zt)),s=await t();if(s.tier==="pro"){console.log(),console.log(c.bold("You are already on Pro.")),console.log(` ${c.dim("Key:")} ${s.key_short}`),console.log(` ${c.dim("Email:")} ${s.customer_email}`),console.log();return}console.log(),console.log(`${c.ok("Opening")} ${qd}`),console.log(c.dim("Enter your email to get a 7-day Pro trial link. No card.")),console.log(c.dim("After you click the verification email, run: recall activate <key>")),console.log(),console.log(c.dim("Tip: if you have a promo code, run `recall trial <CODE>` to skip the browser.")),console.log(),iT(qd)}v();Vt();import{existsSync as Vd,mkdirSync as aT,readFileSync as cT,writeFileSync as lT}from"node:fs";import{homedir as dT}from"node:os";import{join as Zd}from"node:path";import{randomBytes as uT}from"node:crypto";var Do=Zd(dT(),".recall"),yn=Zd(Do,"telemetry.json"),Mo={decided_at:null,decision:null,last_ping_month:null,nonce_month:null,nonce:null,last_error:null};function Tt(){if(!Vd(yn))return{...Mo};try{let e=cT(yn,"utf8"),t=JSON.parse(e);return{...Mo,...t}}catch{return{...Mo}}}function Sn(e){Vd(Do)||aT(Do,{recursive:!0}),lT(yn,JSON.stringify(e,null,2)+`
|
|
1672
|
+
`,{mode:384})}function Qd(){return yn}function $o(e=new Date){let t=e.getUTCFullYear(),s=String(e.getUTCMonth()+1).padStart(2,"0");return`${t}-${s}`}function eu(e,t){return e.nonce&&e.nonce_month===t?e:{...e,nonce:uT(16).toString("hex"),nonce_month:t}}function pT(e,t){return!t.nonce||!t.nonce_month?null:{event:"install",version:e,platform:process.platform,arch:process.arch,month:t.nonce_month,nonce:t.nonce}}async function tu(e){let t=Tt();if(t.decision!=="on")return{status:t.decision==="off"?"opted-out":"no-decision"};let s=$o();if(t.last_ping_month===s)return{status:"already-this-month"};t=eu(t,s);let n=pT(e,t);if(!n)return{status:"error",detail:"no nonce"};try{let r=await fetch(`${tt()}/api/install-ping`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n),signal:AbortSignal.timeout(5e3)});if(!r.ok){let i=`http ${r.status}`;return Sn({...t,last_error:`${new Date().toISOString()} ${i}`}),{status:"error",detail:i}}let o=await r.json().catch(()=>({}));return Sn({...t,last_ping_month:s,last_error:null}),{status:o.deduped?"deduped":"sent"}}catch(r){let o=r instanceof Error?r.message:"unknown";return Sn({...t,last_error:`${new Date().toISOString()} ${o}`}),{status:"error",detail:o}}}function jt(e){let s={...Tt(),decision:e,decided_at:new Date().toISOString(),last_error:null,...e==="off"?{nonce:null,nonce_month:null,last_ping_month:null}:{}};return Sn(s),s}function wn(e){let t=eu(Tt(),$o());return{event:"install",version:e,platform:process.platform,arch:process.arch,month:t.nonce_month??$o(),nonce:t.nonce??"0".repeat(32)}}async function Po(e){let t=Tt();console.log(),console.log(c.bold("Telemetry \u2014 anonymous install ping")),console.log(` ${c.dim("decision:")} ${t.decision??"not yet decided"}`),console.log(` ${c.dim("decided at:")} ${t.decided_at??"\u2014"}`),console.log(` ${c.dim("last ping:")} ${t.last_ping_month??"\u2014"}`),console.log(` ${c.dim("state file:")} ${Qd()}`),t.last_error&&console.log(` ${c.warn("last error:")} ${t.last_error}`),console.log(),console.log(c.dim("Next-ping payload preview (only sent if decision = on):")),console.log(c.dim(JSON.stringify(wn(e),null,2))),console.log(),console.log(c.dim("Toggle with `recall telemetry on` / `recall telemetry off`.")),console.log(c.dim("Full disclosure: https://clauderecall.com/telemetry")),console.log()}async function su(){jt("on"),console.log(),console.log(c.ok("Telemetry: opted in.")),console.log(c.dim("One anonymous ping per machine per month. No PII. Source: docs/specs/v0.16-install-ping.md")),console.log(c.dim("Reverse with `recall telemetry off`.")),console.log()}async function nu(){jt("off"),console.log(),console.log(c.ok("Telemetry: opted out.")),console.log(c.dim("No further pings will be sent. Existing nonce deleted.")),console.log()}async function ru(e){await Po(e)}import{createInterface as mT}from"node:readline/promises";import{stdin as ou,stdout as me}from"node:process";var gT=new Set(["telemetry","trial","upgrade","activate","license","help","version"]);function fT(e){return!(process.env.CI==="true"||process.env.RECALL_QUIET_INSTALL==="1"||process.env.RECALL_DISABLE_TELEMETRY_PROMPT==="1"||!ou.isTTY||!me.isTTY||e&&gT.has(e)||Tt().decision!==null)}async function iu(e,t){if(!fT(e))return;let s=wn(t);me.write(`
|
|
1673
1673
|
`),me.write(` Claude Recall \u2014 anonymous install ping?
|
|
1674
1674
|
|
|
1675
1675
|
`),me.write(` npm download counts mix real installs with bots and scanners.
|
|
@@ -1685,54 +1685,54 @@ Toggle with: recall verify on | off
|
|
|
1685
1685
|
|
|
1686
1686
|
`),me.write(` Full disclosure: https://clauderecall.com/telemetry
|
|
1687
1687
|
|
|
1688
|
-
`);let n=
|
|
1688
|
+
`);let n=mT({input:ou,output:me}),r;try{r=(await n.question(" Send the install ping? [y/N]: ")).trim().toLowerCase()}finally{n.close()}r==="y"||r==="yes"?(jt("on"),me.write(`
|
|
1689
1689
|
Opted in. Reverse any time with \`recall telemetry off\`.
|
|
1690
1690
|
|
|
1691
1691
|
`)):(jt("off"),me.write(`
|
|
1692
1692
|
Opted out. No pings will be sent. Reverse with \`recall telemetry on\`.
|
|
1693
1693
|
|
|
1694
|
-
`))}v();de();At();
|
|
1694
|
+
`))}v();de();At();dr();async function au(){let e=await Te();if(console.log(),e.tier==="free"){console.log(c.bold("Tier: Free")),console.log(c.dim("Free includes: index, list, show, projects, status, the daemon, and the basic web UI.")),e.invalid_reason&&(console.log(),console.log(c.warn(`Stored license is invalid: ${e.invalid_reason}`))),console.log(),console.log("Buy Pro at https://clauderecall.com/pricing"),console.log("Then run: recall activate <license-key>"),console.log();return}console.log(c.bold("Tier: Pro")),console.log(` ${c.dim("Key:")} ${e.key_short}`),console.log(` ${c.dim("Email:")} ${e.customer_email}`),console.log(` ${c.dim("Activated:")} ${e.activated_at}`),e.expires_at&&console.log(` ${c.dim("Expires:")} ${e.expires_at}`),e.test_mode&&console.log(` ${c.warn("Test mode:")} this license was issued in test mode.`);let t=Cs();t&&console.log(` ${c.dim("Last server check:")} ${t.last_checked_at}`),console.log()}async function cu(){let e=await _r({force:!0});if(console.log(),!e.ran){console.log(c.warn("No license stored on this machine.")),console.log(c.dim("Run `recall activate <license-key>` first.")),console.log();return}if(!e.last_checked_at){console.log(c.warn("Could not reach the license server.")),console.log(c.dim("Network error. Try again when you have a connection.")),console.log();return}if(e.revoked){console.log(c.warn("License is revoked.")),e.reason&&console.log(` ${c.dim("Reason:")} ${e.reason}`),console.log(` ${c.dim("Checked:")} ${e.last_checked_at}`),console.log();return}console.log(c.ok("License is valid.")),console.log(` ${c.dim("Checked:")} ${e.last_checked_at}`),console.log()}function lu(){Hi(),console.log(),console.log("License removed from this machine."),console.log(c.dim(`(${ht})`)),console.log()}w();v();import _T from"cli-table3";function Me(e){let t=_();if(e.length>=32){let n=t.prepare("SELECT id FROM threads WHERE id = ?").get(e);return n?n.id:(process.stderr.write(`thread not found: ${e}
|
|
1695
1695
|
`),null)}let s=t.prepare("SELECT id, name FROM threads WHERE id LIKE ? ORDER BY created_at DESC").all(e+"%");if(s.length===1)return s[0].id;if(s.length===0)return process.stderr.write(`no thread matches prefix "${e}"
|
|
1696
1696
|
`),null;process.stderr.write(`ambiguous thread prefix "${e}" \u2014 candidates:
|
|
1697
1697
|
`);for(let n of s)process.stderr.write(` ${X(n.id)} ${n.name}
|
|
1698
1698
|
`);return null}function Rt(e){let t=_();if(e.length>=32){let n=t.prepare("SELECT id FROM sessions WHERE id = ?").get(e);return n?n.id:(process.stderr.write(`session not found: ${e}
|
|
1699
1699
|
`),null)}let s=t.prepare("SELECT id FROM sessions WHERE id LIKE ? LIMIT 2").all(e+"%");return s.length===1?s[0].id:s.length===0?(process.stderr.write(`no session matches prefix "${e}"
|
|
1700
1700
|
`),null):(process.stderr.write(`ambiguous session prefix "${e}". be more specific.
|
|
1701
|
-
`),null)}function
|
|
1701
|
+
`),null)}function hT(e){let t=new Map;if(e.length===0)return t;let s=e.map(()=>"?").join(","),r=_().prepare(`SELECT s.id,
|
|
1702
1702
|
s.first_user_message,
|
|
1703
1703
|
p.name AS project_name,
|
|
1704
1704
|
a.alias AS alias
|
|
1705
1705
|
FROM sessions s
|
|
1706
1706
|
LEFT JOIN projects p ON p.id = s.project_id
|
|
1707
1707
|
LEFT JOIN session_aliases a ON a.session_id = s.id
|
|
1708
|
-
WHERE s.id IN (${s})`).all(...e);for(let o of r)t.set(o.id,o);return t}function
|
|
1708
|
+
WHERE s.id IN (${s})`).all(...e);for(let o of r)t.set(o.id,o);return t}function du(e,t){let s=t.get(e),n=c.accent(X(e));if(!s)return`${n} ${c.dim("(unindexed)")}`;let r=s.alias??s.first_user_message??"",o=s.project_name?c.project(`[${s.project_name}] `):"";return`${n} ${o}${K(r,80)}`}function Fo(e){return e.archived?"archived":e.closed_at?"closed":"open"}function Ut(e){let t=Fo(e);process.stderr.write(c.ok(`\u2713 ${e.name} ${c.dim(`(${X(e.id)})`)} [${t}]
|
|
1709
1709
|
`))}function xe(e,t,s){if(e){process.stdout.write(JSON.stringify(t,null,2)+`
|
|
1710
|
-
`);return}s()}function
|
|
1711
|
-
`);let g=n.get(a.session_id)??[],f=u?"":d+(l?" ":"\u2502 ");g.forEach((h,E)=>{i(h,f,E===g.length-1,!1)})};r.forEach((a,d)=>i(a,"",d===r.length-1,!0));for(let a of e.edges)o.has(a.session_id)||process.stdout.write(`${c.warn("? ")}${
|
|
1712
|
-
`)}function
|
|
1713
|
-
`),process.exitCode=1;return}let r=
|
|
1710
|
+
`);return}s()}function uu(e){let t=xr({includeArchived:e.archived===!0});xe(e.json===!0,t,()=>{if(t.length===0){console.log(c.dim("no threads. create one with `recall thread new <name>`."));return}let s=new _T({head:[c.bold("id"),c.bold("name"),c.bold("sessions"),c.bold("origins"),c.bold("status"),c.bold("created")],colWidths:[10,40,10,9,10,16],wordWrap:!0,style:{head:[],border:["grey"]}});for(let n of t)s.push([c.accent(X(n.id)),K(n.name,38),String(n.session_count),String(n.origin_count),Fo(n),c.dim(J(n.created_at))]);console.log(s.toString()),console.log(c.dim(`${t.length} thread${t.length===1?"":"s"}.`+(e.archived?"":" add --archived to include archived.")))})}function ET(e,t){let s=new Set(e.edges.map(a=>a.session_id)),n=new Map,r=[];for(let a of e.edges){let d=a.parent_session_id;if(a.role==="origin"||!d||!s.has(d)){r.push(a);continue}let l=n.get(d)??[];l.push(a),n.set(d,l)}let o=new Set,i=(a,d,l,u)=>{if(o.has(a.session_id))return;o.add(a.session_id);let p=u?"":l?"\u2514\u2500 ":"\u251C\u2500 ",m=a.role==="origin"?c.warn("[origin] "):"";process.stdout.write(`${d}${p}${m}${du(a.session_id,t)}
|
|
1711
|
+
`);let g=n.get(a.session_id)??[],f=u?"":d+(l?" ":"\u2502 ");g.forEach((h,E)=>{i(h,f,E===g.length-1,!1)})};r.forEach((a,d)=>i(a,"",d===r.length-1,!0));for(let a of e.edges)o.has(a.session_id)||process.stdout.write(`${c.warn("? ")}${du(a.session_id,t)}
|
|
1712
|
+
`)}function pu(e,t){let s=Me(e);if(!s){process.exitCode=1;return}let n=Oe(s);if(!n){process.stderr.write(`thread not found: ${e}
|
|
1713
|
+
`),process.exitCode=1;return}let r=hT(n.edges.map(o=>o.session_id));xe(t.json===!0,n,()=>{let o=Fo(n);if(console.log(c.bold(n.name)),console.log(c.dim(`id ${X(n.id)} \xB7 ${o} \xB7 ${n.session_count} session${n.session_count===1?"":"s"} \xB7 ${n.origin_count} origin${n.origin_count===1?"":"s"} \xB7 created ${J(n.created_at)}`)),n.summary&&console.log(n.summary),console.log(),n.edges.length===0){console.log(c.dim("(no sessions linked \u2014 use `recall thread link ...`)"));return}ET(n,r)})}function mu(e,t){let s;if(t.origin){let r=Rt(t.origin);if(!r){process.exitCode=1;return}s=r}let n=wa({name:e,summary:t.summary??null,originSessionId:s});xe(t.json===!0,n,()=>{Ut(n),process.stderr.write(c.dim(`id ${n.id}
|
|
1714
1714
|
`)),s&&process.stderr.write(c.dim(`origin: ${X(s)}
|
|
1715
|
-
`))})}function
|
|
1716
|
-
`))})}function
|
|
1715
|
+
`))})}function gu(e,t){let s=Me(t.thread);if(!s){process.exitCode=1;return}let n=Rt(e);if(!n){process.exitCode=1;return}let r=null;if(t.parent){let a=Rt(t.parent);if(!a){process.exitCode=1;return}r=a}let o=t.role??(r?"child":"origin"),i=Ta({threadId:s,sessionId:n,parentSessionId:r,role:o});xe(t.json===!0,i,()=>{process.stderr.write(c.ok(`\u2713 linked ${X(n)} \u2192 ${X(s)} [${o}]
|
|
1716
|
+
`))})}function fu(e,t){let s=Me(t.thread);if(!s){process.exitCode=1;return}let n=Rt(e);if(!n){process.exitCode=1;return}let r=Ra(s,n);xe(t.json===!0,r,()=>{if(r.removed===0){process.stderr.write(c.warn(`(no edge existed for ${X(n)} in ${X(s)})
|
|
1717
1717
|
`));return}process.stderr.write(c.ok(`\u2713 unlinked ${X(n)} from ${X(s)}
|
|
1718
|
-
`))})}function
|
|
1719
|
-
`))})}function
|
|
1720
|
-
`)),process.exitCode=1;return}let r=
|
|
1721
|
-
`))})}function
|
|
1722
|
-
`)),process.exitCode=1;return}let r=[];for(let i of n){let a=Rt(i);if(!a){process.exitCode=1;return}r.push(a)}let o=
|
|
1723
|
-
`))})}w();v();import{randomUUID as
|
|
1724
|
-
`).toLowerCase();for(let i of e.authored_paths){let a=i.toLowerCase();if(t.touched_files.has(i)||o.includes(a)){s+=.5,n=i;break}}}if(e.authored_content.length>0&&t.recent_user_messages[0]){let o=
|
|
1725
|
-
`,l),p=u===-1?d.length:u,m=d.slice(l,p);if(l=u===-1?d.length:u+1,!m.trim())continue;let g;try{g=JSON.parse(m)}catch{continue}let f=g;if(f.type==="user"&&f.message?.role==="user"&&typeof f.message.content=="string"&&o.length<s){let E=f.message.content.trim();E&&o.push(E.length>n?E.slice(0,n):E)}let h=f.message?.content;if(Array.isArray(h))for(let E of h){if(!E||typeof E!="object")continue;let b=E;if(b.type!=="tool_use")continue;let S=b.input??{},R=typeof S.file_path=="string"?S.file_path:null;if(R){let T=
|
|
1726
|
-
`);if(t.length<4)return!1;let s=0,n=0;for(let r of t)/^\s*\d{1,5}\t/.test(r)?s++:/^\s*\/[A-Za-z0-9_./-]+/.test(r.trim())&&n++;return s/t.length>.7||n/t.length>.5}function
|
|
1718
|
+
`))})}function _u(e,t){let s=Me(t.thread);if(!s){process.exitCode=1;return}let n=Rt(e);if(!n){process.exitCode=1;return}let r=null;if(t.parent&&t.parent.toLowerCase()!=="none"){let i=Rt(t.parent);if(!i){process.exitCode=1;return}r=i}let o=xa(s,n,r);xe(t.json===!0,o,()=>{process.stderr.write(c.ok(`\u2713 ${X(n)} in ${X(s)} \u2192 `+(r?`child of ${X(r)}`:"origin")+`
|
|
1719
|
+
`))})}function hu(e,t,s){let n=Me(e);if(!n){process.exitCode=1;return}let r=ka(n,t);xe(s.json===!0,r,()=>Ut(r))}function Eu(e,t){let s=Me(e);if(!s){process.exitCode=1;return}let n=Ca(s);xe(t.json===!0,n,()=>Ut(n))}function bu(e,t){let s=Me(e);if(!s){process.exitCode=1;return}let n=La(s);xe(t.json===!0,n,()=>Ut(n))}function Su(e,t){let s=Me(e);if(!s){process.exitCode=1;return}let n=Na(s);xe(t.json===!0,n,()=>Ut(n))}function yu(e,t){let s=Me(e);if(!s){process.exitCode=1;return}let n=Me(t.into);if(!n){process.exitCode=1;return}if(s===n){process.stderr.write(c.err(`cannot merge a thread into itself
|
|
1720
|
+
`)),process.exitCode=1;return}let r=Aa(s,n);xe(t.json===!0,r,()=>{process.stderr.write(c.ok(`\u2713 merged ${X(s)} \u2192 ${X(n)} (${r.session_count} sessions)
|
|
1721
|
+
`))})}function wu(e,t){let s=Me(e);if(!s){process.exitCode=1;return}let n=t.sessions.split(",").map(i=>i.trim()).filter(Boolean);if(n.length===0){process.stderr.write(c.err(`--sessions must be a non-empty comma-separated list
|
|
1722
|
+
`)),process.exitCode=1;return}let r=[];for(let i of n){let a=Rt(i);if(!a){process.exitCode=1;return}r.push(a)}let o=Oa({threadId:s,sessionIds:r,newThreadName:t.name});xe(t.json===!0,o,()=>{Ut(o),process.stderr.write(c.dim(`split off ${r.length} session${r.length===1?"":"s"} from ${X(s)}
|
|
1723
|
+
`))})}w();v();import{randomUUID as BT}from"node:crypto";import{readFileSync as bT,statSync as ST}from"node:fs";var yT=200*1024*1024,Uo=.7,Bo=.5,xu=Bo,wT=[{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"}],TT=["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 Tu(e){if(!e)return null;if(e.startsWith("/")){let s=e.split(" \xB7 ");if(s.length>1)return s[1].trim().toLowerCase()}let t=e.split(" \xB7 ");return t.length>1?t[t.length-1].trim().toLowerCase():null}function RT(e,t){let s=t-e;if(s<0)return{weight:0,label:null};for(let n of wT)if(s<=n.maxGapMs)return{weight:n.weight,label:n.label};return{weight:0,label:null}}function xT(e){if(e.length===0)return{weight:0,matched:null,matchedIndex:-1};let t=[.4,.35,.3,.25,.2,.15,.1],s=Math.min(e.length,t.length);for(let n=0;n<s;n++){let r=e[n];if(!r)continue;let o=r.toLowerCase();for(let i of TT)if(o.includes(i))return{weight:t[n],matched:i,matchedIndex:n}}return{weight:0,matched:null,matchedIndex:-1}}function jo(e,t){if(!e||!t||e.length!==t.length)return 0;let s=0;for(let n=0;n<e.length;n++)s+=e[n]*t[n];return s<-1?-1:s>1?1:s}function kT(e,t){if(e.length===0||t.length===0)return 0;let s=0;for(let n of e)for(let r of t){let o=jo(n,r);o>s&&(s=o)}return s}function CT(e,t){let s=jo(e.mean_embedding,t.mean_embedding),n=jo(e.tail_pool,t.head_pool),r=kT(e.sample_chunks,t.sample_chunks),o=0,i=null;if(s>o&&(o=s,i="mean"),n>o&&(o=n,i="asymmetric"),r>o&&(o=r,i="max_pool"),o<.65)return{weight:0,cosine:o,mode:null};if(o>=.85)return{weight:.3,cosine:o,mode:i};let a=(o-.65)/.2*.3;return{weight:Math.round(a*100)/100,cosine:o,mode:i}}function LT(e,t){return e.cluster_id===null||t.cluster_id===null?{weight:0,same:!1}:e.cluster_id!==t.cluster_id?{weight:0,same:!1}:{weight:.05,same:!0}}function NT(e,t){if(e.size===0||t.size===0)return{weight:0,count:0};let s=0;for(let r of t)e.has(r)&&s++;return s===0?{weight:0,count:0}:{weight:Math.min(.4,s*.1),count:s}}function AT(e,t){let s=Tu(e),n=Tu(t);return s&&n&&s===n?{weight:.1,brand:s}:{weight:0,brand:null}}function Ru(e){return e.replace(/\s+/g," ").trim().toLowerCase()}function OT(e,t){let s=0,n=null,r=!1;if(e.authored_paths.size>0){let o=t.recent_user_messages.slice(0,3).join(`
|
|
1724
|
+
`).toLowerCase();for(let i of e.authored_paths){let a=i.toLowerCase();if(t.touched_files.has(i)||o.includes(a)){s+=.5,n=i;break}}}if(e.authored_content.length>0&&t.recent_user_messages[0]){let o=Ru(t.recent_user_messages[0]);if(o.length>=200)for(let i of e.authored_content){let a=Ru(i);if(a.length<200)continue;let d=Math.min(a.length,240),l=a.slice(0,d);if(o.includes(l)){s+=.4,r=!0;break}}}return{weight:Math.min(.6,s),pathMatch:n,contentMatch:r}}function vT(e,t,s=xu){if(t.started_at_ms<=e.started_at_ms)return null;let n=e.ended_at_ms??e.started_at_ms;if(t.started_at_ms<n)return null;let r=RT(n,t.started_at_ms),o=t.recent_user_messages.length>0?t.recent_user_messages:t.first_user_message?[t.first_user_message]:[],i=xT(o),a=NT(e.touched_files,t.touched_files),d=AT(e.auto_title,t.auto_title),l=CT(e,t),u=LT(e,t),p=OT(e,t),m=r.weight+i.weight+a.weight+d.weight+l.weight+u.weight+p.weight;if(m<s)return null;let g=[];if(r.label&&g.push(`temporal ${r.label} (+${r.weight})`),i.matched){let f=i.matchedIndex===0?"opening message":`message #${i.matchedIndex+1}`;g.push(`continuation phrase "${i.matched}" in ${f} (+${i.weight})`)}if(a.count>0&&g.push(`${a.count} file${a.count===1?"":"s"} overlap (+${a.weight.toFixed(1)})`),d.brand&&g.push(`shared brand "${d.brand}" (+${d.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)})`),u.same&&g.push(`same cluster (+${u.weight})`),p.weight>0){let f=[];p.pathMatch&&f.push(`opened authored path "${p.pathMatch.split("/").pop()}"`),p.contentMatch&&f.push("verbatim-paste of authored content"),g.push(`doc-authorship: ${f.join(" + ")} (+${p.weight.toFixed(2)})`)}return{parent_id:e.id,child_id:t.id,confidence:Math.min(1,m),signals:{temporal:r.weight,continuation:i.weight,file_overlap:a.weight,same_brand:d.weight,semantic:l.weight,cluster:u.weight,doc_authorship:p.weight},reasons:g}}function ku(e,t=xu){let s=[];for(let n=0;n<e.length;n++){let r=e[n],o=null;for(let i=0;i<n;i++){let a=e[i],d=vT(a,r,t);d&&(!o||d.confidence>o.confidence)&&(o=d)}o&&s.push(o)}return s}function Cu(e,t){let s=new Map,n=a=>{let d=a;for(;s.get(d)!==d;)d=s.get(d);let l=a;for(;s.get(l)!==d;){let u=s.get(l);s.set(l,d),l=u}return d},r=(a,d)=>{let l=n(a),u=n(d);l!==u&&s.set(l,u)};for(let a of e)s.has(a.parent_id)||s.set(a.parent_id,a.parent_id),s.has(a.child_id)||s.set(a.child_id,a.child_id),r(a.parent_id,a.child_id);let o=new Map;for(let a of s.keys()){let d=n(a),l=o.get(d);l||(l=[],o.set(d,l)),l.push(a)}let i=new Map;for(let a of t)i.set(a.id,a.started_at_ms);return Array.from(o.values()).map(a=>(a.sort((d,l)=>(i.get(d)??0)-(i.get(l)??0)),{rootId:a[0],sessionIds:a}))}function Lu(e,t={}){let s=t.maxUserMessages??5,n=t.userMessageMaxLen??2e3,r=new Set,o=[],i=new Set,a=[],d;try{if(ST(e).size>yT)return{touched_files:r,recent_user_messages:o,authored_paths:i,authored_content:a};d=bT(e,"utf8")}catch{return{touched_files:r,recent_user_messages:o,authored_paths:i,authored_content:a}}let l=0;for(;l<d.length;){let u=d.indexOf(`
|
|
1725
|
+
`,l),p=u===-1?d.length:u,m=d.slice(l,p);if(l=u===-1?d.length:u+1,!m.trim())continue;let g;try{g=JSON.parse(m)}catch{continue}let f=g;if(f.type==="user"&&f.message?.role==="user"&&typeof f.message.content=="string"&&o.length<s){let E=f.message.content.trim();E&&o.push(E.length>n?E.slice(0,n):E)}let h=f.message?.content;if(Array.isArray(h))for(let E of h){if(!E||typeof E!="object")continue;let b=E;if(b.type!=="tool_use")continue;let S=b.input??{},R=typeof S.file_path=="string"?S.file_path:null;if(R){let T=Tn(R);T&&r.add(T)}if((b.name==="Write"||b.name==="Edit"||b.name==="MultiEdit")&&R){let T=Tn(R);T&&i.add(T);let L=typeof S.content=="string"?S.content:typeof S.new_string=="string"?S.new_string:null;L&&L.length>=200&&a.push(L.length>4096?L.slice(0,4096):L)}if(b.name==="Bash"&&typeof S.command=="string")for(let T of S.command.matchAll(/(?:^|[\s'"`(=])((?:\.\.?\/|\/|src\/|test\/|docs\/|site\/)[A-Za-z0-9_./-]+)/g)){let L=Tn(T[1]);L&&r.add(L)}if((b.name==="Glob"||b.name==="Grep")&&typeof S.pattern=="string"){let T=Tn(S.pattern);T&&!T.includes("*")&&r.add(T)}}}return{touched_files:r,recent_user_messages:o,authored_paths:i,authored_content:a}}function Tn(e){let t=e.trim().replace(/^['"]|['"]$/g,"");return!t||t.length<4||/[<>|;&\$`]/.test(t)?null:t}w();var Nu=10,Au=20;function IT(e){if(!e)return!1;let t=e.split(`
|
|
1726
|
+
`);if(t.length<4)return!1;let s=0,n=0;for(let r of t)/^\s*\d{1,5}\t/.test(r)?s++:/^\s*\/[A-Za-z0-9_./-]+/.test(r.trim())&&n++;return s/t.length>.7||n/t.length>.5}function MT(e){let t=e.byteLength/4;if(!Number.isInteger(t)||t<=0)return null;let s=new Float32Array(t),n=new Float32Array(e.buffer,e.byteOffset,t);return s.set(n),s}function Ou(e){let t=0;for(let n=0;n<e.length;n++)t+=e[n]*e[n];if(t<=0)return!1;let s=1/Math.sqrt(t);for(let n=0;n<e.length;n++)e[n]*=s;return!0}function Ho(e){if(e.length===0)return null;let t=e[0].length,s=new Float32Array(t);for(let n of e)if(n.length===t)for(let r=0;r<t;r++)s[r]+=n[r];for(let n=0;n<t;n++)s[n]/=e.length;return Ou(s)?s:null}function vu(e){let t=new Map;if(e.length===0)return t;let s=_(),n=e.map(()=>"?").join(","),r=[];try{r=s.prepare(`SELECT cm.rowid AS rowid, cm.session_id AS session_id,
|
|
1727
1727
|
cm.text AS text, v.embedding AS embedding
|
|
1728
1728
|
FROM chunk_meta cm
|
|
1729
1729
|
JOIN vec_chunks v ON v.rowid = cm.rowid
|
|
1730
1730
|
WHERE cm.stale = 0
|
|
1731
1731
|
AND cm.session_id IN (${n})
|
|
1732
|
-
ORDER BY cm.session_id, cm.rowid ASC`).all(...e)}catch{return t}if(r.length===0)return t;let o=new Map;for(let i of r){let a=o.get(i.session_id);a||(a=[],o.set(i.session_id,a)),a.push(i)}for(let[i,a]of o){let d=a.filter(T=>!
|
|
1733
|
-
`),d=
|
|
1732
|
+
ORDER BY cm.session_id, cm.rowid ASC`).all(...e)}catch{return t}if(r.length===0)return t;let o=new Map;for(let i of r){let a=o.get(i.session_id);a||(a=[],o.set(i.session_id,a)),a.push(i)}for(let[i,a]of o){let d=a.filter(T=>!IT(T.text)),l=d.length>0?d:a,u=[];for(let T of l){let L=MT(T.embedding);L&&Ou(L)&&u.push(L)}if(u.length===0)continue;let p=Math.min(Nu,u.length),m=Math.max(0,u.length-Nu),g=u.slice(0,p),f=u.slice(m),h=Ho(g),E=Ho(f),b=Ho(u),S=new Map;for(let T=0;T<g.length&&S.size<Au;T++)S.set(T,g[T]);for(let T=0;T<f.length&&S.size<Au;T++)S.set(m+T,f[T]);let R=Array.from(S.values());t.set(i,{session_id:i,full_mean:b,head_pool:h,tail_pool:E,sample_chunks:R})}return t}function DT(e,t){if(e.length!==t.length)return 0;let s=0;for(let n=0;n<e.length;n++)s+=e[n]*t[n];return s<-1?-1:s>1?1:s}function Iu(e,t=.8){let s=new Map,n=[],r=[];for(let[l,u]of e)u.full_mean&&(n.push(l),r.push(u.full_mean));if(n.length===0)return s;let o=new Int32Array(n.length);for(let l=0;l<o.length;l++)o[l]=l;let i=l=>{let u=l;for(;o[u]!==u;)u=o[u];let p=l;for(;o[p]!==u;){let m=o[p];o[p]=u,p=m}return u},a=(l,u)=>{let p=i(l),m=i(u);p!==m&&(o[p]=m)};for(let l=0;l<n.length;l++)for(let u=l+1;u<n.length;u++)DT(r[l],r[u])>=t&&a(l,u);let d=new Map;for(let l=0;l<n.length;l++){let u=i(l),p=d.get(u);p===void 0&&(p=d.size,d.set(u,p)),s.set(n[l],p)}return s}var ds={lo:.4,hi:.7},Rn="claude-haiku-4-5-20251001",Mu=50;var $T={same_workflow:.15,unrelated:-.2,unsure:0};function Du(e,t,s=ds){if(s.lo>s.hi)throw new Error(`borderline band invalid: lo=${s.lo} > hi=${s.hi}`);let n=[];for(let r of e){if(r.confidence<s.lo||r.confidence>s.hi)continue;let o=t.get(r.parent_id),i=t.get(r.child_id);!o||!i||n.push({parent:o,child:i,step1:r})}return n.sort((r,o)=>r.child.started_at_ms-o.child.started_at_ms),n}function PT(e,t){let s=e.replace(/\s+/g," ").trim();return s.length>t?s.slice(0,t-1)+"\u2026":s}function FT(e,t){if(e===null)return"unknown";let s=t-e;if(s<0)return"overlap";let n=Math.round(s/6e4);if(n<60)return`${n}m`;let r=Math.round(s/36e5);return r<24?`${r}h`:`${Math.round(s/864e5)}d`}function jT(e){let s=e.parent.recent_user_messages,n=e.child.recent_user_messages,r=s.slice(0,3),o=s.length>3?s.slice(-3):[],i=n.slice(0,3),a=l=>l.length===0?" (none captured)":l.map(u=>` - ${PT(u,500)}`).join(`
|
|
1733
|
+
`),d=FT(e.parent.ended_at_ms??e.parent.started_at_ms,e.child.started_at_ms);return["You are evaluating whether two Claude Code sessions belong to the same continuous workflow.","","A deterministic scorer rated this pair in the borderline band. Its signals:",e.step1.reasons.length===0?" (no signals fired strongly)":e.step1.reasons.map(l=>` - ${l}`).join(`
|
|
1734
1734
|
`),` step1_confidence: ${e.step1.confidence.toFixed(2)}`,"","PARENT SESSION","First user messages:",a(r),"Last user messages:",a(o),"",`CHILD SESSION (gap from parent: ${d})`,"First user messages:",a(i),"","Reply with EXACTLY one JSON object on a single line, no prose, no markdown:",'{"verdict":"same_workflow"|"unrelated"|"unsure","reason":"<one short sentence>"}',"","Guidance:",'- "same_workflow" = the child is clearly continuing what the parent was doing.','- "unrelated" = different problem, different files, different intent.','- "unsure" = could go either way; do not force a verdict.'].join(`
|
|
1735
|
-
`)}function
|
|
1735
|
+
`)}function UT(e){if(!e)return null;let t=e.trim();if(!t)return null;let s=t;try{let d=JSON.parse(t);typeof d.result=="string"&&(s=d.result.trim())}catch{}let n=s.match(/\{[\s\S]*\}/);if(!n)return null;let r;try{r=JSON.parse(n[0])}catch{return null}if(!r||typeof r!="object")return null;let o=r,i=typeof o.verdict=="string"?o.verdict.toLowerCase():"";if(i!=="same_workflow"&&i!=="unrelated"&&i!=="unsure")return null;let a=typeof o.reason=="string"&&o.reason.trim()?o.reason.trim():"";return{verdict:i,reason:a,delta:$T[i]}}async function $u(e,t={}){if(t.signal?.aborted)return null;let s=t.model??Rn,n=jT(e),r=t.spawn;if(!r)try{let i=await Promise.resolve().then(()=>(Be(),Us));if(!i.isClaudeCliAvailable())return null;r=(a,d)=>i.spawnClaudePrompt(a,[],d)}catch{return null}let o;try{o=await Promise.race([r(n,{model:s}),new Promise(i=>{t.signal&&t.signal.addEventListener("abort",()=>i({success:!1,stdout:""}),{once:!0})})])}catch{return null}return!o.success||!o.stdout?null:UT(o.stdout)}function Pu(e){let t=[];for(let s of e.proposals){let n=`${s.parent_id}::${s.child_id}`,r=e.rescored.get(n);if(!r){t.push(s);continue}let o=Math.max(0,Math.min(1,s.confidence+r.delta));if(o<e.applyThreshold)continue;let i=`LLM: ${r.verdict}${r.reason?` \u2014 ${r.reason}`:""} (${r.delta>=0?"+":""}${r.delta.toFixed(2)})`;t.push({...s,confidence:o,reasons:[...s.reasons,i]})}return t}function Fu(e){return`${e.parent_id}::${e.child_id}`}async function HT(e){if(e.signal?.aborted)return null;let t,s;try{({spawnClaudePrompt:t,isClaudeCliAvailable:s}=await Promise.resolve().then(()=>(Be(),Us)))}catch{return null}if(!s())return null;let n=[];for(let o of e.sessionIds){let i=e.rowById.get(o);if(!i)continue;let a=i.alias||i.auto_title||(i.first_user_message?i.first_user_message.slice(0,120).replace(/\n/g," "):"(no title)");n.push(`- ${a}`)}let r=`Below is a chronological list of related Claude Code sessions that form one continuous workflow. Generate a single short descriptive name for the workflow as a whole. Requirements:
|
|
1736
1736
|
- 4 to 8 words
|
|
1737
1737
|
- Title-case, no trailing punctuation
|
|
1738
1738
|
- Capture the WORKFLOW (e.g., "Update Remotion deps + lint cleanup"), not any one session
|
|
@@ -1743,7 +1743,7 @@ Sessions:
|
|
|
1743
1743
|
`+n.join(`
|
|
1744
1744
|
`)+`
|
|
1745
1745
|
|
|
1746
|
-
Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model:e.model}:{}),i=null,a=new Promise(p=>{e.signal&&(i=()=>p(null),e.signal.addEventListener("abort",i,{once:!0}))}),d=await Promise.race([o,a]);if(i&&e.signal&&e.signal.removeEventListener("abort",i),!d||!d.success)return null;let l=d.stdout.trim();if(!l)return null;let u;try{let p=JSON.parse(l);u=typeof p.result=="string"?p.result:l}catch{u=l}return u=u.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/[.!?]+$/g,"").trim(),u?u.length>80?u.slice(0,77)+"...":u:null}catch{return null}}function
|
|
1746
|
+
Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model:e.model}:{}),i=null,a=new Promise(p=>{e.signal&&(i=()=>p(null),e.signal.addEventListener("abort",i,{once:!0}))}),d=await Promise.race([o,a]);if(i&&e.signal&&e.signal.removeEventListener("abort",i),!d||!d.success)return null;let l=d.stdout.trim();if(!l)return null;let u;try{let p=JSON.parse(l);u=typeof p.result=="string"?p.result:l}catch{u=l}return u=u.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/[.!?]+$/g,"").trim(),u?u.length>80?u.slice(0,77)+"...":u:null}catch{return null}}function WT(e){let t=new Map;for(let o of e){let i=t.get(o.project);i||(i=[],t.set(o.project,i)),i.push(o)}let s=e.map(o=>o.id),n=new Map;try{n=vu(s)}catch{n=new Map}let r=new Map;for(let[o,i]of t){let a=new Map;for(let u of i){let p=n.get(u.id);p&&a.set(u.id,p)}let d=Iu(a,.8),l=[];for(let u of i){let p=JT(u,n,d);p&&l.push(p)}r.set(o,l)}return{byProject:t,scannablesByProject:r}}function XT(e){let t=_(),s={},n="1=1 AND s.message_count > 2";return n+=" AND COALESCE(s.auto_title, '') NOT LIKE '[meta]%' AND COALESCE(s.auto_title, '') NOT LIKE '[output-index]%' AND COALESCE(s.auto_title, '') NOT LIKE '[skill]%'",e.project&&(n+=" AND p.name = @project",s.project=e.project),t.prepare(`SELECT s.id, p.name AS project, s.started_at, s.ended_at,
|
|
1747
1747
|
s.first_user_message, s.auto_title,
|
|
1748
1748
|
NULLIF(sa.alias, '') AS alias,
|
|
1749
1749
|
s.file_path
|
|
@@ -1751,40 +1751,40 @@ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model
|
|
|
1751
1751
|
JOIN projects p ON p.id = s.project_id
|
|
1752
1752
|
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
1753
1753
|
WHERE ${n}
|
|
1754
|
-
ORDER BY p.name ASC, s.started_at ASC`).all(s)}function
|
|
1754
|
+
ORDER BY p.name ASC, s.started_at ASC`).all(s)}function JT(e,t,s){if(!e.started_at)return null;let n=Date.parse(e.started_at);if(!Number.isFinite(n))return null;let r=e.ended_at?Date.parse(e.ended_at):null,o=Lu(e.file_path,{maxUserMessages:7}),i=t?.get(e.id);return{id:e.id,started_at_ms:n,ended_at_ms:Number.isFinite(r)?r:null,first_user_message:e.first_user_message,recent_user_messages:o.recent_user_messages,auto_title:e.auto_title,touched_files:o.touched_files,mean_embedding:i?.full_mean??null,head_pool:i?.head_pool??null,tail_pool:i?.tail_pool??null,sample_chunks:i?.sample_chunks??[],cluster_id:s?.get(e.id)??null,authored_paths:o.authored_paths,authored_content:o.authored_content}}function xn(e){let t=e.id.slice(0,8),s=e.alias||e.auto_title||(e.first_user_message?e.first_user_message.slice(0,50):"(no title)");return`${t} ${s}`}function ju(e){if(!e)return"?";let t=new Date(e);if(Number.isNaN(t.getTime()))return"?";let s=n=>String(n).padStart(2,"0");return`${t.getFullYear()}-${s(t.getMonth()+1)}-${s(t.getDate())} ${s(t.getHours())}:${s(t.getMinutes())}`}function Uu(e){let t=e.alias||e.auto_title||(e.first_user_message?e.first_user_message.slice(0,60).replace(/\n/g," ").trim():`thread from ${e.id.slice(0,8)}`);return t.length>80?t.slice(0,77)+"...":t}async function GT(e){if(!e.rescore.enabled)return{proposals:e.proposals,ran:!1,considered:0,promoted:0,demoted:0,unsure:0,failed:0,capped:!1};let t=e.rescore.band??ds,s=e.rescore.cap??Mu,n=e.rescore.model??Rn,r=new Map(e.scannables.map(m=>[m.id,m])),o=Du(e.proposals,r,t);if(o.length===0)return{proposals:e.proposals,ran:!0,considered:0,promoted:0,demoted:0,unsure:0,failed:0,capped:!1};if(o.length>s)return{proposals:e.proposals,ran:!1,considered:o.length,promoted:0,demoted:0,unsure:0,failed:0,capped:!0};let i=new Map,a=0,d=0,l=0,u=0;for(let m=0;m<o.length&&!e.signal?.aborted;m++){let g=o[m],f=await $u(g,{model:n,signal:e.signal});f?(i.set(Fu(g.step1),f),f.verdict==="same_workflow"?a++:f.verdict==="unrelated"?d++:l++):u++,e.onProgress?.({phase:"rescoring",current:m+1,total:o.length,verdict:f?.verdict??"failed"})}return{proposals:Pu({proposals:e.proposals,rescored:i,applyThreshold:e.applyThreshold}),ran:!0,considered:o.length,promoted:a,demoted:d,unsure:l,failed:u,capped:!1}}async function YT(e){let t=_(),s=new Map(e.rows.map(l=>[l.id,l])),n=Cu(e.edges,e.scannables),r=new Date().toISOString(),o=[],i=new Map,a=0;for(let l of n){if(a++,e.signal?.aborted)break;let u=s.get(l.rootId),p=Uu(u);if(!e.llmNames){i.set(l.rootId,p),e.onProgress?.({phase:"naming",current:a,total:n.length,thread_name:p});continue}let g=await HT({rootRow:u,sessionIds:l.sessionIds,rowById:s,signal:e.signal,model:e.model})??p;i.set(l.rootId,g),e.onProgress?.({phase:"naming",current:a,total:n.length,thread_name:g})}return t.transaction(()=>{for(let l of n){if(!i.has(l.rootId))continue;let u=s.get(l.rootId),p=`auto-scan-${BT()}`,m=i.get(l.rootId)??Uu(u),g=`Auto-detected workflow chain (${l.sessionIds.length} sessions). Source: auto-scan-v1.`;t.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, ?, ?)").run(p,m,g,r),t.prepare(`INSERT INTO thread_edges
|
|
1755
1755
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1756
1756
|
VALUES (?, ?, NULL, 'origin', 1.0, 'auto-scan-v1', ?)`).run(p,l.rootId,r);let f=new Map;for(let h of e.edges)l.sessionIds.includes(h.child_id)&&f.set(h.child_id,h);for(let h of l.sessionIds){if(h===l.rootId)continue;let E=f.get(h);E&&t.prepare(`INSERT INTO thread_edges
|
|
1757
1757
|
(thread_id, session_id, parent_session_id, role, confidence, source, added_at)
|
|
1758
|
-
VALUES (?, ?, ?, 'child', ?, 'auto-scan-v1', ?)`).run(p,h,E.parent_id,E.confidence,r)}o.push({thread_id:p,name:m,session_count:l.sessionIds.length})}})(),{project:e.project,threads:o}}async function Au(e){let t=!!e.apply,s=e.confidenceMin?Number(e.confidenceMin):t?Mo:Do;if(!Number.isFinite(s)||s<0||s>1){console.error(c.err("--confidence-min must be a number in [0, 1]")),process.exitCode=1;return}let n=!!e.llmRescore,r={lo:e.rescoreBandLo?Number(e.rescoreBandLo):ds.lo,hi:e.rescoreBandHi?Number(e.rescoreBandHi):ds.hi};if(n&&(!Number.isFinite(r.lo)||!Number.isFinite(r.hi)||r.lo<0||r.hi>1||r.lo>r.hi)){console.error(c.err("--rescore-band-lo/hi must satisfy 0 \u2264 lo \u2264 hi \u2264 1")),process.exitCode=1;return}let o=e.rescoreModel??yn,i=IT({project:e.project});if(i.length===0){console.log(c.dim("no sessions matched"));return}let{byProject:a,scannablesByProject:d}=vT(i),l=n?Math.min(s,r.lo):s,u=new Map,p=0;for(let[g]of a){let f=d.get(g)??[],h=fu(f,l);if(n&&h.length>0){let E=await DT({proposals:h,scannables:f,applyThreshold:s,rescore:{enabled:!0,band:r,model:o}});h=E.proposals,E.capped?(console.error(c.err(`[${g}] borderline pairs exceeded cap (${E.considered}); skipped LLM rescore. Tighten the band or scan a smaller project.`)),h=h.filter(b=>b.confidence>=s)):n?E.failed===E.considered&&E.considered>0&&(h=h.filter(b=>b.confidence>=s)):h=h.filter(b=>b.confidence>=s)}else n&&(h=h.filter(E=>E.confidence>=s));u.set(g,{proposals:h,scannables:f}),p+=h.length}if(t){if(p===0){console.log(c.dim("no edges above threshold; nothing to apply"));return}let g={threads_created:0,edges_written:0,per_project:[]},f=!e.noLlmNames;for(let[h,{proposals:E,scannables:b}]of u){if(E.length===0)continue;let S=await $T({project:h,rows:a.get(h),edges:E,scannables:b,llmNames:f});g.per_project.push(S),g.threads_created+=S.threads.length,g.edges_written+=E.length+S.threads.length}if(e.json){console.log(JSON.stringify({mode:"apply",threshold:s,...g},null,2));return}console.log(c.ok(`Applied ${g.threads_created} thread${g.threads_created===1?"":"s"} with ${g.edges_written} edges total (threshold ${s}).`));for(let h of g.per_project){console.log(` ${c.bold(h.project)}`);for(let E of h.threads)console.log(` ${c.dim(E.thread_id.slice(0,16)+"\u2026")} ${E.name} ${c.dim(`(${E.session_count} sessions)`)}`)}console.log(),console.log(c.dim("Rollback: sqlite3 ~/.recall/db.sqlite \\")),console.log(c.dim(` "DELETE FROM thread_edges WHERE source='auto-scan-v1';`)),console.log(c.dim(` DELETE FROM threads WHERE id LIKE 'auto-scan-%';"`));return}if(e.json){let g=[];for(let[f,{proposals:h}]of u){let E=new Map(a.get(f).map(b=>[b.id,b]));for(let b of h)g.push({project:f,parent_id:b.parent_id,parent_label:wn(E.get(b.parent_id)),child_id:b.child_id,child_label:wn(E.get(b.child_id)),confidence:b.confidence,signals:b.signals,reasons:b.reasons})}console.log(JSON.stringify({mode:"dry-run",threshold:s,total:p,edges:g},null,2));return}console.log(c.dim(`recall thread scan \xB7 ${a.size} project${a.size===1?"":"s"} \xB7 DRY RUN \xB7 threshold ${s}`)),console.log(c.dim(" Algorithm: temporal + continuation phrase + file overlap + same brand.")),console.log();for(let[g,f]of a){let{proposals:h}=u.get(g),E=new Map(f.map(S=>[S.id,S]));if(console.log(c.bold(g)),console.log(c.dim(` ${f.length} eligible session${f.length===1?"":"s"}; ${h.length} parent-child edge${h.length===1?"":"s"} proposed.`)),h.length===0){console.log(c.dim(" (no edges above threshold \u2014 all sessions are origins)")),console.log();continue}let b=[...h].sort((S,R)=>{let T=E.get(S.child_id),L=E.get(R.child_id);return(T.started_at??"").localeCompare(L.started_at??"")});for(let S of b){let R=E.get(S.parent_id),T=E.get(S.child_id),L=S.confidence.toFixed(2);console.log(` ${c.dim(Lu(R.started_at).padEnd(16))} ${c.bold("PARENT")} ${wn(R)}`),console.log(` ${c.dim(Lu(T.started_at).padEnd(16))} ${c.bold(" \u2514\u2500 child")} ${wn(T)} ${c.dim(`[conf ${L}]`)}`),console.log(` ${" ".repeat(28)}${c.dim(S.reasons.join(" \xB7 "))}`),console.log()}}let m=Array.from(a.entries()).reduce((g,[f,h])=>g+(h.length-(u.get(f)?.proposals.length??0)),0);console.log(`${p} edge${p===1?"":"s"} proposed across ${a.size} project${a.size===1?"":"s"}; ${m} origin session${m===1?"":"s"} (no proposed parent).`),console.log(c.dim(` This is a DRY RUN. To write: rerun with --apply (default threshold ${Mo}).`))}v();P();import{existsSync as PT,readFileSync as FT}from"node:fs";function jT(){let e=`${x}/daemon.port`;if(!PT(e))return null;try{let t=FT(e,"utf8").trim();return/^\d+$/.test(t)?t:null}catch{return null}}async function UT(e){try{return(await ze(`http://127.0.0.1:${e}/api/health`,{signal:AbortSignal.timeout(1e4)})).ok}catch{return!1}}async function BT(e){let t=await ze(`http://127.0.0.1:${e}/api/projects`);if(!t.ok)throw new Error(`failed to list projects: HTTP ${t.status}`);return await t.json()}function HT(e,t){let s=t.replace(/\/+$/,""),n=null,r=-1;for(let o of e){if(!o.decoded_path)continue;let i=o.decoded_path.replace(/\/+$/,"");(s===i||s.startsWith(i+"/"))&&i.length>r&&(n=o,r=i.length)}return n}function WT(e,t){let s=e.find(r=>r.name===t);if(s)return s;let n=e.filter(r=>r.name.toLowerCase().includes(t.toLowerCase()));return n.length===1?n[0]:null}function Ou(e){return e.alias||e.auto_title||(e.first_user_message?K(e.first_user_message,60):"(no title)")}function XT(e){if(!e)return"?";let t=new Date(e);if(Number.isNaN(t.getTime()))return"?";let s=n=>String(n).padStart(2,"0");return`${t.getFullYear()}-${s(t.getMonth()+1)}-${s(t.getDate())} ${s(t.getHours())}:${s(t.getMinutes())}`}function JT(e,t){let s=t==="preflight"?c.dim(`recall threads sync \xB7 ${e.project.name} \xB7 PREFLIGHT (no writes)`):c.dim(`recall threads sync \xB7 ${e.project.name}`);if(console.log(s),console.log(),e.thread.exists?console.log(` ${c.bold("Thread")} ${e.thread.name} ${c.dim(`(reusing, ${e.thread.existing_session_count} existing session${e.thread.existing_session_count===1?"":"s"})`)}`):console.log(` ${c.bold("Thread")} ${e.thread.name} ${c.ok("(new)")}`),console.log(),e.candidates.length===0){if(console.log(c.dim(" No active sessions in the rolling window.")),e.warnings.length>0)for(let l of e.warnings)console.log(c.warn(` \u26A0 ${l}`));return}let n=new Map;for(let l of e.proposed_edges)n.set(l.child_id,l);let r=new Map;for(let l of e.preserved_manual_edges)r.set(l.session_id,l.parent_session_id);let o=new Set(e.candidates.map(l=>l.session_id)),i=e.proposed_additions.length,a=e.candidates.length-i;console.log(` ${c.bold("Candidates")} ${e.candidates.length} active session${e.candidates.length===1?"":"s"} ${c.dim(`(${i} new \xB7 ${a} keep)`)}`);let d=String(e.candidates.length).length;for(let l=0;l<e.candidates.length;l++){let u=e.candidates[l],m=e.proposed_additions.some(S=>S.session_id===u.session_id)?c.ok("NEW "):c.dim("keep"),g=n.get(u.session_id)??(r.has(u.session_id)&&r.get(u.session_id)!==null,null),f=g&&o.has(g.parent_id)?" \u2514\u2500 ":" ",h=XT(u.started_at),E=c.dim(`${String(l+1).padStart(d," ")}.`);console.log(` ${E} ${m} ${c.dim(h.padEnd(16))} ${f}${c.dim(u.session_id.slice(0,8))} ${Ou(u)}`);let b=" ".repeat(d+2);if(g&&o.has(g.parent_id)){let S=g.confidence.toFixed(2),R=Ou(e.candidates.find(T=>T.session_id===g.parent_id));console.log(` ${b}${" ".repeat(28)} parent ${c.dim(g.parent_id.slice(0,8))} (${R}) ${c.dim(`[conf ${S}]`)}`),console.log(` ${b}${" ".repeat(28)} ${c.dim(g.reasons.join(" \xB7 "))}`)}if(r.has(u.session_id)){let S=r.get(u.session_id);S&&console.log(` ${b}${" ".repeat(28)} ${c.bold("manual parent preserved")} ${c.dim(S.slice(0,8))}`)}}if(e.preserved_manual_edges.length>0&&(console.log(),console.log(c.dim(` ${e.preserved_manual_edges.length} manual edge${e.preserved_manual_edges.length===1?"":"s"} will be preserved.`))),e.warnings.length>0){console.log();for(let l of e.warnings)console.log(c.warn(` \u26A0 ${l}`))}}function GT(e){console.log(),console.log(c.ok(`Added ${e.added} session${e.added===1?"":"s"}, set ${e.edges_set} parent edge${e.edges_set===1?"":"s"}, preserved ${e.preserved_manual} manual edge${e.preserved_manual===1?"":"s"}.`)),console.log(c.dim(` thread_id: ${e.thread_id}`))}async function vu(e){let t=jT();if(!t){console.error(c.err("Daemon is not running. Start it with `recall start`, then re-run this command.")),process.exitCode=1;return}if(!await UT(t)){console.error(c.err(`Daemon on port ${t} is not responding. Try \`recall stop && recall start\`.`)),process.exitCode=1;return}let n;try{n=await BT(t)}catch(l){console.error(c.err(l.message)),process.exitCode=1;return}let r;if(e.project){if(r=WT(n,e.project),!r){console.error(c.err(`No project matching "${e.project}". Run \`recall projects\` to list known projects.`)),process.exitCode=1;return}}else{let l=process.cwd();if(r=HT(n,l),!r){console.error(c.err(`Current directory (${l}) does not match any known project. Pass --project <name> or cd into a project root.`)),process.exitCode=1;return}}let o=e.windowHours?Number(e.windowHours):void 0;if(o!==void 0&&(!Number.isFinite(o)||o<=0)){console.error(c.err("--window-hours must be a positive number")),process.exitCode=1;return}let i=e.preflight?"preflight":"apply",a={project_id:r.id,mode:i};o!==void 0&&(a.window_hours=o);let d;try{let l=await st("POST",`http://127.0.0.1:${t}/api/threads/sync-active`,a);if(!l.ok){let u=await l.text().catch(()=>"");throw new Error(`HTTP ${l.status}: ${u.slice(0,200)}`)}d=await l.json()}catch(l){console.error(c.err(`Sync failed: ${l.message}`)),process.exitCode=1;return}if(e.json){console.log(JSON.stringify({mode:i,...d},null,2));return}JT(d.plan,i),i==="apply"&&d.result?GT(d.result):i==="preflight"&&(console.log(),console.log(c.dim(" This is a PREFLIGHT. To write: re-run without --preflight.")))}w();import{writeFileSync as fR,existsSync as _R,mkdirSync as hR}from"node:fs";import{join as Vu}from"node:path";import{homedir as ER}from"node:os";import{execSync as Xt}from"node:child_process";Ke();import uR from"satori";import pR from"sharp";import{readFileSync as Bo}from"node:fs";import{join as On}from"node:path";var Ho=On(ee(),"dist","share","fonts"),zu=!1,qu=[];function mR(){return zu||(qu=[{name:"Inter",data:Bo(On(Ho,"Inter-Regular.woff")),weight:400,style:"normal"},{name:"Inter",data:Bo(On(Ho,"Inter-Bold.woff")),weight:700,style:"normal"},{name:"JetBrains Mono",data:Bo(On(Ho,"JetBrainsMono-Regular.woff")),weight:400,style:"normal"}],zu=!0),qu}async function gR(e){switch(e){case"A":return await Promise.resolve().then(()=>($u(),Du));case"B":return await Promise.resolve().then(()=>(ju(),Fu));case"C":return await Promise.resolve().then(()=>(Hu(),Bu));case"D":return await Promise.resolve().then(()=>(Ju(),Xu));case"E":return await Promise.resolve().then(()=>(Yu(),Gu))}}async function vn(e,t){let n=(await gR(e)).render(t),i=await uR(n,{width:1080,height:e==="E"?1920:1350,fonts:mR()});return await pR(Buffer.from(i)).png({quality:90}).toBuffer()}function Ku(e,t){let s=new URLSearchParams({v:"1",s:t,t:e.sessionTitle,d:e.sessionDate.slice(0,10),tk:String(e.tokenCount),m:String(e.messageCount)});return typeof e.inputTokens=="number"&&s.set("i",String(e.inputTokens)),typeof e.outputTokens=="number"&&s.set("o",String(e.outputTokens)),e.verdict&&s.set("q",e.verdict),`https://clauderecall.com/card?${s.toString()}`}function bR(e,t){if(t.length>=32)return e.prepare("SELECT id FROM sessions WHERE id = ?").get(t)?.id??null;let s=e.prepare("SELECT session_id FROM session_aliases WHERE alias = ?").get(t);if(s)return s.session_id;let n=e.prepare("SELECT id FROM sessions WHERE id LIKE ? LIMIT 2").all(t+"%");return n.length===1?n[0].id:(n.length===0||process.stderr.write(`ambiguous id prefix "${t}". be more specific.
|
|
1759
|
-
`),null)}function
|
|
1758
|
+
VALUES (?, ?, ?, 'child', ?, 'auto-scan-v1', ?)`).run(p,h,E.parent_id,E.confidence,r)}o.push({thread_id:p,name:m,session_count:l.sessionIds.length})}})(),{project:e.project,threads:o}}async function Bu(e){let t=!!e.apply,s=e.confidenceMin?Number(e.confidenceMin):t?Uo:Bo;if(!Number.isFinite(s)||s<0||s>1){console.error(c.err("--confidence-min must be a number in [0, 1]")),process.exitCode=1;return}let n=!!e.llmRescore,r={lo:e.rescoreBandLo?Number(e.rescoreBandLo):ds.lo,hi:e.rescoreBandHi?Number(e.rescoreBandHi):ds.hi};if(n&&(!Number.isFinite(r.lo)||!Number.isFinite(r.hi)||r.lo<0||r.hi>1||r.lo>r.hi)){console.error(c.err("--rescore-band-lo/hi must satisfy 0 \u2264 lo \u2264 hi \u2264 1")),process.exitCode=1;return}let o=e.rescoreModel??Rn,i=XT({project:e.project});if(i.length===0){console.log(c.dim("no sessions matched"));return}let{byProject:a,scannablesByProject:d}=WT(i),l=n?Math.min(s,r.lo):s,u=new Map,p=0;for(let[g]of a){let f=d.get(g)??[],h=ku(f,l);if(n&&h.length>0){let E=await GT({proposals:h,scannables:f,applyThreshold:s,rescore:{enabled:!0,band:r,model:o}});h=E.proposals,E.capped?(console.error(c.err(`[${g}] borderline pairs exceeded cap (${E.considered}); skipped LLM rescore. Tighten the band or scan a smaller project.`)),h=h.filter(b=>b.confidence>=s)):n?E.failed===E.considered&&E.considered>0&&(h=h.filter(b=>b.confidence>=s)):h=h.filter(b=>b.confidence>=s)}else n&&(h=h.filter(E=>E.confidence>=s));u.set(g,{proposals:h,scannables:f}),p+=h.length}if(t){if(p===0){console.log(c.dim("no edges above threshold; nothing to apply"));return}let g={threads_created:0,edges_written:0,per_project:[]},f=!e.noLlmNames;for(let[h,{proposals:E,scannables:b}]of u){if(E.length===0)continue;let S=await YT({project:h,rows:a.get(h),edges:E,scannables:b,llmNames:f});g.per_project.push(S),g.threads_created+=S.threads.length,g.edges_written+=E.length+S.threads.length}if(e.json){console.log(JSON.stringify({mode:"apply",threshold:s,...g},null,2));return}console.log(c.ok(`Applied ${g.threads_created} thread${g.threads_created===1?"":"s"} with ${g.edges_written} edges total (threshold ${s}).`));for(let h of g.per_project){console.log(` ${c.bold(h.project)}`);for(let E of h.threads)console.log(` ${c.dim(E.thread_id.slice(0,16)+"\u2026")} ${E.name} ${c.dim(`(${E.session_count} sessions)`)}`)}console.log(),console.log(c.dim("Rollback: sqlite3 ~/.recall/db.sqlite \\")),console.log(c.dim(` "DELETE FROM thread_edges WHERE source='auto-scan-v1';`)),console.log(c.dim(` DELETE FROM threads WHERE id LIKE 'auto-scan-%';"`));return}if(e.json){let g=[];for(let[f,{proposals:h}]of u){let E=new Map(a.get(f).map(b=>[b.id,b]));for(let b of h)g.push({project:f,parent_id:b.parent_id,parent_label:xn(E.get(b.parent_id)),child_id:b.child_id,child_label:xn(E.get(b.child_id)),confidence:b.confidence,signals:b.signals,reasons:b.reasons})}console.log(JSON.stringify({mode:"dry-run",threshold:s,total:p,edges:g},null,2));return}console.log(c.dim(`recall thread scan \xB7 ${a.size} project${a.size===1?"":"s"} \xB7 DRY RUN \xB7 threshold ${s}`)),console.log(c.dim(" Algorithm: temporal + continuation phrase + file overlap + same brand.")),console.log();for(let[g,f]of a){let{proposals:h}=u.get(g),E=new Map(f.map(S=>[S.id,S]));if(console.log(c.bold(g)),console.log(c.dim(` ${f.length} eligible session${f.length===1?"":"s"}; ${h.length} parent-child edge${h.length===1?"":"s"} proposed.`)),h.length===0){console.log(c.dim(" (no edges above threshold \u2014 all sessions are origins)")),console.log();continue}let b=[...h].sort((S,R)=>{let T=E.get(S.child_id),L=E.get(R.child_id);return(T.started_at??"").localeCompare(L.started_at??"")});for(let S of b){let R=E.get(S.parent_id),T=E.get(S.child_id),L=S.confidence.toFixed(2);console.log(` ${c.dim(ju(R.started_at).padEnd(16))} ${c.bold("PARENT")} ${xn(R)}`),console.log(` ${c.dim(ju(T.started_at).padEnd(16))} ${c.bold(" \u2514\u2500 child")} ${xn(T)} ${c.dim(`[conf ${L}]`)}`),console.log(` ${" ".repeat(28)}${c.dim(S.reasons.join(" \xB7 "))}`),console.log()}}let m=Array.from(a.entries()).reduce((g,[f,h])=>g+(h.length-(u.get(f)?.proposals.length??0)),0);console.log(`${p} edge${p===1?"":"s"} proposed across ${a.size} project${a.size===1?"":"s"}; ${m} origin session${m===1?"":"s"} (no proposed parent).`),console.log(c.dim(` This is a DRY RUN. To write: rerun with --apply (default threshold ${Uo}).`))}v();P();import{existsSync as zT,readFileSync as qT}from"node:fs";function KT(){let e=`${x}/daemon.port`;if(!zT(e))return null;try{let t=qT(e,"utf8").trim();return/^\d+$/.test(t)?t:null}catch{return null}}async function VT(e){try{return(await ze(`http://127.0.0.1:${e}/api/health`,{signal:AbortSignal.timeout(1e4)})).ok}catch{return!1}}async function ZT(e){let t=await ze(`http://127.0.0.1:${e}/api/projects`);if(!t.ok)throw new Error(`failed to list projects: HTTP ${t.status}`);return await t.json()}function QT(e,t){let s=t.replace(/\/+$/,""),n=null,r=-1;for(let o of e){if(!o.decoded_path)continue;let i=o.decoded_path.replace(/\/+$/,"");(s===i||s.startsWith(i+"/"))&&i.length>r&&(n=o,r=i.length)}return n}function eR(e,t){let s=e.find(r=>r.name===t);if(s)return s;let n=e.filter(r=>r.name.toLowerCase().includes(t.toLowerCase()));return n.length===1?n[0]:null}function Hu(e){return e.alias||e.auto_title||(e.first_user_message?K(e.first_user_message,60):"(no title)")}function tR(e){if(!e)return"?";let t=new Date(e);if(Number.isNaN(t.getTime()))return"?";let s=n=>String(n).padStart(2,"0");return`${t.getFullYear()}-${s(t.getMonth()+1)}-${s(t.getDate())} ${s(t.getHours())}:${s(t.getMinutes())}`}function sR(e,t){let s=t==="preflight"?c.dim(`recall threads sync \xB7 ${e.project.name} \xB7 PREFLIGHT (no writes)`):c.dim(`recall threads sync \xB7 ${e.project.name}`);if(console.log(s),console.log(),e.thread.exists?console.log(` ${c.bold("Thread")} ${e.thread.name} ${c.dim(`(reusing, ${e.thread.existing_session_count} existing session${e.thread.existing_session_count===1?"":"s"})`)}`):console.log(` ${c.bold("Thread")} ${e.thread.name} ${c.ok("(new)")}`),console.log(),e.candidates.length===0){if(console.log(c.dim(" No active sessions in the rolling window.")),e.warnings.length>0)for(let l of e.warnings)console.log(c.warn(` \u26A0 ${l}`));return}let n=new Map;for(let l of e.proposed_edges)n.set(l.child_id,l);let r=new Map;for(let l of e.preserved_manual_edges)r.set(l.session_id,l.parent_session_id);let o=new Set(e.candidates.map(l=>l.session_id)),i=e.proposed_additions.length,a=e.candidates.length-i;console.log(` ${c.bold("Candidates")} ${e.candidates.length} active session${e.candidates.length===1?"":"s"} ${c.dim(`(${i} new \xB7 ${a} keep)`)}`);let d=String(e.candidates.length).length;for(let l=0;l<e.candidates.length;l++){let u=e.candidates[l],m=e.proposed_additions.some(S=>S.session_id===u.session_id)?c.ok("NEW "):c.dim("keep"),g=n.get(u.session_id)??(r.has(u.session_id)&&r.get(u.session_id)!==null,null),f=g&&o.has(g.parent_id)?" \u2514\u2500 ":" ",h=tR(u.started_at),E=c.dim(`${String(l+1).padStart(d," ")}.`);console.log(` ${E} ${m} ${c.dim(h.padEnd(16))} ${f}${c.dim(u.session_id.slice(0,8))} ${Hu(u)}`);let b=" ".repeat(d+2);if(g&&o.has(g.parent_id)){let S=g.confidence.toFixed(2),R=Hu(e.candidates.find(T=>T.session_id===g.parent_id));console.log(` ${b}${" ".repeat(28)} parent ${c.dim(g.parent_id.slice(0,8))} (${R}) ${c.dim(`[conf ${S}]`)}`),console.log(` ${b}${" ".repeat(28)} ${c.dim(g.reasons.join(" \xB7 "))}`)}if(r.has(u.session_id)){let S=r.get(u.session_id);S&&console.log(` ${b}${" ".repeat(28)} ${c.bold("manual parent preserved")} ${c.dim(S.slice(0,8))}`)}}if(e.preserved_manual_edges.length>0&&(console.log(),console.log(c.dim(` ${e.preserved_manual_edges.length} manual edge${e.preserved_manual_edges.length===1?"":"s"} will be preserved.`))),e.warnings.length>0){console.log();for(let l of e.warnings)console.log(c.warn(` \u26A0 ${l}`))}}function nR(e){console.log(),console.log(c.ok(`Added ${e.added} session${e.added===1?"":"s"}, set ${e.edges_set} parent edge${e.edges_set===1?"":"s"}, preserved ${e.preserved_manual} manual edge${e.preserved_manual===1?"":"s"}.`)),console.log(c.dim(` thread_id: ${e.thread_id}`))}async function Wu(e){let t=KT();if(!t){console.error(c.err("Daemon is not running. Start it with `recall start`, then re-run this command.")),process.exitCode=1;return}if(!await VT(t)){console.error(c.err(`Daemon on port ${t} is not responding. Try \`recall stop && recall start\`.`)),process.exitCode=1;return}let n;try{n=await ZT(t)}catch(l){console.error(c.err(l.message)),process.exitCode=1;return}let r;if(e.project){if(r=eR(n,e.project),!r){console.error(c.err(`No project matching "${e.project}". Run \`recall projects\` to list known projects.`)),process.exitCode=1;return}}else{let l=process.cwd();if(r=QT(n,l),!r){console.error(c.err(`Current directory (${l}) does not match any known project. Pass --project <name> or cd into a project root.`)),process.exitCode=1;return}}let o=e.windowHours?Number(e.windowHours):void 0;if(o!==void 0&&(!Number.isFinite(o)||o<=0)){console.error(c.err("--window-hours must be a positive number")),process.exitCode=1;return}let i=e.preflight?"preflight":"apply",a={project_id:r.id,mode:i};o!==void 0&&(a.window_hours=o);let d;try{let l=await st("POST",`http://127.0.0.1:${t}/api/threads/sync-active`,a);if(!l.ok){let u=await l.text().catch(()=>"");throw new Error(`HTTP ${l.status}: ${u.slice(0,200)}`)}d=await l.json()}catch(l){console.error(c.err(`Sync failed: ${l.message}`)),process.exitCode=1;return}if(e.json){console.log(JSON.stringify({mode:i,...d},null,2));return}sR(d.plan,i),i==="apply"&&d.result?nR(d.result):i==="preflight"&&(console.log(),console.log(c.dim(" This is a PREFLIGHT. To write: re-run without --preflight.")))}w();import{writeFileSync as kR,existsSync as CR,mkdirSync as LR}from"node:fs";import{join as cp}from"node:path";import{homedir as NR}from"node:os";import{execSync as Xt}from"node:child_process";Ke();import wR from"satori";import TR from"sharp";import{readFileSync as Yo}from"node:fs";import{join as Mn}from"node:path";var zo=Mn(ee(),"dist","share","fonts"),op=!1,ip=[];function RR(){return op||(ip=[{name:"Inter",data:Yo(Mn(zo,"Inter-Regular.woff")),weight:400,style:"normal"},{name:"Inter",data:Yo(Mn(zo,"Inter-Bold.woff")),weight:700,style:"normal"},{name:"JetBrains Mono",data:Yo(Mn(zo,"JetBrainsMono-Regular.woff")),weight:400,style:"normal"}],op=!0),ip}async function xR(e){switch(e){case"A":return await Promise.resolve().then(()=>(Yu(),Gu));case"B":return await Promise.resolve().then(()=>(Ku(),qu));case"C":return await Promise.resolve().then(()=>(Qu(),Zu));case"D":return await Promise.resolve().then(()=>(sp(),tp));case"E":return await Promise.resolve().then(()=>(rp(),np))}}async function Dn(e,t){let n=(await xR(e)).render(t),i=await wR(n,{width:1080,height:e==="E"?1920:1350,fonts:RR()});return await TR(Buffer.from(i)).png({quality:90}).toBuffer()}function ap(e,t){let s=new URLSearchParams({v:"1",s:t,t:e.sessionTitle,d:e.sessionDate.slice(0,10),tk:String(e.tokenCount),m:String(e.messageCount)});return typeof e.inputTokens=="number"&&s.set("i",String(e.inputTokens)),typeof e.outputTokens=="number"&&s.set("o",String(e.outputTokens)),e.verdict&&s.set("q",e.verdict),`https://clauderecall.com/card?${s.toString()}`}function AR(e,t){if(t.length>=32)return e.prepare("SELECT id FROM sessions WHERE id = ?").get(t)?.id??null;let s=e.prepare("SELECT session_id FROM session_aliases WHERE alias = ?").get(t);if(s)return s.session_id;let n=e.prepare("SELECT id FROM sessions WHERE id LIKE ? LIMIT 2").all(t+"%");return n.length===1?n[0].id:(n.length===0||process.stderr.write(`ambiguous id prefix "${t}". be more specific.
|
|
1759
|
+
`),null)}function OR(e){return e.prepare("SELECT session_id FROM recall_events ORDER BY recalled_at DESC LIMIT 1").get()?.session_id??null}function vR(e,t){let s=e.prepare(`
|
|
1760
1760
|
SELECT s.id, s.started_at, s.message_count, s.first_user_message, s.auto_title,
|
|
1761
1761
|
s.total_input_tokens, s.total_output_tokens
|
|
1762
1762
|
FROM sessions s WHERE s.id = ?
|
|
1763
1763
|
`).get(t);if(!s)return null;let r=e.prepare("SELECT alias FROM session_aliases WHERE session_id = ?").get(t)?.alias||s.auto_title||s.first_user_message?.slice(0,80)||s.id.slice(0,8),o=e.prepare(`
|
|
1764
1764
|
SELECT tool_names, raw_json FROM messages
|
|
1765
1765
|
WHERE session_id = ? AND tool_names IS NOT NULL AND tool_names != ''
|
|
1766
|
-
`).all(t),i=o.length,a=new Set;for(let u of o){if(!/Read|Write|Edit/.test(u.tool_names))continue;let p=u.raw_json.match(/"(?:file_path|path)":\s*"([^"]+)"/g);if(p)for(let m of p){let g=m.match(/":\s*"([^"]+)"/);g&&a.add(g[1])}}let d=s.total_input_tokens??0,l=s.total_output_tokens??0;return{sessionTitle:r,sessionDate:s.started_at??new Date().toISOString(),recallDate:new Date().toISOString(),tokenCount:d+l,inputTokens:d,outputTokens:l,messageCount:s.message_count,filesReferenced:a.size,toolCallCount:i,verdict:""}}function
|
|
1767
|
-
`),process.exitCode=1;return}}else if(n=
|
|
1768
|
-
`),process.exitCode=1;return}let o=t.style&&["A","B","C","D"].includes(t.style.toUpperCase())?t.style.toUpperCase():"A";t.verdict&&(r.verdict=t.verdict);let i=await
|
|
1769
|
-
`),t.clipboard&&(
|
|
1770
|
-
`)),t.link){let u=
|
|
1766
|
+
`).all(t),i=o.length,a=new Set;for(let u of o){if(!/Read|Write|Edit/.test(u.tool_names))continue;let p=u.raw_json.match(/"(?:file_path|path)":\s*"([^"]+)"/g);if(p)for(let m of p){let g=m.match(/":\s*"([^"]+)"/);g&&a.add(g[1])}}let d=s.total_input_tokens??0,l=s.total_output_tokens??0;return{sessionTitle:r,sessionDate:s.started_at??new Date().toISOString(),recallDate:new Date().toISOString(),tokenCount:d+l,inputTokens:d,outputTokens:l,messageCount:s.message_count,filesReferenced:a.size,toolCallCount:i,verdict:""}}function IR(e){process.platform==="darwin"?Xt(`osascript -e 'set the clipboard to (read (POSIX file "${e}") as \xABclass PNGf\xBB)'`):process.platform==="linux"&&Xt(`xclip -selection clipboard -t image/png -i "${e}"`)}function MR(e){try{process.platform==="darwin"?Xt(`open "${e}"`):process.platform==="linux"?Xt(`xdg-open "${e}"`):process.platform==="win32"&&Xt(`start "" "${e}"`)}catch{}}async function lp(e,t){let s=_(),n;if(e){if(n=AR(s,e),!n){process.stderr.write(`session not found: ${e}
|
|
1767
|
+
`),process.exitCode=1;return}}else if(n=OR(s),!n){process.stderr.write("No recalled sessions yet. Run `recall context <id>` first, then `recall share`.\n"),process.exitCode=1;return}let r=vR(s,n);if(!r){process.stderr.write(`failed to load metadata for session ${n}
|
|
1768
|
+
`),process.exitCode=1;return}let o=t.style&&["A","B","C","D"].includes(t.style.toUpperCase())?t.style.toUpperCase():"A";t.verdict&&(r.verdict=t.verdict);let i=await Dn(o,r),a=n.slice(0,8),d=t.out??cp(NR(),"Downloads");CR(d)||LR(d,{recursive:!0});let l=cp(d,`recall-card-${a}.png`);if(kR(l,i),process.stderr.write(`Card saved to ${l}
|
|
1769
|
+
`),t.clipboard&&(IR(l),process.stderr.write(`Copied PNG to clipboard.
|
|
1770
|
+
`)),t.link){let u=ap(r,o);process.stdout.write(u+`
|
|
1771
1771
|
`),process.platform==="darwin"&&(Xt("pbcopy",{input:u}),process.stderr.write(`Link copied to clipboard.
|
|
1772
|
-
`))}t.open!==!1&&
|
|
1772
|
+
`))}t.open!==!1&&MR(l)}w();import{createInterface as DR}from"node:readline";import{writeFileSync as $R,existsSync as PR,mkdirSync as FR}from"node:fs";import{join as up}from"node:path";import{homedir as jR}from"node:os";function dp(e){return e.recallCount>=15&&e.uniqueSessionsRecalled>=10?"Context Architect":e.nightSessionRatio>.5?"Night Owl":e.sessionCount>30&&e.avgMessageCount<50?"Rapid Shipper":e.sessionCount<15&&e.avgMessageCount>200?"Deep Diver":e.debugTagRatio>.4?"Debugger":"Power User"}var Ct=["january","february","march","april","may","june","july","august","september","october","november","december"],UR=["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"];function BR(e){let t=new Date;if(!e){let i=t.getFullYear(),a=t.getMonth();return{start:`${i}-${String(a+1).padStart(2,"0")}-01`,end:a===11?`${i+1}-01-01`:`${i}-${String(a+2).padStart(2,"0")}-01`,label:`${Ct[a][0].toUpperCase()}${Ct[a].slice(1)} ${i}`}}let s=e.match(/^(\d{4})-(\d{2})$/);if(s){let i=parseInt(s[1],10),a=parseInt(s[2],10)-1;return a<0||a>11?null:{start:`${i}-${String(a+1).padStart(2,"0")}-01`,end:a===11?`${i+1}-01-01`:`${i}-${String(a+2).padStart(2,"0")}-01`,label:`${Ct[a][0].toUpperCase()}${Ct[a].slice(1)} ${i}`}}let n=Ct.indexOf(e.toLowerCase()),r=n===-1?UR.indexOf(e.toLowerCase()):-1,o=n!==-1?n:r;if(o!==-1){let i=t.getFullYear();return{start:`${i}-${String(o+1).padStart(2,"0")}-01`,end:o===11?`${i+1}-01-01`:`${i}-${String(o+2).padStart(2,"0")}-01`,label:`${Ct[o][0].toUpperCase()}${Ct[o].slice(1)} ${i}`}}return null}function HR(e){if(e.length===0)return"N/A";let t=r=>r===0?"12am":r<12?`${r}am`:r===12?"12pm":`${r-12}pm`,s=Math.min(...e),n=Math.max(...e);return`${t(s)}-${t(n+1>23?0:n+1)}`}async function pp(e,t){let s=BR(e);if(!s){process.stderr.write(`invalid month format: ${e}. Use YYYY-MM, month name, or abbreviation.
|
|
1773
1773
|
`),process.exitCode=1;return}let n=_(),r=n.prepare("SELECT COUNT(*) AS c FROM recall_events WHERE recalled_at >= ? AND recalled_at < ?").get(s.start,s.end).c,o=n.prepare("SELECT COALESCE(SUM(token_count), 0) AS s FROM recall_events WHERE recalled_at >= ? AND recalled_at < ?").get(s.start,s.end).s,i=n.prepare("SELECT COUNT(*) AS c FROM sessions WHERE indexed_at >= ? AND indexed_at < ?").get(s.start,s.end).c,a=n.prepare(`SELECT session_id, COUNT(*) AS c FROM recall_events
|
|
1774
1774
|
WHERE recalled_at >= ? AND recalled_at < ?
|
|
1775
1775
|
GROUP BY session_id ORDER BY c DESC LIMIT 1`).get(s.start,s.end),d="None";if(a){let U=n.prepare("SELECT alias FROM session_aliases WHERE session_id = ?").get(a.session_id),y=n.prepare("SELECT auto_title, first_user_message, id FROM sessions WHERE id = ?").get(a.session_id);d=U?.alias||y?.auto_title||y?.first_user_message?.slice(0,40)||a.session_id.slice(0,8)}let l=n.prepare("SELECT COALESCE(MAX(token_count), 0) AS m FROM recall_events WHERE recalled_at >= ? AND recalled_at < ?").get(s.start,s.end).m,u=n.prepare(`SELECT CAST(strftime('%H', started_at) AS INTEGER) AS h, COUNT(*) AS c
|
|
1776
1776
|
FROM sessions WHERE started_at >= ? AND started_at < ?
|
|
1777
|
-
GROUP BY h ORDER BY c DESC LIMIT 3`).all(s.start,s.end),p=
|
|
1777
|
+
GROUP BY h ORDER BY c DESC LIMIT 3`).all(s.start,s.end),p=HR(u.map(U=>U.h)),m=n.prepare("SELECT COUNT(DISTINCT session_id) AS c FROM recall_events WHERE recalled_at >= ? AND recalled_at < ?").get(s.start,s.end).c,g=n.prepare(`SELECT COUNT(*) AS cnt, COALESCE(AVG(message_count), 0) AS avg_msgs
|
|
1778
1778
|
FROM sessions WHERE started_at >= ? AND started_at < ?`).get(s.start,s.end),f=n.prepare(`SELECT COUNT(*) AS c FROM sessions
|
|
1779
1779
|
WHERE started_at >= ? AND started_at < ?
|
|
1780
1780
|
AND (CAST(strftime('%H', started_at) AS INTEGER) >= 22
|
|
1781
1781
|
OR CAST(strftime('%H', started_at) AS INTEGER) < 4)`).get(s.start,s.end).c,h=n.prepare(`SELECT COUNT(DISTINCT st.session_id) AS c
|
|
1782
1782
|
FROM session_tags st JOIN sessions s ON s.id = st.session_id
|
|
1783
1783
|
WHERE s.started_at >= ? AND s.started_at < ?
|
|
1784
|
-
AND (st.tag LIKE '%debug%' OR st.tag LIKE '%fix%' OR st.tag LIKE '%bug%' OR st.tag LIKE '%error%')`).get(s.start,s.end).c,E={recallCount:r,uniqueSessionsRecalled:m,sessionCount:g.cnt,avgMessageCount:g.avg_msgs,nightSessionRatio:g.cnt>0?f/g.cnt:0,debugTagRatio:g.cnt>0?h/g.cnt:0},b=
|
|
1785
|
-
`)}de();At();Ke();import{createRequire as
|
|
1786
|
-
`);let t=await
|
|
1787
|
-
`);let o=await Te(),i=Kt(),a=
|
|
1784
|
+
AND (st.tag LIKE '%debug%' OR st.tag LIKE '%fix%' OR st.tag LIKE '%bug%' OR st.tag LIKE '%error%')`).get(s.start,s.end).c,E={recallCount:r,uniqueSessionsRecalled:m,sessionCount:g.cnt,avgMessageCount:g.avg_msgs,nightSessionRatio:g.cnt>0?f/g.cnt:0,debugTagRatio:g.cnt>0?h/g.cnt:0},b=dp(E),S=50;try{let{computeAllHealthScores:U}=await Promise.resolve().then(()=>(Oo(),Md)),y=U();y.length>0&&(S=Math.round(y.reduce((B,M)=>B+M.score,0)/y.length))}catch{}let R=t.verdict??"";if(!t.verdict){let U=DR({input:process.stdin,output:process.stderr});R=await new Promise(y=>{U.question("Add your take (one line, or press Enter to skip): ",B=>{U.close(),y(B.trim())})})}let T={month:s.label,totalRecalls:r,totalTokensPiped:o,sessionsIndexed:i,mostRecalledSession:d,biggestRecallTokens:l,healthScore:S,mostActiveHours:p,archetype:b,verdict:R},L=await Dn("E",T),D=s.start.slice(0,7),A=t.out??up(jR(),"Downloads");PR(A)||FR(A,{recursive:!0});let $=up(A,`recall-wrapped-${D}.png`);$R($,L),process.stderr.write(`Wrapped card saved to ${$}
|
|
1785
|
+
`)}de();At();Ke();import{createRequire as WR}from"node:module";import{createInterface as XR}from"node:readline";import{stdin as JR,stdout as GR}from"node:process";var YR=WR(import.meta.url),zR=YR(`${ee()}/package.json`).version,fp="https://clauderecall.com/api/feedback",qo=process.env.RECALL_FEEDBACK_API??fp,mp=qo===fp,Ko=2e3;function _p(e){let t=XR({input:JR,output:GR});return new Promise(s=>t.question(e,n=>{t.close(),s(n.trim())}))}function gp(e){if(!/^[+-]?\d+$/.test(e))return null;let t=Number.parseInt(e,10);return!Number.isFinite(t)||t<1||t>5?null:t}async function qR(e){if(e.score!==void 0){let n=gp(e.score);return n===null?(console.error(`--score must be an integer from 1 to 5 (got "${e.score}").`),null):n}if(!process.stdin.isTTY)return console.error("feedback needs a TTY to prompt. Pass --score 1..5 (and optionally --message) to send non-interactively."),null;console.log("How is Claude Recall going for you?"),console.log(" 1 = bad 2 = meh 3 = ok 4 = good 5 = great"),console.log("");let t=await _p("Score (1-5, q to quit): ");if(t.toLowerCase()==="q"||t==="")return console.log("No worries, skipped."),null;let s=gp(t);return s===null?(console.error("Please enter an integer 1 through 5."),null):s}async function KR(e,t){if(e.message!==void 0)return e.message.trim();if(!process.stdin.isTTY)return"";let s=t<=2?"What is the biggest pain point? (Enter to skip): ":"Anything you would add? (Enter to skip): ";return await _p(s)}function VR(e){return e.length<=Ko?{value:e,truncated:!1}:{value:e.slice(0,Ko),truncated:!0}}async function hp(e={}){mp||process.stderr.write(`[recall] RECALL_FEEDBACK_API override active (${qo}); license token will NOT be sent.
|
|
1786
|
+
`);let t=await qR(e);if(t===null)return e.score!==void 0||!process.stdin.isTTY?1:0;let s=await KR(e,t),{value:n,truncated:r}=VR(s);r&&process.stderr.write(`[recall] --message truncated to ${Ko} characters before send.
|
|
1787
|
+
`);let o=await Te(),i=Kt(),a=mp&&o.tier==="pro"&&i?i.license_jwt:null,d={score:t,comment:n.length>0?n:null,surface:"cli",version:zR,os:process.platform,trigger_kind:"manual",license_jwt:a};try{let l=await fetch(qo,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d)});if(l.ok)return console.log(""),console.log("Thanks. Your feedback landed."),0;let u=await l.json().catch(()=>({}));return l.status===429?(console.log("You submitted feedback recently. Try again later."),0):(console.error(`Feedback failed: ${l.status} ${u.error??"unknown"}`),1)}catch(l){let u=l instanceof Error?l.message:"network error";return console.error(`Feedback could not be sent: ${u}`),1}}var K0=z0(import.meta.url),hs=K0("../package.json").version,C=new q0;C.name("recall").description("Searchable memory for every Claude Code session you have ever run. Local, fast, offline. Run `recall` (no args) for the dashboard, or `recall --help` for the full command list.").version(hs);C.command("index").description("Scan your Claude sessions and build the searchable database. Run this once after install.").option("-f, --force","reindex all files even if unchanged").option("-v, --verbose","show each file as it is processed").action(async e=>{await Ii(e)});C.command("list").description("List your sessions, newest first.").option("-p, --project <name>","filter by project name").option("-n, --limit <n>","max rows to show","30").option("-a, --all","include short sessions (2 messages or fewer)").action(e=>{Mi(e)});C.command("show <id>").description("Print a full session transcript. Accepts the full id or an 8-char prefix.").option("-r, --raw","print raw JSONL lines instead of formatted output").option("-n, --limit <n>","max messages to show").option("--no-pager","do not auto-pipe long output through less").action((e,t)=>{Ui(e,t)});C.command("search <query...>").description("Search every message in every session. Pass --semantic for fused keyword + summary + vector hits (delegates to the running daemon).").option("-p, --project <name>","restrict results to one project").option("-n, --limit <n>","max results","20").option("--semantic","fuse keyword + summary + vector hits via the daemon (Pro; requires `recall start`)").action(async(e,t)=>{await ta(e.join(" "),t)});C.command("projects").description("List every project with how many sessions are in each.").action(()=>{ra()});C.command("status").description("Show database size, session counts, and whether the daemon is running.").action(()=>{na()});C.command("start").description("Start the background daemon. Required for live tab-name tracking and the web UI.").action(async()=>{await Os()});C.command("stop").description("Stop the background daemon.").option("--all","also kill every MCP child and truncate the WAL (one-command WAL-pin recovery)").action(async e=>{process.exitCode=await da({all:e.all})});C.command("open").description("Open the local web UI in your browser. Starts the daemon if it is not already running.").action(async()=>{await ua()});C.command("tui").description("Browse and search sessions in an interactive terminal UI. No browser needed.").action(async()=>{let{runTui:e}=await Promise.resolve().then(()=>(Kp(),qp));await e()});C.command("context <id>").description("Pipe a past session into a fresh Claude chat. Prints condensed markdown to stdout.").option("-f, --full","include full tool call and result bodies (much longer)").option("--since <when>","only messages at or after this time (2h, 30m, 1d, YYYY-MM-DD, or ISO)").option("--subagents","also include subagent / sidechain messages").option("--prelude <text>",'prepend a custom header (e.g. "Continue this conversation")').action(async(e,t)=>{await Ma(e,t)});C.command("mcp").description("Expose Recall as an MCP server over stdio. Add this to your Claude Desktop / Claude Code MCP config to let Claude search your sessions.").option("--allow-writes","enable write tools (add_tag, set_alias, append_note, \u2026). Off by default; rate-limited and audited when on.").action(async e=>{await Da({allowWrites:!!e.allowWrites})});C.command("mcp-prune").description("Kill stuck MCP children that are holding SQLite connections. Default scope is orphans (parent claude is gone); pass --all to nuke every MCP child. Runs a WAL TRUNCATE on success so disk space comes back immediately.").option("--all","prune every MCP child, not just orphans").option("--yes","skip the interactive confirmation prompt").option("--json","emit a single JSON line instead of human-readable output (implies --yes)").action(async e=>{let t=await Ms({all:!!e.all,yes:!!e.yes,json:!!e.json});process.exit(t)});C.command("paste").description("Save clipboard content (or stdin) into Recall. Opt-in. Secrets are blocked by default; pass --force to override.").option("-l, --list","list archived pastes").option("--purge <id>","permanently delete one paste by id or 8-char prefix").option("-f, --force","skip the secret-scan confirmation prompt").option("--pipe","echo the content to stdout after archiving (for shell pipelines)").option("--dry-run","preview what would be archived without writing").option("--label <text>","short description shown in `recall paste --list`").action(async e=>{await $a(e)});C.command("audit-secrets").description("Scan the database for secrets that slipped through. Read-only by default; pass --redact to scrub in place.").option("--redact","rewrite offending rows in place (your source files are never touched)").option("-v, --verbose","print every session and message containing a hit").action(async e=>{await Pa(e)});C.command("titles [action]").description('Audit session titles in the current project. Defaults to "audit".').option("-p, --project <name>","project to audit (defaults to current cwd)").option("--json","machine-readable JSON output").option("--dry-run","classify but do not write the title_quality column").action(async(e,t)=>{if((e??"audit").toLowerCase()==="audit"){await ja(t);return}console.error(`Unknown titles action: ${e}. Supported: audit`),process.exitCode=1});C.command("import-vscode-state").description("Backfill missing tab names by reading VS Code, Cursor, and Windsurf workspace state. Dry-run by default; pass --apply to write.").option("-p, --project <name>","scope to a single project").option("--apply","write the proposed names (default: dry-run)").option("--min-score <n>","minimum match score from 0 to 1 (default 0.7)","0.7").option("--limit <n>","cap the number of proposals returned").option("--source <list>","which editors to scan: vscode | cursor | windsurf | all (default all)","all").option("--json","machine-readable JSON output").action(async e=>{await Ja(e)});C.command("semantic [action] [subAction]").description('Local on-device semantic search. Default action is "status". Free, local, zero tokens (Tier-2): install, status, reindex, uninstall. Advanced \u2014 sends summaries via your Claude plan and costs plan tokens (Tier-1): on, off, backfill, pause, resume, auto-extract <on|off>. Diagnostic: verify-spawn (confirms claude CLI honors --no-session-persistence before re-enabling Tier-1).').option("-n, --limit <n>","max sessions to process during backfill","1000").option("-f, --force","regenerate summaries even when one already exists").option("--rate <perMin>","sessions per minute when enabling").option("--model <name>","claude model id (e.g. claude-haiku-4-5-20251001)").addHelpText("after",`
|
|
1788
1788
|
Two lanes:
|
|
1789
1789
|
Tier-2 (default, recommended): on-device 768-d embeddings via bge-base-en-v1.5
|
|
1790
1790
|
+ sqlite-vec. No network, no token spend. Powered by:
|
|
@@ -1800,7 +1800,7 @@ Two lanes:
|
|
|
1800
1800
|
Reindex chunks-per-session cap (advanced):
|
|
1801
1801
|
Set RECALL_REINDEX_MAX_CHUNKS=200 to cap each session at 200 chunks for a
|
|
1802
1802
|
faster first pass. Default is 0 (no cap, full accuracy).
|
|
1803
|
-
`).action(async(e,t,s)=>{await
|
|
1804
|
-
`)}},r=performance.now(),{requireProOrExit:o}=await Promise.resolve().then(()=>(de(),Zt));await o("Similar sessions"),n("requireProOrExit",r);let i=performance.now(),{isModelInstalled:a}=await Promise.resolve().then(()=>(Js(),
|
|
1805
|
-
`);let f=performance.now();await d(),n("loadEmbedder (cold)",f)}let p=Math.max(1,Math.min(50,Number(t.limit??10))),m=performance.now(),g=await u(e,p);if(n("findSimilarSessions",m),g.length===0){console.log("No similar sessions found.");return}for(let f of g)console.log(` ${f.sessionId} similarity=${f.similarity.toFixed(3)}`)});C.command("stats [id]").description("How much you have spent and how many tokens you have used. No args = overview. Pass a session id for one session, or --project for one project.").option("-p, --project <name>","show stats for a single project").option("-d, --days <n>","restrict the overview to the last N days (7 or 30)").option("--backfill","compute usage for already-indexed messages (safe to rerun)").option("-n, --limit <n>","max messages to scan during backfill").option("--json","emit JSON instead of a formatted table").action(async(e,t)=>{await Cl(e,t)});C.command("correlate [id]").description("Link sessions to the git commits they wrote. Read-only. No args = batch mode; pass an id to correlate one.").option("-n, --limit <n>","max sessions to process in batch mode").option("--json","emit JSON instead of formatted output").action(async(e,t)=>{await Ol(e,t)});var oi=C.command("correlator").description("Diagnose and fix terminal-to-session matching. Subcommands: audit | debug | restore.");function qp(e){return e.description("Show how the daemon is matching open terminals to sessions right now.").option("--json","emit JSON instead of formatted output").action(async t=>{await pd(t)})}function Kp(e){return e.description("Restore aliases that `correlator audit --fix` cleared, when they still cleanly match an open terminal. Dry-run by default.").option("--apply","apply the restoration (otherwise dry-run only)").option("--json","emit JSON instead of formatted output").action(async t=>{await md(t)})}function Vp(e){return e.description("Find sessions with bad terminal-name aliases from the legacy race-window bug. Use --fix to clear suspects.").option("--fix","clear suspect aliases (otherwise dry-run only)").option("-w, --window <seconds>","collision window in seconds (default 60, clamped 5..600)").option("-p, --project <name>","limit detection and --fix to a single project").option("--json","emit JSON instead of formatted output").action(async t=>{await $l(t)})}qp(oi.command("debug"));Kp(oi.command("restore"));Vp(oi.command("audit"));qp(C.command("correlator-debug"));Kp(C.command("correlator-restore"));C.command("name <id-prefix> <name>").description("Rename a session. By default it stays linked to its terminal tab, so renaming the tab later still updates the name. Use --pin to lock the name permanently. Daemon must be running.").option("--json","emit JSON").option("--pin","lock the name; later tab renames will NOT overwrite it").action(async(e,t,s)=>{await cd(e,t,s)});C.command("doctor").description("Diagnose database health: size, WAL, FTS fragmentation, integrity, alias invariant. Exits non-zero on issues.").option("--json","emit JSON instead of formatted output").action(async e=>{let t=await Yl(e);process.exit(t)});C.command("optimize").description("Run maintenance: WAL checkpoint, FTS5 merge, planner stats. Use --vacuum to also reclaim free pages (requires daemon stopped).").option("--vacuum","also run VACUUM to reclaim free pages (rewrites the whole DB; daemon must be stopped)").option("--json","emit JSON instead of formatted output").action(async e=>{let t=await zl(e);process.exit(t)});C.command("purge-phantoms").description("Remove session rows produced by historical daemon-autonomous `claude -p` spawns (auto-titler, summariser, output-index, \u2026). Source JSONLs at ~/.claude/projects/ are NEVER touched. Requires daemon stopped.").option("--dry-run","count phantom rows without deleting anything").option("--json","emit JSON instead of formatted output").action(async e=>{let t=await Kl(e);process.exit(t)});C.command("archive").description("Power-user retention. Subcommands: list, run --before YYYY-MM-DD [--dry-run], restore <session-id>, auto <status|on|off> [--after N].").argument("[action]","list | run | restore | auto","list").argument("[arg]","session id (for restore) or sub-action (for auto)").option("--before <date>","YYYY-MM-DD cutoff for `run`").option("--dry-run","show what would be archived without moving rows").option("--after <days>","days threshold for `auto on`",e=>Number.parseInt(e,10)).action(async(e,t,s)=>{let n=e==="auto",r=await ad({_action:e,_subAction:n?t:void 0,_sessionId:n?void 0:t,before:typeof s.before=="string"?s.before:void 0,dryRun:s.dryRun===!0,after:typeof s.after=="number"?s.after:void 0});process.exit(r)});Vp(C.command("correlator-audit"));C.command("blame <sha>").description("Look up which session(s) wrote the code in a commit. Reverse of `correlate`.").option("--json","emit JSON instead of formatted output").action(async(e,t)=>{await gd(e,t)});C.command("digest").description("Today's rediscovery picks: a session worth revisiting, the costliest of the week, and the one behind your latest commit.").option("--json","emit JSON instead of formatted output").action(async e=>{await hd(e)});C.command("install-extension").description("Install the bundled VS Code / Cursor / Windsurf extension into your editor. Auto-detects which CLIs are installed.").option("--editor <name>","install only into one editor: code | cursor | code-insiders | windsurf").option("--print-path","print the path to the bundled .vsix and exit").action(async e=>{await wd(e)});C.command("health [project]").description("Score how well-organized each project's sessions are. Worst first, or pass a project name for a single breakdown.").option("--json","emit JSON instead of formatted output").action((e,t)=>{xd(e,t)});C.command("verify [action]").description('Toggle verification badges in the UI. Defaults to "status". Actions: on, off, status.').action(e=>{kd(e)});C.command("activate <license-key>").description("Activate your Pro license. Run once after purchase.").action(async e=>{await Dd(e)});C.command("upgrade").description("Open the Pro pricing page in your browser.").action(async()=>{await Pd()});C.command("trial [promo-code]").description("Start a 7-day Pro trial. With a promo code (e.g. RECALL7DAY), redeems it directly from the CLI. With no code, opens the signup page.").action(async e=>{await jd(e)});var Pn=C.command("telemetry").description('Manage the opt-in anonymous install ping. Defaults to "view".');Pn.command("view",{isDefault:!0}).description("Show current decision, state file path, and next-ping payload.").action(async()=>{await Yd(hs)});Pn.command("on").description("Opt in. One anonymous ping per machine per month. See https://clauderecall.com/telemetry").action(async()=>{await Jd()});Pn.command("off").description("Opt out. Deletes the nonce. No further pings.").action(async()=>{await Gd()});Pn.command("status").description("Alias for view.").action(async()=>{await Oo(hs)});var ii=C.command("license").description('Show or remove the Pro license on this machine. Defaults to "status".');ii.command("status",{isDefault:!0}).description("Show the current license tier.").action(async()=>{await Kd()});ii.command("deactivate").description("Remove the stored license from this machine. Reverses `recall activate`.").action(()=>{Zd()});ii.command("check").description("Force a revocation check against the license server. The daemon also runs this on a 24-hour timer in the background.").action(async()=>{await Vd()});var U0=C.command("thread").description("Group related sessions into threads, then list, show, link, merge, or scan them."),B0=C.command("threads").description("Alias for `thread`. Both forms accept the same subcommands.");function Zp(e){e.command("sync").description("Capture sessions running in this repo right now into a thread.").option("--preflight","preview the plan without writing").option("-p, --project <name>","override the cwd-based project").option("--window-hours <n>","rolling activity window (default 6)").option("--json","machine-readable JSON output").action(async t=>{await vu(t)}),e.command("scan").description("Auto-detect parent-child links across past sessions. Dry-run by default; pass --apply to write.").option("-p, --project <name>","scope to a single project").option("--json","emit JSON instead of formatted output").option("--apply","write edges (default: dry-run preview)").option("--confidence-min <n>","override threshold (dry-run 0.5, apply 0.7)").option("--no-llm-names","skip LLM naming (free, offline)").option("--llm-rescore","LLM re-score borderline pairs (small Haiku call each)").option("--rescore-band-lo <n>","low end of the borderline band (default 0.4)").option("--rescore-band-hi <n>","high end of the borderline band (default 0.7)").option("--rescore-model <id>","model id for the rescore call (default Haiku 4.5)").action(t=>{Au(t)}),e.command("list").description("List threads, newest first.").option("--archived","include archived threads").option("--json","emit JSON instead of a table").action(t=>{eu(t)}),e.command("show <id-prefix>").description("Show a thread's header and session tree.").option("--json","emit JSON instead of formatted output").action((t,s)=>{tu(t,s)}),e.command("new <name>").description("Create a new thread.").option("--origin <session-id>","seed with an origin session").option("--summary <text>","optional short summary").option("--json","emit JSON").action((t,s)=>{su(t,s)}),e.command("link <session-id>").description("Link a session into a thread.").requiredOption("--thread <id-prefix>","thread id or prefix").option("--parent <session-id>","parent session within the thread").option("--role <role>","origin | child (default inferred from --parent)").option("--json","emit JSON").action((t,s)=>{nu(t,s)}),e.command("unlink <session-id>").description("Remove a session from a thread.").requiredOption("--thread <id-prefix>","thread id or prefix").option("--json","emit JSON").action((t,s)=>{ru(t,s)}),e.command("set-parent <session-id>").description("Change a session's parent within a thread. Use --parent none to promote to origin.").requiredOption("--thread <id-prefix>","thread id or prefix").option("--parent <session-id|none>",'new parent, or "none" to promote to origin').option("--json","emit JSON").action((t,s)=>{ou(t,s)}),e.command("rename <id-prefix> <new-name>").description("Rename a thread.").option("--json","emit JSON").action((t,s,n)=>{iu(t,s,n)}),e.command("close <id-prefix>").description("Mark a thread as closed (work complete).").option("--json","emit JSON").action((t,s)=>{au(t,s)}),e.command("reopen <id-prefix>").description("Reopen a closed thread.").option("--json","emit JSON").action((t,s)=>{cu(t,s)}),e.command("archive <id-prefix>").description("Archive a thread (hidden from the default list).").option("--json","emit JSON").action((t,s)=>{lu(t,s)}),e.command("merge <source-id-prefix>").description("Merge one thread into another.").requiredOption("--into <id-prefix>","destination thread").option("--json","emit JSON").action((t,s)=>{du(t,s)}),e.command("split <id-prefix>").description("Split sessions out of a thread into a new thread.").requiredOption("--sessions <s1,s2,...>","comma-separated session ids or prefixes").requiredOption("--name <new-name>","name for the new thread").option("--json","emit JSON").action((t,s)=>{uu(t,s)})}Zp(U0);Zp(B0);C.command("share [id]").description("Save a session as a shareable PNG card. No args = your last recalled session. Saved to ~/Downloads and opened.").option("--style <A|B|C|D>","card style (default A: Minimal)").option("--verdict <quote>","your one-line take on the session").option("--link","also generate a shareable URL and copy it to the clipboard").option("--clipboard","also copy the PNG to the system clipboard").option("--out <path>","override the save directory").option("--no-open","do not auto-open the saved PNG").action(async(e,t)=>{await Zu(e,t)});C.command("feedback").description("Send a 1-5 rating and optional comment to the team. Interactive by default; pass --score (and optionally --message) to skip prompts.").option("--score <1-5>","rating from 1 (bad) to 5 (great)").option("--message <text>","optional comment to send with the rating").action(async e=>{let t=await ip(e);t!==0&&(process.exitCode=t)});C.command("wrapped [month]").description("Save your monthly recap as a shareable PNG card. Defaults to the current month. Accepts YYYY-MM, full name, or abbreviation.").option("--verdict <quote>","your one-line take on the month").option("--out <path>","override the save directory").action(async(e,t)=>{await tp(e,t)});process.env.CLAUDECODE&&!process.stdout.isTTY&&process.stderr.write(`<claude-code-hint v="1" type="plugin" value="claude-recall@claude-plugins-official" />
|
|
1806
|
-
`);C.action(async()=>{await
|
|
1803
|
+
`).action(async(e,t,s)=>{await Ac(e,{...s,_autoExtractAction:t})});C.command("embeddings [action]").description('Audit embedding coverage and backfill missing ones. Defaults to "audit". Actions: audit, backfill-summaries, backfill-messages.').option("-p, --project <name>","scope to a single project").option("-n, --limit <n>","max sessions to process during backfill").option("--json","emit JSON instead of formatted output").action(async(e,t)=>{await Dc(e,t)});var Es=C.command("infer").description("Discover links between sessions. Subcommands: outputs | citations | l1 | links | bug-patterns.");function tm(e){return e.description("Extract code, file, and error references from sessions in one project. Uses the local claude CLI.").requiredOption("-p, --project <name>","project name (required)").option("-n, --limit <n>","max sessions to process","200").option("-f, --force","re-extract even if already done").option("--model <name>","override the default Haiku model id").option("-y, --yes","skip the >50-session cost confirmation (for scripts)").option("--json","emit JSON summary instead of formatted output").action(async t=>{await Hc(t)})}function sm(e){return e.description("Find sessions that cite the same files or errors. Lands proposals for review.").requiredOption("-p, --project <name>","project name (required)").option("--json","emit JSON summary instead of formatted output").action(async t=>{await Zc(t)})}function nm(e){return e.description("Fast deterministic link inference across sessions. No LLM cost.").option("-p, --project <name>","restrict to one project (default: all)").option("--json","emit JSON summary instead of formatted output").action(async t=>{await tl(t)})}function rm(e){return e.description("LLM-powered link classification. Pre-filters by confidence, batches the survivors through your local claude CLI (Haiku).").requiredOption("-p, --project <name>","project name (required)").option("--min-conf <n>","pre-filter threshold (default 0.4)","0.4").option("-n, --limit <n>","max candidate pairs to classify (default 1000)","1000").option("--auto-promote","promote high-confidence pairs straight into session_links").option("--auto-promote-threshold <n>","promotion threshold (default 0.95)").option("--model <name>","override the default Haiku model id").option("--json","emit JSON summary instead of formatted output").action(async t=>{await cl(t)})}function om(e){return e.description("Cluster sessions that hit the same bug. Idempotent.").option("-p, --project <name>","restrict to one project (default: all)").option("--min-cluster-size <n>","skip clusters smaller than this (default 3)","3").option("--semantic","add an embedding-distance pass to catch near-duplicate snippets").option("-n, --limit <n>","cap clusters in the output (default 100)","100").option("--json","emit JSON summary instead of formatted output").action(async t=>{await bl(t)})}tm(Es.command("outputs"));sm(Es.command("citations"));nm(Es.command("l1"));rm(Es.command("links"));om(Es.command("bug-patterns"));tm(C.command("extract-outputs"));sm(C.command("infer-citations"));nm(C.command("infer-l1"));rm(C.command("infer-links"));om(C.command("infer-bug-patterns"));C.command("neighborhood <id>").description("Bundle a session with its parents, children, citations, and similar sessions. Pipe into `claude` to seed a fresh chat with rich context.").option("-b, --budget <n>","token budget for the bundle (default 4000)","4000").option("--scoring <mode>","pagerank | embedding-rerank | hybrid (default hybrid)","hybrid").option("-e, --edge-types <list>","comma-separated link types to include: citation,similar,skill_track,bug_pattern,wiki_link,temporal_proximity (default all)").option("--max-depth <n>","how far to walk the link graph (default 2)","2").option("--no-wiki-links","exclude manual wiki links").option("--include-suggestions","include pending suggestions (debug only)").option("--json","emit the full result as JSON instead of markdown").action(async(e,t)=>{await Ol(e,t)});C.command("similar <session-id>").description("Find sessions about the same topic as this one. Pro feature.").option("-n, --limit <n>","max results","10").action(async(e,t)=>{let s=process.env.RECALL_DEBUG_TIMING==="1",n=(f,h)=>{if(s){let E=Math.round(performance.now()-h);process.stderr.write(`[similar:timing] ${f}: ${E}ms
|
|
1804
|
+
`)}},r=performance.now(),{requireProOrExit:o}=await Promise.resolve().then(()=>(de(),Zt));await o("Similar sessions"),n("requireProOrExit",r);let i=performance.now(),{isModelInstalled:a}=await Promise.resolve().then(()=>(Js(),_c)),{loadEmbedder:d,getEmbedderStatus:l}=await Promise.resolve().then(()=>(yt(),Fr)),{findSimilarSessions:u}=await Promise.resolve().then(()=>(em(),Qp));if(n("dynamic imports",i),!a()){console.error("Model not installed. Run `recall semantic install` first."),process.exitCode=1;return}if(!l().loaded){process.stderr.write(`[recall similar] loading embedder model (first run is slow, ~5-30s)\u2026
|
|
1805
|
+
`);let f=performance.now();await d(),n("loadEmbedder (cold)",f)}let p=Math.max(1,Math.min(50,Number(t.limit??10))),m=performance.now(),g=await u(e,p);if(n("findSimilarSessions",m),g.length===0){console.log("No similar sessions found.");return}for(let f of g)console.log(` ${f.sessionId} similarity=${f.similarity.toFixed(3)}`)});C.command("stats [id]").description("How much you have spent and how many tokens you have used. No args = overview. Pass a session id for one session, or --project for one project.").option("-p, --project <name>","show stats for a single project").option("-d, --days <n>","restrict the overview to the last N days (7 or 30)").option("--backfill","compute usage for already-indexed messages (safe to rerun)").option("-n, --limit <n>","max messages to scan during backfill").option("--json","emit JSON instead of a formatted table").action(async(e,t)=>{await Fl(e,t)});C.command("correlate [id]").description("Link sessions to the git commits they wrote. Read-only. No args = batch mode; pass an id to correlate one.").option("-n, --limit <n>","max sessions to process in batch mode").option("--json","emit JSON instead of formatted output").action(async(e,t)=>{await Hl(e,t)});var ui=C.command("correlator").description("Diagnose and fix terminal-to-session matching. Subcommands: audit | debug | restore.");function im(e){return e.description("Show how the daemon is matching open terminals to sessions right now.").option("--json","emit JSON instead of formatted output").action(async t=>{await Td(t)})}function am(e){return e.description("Restore aliases that `correlator audit --fix` cleared, when they still cleanly match an open terminal. Dry-run by default.").option("--apply","apply the restoration (otherwise dry-run only)").option("--json","emit JSON instead of formatted output").action(async t=>{await Rd(t)})}function cm(e){return e.description("Find sessions with bad terminal-name aliases from the legacy race-window bug. Use --fix to clear suspects.").option("--fix","clear suspect aliases (otherwise dry-run only)").option("-w, --window <seconds>","collision window in seconds (default 60, clamped 5..600)").option("-p, --project <name>","limit detection and --fix to a single project").option("--json","emit JSON instead of formatted output").action(async t=>{await Yl(t)})}im(ui.command("debug"));am(ui.command("restore"));cm(ui.command("audit"));im(C.command("correlator-debug"));am(C.command("correlator-restore"));C.command("name <id-prefix> <name>").description("Rename a session. By default it stays linked to its terminal tab, so renaming the tab later still updates the name. Use --pin to lock the name permanently. Daemon must be running.").option("--json","emit JSON").option("--pin","lock the name; later tab renames will NOT overwrite it").action(async(e,t,s)=>{await bd(e,t,s)});C.command("doctor").description("Diagnose database health: size, WAL, FTS fragmentation, integrity, alias invariant. Exits non-zero on issues.").option("--json","emit JSON instead of formatted output").action(async e=>{let t=await rd(e);process.exit(t)});C.command("optimize").description("Run maintenance: WAL checkpoint, FTS5 merge, planner stats. Use --vacuum to also reclaim free pages (requires daemon stopped).").option("--vacuum","also run VACUUM to reclaim free pages (rewrites the whole DB; daemon must be stopped)").option("--json","emit JSON instead of formatted output").action(async e=>{let t=await od(e);process.exit(t)});C.command("purge-phantoms").description("Remove session rows produced by historical daemon-autonomous `claude -p` spawns (auto-titler, summariser, output-index, \u2026). Source JSONLs at ~/.claude/projects/ are NEVER touched. Requires daemon stopped.").option("--dry-run","count phantom rows without deleting anything").option("--json","emit JSON instead of formatted output").action(async e=>{let t=await ad(e);process.exit(t)});C.command("archive").description("Power-user retention. Subcommands: list, run --before YYYY-MM-DD [--dry-run], restore <session-id>, auto <status|on|off> [--after N].").argument("[action]","list | run | restore | auto","list").argument("[arg]","session id (for restore) or sub-action (for auto)").option("--before <date>","YYYY-MM-DD cutoff for `run`").option("--dry-run","show what would be archived without moving rows").option("--after <days>","days threshold for `auto on`",e=>Number.parseInt(e,10)).action(async(e,t,s)=>{let n=e==="auto",r=await Ed({_action:e,_subAction:n?t:void 0,_sessionId:n?void 0:t,before:typeof s.before=="string"?s.before:void 0,dryRun:s.dryRun===!0,after:typeof s.after=="number"?s.after:void 0});process.exit(r)});cm(C.command("correlator-audit"));C.command("blame <sha>").description("Look up which session(s) wrote the code in a commit. Reverse of `correlate`.").option("--json","emit JSON instead of formatted output").action(async(e,t)=>{await xd(e,t)});C.command("digest").description("Today's rediscovery picks: a session worth revisiting, the costliest of the week, and the one behind your latest commit.").option("--json","emit JSON instead of formatted output").action(async e=>{await Ld(e)});C.command("install-extension").description("Install the bundled VS Code / Cursor / Windsurf extension into your editor. Auto-detects which CLIs are installed.").option("--editor <name>","install only into one editor: code | cursor | code-insiders | windsurf").option("--print-path","print the path to the bundled .vsix and exit").action(async e=>{await Id(e)});C.command("health [project]").description("Score how well-organized each project's sessions are. Worst first, or pass a project name for a single breakdown.").option("--json","emit JSON instead of formatted output").action((e,t)=>{$d(e,t)});C.command("verify [action]").description('Toggle verification badges in the UI. Defaults to "status". Actions: on, off, status.').action(e=>{Pd(e)});C.command("activate <license-key>").description("Activate your Pro license. Run once after purchase.").action(async e=>{await Gd(e)});C.command("upgrade").description("Open the Pro pricing page in your browser.").action(async()=>{await zd()});C.command("trial [promo-code]").description("Start a 7-day Pro trial. With a promo code (e.g. RECALL7DAY), redeems it directly from the CLI. With no code, opens the signup page.").action(async e=>{await Kd(e)});var Un=C.command("telemetry").description('Manage the opt-in anonymous install ping. Defaults to "view".');Un.command("view",{isDefault:!0}).description("Show current decision, state file path, and next-ping payload.").action(async()=>{await ru(hs)});Un.command("on").description("Opt in. One anonymous ping per machine per month. See https://clauderecall.com/telemetry").action(async()=>{await su()});Un.command("off").description("Opt out. Deletes the nonce. No further pings.").action(async()=>{await nu()});Un.command("status").description("Alias for view.").action(async()=>{await Po(hs)});var pi=C.command("license").description('Show or remove the Pro license on this machine. Defaults to "status".');pi.command("status",{isDefault:!0}).description("Show the current license tier.").action(async()=>{await au()});pi.command("deactivate").description("Remove the stored license from this machine. Reverses `recall activate`.").action(()=>{lu()});pi.command("check").description("Force a revocation check against the license server. The daemon also runs this on a 24-hour timer in the background.").action(async()=>{await cu()});var V0=C.command("thread").description("Group related sessions into threads, then list, show, link, merge, or scan them."),Z0=C.command("threads").description("Alias for `thread`. Both forms accept the same subcommands.");function lm(e){e.command("sync").description("Capture sessions running in this repo right now into a thread.").option("--preflight","preview the plan without writing").option("-p, --project <name>","override the cwd-based project").option("--window-hours <n>","rolling activity window (default 6)").option("--json","machine-readable JSON output").action(async t=>{await Wu(t)}),e.command("scan").description("Auto-detect parent-child links across past sessions. Dry-run by default; pass --apply to write.").option("-p, --project <name>","scope to a single project").option("--json","emit JSON instead of formatted output").option("--apply","write edges (default: dry-run preview)").option("--confidence-min <n>","override threshold (dry-run 0.5, apply 0.7)").option("--no-llm-names","skip LLM naming (free, offline)").option("--llm-rescore","LLM re-score borderline pairs (small Haiku call each)").option("--rescore-band-lo <n>","low end of the borderline band (default 0.4)").option("--rescore-band-hi <n>","high end of the borderline band (default 0.7)").option("--rescore-model <id>","model id for the rescore call (default Haiku 4.5)").action(t=>{Bu(t)}),e.command("list").description("List threads, newest first.").option("--archived","include archived threads").option("--json","emit JSON instead of a table").action(t=>{uu(t)}),e.command("show <id-prefix>").description("Show a thread's header and session tree.").option("--json","emit JSON instead of formatted output").action((t,s)=>{pu(t,s)}),e.command("new <name>").description("Create a new thread.").option("--origin <session-id>","seed with an origin session").option("--summary <text>","optional short summary").option("--json","emit JSON").action((t,s)=>{mu(t,s)}),e.command("link <session-id>").description("Link a session into a thread.").requiredOption("--thread <id-prefix>","thread id or prefix").option("--parent <session-id>","parent session within the thread").option("--role <role>","origin | child (default inferred from --parent)").option("--json","emit JSON").action((t,s)=>{gu(t,s)}),e.command("unlink <session-id>").description("Remove a session from a thread.").requiredOption("--thread <id-prefix>","thread id or prefix").option("--json","emit JSON").action((t,s)=>{fu(t,s)}),e.command("set-parent <session-id>").description("Change a session's parent within a thread. Use --parent none to promote to origin.").requiredOption("--thread <id-prefix>","thread id or prefix").option("--parent <session-id|none>",'new parent, or "none" to promote to origin').option("--json","emit JSON").action((t,s)=>{_u(t,s)}),e.command("rename <id-prefix> <new-name>").description("Rename a thread.").option("--json","emit JSON").action((t,s,n)=>{hu(t,s,n)}),e.command("close <id-prefix>").description("Mark a thread as closed (work complete).").option("--json","emit JSON").action((t,s)=>{Eu(t,s)}),e.command("reopen <id-prefix>").description("Reopen a closed thread.").option("--json","emit JSON").action((t,s)=>{bu(t,s)}),e.command("archive <id-prefix>").description("Archive a thread (hidden from the default list).").option("--json","emit JSON").action((t,s)=>{Su(t,s)}),e.command("merge <source-id-prefix>").description("Merge one thread into another.").requiredOption("--into <id-prefix>","destination thread").option("--json","emit JSON").action((t,s)=>{yu(t,s)}),e.command("split <id-prefix>").description("Split sessions out of a thread into a new thread.").requiredOption("--sessions <s1,s2,...>","comma-separated session ids or prefixes").requiredOption("--name <new-name>","name for the new thread").option("--json","emit JSON").action((t,s)=>{wu(t,s)})}lm(V0);lm(Z0);C.command("share [id]").description("Save a session as a shareable PNG card. No args = your last recalled session. Saved to ~/Downloads and opened.").option("--style <A|B|C|D>","card style (default A: Minimal)").option("--verdict <quote>","your one-line take on the session").option("--link","also generate a shareable URL and copy it to the clipboard").option("--clipboard","also copy the PNG to the system clipboard").option("--out <path>","override the save directory").option("--no-open","do not auto-open the saved PNG").action(async(e,t)=>{await lp(e,t)});C.command("feedback").description("Send a 1-5 rating and optional comment to the team. Interactive by default; pass --score (and optionally --message) to skip prompts.").option("--score <1-5>","rating from 1 (bad) to 5 (great)").option("--message <text>","optional comment to send with the rating").action(async e=>{let t=await hp(e);t!==0&&(process.exitCode=t)});C.command("wrapped [month]").description("Save your monthly recap as a shareable PNG card. Defaults to the current month. Accepts YYYY-MM, full name, or abbreviation.").option("--verdict <quote>","your one-line take on the month").option("--out <path>","override the save directory").action(async(e,t)=>{await pp(e,t)});process.env.CLAUDECODE&&!process.stdout.isTTY&&process.stderr.write(`<claude-code-hint v="1" type="plugin" value="claude-recall@claude-plugins-official" />
|
|
1806
|
+
`);C.action(async()=>{await Jd()});C.hook("preAction",async(e,t)=>{let s=t.name();try{await iu(s,hs)}catch{}tu(hs).catch(()=>{})});C.parseAsync(process.argv).catch(e=>{console.error(e),process.exit(1)});
|