@leftoff/cli 0.0.0-stage → 0.6.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/.claude-plugin/marketplace.json +14 -0
- package/CHANGELOG.md +564 -0
- package/LICENSE.md +9 -0
- package/README.md +28 -2
- package/assets/sample-transcript.jsonl +12 -0
- package/bin/checkpoints.js +7 -0
- package/bin/leftoff.js +585 -0
- package/hooks/README.md +22 -0
- package/hooks/settings-hooks.example.json +82 -0
- package/mod/.claude-plugin/plugin.json +15 -0
- package/mod/hooks/core.js +1397 -0
- package/mod/hooks/hooks.json +4 -0
- package/mod/hooks/register.js +1006 -0
- package/package.json +45 -4
- package/templates/agent-feed-worker/README.md +45 -0
- package/templates/agent-feed-worker/src/index.js +69 -0
- package/templates/agent-feed-worker/wrangler.toml +20 -0
- package/ui/app.css +1282 -0
- package/ui/app.js +2979 -0
- package/ui/apple-touch-icon.png +0 -0
- package/ui/changelog-md.js +64 -0
- package/ui/favicon-32.png +0 -0
- package/ui/favicon-badge-32.png +0 -0
- package/ui/favicon-badge.svg +1 -0
- package/ui/favicon.ico +0 -0
- package/ui/favicon.svg +1 -0
- package/ui/go.js +68 -0
- package/ui/icon-192.png +0 -0
- package/ui/icon-512.png +0 -0
- package/ui/icon-maskable-512.png +0 -0
- package/ui/icons.js +41 -0
- package/ui/index.html +55 -0
- package/ui/manifest.webmanifest +37 -0
- package/ui/needs.js +197 -0
- package/ui/offline.css +11 -0
- package/ui/offline.html +24 -0
- package/ui/pair.html +31 -0
- package/ui/picker.js +294 -0
- package/ui/relay-client.js +187 -0
- package/ui/relay-crypto.js +155 -0
- package/ui/relay-pair.js +48 -0
- package/ui/repos.js +167 -0
- package/ui/settings-nav.js +23 -0
- package/ui/summary-examples.js +66 -0
- package/ui/sw.js +34 -0
- package/ui/theme.js +41 -0
- package/ui/vendor/LUCIDE-LICENSE.txt +45 -0
package/bin/leftoff.js
ADDED
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var _w=Object.defineProperty;var V=(t,e)=>()=>(t&&(e=t(t=0)),e);var zt=(t,e)=>{for(var n in e)_w(t,n,{get:e[n],enumerable:!0})};function $s(t,e){let n=Array.isArray(t)?t.slice():{...t};if(!e||typeof e!="object")return n;for(let s of Object.keys(e)){let o=t?t[s]:void 0,r=e[s];o&&r&&typeof o=="object"&&typeof r=="object"&&!Array.isArray(o)&&!Array.isArray(r)?n[s]=$s(o,r):n[s]=r}return n}function nr(t){return String(t||"").replace(/[^A-Za-z0-9]/g,"").slice(0,8).toLowerCase()||"unknown0"}function _c(t,e=40){return String(t||"").toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g,"").replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,e).replace(/-+$/g,"")||"untitled"}function Fi(t){let e=String(t||"").split(/[\\/]+/).filter(Boolean);return e.length?e[e.length-1]:""}function fe(t){let e=t&&t.repo;return e&&e.name?String(e.name).split("/").pop():e&&e.root?Fi(e.root):Fi(t&&t.cwd)||"no-repo"}function F(t,e){let n=String(t??"").replace(/\s+/g," ").trim();return n.length>e?n.slice(0,Math.max(0,e-1)).trimEnd()+"…":n}function G(t){let e=t instanceof Date?t:new Date(typeof t=="number"?t:Date.parse(t));return isNaN(e.getTime())?null:e.toISOString().replace(/\.\d{3}Z$/,"Z")}function R(t){return t==null||t===""?NaN:typeof t=="number"?t:Date.parse(t)}function Cw(t){return G(t).replace(/[-:]/g,"")}function Ow(t){let e=G(t);return e.slice(0,10)+"T"+e.slice(11,19).replace(/:/g,"")+"Z"}function Bi(t,e,n=0){return"cp_"+Cw(e)+"_"+nr(t)+(n?"-"+(n+1):"")}function Nc(t,e=0){return Ow(t)+(e?"-"+(e+1):"")+".md"}function If(t){return[(G(t.startedAt||Date.now())||"").slice(0,10),_c(fe(t),24),_c(t.title,40),nr(t.id)].join("_")}function Xo(t){return _c(String(t||"").replace(/\.local$/i,""),40)||"unknown"}function Ki({env:t,hostname:e,cfg:n={}}={}){if(t)return Xo(t);let s=n.machineNames||{};return e&&s[e]?Xo(s[e]):n.machine?Xo(n.machine):Xo(e||"unknown")}function Ec(t){return t.replace(/[A-Z]/g,e=>"_"+e.toLowerCase())}function Nf(t){return t.replace(/_([a-z0-9])/g,(e,n)=>n.toUpperCase())}function Ui(t,e,n){if(Array.isArray(t))return t.map(o=>n?Ui(o,e,n):o);if(!t||typeof t!="object")return t;let s={};for(let[o,r]of Object.entries(t))s[e(o)]=n&&r&&typeof r=="object"?Ui(r,e,n):r;return s}function Nw(t){return!!(t===""||Iw.test(t)||Df.test(t)&&/\d/.test(t)||/^[\s\-?:,\[\]{}#&*!|>'"%@`]/.test(t)||/\s$/.test(t)||/:(\s|$)|\s#|[,\[\]{}\n\r\t"]/.test(t))}function Cc(t){if(t==null)return"null";if(typeof t=="boolean"||typeof t=="number")return Number.isFinite(t)||typeof t=="boolean"?String(t):"null";if(Array.isArray(t))return"["+t.map(Cc).join(", ")+"]";if(typeof t=="object")return"{"+Object.entries(t).filter(([,s])=>s!==void 0).map(([s,o])=>s+": "+Cc(o)).join(", ")+"}";let e=String(t);return Nw(e)?JSON.stringify(e):e}function ms(t,e=[]){let n=[...e.filter(o=>o in t),...Object.keys(t).filter(o=>!e.includes(o))],s=["---"];for(let o of n)t[o]!==void 0&&s.push(o+": "+Cc(t[o]));return s.push("---"),s.join(`
|
|
3
|
+
`)+`
|
|
4
|
+
`}function Af(t){let e=[],n=0,s=null,o="";for(let r=0;r<t.length;r++){let i=t[r];if(s){if(o+=i,i==="\\"&&s==='"'){o+=t[++r]||"";continue}if(i===s){if(s==="'"&&t[r+1]==="'"){o+=t[++r];continue}s=null}continue}if(i==='"'||i==="'"){s=i,o+=i;continue}if((i==="["||i==="{")&&n++,(i==="]"||i==="}")&&n--,i===","&&n===0){e.push(o.trim()),o="";continue}o+=i}return o.trim()!==""&&e.push(o.trim()),e}function Mw(t){let e=null;for(let n=0;n<t.length;n++){let s=t[n];if(e){s===e&&(e=null);continue}if(s==='"'||s==="'"){e=s;continue}if(s==="#"&&(n===0||/\s/.test(t[n-1])))return t.slice(0,n).trimEnd()}return t}function Qo(t){let e=Mw(String(t).trim());if(e===""||e==="~"||e==="null"||e==="Null"||e==="NULL")return null;if(/^(true|True|TRUE)$/.test(e))return!0;if(/^(false|False|FALSE)$/.test(e))return!1;if(e[0]==='"')try{return JSON.parse(e)}catch{return e.slice(1,-1)}if(e[0]==="'")return e.slice(1,e.endsWith("'")?-1:void 0).replace(/''/g,"'");if(e[0]==="[")return Af(e.slice(1,e.lastIndexOf("]"))).map(Qo);if(e[0]==="{"){let n={};for(let s of Af(e.slice(1,e.lastIndexOf("}")))){let o=s.match(/^("(?:[^"\\]|\\.)*"|'[^']*'|[^:]+?)\s*:\s*(.*)$/);o&&(n[String(Qo(o[1]))]=Qo(o[2]))}return n}if(Df.test(e)&&/\d/.test(e)){let n=Number(e.replace(/_/g,""));if(!isNaN(n))return n}return e}function Be(t){let e=String(t||"").replace(/^\uFEFF/,"").replace(/\r\n/g,`
|
|
5
|
+
`);if(!e.startsWith(`---
|
|
6
|
+
`)&&e!=="---")return{data:{},body:e};let n=e.indexOf(`
|
|
7
|
+
---`,4);if(n<0)return{data:{},body:e};let s=e.slice(4,n),o=e.slice(n+4);o=o.replace(/^[^\n]*\n?/,"");let r={},i=s.split(`
|
|
8
|
+
`);for(let a=0;a<i.length;a++){let l=i[a];if(!l.trim()||/^\s*#/.test(l))continue;let c=l.match(/^([A-Za-z0-9_.-]+)\s*:\s?(.*)$/);if(!c)continue;let[,u,d]=c;if(d.trim()===""){let f=[];for(;a+1<i.length&&/^\s+-\s+/.test(i[a+1]);)f.push(Qo(i[++a].replace(/^\s+-\s+/,"")));r[u]=f.length?f:null}else r[u]=Qo(d)}return{data:r,body:o}}function Ys(t){let e=t.id||t.sessionId,n=t.cwd;return(n?"cd "+(o=>/^[A-Za-z0-9_./~@+-]+$/.test(o)?o:"'"+String(o).replace(/'/g,"'\\''")+"'")(n)+" && ":"")+"claude --resume "+e}function Rf(t){let e={id:t.id,session_id:t.sessionId,session_title:t.sessionTitle||null,created_at:G(t.createdAt),trigger:t.trigger,machine:t.machine||void 0,repo:t.repo||null,branch:t.branch||null,cwd:t.cwd||null,files_touched:t.filesTouched||[],turns_since_last:t.turnsSinceLast==null?null:t.turnsSinceLast,user_note:t.userNote||void 0,open_question:t.openQuestion||void 0,parse_ok:t.parseOk===!1?!1:void 0,length:t.length&&ir[t.length]?t.length:void 0,generator:t.generator?Mf(t.generator):{by:"unknown"},schema:1},n=["**Doing:** "+ln(t.doing),"**Stopped at:** "+ln(t.stoppedAt),"**Next:** "+ln(t.next)];t.openQuestion&&n.push("**Open question:** "+ln(t.openQuestion)),t.userNote&&n.push("**Note:** "+ln(t.userNote));let s=r=>"- "+ln(r).replace(/^\*+/,"");for(let r of qw){let i=t[r];if(r==="recap"){i&&n.push("","### "+Ef[r],ln(i).replace(/^\*+/,""));continue}if(!(!Array.isArray(i)||!i.length))if(n.push("","### "+Ef[r]),r==="files")for(let a of i)n.push(s(a.path?"`"+String(a.path).replace(/`/g,"")+"`"+(a.why?" — "+ln(a.why):""):a.why));else if(r==="nextSteps")i.forEach((a,l)=>n.push(l+1+". "+ln(a).replace(/^\*+/,"")));else for(let a of i)n.push(s(a))}let o=Ys({id:t.sessionId,cwd:t.cwd});return ms(e,Lw)+n.join(`
|
|
9
|
+
`)+`
|
|
10
|
+
|
|
11
|
+
Resume: \``+o+"`\n"}function ln(t){return String(t??"").replace(/\s*\n\s*/g," ").trim()}function jf(t){let{data:e,body:n}=Be(t),s={id:e.id||null,sessionId:e.session_id||null,sessionTitle:e.session_title||null,createdAt:e.created_at?G(e.created_at):null,trigger:e.trigger||"manual",machine:e.machine==null?null:String(e.machine),repo:e.repo==null?null:String(e.repo),branch:e.branch==null?null:String(e.branch),cwd:e.cwd||null,filesTouched:Array.isArray(e.files_touched)?e.files_touched.map(String):[],turnsSinceLast:typeof e.turns_since_last=="number"?e.turns_since_last:null,userNote:e.user_note||null,openQuestion:e.open_question||null,parseOk:e.parse_ok!==!1,generator:e.generator&&typeof e.generator=="object"?Lf(e.generator):{by:"unknown"},length:e.length&&ir[e.length]?e.length:null,doing:"",stoppedAt:"",next:""},o=n.split(`
|
|
12
|
+
`),r=0;for(;r<o.length;r++){let a=o[r];if(/^###\s/.test(a))break;let l=a.match(/^\s*\*\*([A-Za-z ]+):\*\*\s*(.*)$/);if(!l)continue;let c=Dw[l[1].trim().toLowerCase()];c&&(s[c]=l[2].trim())}let i=[];for(;r<o.length&&!/^Resume: `/.test(o[r]);r++)i.push(o[r]);return Object.assign(s,$f(i)),s}function Pf(t,e=[]){let n={};for(let a of Pw)n[Ec(a)]=t[a]===void 0?null:t[a];n.repo&&typeof n.repo=="object"&&(n.repo=Mf(n.repo));for(let a of jw)a==="folder"||a==="statusChanges"||t[a]!==void 0&&t[a]!==null&&t[a]!==""&&t[a]!==0&&t[a]!==!1&&(n[Ec(a)]=t[a]);n.schema=1;let s=e.slice().sort((a,l)=>R(l.createdAt)-R(a.createdAt)),o=["# "+(t.title||"Untitled session"),""],r=fe(t),i=t.repo&&t.repo.branch;o.push("`"+r+(i?" · "+i:"")+"` · "+(t.machine?t.machine+" · ":"")+(t.status||"unknown")+" · started "+(G(t.startedAt)||"?")),o.push(""),o.push("Resume: `"+Ys(t)+"`"),o.push(""),o.push("## Checkpoints"),o.push(""),s.length||o.push("_None yet._");for(let a of s){let l=G(a.createdAt).replace("T"," ").slice(0,16)+"Z";o.push("- ["+l+" · "+a.trigger+"](checkpoints/"+(a.file||Nc(a.createdAt))+") — Next: "+F(a.next,160))}return ms(n,Rw)+o.join(`
|
|
13
|
+
`)+`
|
|
14
|
+
`}function Ff(t){let{data:e}=Be(t),n={};for(let[s,o]of Object.entries(e))s!=="schema"&&(n[Nf(s)]=o);n.repo&&typeof n.repo=="object"&&(n.repo=Lf(n.repo)),n.repo&&typeof n.repo=="string"&&(n.repo={root:null,name:n.repo});for(let s of["startedAt","lastActivityAt","lastHeartbeatAt","endedAt","lastCheckpointAt","lastEventAt","dormantStretch","closedAt","pidStartedAt"])n[s]&&(n[s]=G(n[s]));return n.turns=typeof n.turns=="number"?n.turns:0,n.turnsSinceCheckpoint=typeof n.turnsSinceCheckpoint=="number"?n.turnsSinceCheckpoint:0,n.backgroundTasks=typeof n.backgroundTasks=="number"?n.backgroundTasks:0,n.pinned=!!n.pinned,n.archived=!!n.archived,n}function Ke(t){return nr(t).slice(0,6)}function sr(t){return"cp:"+Ke(t)}function or(t){let e=String(t??""),n=e.match(Fw);return n?{name:e.slice(0,n.index).trim(),id:n[1].toLowerCase()}:{name:e.trim(),id:null}}function hs(t){return or(t).name}function Uw(t,e){if(!t||!e)return!1;if(or(t).id)return!0;let n=e.title||null;return!!n&&String(t).trim()===Lc("{name}",{name:n,id:e.id})}function Hw(t){return typeof t!="string"||!t.trim()||t===Mc||!/\{name\}|\{repo\}|\{id\}/.test(t)?ye.tabTitleFormat:t}function Lc(t,{name:e,id:n,repo:s,marker:o=!1}={}){let r=Hw(t),i=F(hs(e||"")||s||"claude",60).replace(/[\x00-\x1f\x7f]/g," "),a=r.replace(/\{name\}/g,i).replace(/\{repo\}/g,String(s||"").replace(/[\x00-\x1f\x7f]/g," ")).replace(/\{id\}/g,Ke(n)).replace(/\s+/g," ").trim();return o&&n&&!r.includes("{id}")&&(a+=" · "+sr(n)),a}function Bw(t){let e=String(t.TERM_PROGRAM||""),n=String(t.TERM||""),s=e.toLowerCase();return s==="ghostty"?"ghostty":s==="iterm.app"?"iterm2":s==="wezterm"?"wezterm":s==="apple_terminal"?"apple-terminal":s==="vscode"?"vscode":s==="kitty"||t.KITTY_WINDOW_ID||n==="xterm-kitty"?"kitty":t.WEZTERM_PANE?"wezterm":t.ITERM_SESSION_ID?"iterm2":t.GHOSTTY_RESOURCES_DIR||n==="xterm-ghostty"?"ghostty":"unknown"}function Ln(t={}){let e=t||{},n={kind:"unknown",label:"",program:e.TERM_PROGRAM||null,version:e.TERM_PROGRAM_VERSION||null},s=Bw(e),o={};if(e.ITERM_SESSION_ID){let r=String(e.ITERM_SESSION_ID);o.itermSessionId=r.includes(":")?r.slice(r.indexOf(":")+1):r}return e.WEZTERM_PANE&&(o.weztermPane=String(e.WEZTERM_PANE)),e.WEZTERM_UNIX_SOCKET&&(o.weztermSocket=String(e.WEZTERM_UNIX_SOCKET)),e.KITTY_WINDOW_ID&&(o.kittyWindowId=String(e.KITTY_WINDOW_ID)),e.KITTY_LISTEN_ON&&(o.kittyListenOn=String(e.KITTY_LISTEN_ON)),e.TERM_SESSION_ID&&(o.termSessionId=String(e.TERM_SESSION_ID)),e.TMUX&&e.TMUX_PANE?(n.kind="tmux",n.tmuxPane=String(e.TMUX_PANE),n.tmuxSocket=String(e.TMUX).split(",")[0]||null,n.host=s==="unknown"?null:s,n.label="tmux"+(n.host?" in "+Tf[n.host]:""),{...n,...o}):(n.kind=s,n.label=s==="unknown"?e.TERM_PROGRAM||e.TERM||"unknown terminal":Tf[s],s==="ghostty"&&e.GHOSTTY_RESOURCES_DIR&&(n.resourcesDir=String(e.GHOSTTY_RESOURCES_DIR)),s==="vscode"&&/cursor/i.test(String(e.TERM_PROGRAM_VERSION||""))&&(n.label="Cursor"),{...n,...o})}function rr(t){let e=String(t||"").trim().match(/^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/);return e?Number(e[1]||0)*86400+Number(e[2]||0)*3600+Number(e[3])*60+Number(e[4]):null}function Dc(t){let e=String(t||"").trim();return!e||/^\?+$/.test(e)||e==="-"?null:e.startsWith("/dev/")?e:"/dev/"+(/^s\d+$/.test(e)?"tty"+e:e)}function Hf(t,e=Date.now()){let n=String(t||"").trim().split(/\s+/),s=Number(n[0]);if(!Number.isInteger(s)||s<=1)return null;let o=rr(n[2]);return{pid:s,tty:Dc(n[1]),pidStartedAt:o==null?null:G(e-o*1e3)}}function Rt(t,e,n=ye){if(t.endedAt)return"ended";let s=R(t.lastActivityAt||t.startedAt),o=isNaN(s)?0:e-s,r=n.dormantAfterMin*ji;if(t.capture==="mod"){let i=R(t.lastHeartbeatAt||t.lastEventAt||t.startedAt),a=R(t.pidAliveAt);if(!(!isNaN(a)&&e-a<=Math.max(n.staleAfterSec*1e3,9e4))&&!isNaN(i)&&e-i>n.staleAfterSec*1e3)return"stale"}return t.needsInput?"needs_input":t.inTurn?t.capture==="hooks"&&o>=2*r?"dormant":"active":t.backgroundTasks>0?"background":o>=r?"dormant":"waiting"}function Rc(t,e,n=ye){if(t.ignored)return null;let s=Rt(t,e,n),o=R(t.lastCheckpointAt),r=R(t.lastActivityAt||t.startedAt),i=isNaN(o)||e-o>=n.minGapMin*ji,a=(t.turnsSinceCheckpoint||0)>0||!isNaN(r)&&(isNaN(o)?!!t.lastActivityAt:r>o);if(s==="ended"||s==="stale")return t.finalCheckpointDone||!a||!i||!t.lastActivityAt?null:t.endReason==="clear"?"clear":"session_end";if(e-r<n.dormantAfterMin*ji){if(n.longRunCheckpointMin&&s==="active"&&t.activeSince){let c=Math.max(R(t.activeSince),isNaN(o)?0:o);if(e-c>=n.longRunCheckpointMin*ji&&i)return"long_run"}return null}return t.dormantStretch&&t.dormantStretch===t.lastActivityAt||!i?null:s==="dormant"&&(t.turnsSinceCheckpoint||0)>=1||s==="needs_input"&&a?"dormant":null}function ht(t,e=0){return t?!(Number(t.turns)>0)&&!t.title&&!(Number(e)>0)&&!t.lastCheckpointId&&!(Number(t.historyTurns)>0)&&t.startSource!=="resume":!0}function Ws(t,e=0){return ht(t,e)&&!t.cwd&&!(Number(t.pid)>1)}function Bf(t,e){let n=G(e.at)||G(Date.now()),s=e.data||{};t.id=t.id||e.sessionId,t.capture=e.capture||t.capture||"hooks",e.machine&&!t.machine&&(t.machine=Xo(e.machine)),t.startedAt=t.startedAt||n,t.lastEventAt=n;let o=()=>{t.lastActivityAt=n,t.finalCheckpointDone=!1,t.endedAt&&e.type!=="session.ended"&&(t.endedAt=null,t.endReason=null,t.closedAt=null)};switch(s.cwd&&(t.cwd=s.cwd),s.transcriptPath&&(t.transcriptPath=s.transcriptPath),s.model&&(t.model=s.model),s.surface&&(t.surface=s.surface),s.repo&&typeof s.repo=="object"&&(t.repo={...t.repo||{},...Kw(s.repo)}),s.sessionTitle&&!or(s.sessionTitle).id&&s.sessionTitle!==t.title&&!Uw(s.sessionTitle,t)&&(t.title=s.sessionTitle,t.titleSource="custom"),s.proc&&typeof s.proc=="object"&&Number(s.proc.pid)>1&&(t.pid=Number(s.proc.pid),t.tty=s.proc.tty||null,t.pidStartedAt=s.proc.pidStartedAt||null,t.pidSource=s.proc.source||(t.capture==="mod"?"mod":"hook")),s.term&&typeof s.term=="object"?t.term=s.term:s.termEnv&&typeof s.termEnv=="object"&&(t.term=Ln(s.termEnv)),e.type){case"session.opened":t.lastHeartbeatAt=n,t.endedAt&&(t.endedAt=null,t.endReason=null,t.closedAt=null);break;case"session.started":s.parentSessionId&&s.parentSessionId!==t.id&&(t.parentSessionId=s.parentSessionId),(s.source==="resume"||s.source==="fork"||s.source==="clear")&&(t.endedAt=null,t.endReason=null,t.closedAt=null),s.source==="resume"&&(t.startSource="resume"),t.capture==="mod"&&(t.lastHeartbeatAt=n);break;case"activity":o(),s.kind==="tool"&&s.phase==="post"&&t.needsInput&&t.lastNotification==="permission_prompt"&&(t.needsInput=!1),s.kind==="prompt"&&(t.needsInput=!1,t.question=null,t.lastNotification=null,s.prompt&&!t.firstPrompt&&(t.firstPrompt=F(s.prompt,300)),t.capture==="hooks"&&(t.inTurn=!0,t.activeSince=t.activeSince||n));break;case"turn_started":o(),t.inTurn=!0,t.needsInput=!1,t.question=null,t.activeSince=t.activeSince||n;break;case"turn_completed":o(),t.inTurn=!1,t.activeSince=null,t.needsInput=!1,t.turns=(t.turns||0)+1,t.turnsSinceCheckpoint=(t.turnsSinceCheckpoint||0)+1,t.backgroundTasks=Number(s.backgroundTasks||0)+Number(s.sessionCrons||0),s.lastAssistantPreview&&(t.lastPreview=F(s.lastAssistantPreview,280)),t.question=s.question?F(s.question,400):null,t.questionAt=s.question?n:null;break;case"notification":t.lastNotification=s.notificationType||null,t.lastNotificationMessage=s.message?F(s.message,200):null,(Ew.includes(s.notificationType)||/^elicitation/.test(s.notificationType||""))&&(t.needsInput||(t.needsInputAt=n),t.needsInput=!0);break;case"title_changed":s.title&&hs(s.title)&&(t.title=hs(s.title),t.titleSource=s.titleSource||"custom");break;case"heartbeat":t.lastHeartbeatAt=n,typeof s.inTurn=="boolean"&&!s.inTurn&&t.inTurn&&t.capture==="mod"&&(t.inTurn=!1);break;case"session.ended":t.endedAt=n,t.endReason=s.reason||"other",t.inTurn=!1;break}return t}function jc(t){let e=String(t??"").replace(/\r\n?/g,`
|
|
15
|
+
`).trim();if(!e||/```\s*$/.test(e))return null;let n=e.split(/\n\s*\n/).map(r=>r.trim()).filter(Boolean),s=n[n.length-1]||"",o=s.replace(/[\s*_`)\]"'”’»]+$/u,"");return/[??]$/.test(o)?(n.length>1&&/^([-*•]|\d+[.)])\s/.test(s)&&/[?:]\s*$/.test(n[n.length-2])&&(s=n[n.length-2]+`
|
|
16
|
+
`+s),s=s.replace(/^#+\s*/,"").replace(/[*_`]+$/,""),s.length>400?"…"+s.slice(-399):s):null}function Kw(t){let e={};for(let[n,s]of Object.entries(t))s!=null&&(e[n]=s);return e}function Gw(t=[]){let e=[];for(let n of t)try{let s=String(n),o="g";s.startsWith("(?i)")&&(s=s.slice(4),o="gi"),e.push(new RegExp(s,o))}catch{}return e}function Gi(t,e){let n=String(t??"");for(let s of Gw(e))n=n.replace(s,"[REDACTED]");return n}function er(t,e){let n=String(t||"");return e&&n.startsWith(e.replace(/\/+$/,"")+"/")?n.slice(e.replace(/\/+$/,"").length+1):n}function Hi(t,e={},n){let s=e||{};return s.file_path?er(s.file_path,n):s.notebook_path?er(s.notebook_path,n):s.command?F(s.command,80):s.pattern?F(s.pattern,60):s.url?F(s.url,80):s.path?er(s.path,n):s.description?F(s.description,60):""}function $w(t,e){let n=[];for(let s of t||[])for(let o of s.toolUses||[]){let r=o.tool||o.name,i=o.input&&(o.input.file_path||o.input.notebook_path);if(Kf.includes(r)&&i){let a=er(i,e);n.includes(a)||n.push(a)}}return n}function Yw(t,{maxInputChars:e=24e3,cwd:n}={}){let s=[],o=0,r=!1;for(let i=(t||[]).length-1;i>=0;i--){let a=t[i],l=[];if(a.role==="user"){if(a.text&&a.text.trim()&&l.push("USER: "+a.text.trim()),!r){let u=(a.toolResults||[]).find(d=>d.isError);u&&(l.push("LAST ERROR: "+F(u.text,500)),r=!0)}}else{a.text&&a.text.trim()&&l.push("ASSISTANT: "+F(a.text,1500));for(let u of a.toolUses||[]){let d=u.tool||u.name,f=" -> "+d+(Hi(d,u.input,n)?" "+Hi(d,u.input,n):"");u.isError&&(f+=" -> error",!r&&u.text&&(f+=": "+F(u.text,500),r=!0)),l.push(f)}}if(!l.length)continue;let c=l.join(`
|
|
17
|
+
`);if(o+c.length>e&&s.length)break;s.push(c),o+=c.length+1}return s.reverse().join(`
|
|
18
|
+
`)}function cn(t,{strict:e=!1}={}){let n=String(t??"").trim().toLowerCase();if(ir[n])return n;let s={s:"short",brief:"short",i:"informative",info:"informative",medium:"informative",normal:"informative",default:"informative",l:"long",full:"long",detailed:"long"}[n];return s||(e?null:Ww)}function Pc(t=ye,e){if(e){let n=cn(e,{strict:!0});if(n)return n}return cn(t&&t.summary&&t.summary.length)}function $i({session:t,previous:e,messages:n,cfg:s=ye,wantTitle:o=!1,userNote:r,length:i}){let a=Pc(s,i),l=s.summarizer&&s.summarizer.maxInputChars||24e3,c=t.cwd,u=$w(n,c),d=t.repo&&t.repo.branch,f=e?["Doing: "+e.doing,"Stopped at: "+e.stoppedAt,"Next: "+e.next].join(`
|
|
19
|
+
`):"none",p=["Repo: "+fe(t)+(d?" (branch "+d+")":"")+" · cwd "+(c||"?"),"Title: "+(t.title||"(none yet)"),"Previous checkpoint: "+f,"Files touched since then: "+(u.length?u.join(", "):"none")];r&&p.push("Note from the developer: "+r),t.needsInput&&p.push("State: the session is waiting on the developer ("+(t.lastNotification||"a prompt")+")."),o&&p.push("Also write the line: Title: <max 6 words>.");let h=Yw(n,{maxInputChars:l,cwd:c}),m=s.redact&&s.redact.patterns||[],y=Gi(p.join(`
|
|
20
|
+
`)+`
|
|
21
|
+
Transcript (oldest→newest, trimmed):
|
|
22
|
+
`+(h||"(empty)"),m);return{system:Gf[a],prompt:y,filesTouched:u,inputChars:y.length,length:a,maxTokens:ir[a].maxTokens}}function Zw(t){let e=String(t||"").trim(),n=e.match(/^`?([^`\s][^`]*?)`?\s*(?:—|–|-{1,2}|:)\s+(.+)$/);if(n&&/[./]/.test(n[1])&&!/\s/.test(n[1].trim()))return{path:n[1].trim(),why:n[2].trim()};let s=e.match(/^`?([^`\s]+)`?$/);return s?{path:s[1],why:""}:{path:"",why:e}}function Fc(){return{decisions:[],questions:[],files:[],didntWork:[],recap:"",nextSteps:[]}}function $f(t){let e=Fc(),n=null,s=(o,r)=>{if(r=String(r||"").replace(/\*\*/g,"").trim(),!(!r||Vw.test(r))){if(o==="recap"){e.recap=e.recap?e.recap+" "+r:r;return}if(o==="files"){let i=Zw(r);(i.path||i.why)&&e.files.push(i);return}e[o].push(r)}};for(let o of t){let r=String(o).trim();if(!r)continue;let i=r.replace(/^#+\s*/,"").replace(/\*\*/g,"").replace(/^_+|_+$/g,"").trim(),a=i.match(/^([A-Za-z' ’0-9]+?)\s*:\s*(.*)$/)||i.match(/^([A-Za-z' ’0-9]+?)$/),l=a?Jw[a[1].replace(/’/g,"'").trim().toLowerCase()]:null;if(l&&(a[2]!==void 0||/^#/.test(r)||!n)){n=l,a[2]&&s(l,a[2].replace(/^[-*•]\s*/,""));continue}if(!n)continue;let c=r.match(/^(?:[-*•]|\d+[.)])\s+(.*)$/);c?s(n,c[1]):n==="recap"?s(n,r):/^(doing|stopped at|next|title|open question|note|resume)\s*:/i.test(i)?n=null:s(n,r)}for(let o of Object.keys(tr))e[o]=e[o].slice(0,tr[o]);return e.recap=F(e.recap,1600),e}function Yf(t){let e={doing:"",stoppedAt:"",next:"",title:null,openQuestion:null,parseOk:!0},n={doing:"doing","stopped at":"stoppedAt",next:"next",title:"title","open question":"openQuestion"},s=[];for(let o of String(t||"").split(`
|
|
23
|
+
`)){let i=o.replace(/^[\s*_#>-]+/,"").replace(/\*\*/g,"").match(/^(doing|stopped at|next|title|open question)\s*:\s*(.+)$/i);i?e[n[i[1].toLowerCase()]]=i[2].trim():s.push(o)}return!e.doing||!e.stoppedAt||!e.next?{doing:F(t,600)||"(empty reply)",stoppedAt:e.stoppedAt||"",next:e.next||"",title:e.title,openQuestion:null,parseOk:!1,...Fc()}:(e.title&&(e.title=F(e.title.replace(/^["']|["']$/g,""),60)),{...e,...$f(s)})}function Uc(t,e={},{length:n="short",cwd:s}={}){let o=t||[],r=[...o].reverse().find(S=>S.role==="user"&&S.text&&S.text.trim()),i=[...o].reverse().find(S=>S.role==="assistant"&&S.text&&S.text.trim()),a=null;for(let S=o.length-1;S>=0&&!a;S--){let v=o[S],A=(v.toolResults||[]).find(g=>g.isError),b=(v.toolUses||[]).find(g=>g.isError);if(A?a=A.text:b&&(a=b.text||b.tool+" failed"),v===i&&!a)break}let l=S=>{let v=F(S,400),A=v.match(/^(.{20,240}?[.!?])(\s|$)/);return A?A[1]:F(v,240)},c=r?"Working on: “"+F(r.text.split(`
|
|
24
|
+
`)[0],140)+"”":e.firstPrompt?"Working on: “"+F(e.firstPrompt,140)+"”":"No prompt captured yet.",u=a?"Last error: "+F(a,200):i?l(i.text):e.lastPreview?l(e.lastPreview):"No reply captured yet.",d="Resume the session and continue from the last reply.",p=(i?F(i.text,4e3).slice(-300):"").match(/([^.!?\n]{8,200}\?)\s*$/);e.needsInput?d="YOU: answer the pending "+String(e.lastNotification||"prompt").replace(/_/g," ")+" in this session.":p?d="YOU: "+F(p[1],180):a&&(d="Look at the last error and decide whether to retry or change approach.");let h={doing:c,stoppedAt:u,next:d,title:null,openQuestion:null,parseOk:!0},m=cn(n);if(m==="short")return h;let y=Fc(),w=s||e.cwd,k=new Map;for(let S of o)for(let v of S.toolUses||[]){let A=v.tool||v.name,b=v.input&&(v.input.file_path||v.input.notebook_path);if(Kf.includes(A)&&b){let g=er(b,w);k.has(g)||k.set(g,A==="Write"?"written":"edited")}v.isError&&y.didntWork.length<tr.didntWork&&y.didntWork.push(F(A+(Hi(A,v.input,w)?" "+Hi(A,v.input,w):"")+" failed"+(v.text?": "+v.text.split(`
|
|
25
|
+
`)[0]:""),200))}y.files=[...k].slice(-tr.files).map(([S,v])=>({path:S,why:v}));for(let S of o)if(S.role==="assistant"&&S.text){let v=jc(S.text);v&&!y.questions.includes(v)&&y.questions.push(F(v,200))}if(y.questions=y.questions.slice(-tr.questions),m==="long"){let S=o.find(A=>A.role==="user"&&A.text&&A.text.trim()),v=o.filter(A=>A.role==="user"&&A.text&&A.text.trim()).length;y.recap=[S?"The session started with: “"+F(S.text.split(`
|
|
26
|
+
`)[0],160)+"”.":"",v>1?"There were "+v+" prompts since.":"",k.size?k.size+" file"+(k.size===1?" was":"s were")+" changed.":"",a?"The last tool call failed.":"","Written without a model, from the transcript alone."].filter(Boolean).join(" "),y.nextSteps=[d]}return{...h,...y}}function Wf(t){let e=String(t.firstPrompt||"").replace(/\s+/g," ").trim().split(" ").filter(Boolean).slice(0,6).join(" "),n=t.repo&&t.repo.branch;return fe(t)+(n?"@"+n:"")+(e?" · "+e:"")}function Jf(t){let e=String(t||"").split(`
|
|
27
|
+
`).map(n=>n.trim()).find(Boolean)||"";return F(e.replace(/^title\s*:\s*/i,"").replace(/^["'`*]+|["'`*.]+$/g,""),60)}function qf(t,e=6){let n=String(t||"").replace(/<think>[\s\S]*?<\/think>/gi,"").trim(),s=[];for(let o of n.split(`
|
|
28
|
+
`)){let r=o.match(/^\s*(?:[-*•]|\d+[.)])\s+(.*\S)\s*$/);r&&s.push(r[1].replace(/\*\*/g,"").trim())}return s.length<2?null:s.slice(0,e)}function Pi(t){return String(t||"").replace(/\*\*(.+?)\*\*/g,"$1").replace(/__(.+?)__/g,"$1").replace(/\s*<!--.*?-->\s*$/,"").replace(/\s+/g," ").trim()}function Tc(t){let e=t.match(Xw);return e?{indent:e[1].replace(/\t/g," ").length,done:e[2]!==" ",checkbox:!0,text:e[3]}:(e=t.match(Qw),e?{indent:e[1].replace(/\t/g," ").length,done:!1,checkbox:!1,numbered:!0,n:Number(e[2]),text:e[3]}:(e=t.match(e0),e&&!/^[-*_]{3,}$/.test(t.trim())?{indent:e[1].replace(/\t/g," ").length,done:!1,checkbox:!1,text:e[2]}:null))}function ps(t){return String(t||"").replace(/^#+\s*/,"").replace(/\*\*|__/g,"").replace(/[::]\s*$/,"").replace(/\s+/g," ").trim()}function Vf(t,{minItems:e=2}={}){let n=String(t||"").replace(/\r\n/g,`
|
|
29
|
+
`).split(`
|
|
30
|
+
`),s=[],o=!1,r=null,i=-10;for(let a=0;a<n.length;a++){let l=n[a];if(Cf.test(l)){o=!o;continue}if(o||!l.trim())continue;let c=Tc(l);if(!c){let k=l.match(t0);k?(r=ps(k[1]||k[2]||k[3]||k[4]),i=a):r=null;continue}let u=c.indent,d=[{text:Pi(c.text),done:c.done,checkbox:c.checkbox,numbered:!!c.numbered}],f=a+1;for(;f<n.length;f++){let k=n[f];if(Cf.test(k))break;if(!k.trim()){let A=f+1;for(;A<n.length&&!n[A].trim();)A++;let b=A<n.length?Tc(n[A]):null;if(b&&b.indent<=u+1){f=A-1;continue}break}let S=Tc(k),v=k.match(/^\s*/)[0].replace(/\t/g," ").length;if(S&&S.indent<=u+1){d.push({text:Pi(S.text),done:S.done,checkbox:S.checkbox,numbered:!!S.numbered});continue}if(v>u){let A=d[d.length-1],b=Pi(S?S.text:k);A&&b&&(A.text=S?A.sub?A.text+"; "+b:A.text+" — "+b:A.text+" "+b),A&&S&&(A.sub=!0);continue}break}let p=d.filter(k=>k.checkbox).length,h=r&&i>=a-3?r:null,m=p>=Math.ceil(d.length/2)?"checklist":d.every(k=>k.numbered)?"numbered":"bulleted",y=m==="checklist"||h&&n0.test(h),w=d.filter(k=>k.text);y&&w.length>=e&&s.push({kind:m,heading:h,items:w.map(k=>({text:k.text,done:!!k.done})),line:a}),r=null,a=f-1}return s}function Yi(t,{minItems:e=2,fallback:n=null}={}){let s=[];return(t||[]).forEach((o,r)=>{if(!(!o||o.role!=="assistant"||!o.text))for(let i of Vf(o.text,{minItems:e}))s.push({...i,message:r,at:o.at||null})}),s.map((o,r)=>({...o,n:r+1,name:ar(o,{fallback:n})}))}function Wi(t,e){let n=(t||[]).map(r=>Oc(r.text||r)).filter(Boolean),s=new Set((e||[]).map(r=>Oc(r.text||r)).filter(Boolean));return!n.length||!s.size?0:n.filter(r=>s.has(r)).length/Math.min(n.length,s.size)}function s0(t){let e=2166136261;for(let n of(t||[]).map(s=>Oc(s.text||s)).join("|"))e^=n.charCodeAt(0),e=Math.imul(e,16777619)>>>0;return e.toString(16).padStart(8,"0")}function ar(t,{fallback:e=null}={}){let n=ps(t&&t.heading);if(n=n.split(/(?<=[.!?])\s+/).pop(),n=n.replace(/^(?:ok(?:ay)?|sure|great|alright|done)[,.!]?\s+/i,""),n=n.replace(/^(?:so\s+)?(?:here(?:'|’)?s|here is|here are|these are|this is|below (?:is|are))\s+(?:(?:the|my|a|an|your|our|some)\s+)?/i,""),n=n.replace(/^(?:the|my|your|our)\s+/i,"").replace(/[.!]+$/,"").trim(),n)return F(n.charAt(0).toUpperCase()+n.slice(1),60);let s=ps(e);return s?F(s,48)+" checklist":"Checklist"}function Zf(t,{minItems:e=3,maxItems:n=60,fallback:s=null}={}){let o=Vf(t,{minItems:e});if(!o.length)return null;let r=o[o.length-1],i=r.items.slice(0,n).map(a=>({text:F(a.text,300),done:!!a.done}));return{count:r.items.length,heading:r.heading||null,kind:r.kind,name:ar(r,{fallback:s}),items:i,fp:s0(i)}}function zi(t,e=48){return String(t||"").toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g,"").replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,e).replace(/-+$/,"")||"list"}function Hc(t){let{data:e,body:n}=Be(t),s={name:e.name==null?null:String(e.name),repo:e.repo==null?null:String(e.repo),repoKey:e.repo_key==null?null:String(e.repo_key),sourceSession:e.source_session||null,sourceMachine:e.source_machine==null?null:String(e.source_machine),sourceTitle:e.source_title==null?null:String(e.source_title),created:e.created?G(e.created):null,updated:e.updated?G(e.updated):null,archived:e.archived===!0,nextId:Number(e.next_id)||0},o=[],r=[],i=!1;for(let a of String(n||"").replace(/\r\n/g,`
|
|
31
|
+
`).replace(/\n+$/,"").split(`
|
|
32
|
+
`)){let l=a.match(r0);if(l&&!l[1]){let c={id:l[4]?Number(l[4]):null,text:l[3].trim(),done:l[2]!==" ",at:l[5]?G(l[5]):null};o.push(c),r.push({item:c});continue}if(!i&&/^#\s+/.test(a)){i=!0,s.name||(s.name=a.replace(/^#\s+/,"").trim()),r.push({title:!0});continue}r.push({line:a})}return{meta:s,items:o,segs:r}}function zs(t){let e=Math.max(t.meta.nextId||1,...t.items.map(s=>(s.id||0)+1)),n=new Set;for(let s of t.items)(!s.id||n.has(s.id))&&(s.id=e++),n.add(s.id);return t.meta.nextId=e,t}function Bc(t){zs(t);let e=t.meta,n=ms({name:e.name||"List",repo:e.repo||null,repo_key:e.repoKey||null,source_session:e.sourceSession||null,source_machine:e.sourceMachine||null,source_title:e.sourceTitle||null,created:e.created||null,updated:e.updated||null,archived:!!e.archived,next_id:e.nextId,schema:1},o0),s=t.segs&&t.segs.length?t.segs.slice():[{title:!0},{line:""}],o=new Set(s.filter(d=>d.item).map(d=>d.item)),r=t.items.filter(d=>!o.has(d)),i=new Set(t.items),a=-1;s.forEach((d,f)=>{d.item&&(a=f)});let l=a>=0?a+1:s.length;s.splice(l,0,...r.map(d=>({item:d}))),s.some(d=>d.title)||s.unshift({title:!0},{line:""});let c=d=>String(d??"").replace(/\s*\n\s*/g," ").replace(/<!--/g,"<!-").trim(),u=[];for(let d of s)d.title?u.push("# "+c(e.name||"List")):d.item?i.has(d.item)&&u.push("- ["+(d.item.done?"x":" ")+"] "+c(d.item.text)+" <!-- #"+d.item.id+(d.item.at?" "+d.item.at:"")+" -->"):u.push(d.line);return n+u.join(`
|
|
33
|
+
`).replace(/\n+$/,"")+`
|
|
34
|
+
`}function Xf({name:t,items:e=[],repo:n=null,repoKey:s=null,sourceSession:o=null,sourceMachine:r=null,sourceTitle:i=null,now:a=new Date().toISOString()}){let l={meta:{name:ps(t)||"List",repo:n,repoKey:s,sourceSession:o,sourceMachine:r,sourceTitle:i,created:G(a),updated:G(a),archived:!1,nextId:0},items:[],segs:null};for(let c of e)c&&String(c.text||"").trim()&&l.items.push({id:null,text:Pi(c.text),done:!!c.done,at:c.done?G(a):null});return zs(l)}function Qf(t,e){let n=(R(e.meta.updated)||0)>(R(t.meta.updated)||0)?e:t,s=n===t?e:t,o=c=>String(c||"").toLowerCase().replace(/[^a-z0-9]+/g," ").trim(),r={meta:{...s.meta,...n.meta,nextId:Math.max(t.meta.nextId||0,e.meta.nextId||0)},items:[],segs:n.segs},i=new Map,a=c=>{let u=c.id?i.get(c.id):null;if(u&&o(u.text)!==o(c.text)&&!i0(u.text,c.text)&&(u=null),u||(u=r.items.find(d=>o(d.text)===o(c.text))),!u){let d={...c};r.items.push(d),d.id&&!i.has(d.id)?i.set(d.id,d):d.id&&(d.id=null);return}(R(c.at)||0)>(R(u.at)||0)&&Object.assign(u,{text:c.text,done:c.done,at:c.at})};for(let c of n.items)a(c);for(let c of s.items)a(c);r.segs=(n.segs||[]).map(c=>c.item?{item:r.items.find(u=>u.id===c.item.id&&u.id)||r.items.find(u=>o(u.text)===o(c.item.text))||c.item}:c);let l=new Set;return r.segs=r.segs.filter(c=>!c.item||!l.has(c.item)&&l.add(c.item)),zs(r)}function i0(t,e){let n=new Set(String(t).toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)),s=[...new Set(String(e).toLowerCase().split(/[^a-z0-9]+/).filter(Boolean))];return!n.size||!s.length?!1:s.filter(o=>n.has(o)).length/Math.max(n.size,s.length)>.5}function eh(t){let e=t.items.length,n=t.items.filter(s=>s.done).length;return{done:n,total:e,label:n+"/"+e}}var ye,Ic,Of,Ew,Mf,Lf,Iw,Df,Lw,Dw,Rw,jw,Pw,Fw,Mc,Uf,Tf,ji,Kf,ir,G1,Ww,Ac,_f,zw,Gf,$1,Jw,Ef,qw,Vw,tr,zf,Cf,Xw,Qw,e0,t0,n0,Oc,o0,r0,ve=V(()=>{ye={port:7429,dormantAfterMin:15,minGapMin:30,staleAfterSec:180,longRunCheckpointMin:null,digest:{time:"07:00",lookbackHours:24,topOfMind:!0,days:["sun","mon","tue","wed","thu","fri","sat"],skipEmpty:!0,timeZone:null},summarizer:{provider:"auto",model:"haiku",baseUrl:null,apiKeyEnv:null,maxInputChars:24e3},machine:null,machineNames:{},redact:{patterns:["(?i)api[_-]?key\\s*[:=]\\s*\\S+","sk-[A-Za-z0-9_-]{20,}"]},ignoreRepos:[],updates:{check:!0,source:null},remoteHosts:[],remoteRequireToken:!0,tabTitle:!0,tabTitleFormat:"{name}",liveCheckSec:30,notify:{needsYou:!0,delaySec:10},remote:{allowAnswers:!1},summary:{length:"informative"}},Ic=["dormant","manual","session_end","clear","long_run"],Of=["session.opened","session.started","activity","turn_started","turn_completed","notification","title_changed","heartbeat","session.ended"],Ew=["permission_prompt","elicitation_dialog","agent_needs_input"];Mf=t=>Ui(t,Ec,!0),Lf=t=>Ui(t,Nf,!0),Iw=/^(null|~|true|false|yes|no|on|off|y|n)$/i,Df=/^[-+]?(\d[\d_]*)?(\.\d+)?([eE][-+]?\d+)?$/;Lw=["id","session_id","session_title","created_at","trigger","machine","repo","branch","cwd","files_touched","turns_since_last","user_note","open_question","parse_ok","length","generator","schema"];Dw={doing:"doing","stopped at":"stoppedAt",next:"next","open question":"openQuestion",note:"userNote"};Rw=["id","title","title_source","machine","cwd","repo","transcript_path","model","surface","capture","status","started_at","last_activity_at","last_heartbeat_at","ended_at","end_reason","turns","last_checkpoint_id","last_checkpoint_at","parent_session_id","pinned","archived","ignored","schema"],jw=["inTurn","needsInput","backgroundTasks","turnsSinceCheckpoint","dormantStretch","finalCheckpointDone","firstPrompt","lastPreview","lastNotification","lastEventAt","statusChanges","folder","pid","tty","pidStartedAt","pidSource","term","closedAt","answers","lastNotificationMessage","needsInputAt","question","questionAt","startSource","historyTurns","healedAt","healedVia","archivedReason"],Pw=["id","title","titleSource","machine","cwd","repo","transcriptPath","model","surface","capture","status","startedAt","lastActivityAt","lastHeartbeatAt","endedAt","endReason","turns","lastCheckpointId","lastCheckpointAt","parentSessionId","pinned","archived","ignored"];Fw=/\s*(?:·|-|\|)?\s*cp:([a-z0-9]{6})\s*$/i;Mc="{name} · cp:{id}";Uf=["TERM_PROGRAM","TERM_PROGRAM_VERSION","TERM","GHOSTTY_RESOURCES_DIR","ITERM_SESSION_ID","WEZTERM_PANE","WEZTERM_UNIX_SOCKET","KITTY_WINDOW_ID","KITTY_LISTEN_ON","TMUX","TMUX_PANE","TERM_SESSION_ID"],Tf={ghostty:"Ghostty",iterm2:"iTerm2",wezterm:"WezTerm",kitty:"kitty","apple-terminal":"Terminal.app",tmux:"tmux",vscode:"VS Code",unknown:"unknown terminal"};ji=6e4;Kf=["Edit","Write","MultiEdit","NotebookEdit"];ir={short:{label:"Short",maxTokens:300,approx:"~300 tokens",blurb:"Doing / Stopped at / Next. Three lines, nothing else."},informative:{label:"Informative",maxTokens:700,approx:"~700 tokens",blurb:"The three lines, plus key decisions, open questions, files touched and why, and what didn't work."},long:{label:"Long",maxTokens:1500,approx:"~1500 tokens",blurb:"Everything in Informative, plus a short narrative recap and the next 3 steps."}},G1=Object.keys(ir),Ww="informative";Ac=["You write checkpoints for a developer juggling many coding-agent sessions. Given the recent transcript of ONE session, start with exactly these three lines:","Doing: <what the session was working on, one sentence, concrete nouns: files, features, errors>","Stopped at: <the exact state when it went quiet: what passed, what failed, what is half-done>","Next: <the single next action a human or the agent should take>",'If the session is blocked on the human (a question, a permission, a decision), start Next with "YOU:".'],_f=['Then these sections, each a heading line followed by "- " bullets (write "- none" when there is nothing; never invent):',"Decisions:","- <a decision made in the session and the reason, one line each, at most 4>","Open questions:","- <an unresolved question or uncertainty, at most 3>","Files:","- <path> — <why it was touched, a few words; at most 6 files>","Didn't work:","- <an approach that was tried and abandoned or failed, and why; at most 3>"],zw=["Then:","Recap: <a narrative paragraph of 4-7 sentences: the goal, how the session got here, the current state>","Next steps:","1. <the next step>","2. <the step after>","3. <the step after that>"],Gf={short:[...Ac,"Write only those three lines.",'Optionally a 4th line "Title: <max 6 words>" if asked for a title.',"No preamble. No markdown. Under 60 words total."].join(`
|
|
35
|
+
`),informative:[...Ac,..._f,'Optionally a final line "Title: <max 6 words>" if asked for a title.',"No preamble. No other markdown. Under 250 words total."].join(`
|
|
36
|
+
`),long:[...Ac,..._f,...zw,'Optionally a final line "Title: <max 6 words>" if asked for a title.',"No preamble. No other markdown. Under 600 words total."].join(`
|
|
37
|
+
`)},$1=Gf.short;Jw={decisions:"decisions","key decisions":"decisions","open questions":"questions",questions:"questions",files:"files","files touched":"files","didn't work":"didntWork","didnt work":"didntWork","did not work":"didntWork","what didn't work":"didntWork","what didnt work":"didntWork",tried:"didntWork",recap:"recap","next steps":"nextSteps","next 3 steps":"nextSteps"},Ef={decisions:"Key decisions",questions:"Open questions",files:"Files touched",didntWork:"What didn't work",recap:"Recap",nextSteps:"Next steps"},qw=["decisions","questions","files","didntWork","recap","nextSteps"],Vw=/^(none|n\/a|nothing|-|—|none yet|no(ne)? (open )?questions?)\.?$/i,tr={decisions:6,questions:5,files:10,didntWork:5,nextSteps:5};zf="Write a title of at most 6 words for a coding session, based on its first prompt. Reply with the title only, no quotes, no punctuation at the end.";Cf=/^\s*(```|~~~)/,Xw=/^(\s*)(?:[-*+]|\d{1,3}[.)])\s+\[( |x|X)\]\s+(.*\S)\s*$/,Qw=/^(\s*)(\d{1,3})[.)]\s+(.*\S)\s*$/,e0=/^(\s*)[-*+•]\s+(.*\S)\s*$/,t0=/^\s*(?:#{1,6}\s+(.+?)\s*#*|\*\*(.+?)\*\*\s*:?|__(.+?)__\s*:?|(\S[^\n]{0,100}?)\s*:)\s*$/,n0=/\b(need to|you'?ll have to|make sure|in order to|do next|to do|how to|instructions|checklist|next(?: steps?)?|steps?|to-?dos?|tasks?|plan|remaining|follow[- ]?ups?|action items?|check-?list|left to do|what'?s left|still to do|open items?|road ?map|phases?|milestones?|punch ?list|before (?:we|you|i) (?:ship|merge|launch))\b/i;Oc=t=>String(t||"").toLowerCase().replace(/[`*_~]/g,"").replace(/[^a-z0-9]+/g," ").trim();o0=["name","repo","repo_key","source_session","source_machine","source_title","created","updated","archived","next_id","schema"],r0=/^(\s*)[-*+]\s+\[( |x|X)\]\s+(.*?)\s*(?:<!--\s*#(\d+)(?:\s+(\S+))?\s*-->)?\s*$/});import ae from"node:fs";import Ji from"node:os";import xe from"node:path";import a0 from"node:crypto";function qi(){return xe.join(Ji.homedir(),"checkpoints")}function th(){return xe.join(process.env.XDG_CONFIG_HOME||xe.join(Ji.homedir(),".config"),"checkpoints","folder")}function l0(){try{return ae.readFileSync(th(),"utf8").trim()||null}catch{return null}}function nh(t){let e=th();if(xe.resolve(t)===qi()){try{ae.unlinkSync(e)}catch{}return null}return ae.mkdirSync(xe.dirname(e),{recursive:!0}),ae.writeFileSync(e,xe.resolve(t)+`
|
|
38
|
+
`),e}function q(){return process.env.LEFTOFF_DIR||process.env.CHECKPOINTS_DIR||l0()||qi()}function le(t){return Ki({env:process.env.CHECKPOINTS_MACHINE,hostname:Ji.hostname(),cfg:t||{}})}function we(t=q()){return{root:t,config:xe.join(t,"config.json"),readme:xe.join(t,"README.md"),sessions:xe.join(t,"sessions"),digests:xe.join(t,"digests"),catchups:xe.join(t,"catchups"),spool:xe.join(t,".spool"),secret:xe.join(t,".secret"),token:xe.join(t,".secret","token")}}function Te(t=q()){let e=we(t);for(let n of[e.root,e.sessions,e.digests,e.spool])ae.mkdirSync(n,{recursive:!0});ae.mkdirSync(e.secret,{recursive:!0,mode:448}),ae.existsSync(e.readme)||ae.writeFileSync(e.readme,c0),ae.existsSync(e.config)||ae.writeFileSync(e.config,JSON.stringify(ye,null,2)+`
|
|
39
|
+
`),ae.existsSync(e.token)||ae.writeFileSync(e.token,a0.randomBytes(32).toString("hex")+`
|
|
40
|
+
`,{mode:384});try{ae.chmodSync(e.token,384)}catch{}return e}function Z(t=q()){let e=we(t),n={};try{n=JSON.parse(ae.readFileSync(e.config,"utf8"))}catch{}n.summarizer&&n.summarizer.mode&&!n.summarizer.provider&&(n.summarizer={...n.summarizer,provider:n.summarizer.mode}),n.tabTitleFormat===Mc&&delete n.tabTitleFormat;let s=$s(ye,n);s.summarizer&&delete s.summarizer.mode;let o=process.env.LEFTOFF_PORT||process.env.CHECKPOINTS_PORT;return o&&(s.port=Number(o)),process.env.CHECKPOINTS_SUMMARIZER&&(s=$s(s,{summarizer:{provider:process.env.CHECKPOINTS_SUMMARIZER}})),s}function Js(t,e=q()){let n=we(e),s=n.config+".tmp-"+process.pid;ae.writeFileSync(s,JSON.stringify(t,null,2)+`
|
|
41
|
+
`),ae.renameSync(s,n.config)}function Se(t=q()){try{return ae.readFileSync(we(t).token,"utf8").trim()}catch{return null}}function un(t=q()){try{return JSON.parse(ae.readFileSync(we(t).config,"utf8"))}catch{return null}}function Jt(t=process.env){return xe.join(t.XDG_CONFIG_HOME||xe.join(t.HOME||Ji.homedir(),".config"),"checkpoints","secrets")}function sh(t,e=process.env){let n=String(t||"").match(/^\.secret\/([^/]+)$/);return n?xe.join(Jt(e),n[1]):null}function Vi(t,e,n=q(),s=process.env){let o=Jt(s);ae.mkdirSync(o,{recursive:!0,mode:448});try{ae.chmodSync(o,448)}catch{}let r=xe.join(o,t);ae.writeFileSync(r,String(e).trim()+`
|
|
42
|
+
`,{mode:384});try{ae.chmodSync(r,384)}catch{}try{ae.unlinkSync(xe.join(we(n).secret,t))}catch{}return xe.join(".secret",t)}function oh(t=q(),e=process.env){let n=[],s=[];try{s=ae.readdirSync(we(t).secret)}catch{return n}for(let o of s){if(!/-api-key$/.test(o))continue;let r=xe.join(we(t).secret,o),i=xe.join(Jt(e),o);try{if(!ae.existsSync(i)&&(ae.mkdirSync(xe.dirname(i),{recursive:!0,mode:448}),ae.copyFileSync(r,i),ae.chmodSync(i,384),ae.readFileSync(i,"utf8")!==ae.readFileSync(r,"utf8")))throw new Error("copy differs");ae.unlinkSync(r),n.push(o)}catch{}}return n}var J,c0,ke=V(()=>{ve();J=(()=>{try{return JSON.parse(ae.readFileSync(new URL("../package.json",import.meta.url),"utf8")).version}catch{return"0.0.0"}})();c0=`# Leftoff
|
|
43
|
+
|
|
44
|
+
This folder is written by the Leftoff daemon and the Claude Code mod.
|
|
45
|
+
It is the source of truth: plain markdown with YAML frontmatter, safe to read,
|
|
46
|
+
grep and sync. The daemon rebuilds its index from here at start.
|
|
47
|
+
|
|
48
|
+
- sessions/<date>_<repo>_<title>_<id8>/session.md: one per Claude Code session
|
|
49
|
+
- sessions/.../checkpoints/<timestamp>.md: immutable checkpoints
|
|
50
|
+
- digests/YYYY-MM-DD.md: the daily "where you left off" digest
|
|
51
|
+
- catchups/YYYY-MM-DDTHHMM.md: each "Catch me up" recap (leftoff catchup, or the board)
|
|
52
|
+
- lists/<repo>/<slug>.md: the checklists you saved (leftoff todo, or the board)
|
|
53
|
+
- machines/<machine>.json: each Mac's public key and addresses (other machines answering and Go)
|
|
54
|
+
- agents/<name>/: an external agent's feed (Tab and others), pulled by one Mac; no keys
|
|
55
|
+
- config.json: port, thresholds, digest time, summarizer
|
|
56
|
+
- .spool/: events written while the daemon was down (drained at start)
|
|
57
|
+
- .secret/token: the local API token (mode 0600). Don't share it.
|
|
58
|
+
(API keys live outside the folder, in ~/.config/checkpoints/secrets/, since 0.5.3.)
|
|
59
|
+
`});var xh={};zt(xh,{BREW_URL:()=>uh,CODE_PREFIX:()=>lr,DOWNLOAD_URL:()=>ch,FOLDER_ID:()=>Ae,FOLDER_LABEL:()=>ah,INSTALL_COMMANDS:()=>$c,INSTALL_LINE:()=>cr,PAIR_TTL_MS:()=>lh,PROOF_RE:()=>qc,PairCodeError:()=>dn,STIGNORE_LINES:()=>Qi,SyncthingClient:()=>Xi,SyncthingError:()=>Ve,acceptKnocked:()=>wr,armPairing:()=>mr,autoAcceptTick:()=>gr,brewPath:()=>ta,cancelPairing:()=>Zc,codeDigest:()=>Jc,connect:()=>ot,decodePairCode:()=>zc,encodePairCode:()=>Gc,ensureDevice:()=>sa,ensureFolder:()=>na,explainBrewFailure:()=>yh,findSyncthingConfig:()=>mh,guiUrl:()=>ph,installPlan:()=>ys,knockedDevices:()=>yr,localDir:()=>Vs,nameWithoutProof:()=>Zs,nestedIn:()=>Ss,pairProof:()=>oa,parseSyncthingConfig:()=>hh,peerAccepted:()=>ra,progressLine:()=>Qc,proofName:()=>Vc,publicPairing:()=>jn,readManaged:()=>pn,readPairing:()=>It,readRunStatus:()=>Wc,runInstall:()=>dr,sameDevice:()=>_e,shortId:()=>Dn,startSyncthing:()=>fr,statusFile:()=>Yc,stopSyncing:()=>pr,stuckSteps:()=>Xc,suggestPath:()=>hr,syncthingConfigCandidates:()=>fh,syncthingInstall:()=>hn,syncthingStatus:()=>qt,tilde:()=>Ee,untilde:()=>ea,waitForSyncthing:()=>ws,writeManaged:()=>Rn,writeRunStatus:()=>Qs,writeStignore:()=>Sh});import Me from"node:fs";import u0 from"node:os";import ie from"node:path";import d0 from"node:http";import f0 from"node:https";import Xs from"node:crypto";import{spawn as ih,spawnSync as rh}from"node:child_process";function Vs(t=process.env){return ie.join(t.XDG_CONFIG_HOME||ie.join(pt(t),".config"),"checkpoints")}function ur(t){try{return JSON.parse(Me.readFileSync(t,"utf8"))}catch{return null}}function gs(t,e){Me.mkdirSync(ie.dirname(t),{recursive:!0,mode:448});let n=t+".tmp-"+process.pid;Me.writeFileSync(n,JSON.stringify(e,null,2)+`
|
|
60
|
+
`,{mode:384}),Me.renameSync(n,t);try{Me.chmodSync(t,384)}catch{}}function fh({env:t=process.env,platform:e=process.platform}={}){let n=pt(t),s=[];for(let i of[t.LEFTOFF_SYNCTHING_HOME,t.STHOMEDIR])i&&s.push(ie.join(i,"config.xml"));let o=ie.join(n,"Library","Application Support","Syncthing","config.xml"),r=[ie.join(t.XDG_STATE_HOME||ie.join(n,".local","state"),"syncthing","config.xml"),ie.join(t.XDG_CONFIG_HOME||ie.join(n,".config"),"syncthing","config.xml")];return e==="darwin"?s.push(o,...r):s.push(...r,o),[...new Set(s)]}function hh(t,{home:e=pt()}={}){let n={gui:null,folders:[],devices:[]},s=String(t).match(/<gui\b([^>]*)>([\s\S]*?)<\/gui>/);if(s){let o=((s[2].match(/<address>([^<]*)<\/address>/)||[])[1]||"").trim(),r=((s[2].match(/<apikey>([^<]*)<\/apikey>/)||[])[1]||"").trim(),i=/\btls="true"/.test(s[1]);n.gui={address:Zi(o),tls:i,apiKey:Zi(r)||null,url:ph(Zi(o),i)}}for(let o of String(t).matchAll(/<folder\b([^>]*?)(\/>|>([\s\S]*?)<\/folder>)/g)){let r=o[1],i=qs(r,"path");if(!i)continue;i=ie.resolve(i.replace(/^~(?=\/|$)/,e));let a=[...String(o[3]||"").matchAll(/<device\b[^>]*\bid="([^"]+)"/g)].map(l=>l[1]);n.folders.push({id:qs(r,"id")||"",label:qs(r,"label")||"",path:i,type:qs(r,"type")||"sendreceive",devices:a})}for(let o of String(t).matchAll(/<device\b([^>]*\bname="[^"]*"[^>]*)>/g))n.devices.push({id:qs(o[1],"id"),name:qs(o[1],"name")});return n}function ph(t,e=!1){if(!t||/^unix:/.test(t))return null;let s=t.replace(/^https?:\/\//,"").match(/^(\[[^\]]+\]|[^:]*):(\d+)$/);if(!s)return null;let o=s[1];return(o===""||o==="0.0.0.0")&&(o="127.0.0.1"),o==="[::]"&&(o="[::1]"),(e?"https":"http")+"://"+o+":"+s[2]}function mh(t={}){let e=t.env||process.env;for(let n of fh(t)){let s;try{s=Me.readFileSync(n,"utf8")}catch{continue}return{file:n,...hh(s,{home:pt(e)})}}return null}function ot(t={}){let e=t.env||process.env,n=mh({env:e,platform:t.platform});if(!n)return{ok:!1,code:"no_config",reason:"Syncthing isn’t set up on this Mac yet"};try{return{ok:!0,cfg:n,client:Xi.fromConfig(n,{managedDevices:()=>pn(e).devices,...t.client})}}catch(s){return{ok:!1,cfg:n,code:s.code,reason:s.message}}}function ta(t=process.env){return t.LEFTOFF_NO_BREW?null:gh("brew",t)||["/opt/homebrew/bin/brew","/usr/local/bin/brew","/home/linuxbrew/.linuxbrew/bin/brew"].find(e=>{try{return Me.accessSync(e,Me.constants.X_OK),!0}catch{return!1}})||null}function hn(t=process.env){let e=ta(t);if(e){let s=ie.join(ie.dirname(e),"syncthing");if(h0(s))return{installed:!0,via:"brew",path:s,brew:e}}let n=gh("syncthing",t);if(n)return{installed:!0,via:e&&n.startsWith(ie.dirname(ie.dirname(e)))?"brew":"path",path:n,brew:e};for(let s of["/Applications/Syncthing.app",ie.join(pt(t),"Applications","Syncthing.app")])if(p0(s))return{installed:!0,via:"app",path:s,brew:e};return{installed:!1,via:null,path:null,brew:e}}function ys(t=process.env){return ta(t)?{brew:!0,command:cr,steps:$c.map(n=>["brew",...n].join(" "))}:{brew:!1,directions:["Install Homebrew from brew.sh (one line in Terminal), then come back and press Check again. Leftoff will install Syncthing for you.","Or download Syncthing for macOS from syncthing.net/downloads, open it once, then press Check again."],links:{brew:uh,download:ch}}}function yh(t,e){let n=t.join(`
|
|
61
|
+
`);if(/Permission denied|Operation not permitted/i.test(n))return"Homebrew couldn’t write to its folder (permissions). Run `brew doctor` in Terminal, then try again.";if(/Could not resolve host|Failed to download|curl: \(\d+\)|network/i.test(n))return"Homebrew couldn’t download Syncthing. Check your internet connection and try again.";if(/another active Homebrew|already running|lock/i.test(n)&&/brew/i.test(n))return"Homebrew is busy with another install. Wait for it to finish, then try again.";if(/No available formula|No formulae found/i.test(n))return"Homebrew doesn’t know Syncthing (an old Homebrew?). Run `brew update`, then try again.";if(/xcode-select|Command Line Tools/i.test(n))return"Homebrew needs Apple’s Command Line Tools. Run `xcode-select --install`, then try again.";let s=t.filter(o=>/\S/.test(o)).slice(-1)[0]||"";return"Homebrew stopped (exit "+e+")"+(s?": "+s.replace(/^Error:\s*/,"").slice(0,200):"")+"."}async function dr({env:t=process.env,onLine:e=()=>{},onStage:n=()=>{}}={}){let s=ta(t);if(!s)return{ok:!1,code:"no_brew",error:"Homebrew isn’t installed on this Mac."};let o=[],r={...t,HOMEBREW_NO_ENV_HINTS:"1",HOMEBREW_NO_INSTALL_CLEANUP:"1",NONINTERACTIVE:"1"};for(let i of $c){let a=["brew",...i].join(" ");n(a),e("$ "+a);let l=await new Promise(c=>{let u=ih(s,i,{env:r,stdio:["ignore","pipe","pipe"]}),d="",f=p=>{d+=p;let h;for(;(h=d.search(/[\r\n]/))>=0;){let m=d.slice(0,h);d=d.slice(h+1),m.trim()&&(o.push(m),o.length>400&&o.shift(),e(m))}};u.stdout.on("data",f),u.stderr.on("data",f),u.on("error",p=>{o.push(p.message),e(p.message),c(127)}),u.on("close",p=>{d.trim()&&(o.push(d),e(d)),c(p??1)})});if(l!==0)return{ok:!1,code:"brew_failed",exit:l,stage:a,error:yh(o,l),lines:o}}return{ok:!0,lines:o}}function fr(t=process.env){let e=hn(t);if(!e.installed)return{ok:!1,code:"not_installed",error:"Syncthing isn’t installed"};if(e.via==="brew"){let o=rh(e.brew,["services","start","syncthing"],{env:{...t,HOMEBREW_NO_ENV_HINTS:"1"},encoding:"utf8",timeout:6e4});return o.status===0?{ok:!0,how:"brew services start syncthing"}:{ok:!1,code:"start_failed",error:(o.stderr||o.stdout||"").trim().split(`
|
|
62
|
+
`).pop()||"brew services failed"}}if(e.via==="app")return rh("open",["-a",e.path],{encoding:"utf8"}).status===0?{ok:!0,how:"opened Syncthing.app"}:{ok:!1,code:"start_failed",error:"couldn’t open Syncthing.app"};Me.mkdirSync(Vs(t),{recursive:!0});let n=Me.openSync(ie.join(Vs(t),"syncthing.log"),"a"),s=ih(e.path,["serve","--no-browser","--no-restart"],{env:t,detached:!0,stdio:["ignore",n,n]});return s.unref(),{ok:!0,how:"syncthing serve (pid "+s.pid+")"}}async function ws({env:t=process.env,timeoutMs:e=9e4,every:n=500}={}){let s=Date.now(),o=null;for(;Date.now()-s<e;){let r=ot({env:t});if(r.ok)try{return await r.client.ping(),{ok:!0,...r}}catch(i){o=i.message}else o=r.reason;await dh(n)}return{ok:!1,code:"not_running",error:"Syncthing didn’t come up"+(o?" ("+o+")":"")}}function pn(t=process.env){let e=ur(wh(t))||{};return{devices:e.devices||[],originalName:e.originalName||null,usedCodes:e.usedCodes||[],folderPath:e.folderPath||null}}function Rn(t,e=process.env){gs(wh(e),{...pn(e),...t})}function m0(t,e){let n=pn(e);n.devices.some(s=>_e(s,t))||Rn({devices:[...n.devices,t]},e)}function Wc(t=process.env){return ur(Yc(t))}function Qs(t,e=process.env){gs(Yc(e),{...t,at:new Date().toISOString()})}function Ss(t,e,n=Ae){let s=ie.resolve(t);for(let o of e||[]){if(o.id===n)continue;let r=ie.resolve(o.path);if(s===r||s.startsWith(r+ie.sep))return{id:o.id,label:o.label||o.id,path:r,how:"inside"};if(r.startsWith(s+ie.sep))return{id:o.id,label:o.label||o.id,path:r,how:"contains"}}return null}function hr(t,e=process.env){let n=(t||[]).find(s=>s.id===Ae);if(n)return n.path;for(let s of[ie.join(pt(e),"Sync","Leftoff"),ie.join(pt(e),"Leftoff"),ie.join(pt(e),"Leftoff-Sync")])if(!Ss(s,t))return s;return ie.join(pt(e),"Leftoff-Sync")}async function na(t,{path:e,peers:n=[],myID:s}){let o=await t.folders(),r=Ss(e,o);if(r)throw new Ve(Ee(e)+(r.how==="inside"?" is inside":" contains")+" your Syncthing folder “"+r.label+"”. Pick a folder outside it; Leftoff won’t change that folder.",{code:"nested"});let i=o.find(c=>c.id===Ae);if(i){if(ie.resolve(i.path.replace(/^~(?=\/|$)/,pt()))!==ie.resolve(e))throw new Ve("Syncthing already has a Leftoff folder at "+Ee(i.path)+". Use that one, or press Stop syncing first.",{code:"folder_elsewhere"});let c=new Set((i.devices||[]).map(d=>Ot(d.deviceID))),u=n.filter(d=>!c.has(Ot(d)));return u.length&&await t.patchFolder(Ae,{devices:[...i.devices||[],...u.map(d=>({deviceID:d,introducedBy:"",encryptionPassword:""}))]}),await t.setIgnores(Ae,Qi).catch(()=>{}),{created:!1,folder:{...i,path:e}}}Me.mkdirSync(e,{recursive:!0}),Sh(e);let l={...await t.defaultsFolder()||{},id:Ae,label:ah,path:e,type:"sendreceive",paused:!1,fsWatcherEnabled:!0,rescanIntervalS:3600,ignorePerms:!1,devices:[s,...n].filter(Boolean).map(c=>({deviceID:c,introducedBy:"",encryptionPassword:""}))};return await t.putFolder(l),await t.setIgnores(Ae,Qi).catch(()=>{}),{created:!0,folder:l}}function Sh(t){let e=ie.join(t,".stignore"),n="";try{n=Me.readFileSync(e,"utf8")}catch{}return n.includes("Written by Leftoff")?!1:(Me.writeFileSync(e,(n?n.replace(/\n?$/,`
|
|
63
|
+
`):"")+Qi.join(`
|
|
64
|
+
`)+`
|
|
65
|
+
`),!0)}async function sa(t,{id:e,name:n,env:s=process.env}){let r=(await t.devices()).find(l=>_e(l.deviceID,e));if(r)return{created:!1,device:r};m0(e,s);let a={...await t.defaultsDevice()||{},deviceID:e,name:n,addresses:["dynamic"],compression:"metadata",introducer:!1,autoAcceptFolders:!1,paused:!1};return await t.putNewDevice(a),{created:!0,device:a}}async function pr(t){let e=await t.folder(Ae);return e?(await t.deleteFolder(Ae),{removed:!0,path:e.path}):{removed:!1,reason:"no Leftoff folder in Syncthing"}}function kh(t){let e=0,n=0,s="";for(let o of t)for(n=n<<8|o,e+=8;e>=5;)s+=Kc[n>>>e-5&31],e-=5;return e>0&&(s+=Kc[n<<5-e&31]),s}function g0(t){let e=String(t).toUpperCase().replace(/[OIL]/g,r=>r==="O"?"0":"1").replace(/[^0-9A-Z]/g,""),n=0,s=0,o=[];for(let r of e){let i=Kc.indexOf(r);if(i<0)throw new Error("bad character");s=(s<<5|i)&65535,n+=5,n>=8&&(o.push(s>>>n-8&255),n-=8)}return Buffer.from(o)}function y0(t){let e=Ot(t);if(e.length!==56)throw new Error("not a Syncthing device id");let n=0,s=0,o=[];for(let r of e)s=(s<<5|bh.indexOf(r))&65535,n+=5,n>=8&&(o.push(s>>>n-8&255),n-=8);return Buffer.from(o)}function w0(t){let e=0,n=0,s="";for(let o of t)for(n=(n<<8|o)&65535,e+=8;e>=5;)s+=bh[n>>>e-5&31],e-=5;return s.match(/.{7}/g).join("-")}function Gc({deviceId:t,folderId:e=Ae,machine:n="",secret:s,expiresAt:o}){if(!s||s.length!==16)throw new Error("secret must be 16 bytes");let r=Buffer.from(String(n).slice(0,40),"utf8").subarray(0,40),i=Buffer.from(String(e),"utf8").subarray(0,64),a=Buffer.alloc(4);a.writeUInt32BE(Math.floor(o/1e3));let l=Buffer.concat([Buffer.from([1]),a,s,y0(t),Buffer.from([i.length]),i,Buffer.from([r.length]),r]),c=Xs.createHash("sha256").update(l).digest().subarray(0,4);return lr+"-"+kh(Buffer.concat([l,c])).match(/.{1,5}/g).join("-")}function zc(t,{now:e=Date.now()}={}){let n=String(t||"").trim().replace(/^.*?\bjoin\s+/i,"").replace(/\s+/g,"");if(!new RegExp("^"+lr+"-?","i").test(n))throw new dn("That isn’t a Leftoff pairing code (it starts with "+lr+"-).","bad_code");n=n.replace(new RegExp("^"+lr+"-?","i"),"");let s;try{s=g0(n)}catch{throw new dn("That code has a character that doesn’t belong; copy it again.","bad_code")}if(s.length<62)throw new dn("That code is cut short; copy all of it.","bad_code");let o=s.subarray(0,s.length-4),r=s.subarray(s.length-4);if(!Xs.createHash("sha256").update(o).digest().subarray(0,4).equals(r))throw new dn("That code has a typo or is cut short; copy it again.","bad_code");if(o[0]!==1)throw new dn("That code is from a newer Leftoff; upgrade this Mac (leftoff upgrade).","bad_code");let i=1,a=o.readUInt32BE(i)*1e3;i+=4;let l=Buffer.from(o.subarray(i,i+16));i+=16;let c=w0(o.subarray(i,i+35));i+=35;let u=o[i++],d=o.subarray(i,i+u).toString("utf8");i+=u;let f=o[i++],p=o.subarray(i,i+f).toString("utf8");if(e>a)throw new dn("That code expired. Make a new one on "+(p||"the other Mac")+" (Settings → Sync → Pair another Mac).","expired");return{deviceId:c,folderId:d,machine:p,secret:l,expiresAt:a}}function oa(t,{joinerId:e,firstId:n,folderId:s=Ae}){let o=Xs.createHmac("sha256",t).update(`leftoff-pair-v1
|
|
66
|
+
`+Ot(e)+`
|
|
67
|
+
`+Ot(n)+`
|
|
68
|
+
`+s).digest();return kh(o).slice(0,16)}function It(t=process.env,e=Date.now()){let n=ur(fn(t));return n?{...n,live:!n.used&&e<n.expiresAt}:null}function jn(t,e=Date.now()){return t?{armed:!!(t.live&&e<t.expiresAt),expiresAt:new Date(t.expiresAt).toISOString(),expired:!t.used&&e>=t.expiresAt,used:!!t.used,paired:t.paired||null,ignored:(t.ignored||[]).length}:{armed:!1}}function mr({deviceId:t,machine:e,folderId:n=Ae,ttlMs:s=lh,env:o=process.env,now:r=Date.now(),reuse:i=!1}){let a=It(o,r);if(i&&a&&a.live&&_e(a.deviceId,t))return{...a,code:Gc({deviceId:t,folderId:a.folderId,machine:a.machine,secret:Buffer.from(a.secret,"base64"),expiresAt:a.expiresAt})};let l=Xs.randomBytes(16),c={deviceId:t,folderId:n,machine:e,secret:l.toString("base64"),createdAt:r,expiresAt:r+s,used:!1,paired:null,ignored:[]};return gs(fn(o),c),{...c,live:!0,code:Gc({deviceId:t,folderId:n,machine:e,secret:l,expiresAt:c.expiresAt})}}function Zc(t=process.env){try{Me.unlinkSync(fn(t))}catch{}}async function gr({client:t,env:e=process.env,now:n=Date.now(),log:s=()=>{}}){let o=It(e,n);if(!o)return{state:"idle"};if(o.used)return{state:"paired",paired:o.paired};if(n>=o.expiresAt)return o.expiredLogged||(gs(fn(e),{...o,secret:null,expiredLogged:!0,live:void 0}),s("syncthing: the pairing code expired unused; nothing was accepted")),{state:"expired"};let r=Buffer.from(o.secret,"base64"),i=await t.pendingDevices(),a=new Set(o.ignored||[]),l=new Set(o.dismissed||[]),c=[...o.knocked||[]],u=!1;for(let[d,f]of Object.entries(i||{})){if(!/^[A-Z2-7]{56}$/.test(Ot(d)))continue;let p=String(f&&f.name||""),h=p.match(qc);if(!(h&&Xs.timingSafeEqual(Buffer.from(h[1]),Buffer.from(oa(r,{joinerId:d,firstId:o.deviceId,folderId:o.folderId}))))){let k=Zs(p).slice(0,80),S=c.find(v=>_e(v.deviceId,d));if(S?(S.name!==k||S.at!==(f&&f.time||S.at))&&(S.name=k,S.at=f&&f.time||S.at,S.seen=new Date(n).toISOString(),u=!0):(c.push({deviceId:d,name:k,at:f&&f.time||new Date(n).toISOString(),seen:new Date(n).toISOString()}),u=!0),a.has(Ot(d))||(a.add(Ot(d)),u=!0,s("syncthing: a device named “"+k+"” ("+Dn(d)+") asked to connect without this pairing code’s proof; not accepted")),!l.has(Ot(d))){l.add(Ot(d)),u=!0;try{await t.dismissPendingDevice(d),s("syncthing: cleared Syncthing’s pending record of "+Dn(d)+" once, so it can knock again with its current name")}catch{}}continue}let y=Zs(p)||"other Mac",w={deviceId:d,name:y,at:new Date(n).toISOString(),how:"code"};return gs(fn(e),{...o,live:void 0,secret:null,used:!0,paired:w,ignored:[...a],dismissed:[...l],knocked:c.filter(k=>!_e(k.deviceId,d))}),await vh(t,{id:d,name:y,folderId:o.folderId,env:e}),s("syncthing: paired with "+y+" ("+Dn(d)+") by code; sharing the Leftoff folder with it"),{state:"accepted",paired:w}}return u&&gs(fn(e),{...o,live:void 0,ignored:[...a],dismissed:[...l],knocked:c}),{state:"waiting",knocked:c.length}}async function vh(t,{id:e,name:n,folderId:s=Ae,env:o}){await sa(t,{id:e,name:n,env:o});let r=await t.folder(s);r&&!(r.devices||[]).some(i=>_e(i.deviceID,e))&&await t.patchFolder(s,{devices:[...r.devices||[],{deviceID:e,introducedBy:"",encryptionPassword:""}]})}async function yr(t,{env:e=process.env}={}){let n=ur(fn(e));if(!n||n.used||!(n.knocked||[]).length)return[];let[s,o]=await Promise.all([t.devices().catch(()=>[]),t.pendingDevices().catch(()=>({}))]),r=[];for(let i of n.knocked){if(s.some(c=>_e(c.deviceID,i.deviceId))||n.paired&&_e(n.paired.deviceId,i.deviceId))continue;let a=Object.entries(o||{}).find(([c])=>_e(c,i.deviceId));if(!a&&!(Date.parse(i.seen||i.at)>Date.now()-30*6e4))continue;let l=a&&Zs(a[1]&&a[1].name).slice(0,80)||i.name;r.push({deviceId:i.deviceId,short:Dn(i.deviceId),name:l||Dn(i.deviceId),at:a&&a[1]&&a[1].time||i.at,pending:!!a})}return r}async function wr(t,{deviceId:e,env:n=process.env,now:s=Date.now(),log:o=()=>{}}){let i=(await yr(t,{env:n})).find(c=>_e(c.deviceId,e)||e&&c.name===e);if(!i)throw new Ve("That Mac didn’t ask to connect during this pairing (or it’s already added). Make a new code and try again.",{code:"not_knocked"});let a=ur(fn(n))||{},l={deviceId:i.deviceId,name:i.name,at:new Date(s).toISOString(),how:"manual"};return gs(fn(n),{...a,live:void 0,secret:null,used:!0,paired:l,knocked:(a.knocked||[]).filter(c=>!_e(c.deviceId,i.deviceId))}),await vh(t,{id:i.deviceId,name:i.name,folderId:a.folderId||Ae,env:n}),o("syncthing: accepted "+i.name+" ("+i.short+") by an explicit click, without the pairing proof; sharing only the Leftoff folder with it"),{accepted:!0,paired:l}}async function ra(t,{peerId:e,folderId:n=Ae}){let s=await t.connections().catch(()=>({})),o=Object.entries(s||{}).find(([l])=>_e(l,e));if(!o||!o[1]||!o[1].connected)return{connected:!1,accepted:!1,state:"disconnected"};let r=await t.completion(e,n).catch(()=>null),i=r&&r.remoteState;if(i==="valid")return{connected:!0,accepted:!0,state:"valid"};if(i)return{connected:!0,accepted:!1,state:i};let a=await t.dbStatus(n).catch(()=>null);return{connected:!0,accepted:!!(r&&a&&a.globalFiles>0),state:"unknown"}}function Xc({first:t,name:e}){return["On "+t+", open http://127.0.0.1:8384 (Syncthing).","Click Add Device on the “"+e+"” wants to connect banner, then Save.","On that device’s Sharing tab, tick Leftoff, then Save."]}function S0(t,e){let n=t&&t[Ae];return[n&&n.lastFile&&n.lastFile.at,n&&n.lastScan,e&&e.stateChanged].filter(o=>o&&!/^0001-/.test(o)).sort().slice(-1)[0]||null}async function qt({env:t=process.env,root:e=null,platform:n}={}){let s=hn(t),o={installed:s.installed,via:s.via,brew:!!s.brew,running:!1,version:null,reason:null,folder:null,peers:[],others:0,suggestedPath:null,nested:null,knocked:[]},r=ot({env:t,platform:n});if(r.cfg&&!s.installed&&(o.installed=!0,o.via="config"),!r.ok)return o.reason=r.reason,o;try{await r.client.ping(),o.running=!0}catch(a){return o.reason=a.message,o}let i=r.client;try{let[a,l,c,u]=await Promise.all([i.version().catch(()=>null),i.systemStatus(),i.folders(),i.devices()]);o.version=a&&a.version,o.myID=l.myID,o.others=c.filter(f=>f.id!==Ae).length,o.suggestedPath=hr(c,t),e&&(o.nested=Ss(e,c));let d=c.find(f=>f.id===Ae);if(d){let[f,p,h,m,y]=await Promise.all([i.dbStatus().catch(()=>null),i.folderErrors(),i.statsFolder().catch(()=>({})),i.connections().catch(()=>({})),i.statsDevice().catch(()=>({}))]);o.folder={id:d.id,label:d.label,path:d.path,display:Ee(d.path,t),isCurrent:!!e&&ie.resolve(d.path)===ie.resolve(e),paused:!!d.paused,state:f?f.state:"unknown",globalFiles:f?f.globalFiles:null,inSyncFiles:f?f.inSyncFiles:null,needFiles:f?f.needFiles:null,errors:[...(p||[]).map(w=>({path:w.path,error:w.error})),...f&&f.error?[{path:"",error:f.error}]:[]].slice(0,10),lastSync:S0(h,f)};for(let w of d.devices||[]){if(_e(w.deviceID,l.myID))continue;let k=u.find(g=>_e(g.deviceID,w.deviceID))||{},S=m[w.deviceID]||{},v=null,A=null;if(S.connected)try{let g=await i.completion(w.deviceID);v=g&&g.completion!=null?Math.max(0,Math.min(100,Math.round(g.completion))):null,A=g&&g.remoteState||null}catch{}let b=!A||A==="valid";o.peers.push({name:k.name||Dn(w.deviceID),short:Dn(w.deviceID),connected:!!S.connected&&b,notSharing:!!S.connected&&!b,lastSeen:(y[w.deviceID]||{}).lastSeen||null,completion:b?v:null})}o.knocked=await yr(i,{env:t}).catch(()=>[])}}catch(a){o.reason=a.message}return o}function Qc(t){if(!t)return"Waiting for Syncthing…";let e=t.globalFiles||0,n=t.inSyncFiles!=null?t.inSyncFiles:Math.max(0,e-(t.needFiles||0));return e?t.needFiles>0||t.state==="syncing"?"Syncing "+n+"/"+e+" files…":"In sync · "+e+" files":"Waiting for the first files…"}var Ae,ah,lh,$c,cr,ch,uh,pt,h0,p0,dh,Ee,ea,Zi,qs,Ve,Ot,_e,Dn,Xi,gh,wh,Yc,Qi,Kc,bh,lr,dn,Jc,qc,Vc,Zs,fn,eo=V(()=>{Ae="leftoff",ah="Leftoff",lh=900*1e3,$c=[["install","syncthing"],["services","start","syncthing"]],cr="brew install syncthing && brew services start syncthing",ch="https://syncthing.net/downloads/",uh="https://brew.sh",pt=(t=process.env)=>t.HOME||u0.homedir(),h0=t=>{try{return Me.statSync(t),!0}catch{return!1}},p0=t=>{try{return Me.statSync(t).isDirectory()}catch{return!1}},dh=t=>new Promise(e=>setTimeout(e,t)),Ee=(t,e=process.env)=>{let n=pt(e);return t&&(t===n?"~":t.startsWith(n+ie.sep)?"~"+t.slice(n.length):t)},ea=(t,e=process.env)=>t==="~"?pt(e):String(t).startsWith("~/")?ie.join(pt(e),String(t).slice(2)):String(t);Zi=t=>String(t).replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&"),qs=(t,e)=>{let n=t.match(new RegExp("\\b"+e+'="([^"]*)"'));return n?Zi(n[1]):null};Ve=class extends Error{constructor(e,{status:n=0,code:s="syncthing"}={}){super(e),this.status=n,this.code=s}},Ot=t=>String(t||"").toUpperCase().replace(/[^A-Z2-7]/g,""),_e=(t,e)=>!!t&&!!e&&Ot(t)===Ot(e),Dn=t=>String(t||"").split("-")[0]||"",Xi=class t{constructor({url:e,apiKey:n,timeoutMs:s=6e3,folderId:o=Ae,managedDevices:r=()=>[],log:i=null}={}){if(!e)throw new Ve("Syncthing’s GUI address isn’t readable from its config",{code:"no_address"});this.url=e.replace(/\/$/,""),Object.defineProperty(this,"apiKey",{value:n,enumerable:!1}),this.timeoutMs=s,this.folderId=o,this.managedDevices=r,this.calls=[],this.log=i}static fromConfig(e,n={}){if(!e||!e.gui)throw new Ve("Syncthing hasn’t written its settings yet",{code:"no_config"});if(!e.gui.apiKey)throw new Ve("Syncthing’s config has no API key yet",{code:"no_api_key"});return new t({url:e.gui.url,apiKey:e.gui.apiKey,...n})}req(e,n,s,{timeoutMs:o}={}){let r=new URL(this.url+n),i=r.protocol==="https:"?f0:d0,a=s===void 0?null:Buffer.from(JSON.stringify(s));return e!=="GET"&&this.calls.push({method:e,path:r.pathname+r.search}),new Promise((l,c)=>{let u=i.request(r,{method:e,headers:{"X-API-Key":this.apiKey||"",accept:"application/json",...a?{"content-type":"application/json","content-length":a.length}:{}},rejectUnauthorized:!1,timeout:o||this.timeoutMs},d=>{let f=[];d.on("data",p=>f.push(p)),d.on("end",()=>{let p=Buffer.concat(f).toString("utf8");if(d.statusCode>=400){let h=d.statusCode===401||d.statusCode===403?"Syncthing refused Leftoff’s request (its API key changed? Restart Syncthing and try again)":"Syncthing answered "+d.statusCode+(p?": "+p.trim().slice(0,160):"");return c(new Ve(h,{status:d.statusCode,code:d.statusCode===403||d.statusCode===401?"syncthing_forbidden":d.statusCode===404?"syncthing_not_found":"syncthing_error"}))}if(!p)return l(null);try{l(JSON.parse(p))}catch{l(p)}})});u.on("timeout",()=>u.destroy(new Ve("Syncthing didn’t answer in time",{code:"syncthing_timeout"}))),u.on("error",d=>c(d instanceof Ve?d:new Ve(d.code==="ECONNREFUSED"?"Syncthing isn’t running":"Can’t reach Syncthing ("+(d.code||d.message)+")",{code:d.code==="ECONNREFUSED"?"syncthing_down":"syncthing_unreachable"}))),a&&u.write(a),u.end()})}ping(){return this.req("GET","/rest/system/ping",void 0,{timeoutMs:2500})}systemStatus(){return this.req("GET","/rest/system/status")}version(){return this.req("GET","/rest/system/version")}folders(){return this.req("GET","/rest/config/folders")}devices(){return this.req("GET","/rest/config/devices")}async folder(e=this.folderId){try{return await this.req("GET","/rest/config/folders/"+encodeURIComponent(e))}catch(n){if(n.status===404)return null;throw n}}defaultsFolder(){return this.req("GET","/rest/config/defaults/folder").catch(()=>({}))}defaultsDevice(){return this.req("GET","/rest/config/defaults/device").catch(()=>({}))}pendingDevices(){return this.req("GET","/rest/cluster/pending/devices").then(e=>e||{})}pendingFolders(){return this.req("GET","/rest/cluster/pending/folders").then(e=>e||{})}completion(e,n=this.folderId){return this.req("GET","/rest/db/completion?folder="+encodeURIComponent(n)+(e?"&device="+encodeURIComponent(e):""))}dbStatus(e=this.folderId){return this.req("GET","/rest/db/status?folder="+encodeURIComponent(e))}connections(){return this.req("GET","/rest/system/connections").then(e=>e&&e.connections||{})}statsFolder(){return this.req("GET","/rest/stats/folder").then(e=>e||{})}statsDevice(){return this.req("GET","/rest/stats/device").then(e=>e||{})}folderErrors(e=this.folderId){return this.req("GET","/rest/folder/errors?folder="+encodeURIComponent(e)).then(n=>n&&n.errors||[]).catch(()=>[])}events(e=0,{timeoutS:n=1,types:s=null}={}){return this.req("GET","/rest/events?since="+e+"&timeout="+n+(s?"&events="+s.join(","):""),void 0,{timeoutMs:(n+3)*1e3}).then(o=>o||[])}assertOurFolder(e){if(e!==this.folderId)throw new Ve("Leftoff never changes another Syncthing folder ("+e+")",{code:"not_ours"})}assertOurDevice(e,{self:n=null}={}){if(!(n&&_e(e,n))&&!this.managedDevices().some(s=>_e(s,e)))throw new Ve("Leftoff never changes a Syncthing device it didn’t add",{code:"not_ours"})}async putFolder(e){return this.assertOurFolder(e.id),this.req("PUT","/rest/config/folders/"+encodeURIComponent(e.id),e)}async patchFolder(e,n){return this.assertOurFolder(e),this.req("PATCH","/rest/config/folders/"+encodeURIComponent(e),n)}async deleteFolder(e){return this.assertOurFolder(e),this.req("DELETE","/rest/config/folders/"+encodeURIComponent(e))}async setIgnores(e,n){return this.assertOurFolder(e),this.req("POST","/rest/db/ignores?folder="+encodeURIComponent(e),{ignore:n})}async putNewDevice(e){return this.assertOurDevice(e.deviceID),this.req("PUT","/rest/config/devices/"+encodeURIComponent(e.deviceID),e)}async patchDevice(e,n,{self:s=null}={}){return this.assertOurDevice(e,{self:s}),this.req("PATCH","/rest/config/devices/"+encodeURIComponent(e),n)}async reconnectDevice(e,{allow:n=null}={}){n&&_e(e,n)||this.assertOurDevice(e);let s="?device="+encodeURIComponent(e);return await this.req("POST","/rest/system/pause"+s),await dh(150),this.req("POST","/rest/system/resume"+s)}dismissPendingDevice(e){return this.req("DELETE","/rest/cluster/pending/devices?device="+encodeURIComponent(e))}};gh=(t,e)=>{for(let n of String(e.PATH||"").split(ie.delimiter)){if(!n)continue;let s=ie.join(n,t);try{if(Me.accessSync(s,Me.constants.X_OK),Me.statSync(s).isFile())return s}catch{}}return null};wh=t=>ie.join(Vs(t),"syncthing.json");Yc=(t=process.env)=>ie.join(Vs(t),"syncthing-status.json");Qi=["// Written by Leftoff. These stay on each Mac and never sync.","/.secret","/.daemon.pid","/.daemon.log","(?d).DS_Store","(?d)*.tmp","(?d)*.tmp-*","(?d)*.lock","(?d)/lists/.lock*"];Kc="0123456789ABCDEFGHJKMNPQRSTVWXYZ";bh="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";lr="LO1";dn=class extends Error{constructor(e,n){super(e),this.code=n}};Jc=t=>Xs.createHash("sha256").update(String(t).toUpperCase().replace(/[^0-9A-Z]/g,"")).digest("hex").slice(0,24);qc=/\(leftoff-pair ([0-9A-Z]{16})\)\s*$/,Vc=(t,e)=>String(t||"Mac").slice(0,40)+" (leftoff-pair "+e+")",Zs=t=>String(t||"").replace(qc,"").trim(),fn=t=>ie.join(Vs(t),"syncthing-pairing.json")});import Sr from"node:fs";function Ah(t){return t==null?"":typeof t=="string"?t:Array.isArray(t)?t.map(e=>typeof e=="string"?e:e&&e.type==="text"&&e.text||"").filter(Boolean).join(`
|
|
69
|
+
`):typeof t=="object"&&t.text?String(t.text):""}function to(t){let e=[],n={cwd:null,gitBranch:null,sessionId:null,customTitle:null,summary:null,lines:0,skipped:0},s=new Map;for(let o of String(t||"").split(`
|
|
70
|
+
`)){if(!o.trim())continue;n.lines++;let r;try{r=JSON.parse(o)}catch{n.skipped++;continue}if(!r||typeof r!="object"){n.skipped++;continue}if(r.cwd&&(n.cwd=r.cwd),r.gitBranch&&(n.gitBranch=r.gitBranch),r.sessionId&&(n.sessionId=r.sessionId),r.type==="summary"&&r.summary){n.summary=r.summary;continue}if((r.type==="custom-title"||r.type==="custom_title")&&(r.customTitle||r.title)){n.customTitle=r.customTitle||r.title;continue}if(r.isSidechain||r.isMeta)continue;let i=r.message;if(!i||r.type!=="user"&&r.type!=="assistant"){r.type!=="system"&&n.skipped++;continue}let a=i.role||r.type,l=i.content;if(a==="user"){let c=[],u="";if(typeof l=="string")u=l;else if(Array.isArray(l)){for(let d of l)if(d)if(d.type==="tool_result"){let f={tool_use_id:d.tool_use_id,text:Ah(d.content),isError:!!d.is_error};c.push(f);let p=s.get(d.tool_use_id);p&&(p.isError=f.isError,p.text=f.text)}else d.type==="text"&&(u+=(u?`
|
|
71
|
+
`:"")+(d.text||""))}if(/^\s*<(local-command-stdout|command-name|local-command-caveat)/.test(u)&&(u=""),!u&&!c.length)continue;e.push({role:"user",text:u,toolUses:[],toolResults:c})}else{let c=[],u="";for(let f of Array.isArray(l)?l:[{type:"text",text:Ah(l)}])if(f){if(f.type==="text"&&f.text)u+=(u?`
|
|
72
|
+
`:"")+f.text;else if(f.type==="tool_use"){let p={tool_use_id:f.id,tool:f.name,input:f.input||{},isError:!1,text:void 0};c.push(p),f.id&&s.set(f.id,p)}}if(!u&&!c.length)continue;let d=e[e.length-1];if(d&&d.role==="assistant"&&d._mid&&d._mid===i.id){u&&(d.text+=(d.text?`
|
|
73
|
+
`:"")+u),d.toolUses.push(...c);continue}e.push({role:"assistant",text:u,toolUses:c,_mid:i.id||null})}}for(let o of e)delete o._mid;return{messages:e,meta:n}}function kr(t,{maxBytes:e=8*1024*1024}={}){try{let n=Sr.statSync(t),s;if(n.size>e){let o=Sr.openSync(t,"r"),r=Buffer.alloc(e);Sr.readSync(o,r,0,e,n.size-e),Sr.closeSync(o),s=r.toString("utf8"),s=s.slice(s.indexOf(`
|
|
74
|
+
`)+1)}else s=Sr.readFileSync(t,"utf8");return to(s)}catch{return{messages:[],meta:{missing:!0}}}}var br=V(()=>{});import Pn from"node:fs";import k0 from"node:os";import eu from"node:path";import{execFile as b0}from"node:child_process";function A0(t=process.env){return t.CLAUDE_CONFIG_DIR||eu.join(t.HOME||k0.homedir(),".claude")}function vr(t,{env:e=process.env,dirs:n}={}){if(!t||!x0.test(t))return null;let s=n||[eu.join(A0(e),"projects")],o=null;for(let r of s){let i=[];try{i=Pn.readdirSync(r,{withFileTypes:!0}).filter(a=>a.isDirectory()).map(a=>a.name)}catch{continue}for(let a of i){let l=eu.join(r,a,t+".jsonl");try{let c=Pn.statSync(l);(!o||c.mtimeMs>o.mtimeMs)&&(o={file:l,mtimeMs:c.mtimeMs,size:c.size})}catch{}}}return o?o.file:null}function tu(t,{maxBytes:e=v0}={}){try{let n=Pn.statSync(t).size,s;if(n>e){let a=Pn.openSync(t,"r"),l=Buffer.alloc(256*1024),c=Pn.readSync(a,l,0,l.length,0);Pn.closeSync(a),s=l.subarray(0,c).toString("utf8");let{meta:u}=to(s);return{cwd:u.cwd||null,prompts:1,customTitle:u.customTitle||null,gitBranch:u.gitBranch||null,truncated:!0}}s=Pn.readFileSync(t,"utf8");let{messages:o,meta:r}=to(s),i=o.filter(a=>a.role==="user"&&a.text&&a.text.trim()).length;return{cwd:r.cwd||null,prompts:i,customTitle:r.customTitle||null,gitBranch:r.gitBranch||null}}catch{return null}}function _h(t,e,n=5e3){return new Promise(s=>{b0(t,e,{env:{...process.env,LC_ALL:"C"},timeout:n,maxBuffer:8*1024*1024},(o,r)=>s(o&&!r?"":String(r||"")))})}function T0(t){let e=[];for(let n of String(t||"").split(`
|
|
75
|
+
`)){let s=n.match(/^\s*(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(.*)$/);s&&e.push({pid:Number(s[1]),ppid:Number(s[2]),tty:s[3],etime:s[4],args:s[5].trim()})}return e}function _0(t){let e=new Map;for(let n of String(t||"").split(`
|
|
76
|
+
`)){let s=n.match(/^\s*(\d+)\s+(.*)$/);s&&e.set(Number(s[1]),s[2].trim())}return e}function Eh(t,e){let n=ia(e||"").toLowerCase(),s=ia(String(t.args||"").split(/\s+/)[0]).toLowerCase();if(/^claude(\s|$)/.test(n)||s==="claude")return!0;let o=String(t.args||"").split(/\s+/);return/^(node|bun)$/.test(s)&&(ia(o[1])==="claude"||/@anthropic-ai\/claude-code\/cli\.m?js$/.test(o[1]||""))}function E0(t,e,n=new Map){let s=String(t).replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),o=new RegExp("(?:^|\\s)(?:--resume|-r|--session-id)(?:=|\\s+)"+s+"(?=\\s|$)"),r=e.filter(l=>o.test(l.args)&&Eh(l,n.get(l.pid))),i=new Set(r.map(l=>l.pid)),a=r.filter(l=>!r.some(c=>c.ppid===l.pid&&i.has(c.pid)));return a.length===1?a[0]:null}async function Th(t,{platform:e=process.platform,runner:n=_h}={}){let s=new Map,o=t.filter(a=>Number.isInteger(a)&&a>1);if(!o.length)return s;if(e==="linux"){for(let a of o)try{s.set(a,Pn.readlinkSync("/proc/"+a+"/cwd"))}catch{}return s}let r=await n("lsof",["-a","-d","cwd","-p",o.join(","),"-Fpn"]),i=null;for(let a of r.split(`
|
|
77
|
+
`))a.startsWith("p")?i=Number(a.slice(1)):a.startsWith("n")&&i&&s.set(i,a.slice(1));return s}function O0(t,e,n=new Map){let s=new Map(e.map(r=>[r.pid,r])),o=s.get(t);for(let r=0;o&&r<12;r++){let i=ia(n.get(o.pid)||String(o.args).split(/\s+/)[0]);for(let[a,l]of C0)if(a.test(i))return{...Ln(l==="kitty"?{TERM:"xterm-kitty"}:{TERM_PROGRAM:l}),source:"ps"};if(/^tmux/i.test(i))return null;o=s.get(o.ppid)}return null}async function Ch(t,{cwd:e=null,claimedPids:n=new Set,env:s=process.env,platform:o=process.platform,runner:r=_h,now:i=Date.now(),transcriptDirs:a}={}){let l={via:[]},c=vr(t,{env:s,dirs:a});if(c){l.transcriptPath=c;let h=tu(c);h&&(h.cwd&&(l.cwd=h.cwd),l.prompts=h.prompts,h.customTitle&&(l.customTitle=h.customTitle),l.via.push("transcript"))}let u=e||l.cwd||null,d=T0(await r("ps",["-axo","pid=,ppid=,tty=,etime=,command="]));if(!d.length)return l;let f=_0(await r("ps",["-axo","pid=,comm="])),p=E0(t,d,f);if(p)l.via.push("ps-args");else if(u){let h=d.filter(k=>Eh(k,f.get(k.pid))&&!n.has(k.pid)),m=h.filter(k=>!h.some(S=>S.ppid===k.pid)),y=await Th(m.map(k=>k.pid),{platform:o,runner:r}),w=m.filter(k=>y.get(k.pid)===u);w.length===1&&(p=w[0],l.via.push("ps-cwd"))}if(p){let h=rr(p.etime);l.proc={pid:p.pid,tty:Dc(p.tty),pidStartedAt:h==null?null:G(i-h*1e3),source:"daemon"};let m=O0(p.pid,d,f);if(m&&(l.term=m,l.via.push("ps-term")),!l.cwd&&!e){let y=(await Th([p.pid],{platform:o,runner:r})).get(p.pid);y&&(l.cwd=y)}}return l}var v0,x0,ia,C0,nu=V(()=>{br();ve();v0=32*1024*1024,x0=/^[0-9A-Za-z][0-9A-Za-z_-]{6,80}$/;ia=t=>String(t||"").split("/").pop();C0=[[/^ghostty$/i,"ghostty"],[/^iterm2?$/i,"iTerm.app"],[/^terminal$/i,"Apple_Terminal"],[/^wezterm(-gui)?$/i,"WezTerm"],[/^kitty$/i,"kitty"]]});import Un from"node:fs";import Fn from"node:path";import I0 from"node:crypto";function Tr(t){let e=N0[t]||t;if(!Ar.includes(e))throw Object.assign(new Error("kind is machine, repo, session or agent"),{code:"bad_kind"});return e}function _r(t){return Ih.test(String(t))}function la(t){return String(t).replace(Ih,"")}function Er(t){let e=String(t??"").normalize("NFC").replace(/\s+/g," ").trim();if(!e)throw Object.assign(new Error("a space needs a name"),{code:"bad_space"});if(e.length>32)throw Object.assign(new Error("a space name is at most 32 characters"),{code:"bad_space"});if(!/^[\p{L}\p{N}][\p{L}\p{N} ._&'-]*$/u.test(e))throw Object.assign(new Error("a space name is letters, numbers, spaces and . _ & ' -"),{code:"bad_space"});return e}function Nh(t,e){let n=String(e??"").trim();if(!n||n.length>400||/[\u0000-\u001F]/.test(n))throw Object.assign(new Error("bad "+t.replace(/s$/,"")+" key"),{code:"bad_key"});return n}function ks(){return{schema:1,machines:{},repos:{},sessions:{},agents:{}}}function M0(t){if(t==null)return null;if(typeof t=="string")return{space:t,at:"1970-01-01T00:00:00.000Z",by:""};if(typeof t!="object")return null;let e=t.space==null?null:String(t.space),n=typeof t.at=="string"&&!Number.isNaN(Date.parse(t.at))?t.at:"1970-01-01T00:00:00.000Z";return{space:e,at:n,by:typeof t.by=="string"?t.by:""}}function Mh(t){let e=ks();if(!t||typeof t!="object")return e;for(let n of Ar){let s=t[n];if(!(!s||typeof s!="object"))for(let[o,r]of Object.entries(s)){let i=M0(r);i&&(e[n][o]=i)}}return e}function L0(t,e){if(!t)return e;if(!e)return t;let n=Date.parse(t.at),s=Date.parse(e.at);return n!==s?n>s?t:e:t.by!==e.by?t.by>e.by?t:e:t.space==null!=(e.space==null)?t.space==null?e:t:String(t.space)>=String(e.space)?t:e}function D0(...t){let e=ks();for(let n of t){let s=Mh(n);for(let o of Ar)for(let[r,i]of Object.entries(s[o]))e[o][r]=L0(e[o][r],i)}return e}function bs(t){let e=[],n=[];try{n=Un.readdirSync(t)}catch{}for(let r of n)(r===xr||r.startsWith("spaces.")&&r.endsWith(".json")&&_r(r)&&la(r)===xr)&&e.push(r);let s=[],o=[];for(let r of e.sort())try{s.push(JSON.parse(Un.readFileSync(Fn.join(t,r),"utf8"))),r!==xr&&o.push(r)}catch{}return{map:D0(...s),conflicts:o}}function ca(t,e){Un.mkdirSync(Fn.dirname(t),{recursive:!0});let n=Fn.join(Fn.dirname(t),".syncthing."+Fn.basename(t)+"."+process.pid+"."+I0.randomBytes(4).toString("hex")+".tmp");Un.writeFileSync(n,e),Un.renameSync(n,t)}function R0(t,e,{removeConflicts:n=[]}={}){let s=Mh(e),o={schema:1};for(let r of Ar)o[r]=Object.fromEntries(Object.entries(s[r]).sort(([i],[a])=>i.localeCompare(a)));ca(Fn.join(t,xr),JSON.stringify(o,null,2)+`
|
|
78
|
+
`);for(let r of n)try{Un.unlinkSync(Fn.join(t,r))}catch{}}function su(t,e,{machine:n="",now:s=Date.now()}={}){let{map:o,conflicts:r}=bs(t),i={at:new Date(s).toISOString(),by:n},a=e(o,i);return R0(t,o,{removeConflicts:r}),a===void 0?o:a}function Nt(t,e){let n={};for(let[s,o]of Object.entries(t&&t[e]||{}))o&&o.space!=null&&(n[s]=o.space);return n}function Vt(t,e=[]){let n=new Set;for(let s of Ar)for(let o of Object.values(Nt(t,s)))n.add(o);for(let s of e)n.add(Hn(t,s));return so([...n])}function so(t){return[...new Set(t.filter(Boolean))].sort((e,n)=>e==="Work"?-1:n==="Work"?1:e.localeCompare(n,"en",{sensitivity:"base"})||e.localeCompare(n))}function Hn(t,e){let n=t&&t.machines&&t.machines[e];return n&&n.space!=null?n.space:e||"Unsorted"}function P0(t,{repoKey:e,repoName:n,repoLabel:s}={}){let o=Nt(t,"repos");if(e&&o[e]!=null)return{key:e,space:o[e]};let r=[s,n,j0(e)].filter(Boolean).map(Oh);for(let i of r)for(let[a,l]of Object.entries(o))if(Oh(a)===i)return{key:a,space:l};return null}function Bn(t,e,{repoKey:n,repoName:s,repoLabel:o,machine:r}={}){let i=Nt(t,"sessions");if(e&&e.id&&i[e.id]!=null)return{space:i[e.id],source:"session",key:e.id};let a=e&&(e.agent&&e.agent.name||(typeof e.id=="string"&&e.id.startsWith("agent:")?e.id.split(":")[1]:null));if(a){let d=Nt(t,"agents");if(d[a]!=null)return{space:d[a],source:"agent",key:a}}let l=!a&&P0(t,{repoKey:n||e&&(e.repoKey||e.repo&&e.repo.root||e.cwd),repoName:s||e&&(e.repoName||e.repo&&e.repo.name),repoLabel:o||e&&e.repoLabel});if(l)return{space:l.space,source:"repo",key:l.key};let c=r||(e&&a?e.agent&&e.agent.puller:e&&e.machine)||"",u=t&&t.machines&&t.machines[c];return u&&u.space!=null?{space:u.space,source:"machine",key:c}:{space:c||"Unsorted",source:"default",key:c}}function Lh(t,{home:e=""}={}){let n=[e,"/root"].filter(Boolean).map(i=>i.replace(/\/+$/,"")),s=i=>n.includes(i)||/^\/(Users|home)\/[^/]+$/.test(i)||/^[A-Za-z]:\\Users\\[^\\]+$/.test(i),o=i=>{for(let l of n)if(l&&(i===l||i.startsWith(l+"/")))return"~"+i.slice(l.length);let a=/^\/(Users|home)\/[^/]+(?=\/|$)/.exec(i);return a?"~"+i.slice(a[0].length):i},r=new Map;for(let i of t||[]){let a=String(i&&i.key||"").trim().replace(/\/+$/,"")||(i&&i.key?"/":""),l=String(i&&i.name||"").trim();if(!l||["/",".","..","~","no-repo"].includes(l)||/^[\\/]+$/.test(l)||!a||a==="/"||a==="~"||s(a))continue;let c=Date.parse(i&&i.at||"")||0,u=l.toLowerCase(),d=r.get(u);if(d&&d.t>=c)continue;let f=a.includes("/")?o(a.slice(0,a.lastIndexOf("/"))||"/"):"";r.set(u,{name:l,parent:f,lastAt:c?new Date(c).toISOString():null,t:c})}return[...r.values()].sort((i,a)=>a.t-i.t||i.name.localeCompare(a.name)).map(({t:i,...a})=>a)}var xr,no,Ar,N0,Ih,Oh,j0,aa,vs=V(()=>{xr="spaces.json",no=["Work","Personal"],Ar=["machines","repos","sessions","agents"],N0={machine:"machines",repo:"repos",session:"sessions",agent:"agents"};Ih=/\.sync-conflict-(\d{8})-(\d{6})-([A-Za-z0-9]{1,16})(?=\.[^.]+$|$)/;Oh=t=>String(t||"").toLowerCase(),j0=t=>{let e=String(t||"").split(/[\\/]+/).filter(Boolean);return e.length?e[e.length-1]:""};aa=class{constructor(e,n,{now:s=()=>Date.now()}={}){this.root=e,this.machine=n,this.now=s,this.cache=null,this.checkedAt=0}sig(){let e=[];try{e=Un.readdirSync(this.root).filter(n=>n===xr||n.startsWith("spaces.")&&_r(n))}catch{}return e.sort().map(n=>{try{let s=Un.statSync(Fn.join(this.root,n));return n+":"+s.mtimeMs+":"+s.size}catch{return n}}).join("|")}map({fresh:e=!1}={}){let n=Date.now();if(!e&&this.cache&&n-this.checkedAt<2e3)return this.cache.map;let s=this.sig();if(this.checkedAt=n,!e&&this.cache&&this.cache.sig===s)return this.cache.map;let{map:o,conflicts:r}=bs(this.root);return this.cache={sig:s,map:o,conflicts:r},o}update(e){let n=su(this.root,e,{machine:this.machine,now:this.now()});return this.cache=null,n}resolve(e,n){return Bn(this.map(),e,n)}machineSpace(e=this.machine){return Hn(this.map(),e)}}});import se from"node:fs";import te from"node:path";import ou from"node:crypto";function Kn(t,e){se.mkdirSync(te.dirname(t),{recursive:!0});let n=te.join(te.dirname(t),"."+te.basename(t)+"."+process.pid+"."+ou.randomBytes(4).toString("hex")+".tmp");se.writeFileSync(n,e),se.renameSync(n,t)}function mn(t){try{return se.readdirSync(t,{withFileTypes:!0}).filter(e=>e.isDirectory()&&!e.name.startsWith(".")).map(e=>e.name)}catch{return[]}}function Dh(t){return se.existsSync(te.join(t,"session.md"))||se.existsSync(te.join(t,"checkpoints"))}function Rh(t,e,n,s){let o=te.join(we(t).spool,e,String(n||"unknown").replace(/[^A-Za-z0-9_-]/g,"_"));se.mkdirSync(o,{recursive:!0});let r=te.join(o,Date.now()+"-"+ou.randomBytes(4).toString("hex")+".json");return se.writeFileSync(r,JSON.stringify(s)+`
|
|
79
|
+
`,{flag:"wx"}),r}var mt,gn=V(()=>{ve();ke();vs();mt=class{constructor(e,n="unknown"){this.root=e,this.machine=n,this.p=we(e)}get mySessions(){return te.join(this.p.sessions,this.machine)}get mySpool(){return te.join(this.p.spool,this.machine)}get myDigests(){return te.join(this.p.digests,this.machine)}sessionDir(e){return e.folder||(e.folder=this.findFolderFor(e.id)||this.machine+"/"+If(e)),te.join(this.p.sessions,e.folder)}findFolderFor(e){let n="_"+nr(e);for(let s of mn(this.mySessions)){if(!s.endsWith(n))continue;let o=te.join(this.mySessions,s,"session.md");if(!se.existsSync(o))return this.machine+"/"+s;try{if(Be(se.readFileSync(o,"utf8")).data.id===e)return this.machine+"/"+s}catch{}}return null}writeSession(e,n){let s=te.join(this.sessionDir(e),"session.md");return Kn(s,Pf(e,n)),s}writeCheckpoint(e,n){let s=te.join(this.sessionDir(e),"checkpoints");se.mkdirSync(s,{recursive:!0});for(let o=0;o<50;o++){let r=te.join(s,Nc(n.createdAt,o)),i={...n,machine:n.machine||e.machine||this.machine,id:n.id&&o===0?n.id:Bi(n.sessionId,n.createdAt,o),file:te.basename(r)};try{return se.writeFileSync(r,Rf(i),{flag:"wx"}),i}catch(a){if(a.code!=="EEXIST")throw a}}throw new Error("could not find a free checkpoint file name")}loadSessionDir(e,n,s){let o=[];try{for(let i of se.readdirSync(te.join(e,"checkpoints")).filter(a=>a.endsWith(".md")).sort())try{let a=jf(se.readFileSync(te.join(e,"checkpoints",i),"utf8"));a.file=i,!a.machine&&s&&(a.machine=s),a.sessionId&&a.createdAt&&o.push(a)}catch{}}catch{}let r=null;try{r=Ff(se.readFileSync(te.join(e,"session.md"),"utf8"))}catch{}if(!r||!r.id){if(!o.length)return null;let i=o[o.length-1];r={id:i.sessionId,title:i.sessionTitle,titleSource:i.sessionTitle?"custom":null,cwd:i.cwd,machine:i.machine||s,repo:i.repo?{name:i.repo,branch:i.branch}:null,capture:"mod",status:"stale",startedAt:o[0].createdAt,lastActivityAt:i.createdAt,turns:0,turnsSinceCheckpoint:0,backgroundTasks:0,lastHeartbeatAt:i.createdAt,needsRewrite:!0}}return!r.machine&&s&&(r.machine=s),r.folder=n,{session:r,checkpoints:o}}loadAll({machine:e}={}){let n=[];for(let s of mn(this.p.sessions)){let o=te.join(this.p.sessions,s);if(Dh(o)){let r=this.loadSessionDir(o,s,null);r&&(!e||r.session.machine===e)&&n.push(r);continue}if(!(e&&s!==e))for(let r of mn(o)){let i=this.loadSessionDir(te.join(o,r),s+"/"+r,s);i&&n.push(i)}}return n}machines(){let e=new Set([this.machine]);for(let n of mn(this.p.sessions))Dh(te.join(this.p.sessions,n))||e.add(n);for(let n of mn(this.p.digests))e.add(n);return[...e].sort()}writeDigest(e,n){let s=te.join(this.p.digests,e+".md");ca(s,n);for(let o of this.digestCopies(e))if(o!==e+".md")try{se.unlinkSync(te.join(this.p.digests,o))}catch{}return s}writeMachineDigest(e,n){let s=te.join(this.myDigests,e+".md");return Kn(s,n),s}digestCopies(e){let n=[];try{n=se.readdirSync(this.p.digests)}catch{}return n.filter(s=>s===e+".md"||s.startsWith(e+".")&&s.endsWith(".md")&&_r(s)&&la(s)===e+".md")}readSharedDigest(e){let n=null,s=this.digestCopies(e);for(let o of s)try{let r=te.join(this.p.digests,o),i=se.readFileSync(r,"utf8"),a=Be(i).data||{},l=Date.parse(a.generated_at)||se.statSync(r).mtimeMs;(!n||l>n.t||l===n.t&&o===e+".md")&&(n={t:l,file:o,markdown:i,by:a.machine||null})}catch{}return n?{machine:null,shared:!0,by:n.by,file:n.file,duplicates:s.length-1,markdown:n.markdown}:null}readDigest(e,n){if(n&&n!=="all")try{return{machine:n,shared:!1,markdown:se.readFileSync(te.join(this.p.digests,n,e+".md"),"utf8")}}catch{return null}let s=this.readSharedDigest(e);if(s||n==="all")return s;for(let o of[this.machine,...mn(this.p.digests).filter(r=>r!==this.machine)])try{return{machine:o,shared:!1,markdown:se.readFileSync(te.join(this.p.digests,o,e+".md"),"utf8")}}catch{}return null}listDigests(){let e=[],n=new Set;try{for(let o of se.readdirSync(this.p.digests)){let r=/^(\d{4}-\d{2}-\d{2})\.(?:.*\.)?md$/.exec(o);r&&(o===r[1]+".md"||la(o)===r[1]+".md")&&n.add(r[1])}}catch{}for(let o of n)e.push({date:o,machine:null,shared:!0});for(let o of mn(this.p.digests))try{for(let r of se.readdirSync(te.join(this.p.digests,o)))/^\d{4}-\d{2}-\d{2}\.md$/.test(r)&&e.push({date:r.slice(0,10),machine:o})}catch{}let s=o=>o.shared?0:o.machine===this.machine?1:2;return e.sort((o,r)=>r.date.localeCompare(o.date)||s(o)-s(r)||String(o.machine).localeCompare(String(r.machine)))}stampAlive(e={},n=Date.now()){let s=te.join(this.p.digests,".machines",this.machine.replace(/[^\w.-]/g,"_")+".json");ca(s,JSON.stringify({machine:this.machine,seenAt:new Date(n).toISOString(),...e},null,2)+`
|
|
80
|
+
`)}aliveMachines(){let e=[],n=te.join(this.p.digests,".machines"),s=[];try{s=se.readdirSync(n).filter(o=>o.endsWith(".json")&&!o.startsWith(".")&&!_r(o))}catch{}for(let o of s)try{let r=JSON.parse(se.readFileSync(te.join(n,o),"utf8"));r&&r.machine&&e.push(r)}catch{}return e}writeCatchup(e,n){se.mkdirSync(this.p.catchups,{recursive:!0});let s=String(e).replace(/\.md$/,"");for(let o=0;o<50;o++){let r=o?s+"-"+(o+1):s,i=te.join(this.p.catchups,r+".md");try{let a=te.join(this.p.catchups,"."+r+"."+process.pid+"."+ou.randomBytes(4).toString("hex")+".tmp");se.writeFileSync(a,n);try{try{se.linkSync(a,i)}catch(l){if(l.code==="EEXIST")throw l;se.writeFileSync(i,n,{flag:"wx"})}}finally{try{se.unlinkSync(a)}catch{}}return{name:r,file:i}}catch(a){if(a.code!=="EEXIST")throw a}}throw new Error("could not find a free catch-up file name")}readCatchup(e){if(!/^\d{4}-\d{2}-\d{2}T\d{4}(?:-\d+)?$/.test(String(e||"")))return null;try{return{name:e,markdown:se.readFileSync(te.join(this.p.catchups,e+".md"),"utf8")}}catch{return null}}listCatchups(){let e=[];try{e=se.readdirSync(this.p.catchups).filter(o=>/^\d{4}-\d{2}-\d{2}T\d{4}(?:-\d+)?\.md$/.test(o))}catch{return[]}let n=[];for(let o of e){let r={};try{let i=se.openSync(te.join(this.p.catchups,o),"r");try{let a=Buffer.alloc(8192),l=se.readSync(i,a,0,a.length,0);r=Be(a.slice(0,l).toString("utf8")).data||{}}finally{se.closeSync(i)}}catch{}n.push({name:o.slice(0,-3),generatedAt:r.generated_at?G(r.generated_at):null,from:r.window_from?G(r.window_from):null,to:r.window_to?G(r.window_to):null,label:r.window_label||null,sessions:typeof r.sessions=="number"?r.sessions:null,needsYou:Array.isArray(r.needs_you)?r.needs_you.length:0,repos:r.repos==="all"||r.repos==null?"all":[].concat(r.repos).map(String),machine:r.made_on==null?null:String(r.made_on),model:r.model==null?null:String(r.model)})}let s=o=>Date.parse(o.generatedAt||"")||0;return n.sort((o,r)=>s(r)-s(o)||r.name.localeCompare(o.name))}lastCatchupAt(){let e=this.listCatchups()[0];if(!e)return null;let n=Date.parse(e.to||e.generatedAt||"");return isNaN(n)?null:n}readSpool(){let e=[];for(let n of mn(this.mySpool)){let s=te.join(this.mySpool,n),o=[];try{o=se.readdirSync(s)}catch{}for(let r of o){if(!r.endsWith(".json"))continue;let i=te.join(s,r),a=null;try{a=se.readFileSync(i,"utf8")}catch{continue}e.push({file:i,t:Number(r.split("-")[0])||0,body:a})}}return e.sort((n,s)=>n.t-s.t||n.file.localeCompare(s.file))}spoolCount(){let e=0;for(let n of mn(this.mySpool))try{e+=se.readdirSync(te.join(this.mySpool,n)).filter(s=>s.endsWith(".json")).length}catch{}return e}}});import{spawn as F0,execFileSync as U0}from"node:child_process";import H0 from"node:fs";import ru from"node:path";function $0(){if(process.env.CHECKPOINTS_NO_CLAUDE)return!1;try{return U0(process.platform==="win32"?"where":"which",["claude"],{stdio:"ignore"}),!0}catch{return!1}}function Y0(t){let e=t.summarizer||{},n=String(e.provider||e.mode||"auto").toLowerCase();return Ph[n]?Ph[n]:$0()?"claude":process.env.ANTHROPIC_API_KEY?"anthropic":"heuristic"}function W0({system:t,prompt:e,model:n},s=6e4){return new Promise((o,r)=>{let i=["-p","--model",n||"haiku","--output-format","text"];t&&i.push("--append-system-prompt",t);let a=F0("claude",i,{env:{...process.env,CHECKPOINTS_INTERNAL:"1",LEFTOFF_INTERNAL:"1"},stdio:["pipe","pipe","pipe"]}),l="",c="",u=setTimeout(()=>{a.kill("SIGKILL"),r(new Error("claude -p timed out"))},s);a.stdout.on("data",d=>l+=d),a.stderr.on("data",d=>c+=d),a.on("error",d=>{clearTimeout(u),r(d)}),a.on("close",d=>{clearTimeout(u),d===0?o({text:l.trim(),usage:null}):r(new Error("claude -p exited "+d+": "+c.slice(0,300)))}),a.stdin.end(e)})}async function iu(t,e,n,s,o){let r=await t(e,{method:"POST",headers:{"content-type":"application/json",...n},body:JSON.stringify(s),signal:AbortSignal.timeout(o)}),i=await r.json().catch(()=>({}));if(!r.ok)throw new Error(e+" -> "+r.status+": "+(i.error&&(i.error.message||i.error)||"error"));return i}function z0(t={},e=process.env){return yn("anthropic",t,e)}function yn(t,{apiKeyEnv:e,apiKeyFile:n,root:s}={},o=process.env){let r=da[t]||{},i=e||r.env;if(i&&o[i])return o[i];for(let a of[n,e==="__none__"?null:r.file]){if(!a)continue;let l=ru.isAbsolute(a)?null:sh(a,o.HOME||o.XDG_CONFIG_HOME?o:process.env);for(let c of[l,ru.isAbsolute(a)?a:ru.join(s||q(),a)])if(c)try{let u=H0.readFileSync(c,"utf8").trim();if(u)return u}catch{}}return null}async function J0(t,{system:e,prompt:n,model:s,maxTokens:o,apiKeyEnv:r,apiKeyFile:i,root:a}){let l=z0({apiKeyEnv:r,apiKeyFile:i,root:a});if(!l)throw new Error((r||"ANTHROPIC_API_KEY")+" is not set");let c=(process.env.ANTHROPIC_BASE_URL||"https://api.anthropic.com").replace(/\/$/,""),u=await iu(t,c+"/v1/messages",{"x-api-key":l,"anthropic-version":"2023-06-01"},{model:jh[s]||s||jh.haiku,max_tokens:o||300,system:e,messages:[{role:"user",content:n}]},3e4);return{text:(u.content||[]).filter(d=>d.type==="text").map(d=>d.text).join(`
|
|
81
|
+
`),usage:u.usage?{inputTokens:u.usage.input_tokens,outputTokens:u.usage.output_tokens}:null}}async function Fh(t,{system:e,prompt:n,model:s,maxTokens:o,baseUrl:r,key:i,cloud:a}){let l=await iu(t,r.replace(/\/$/,"")+"/api/chat",i?{authorization:"Bearer "+i}:{},{model:s,stream:!1,messages:[...e?[{role:"system",content:e}]:[],{role:"user",content:n}],options:{num_predict:a?Math.max(1024,(o||300)*4):o||300,temperature:.2}},a?6e4:12e4);return{text:l.message&&l.message.content||"",usage:{inputTokens:l.prompt_eval_count,outputTokens:l.eval_count}}}async function q0(t,{system:e,prompt:n,model:s,maxTokens:o,baseUrl:r,apiKeyEnv:i}){let a=i?process.env[i]:null,l=await iu(t,r.replace(/\/$/,"")+"/chat/completions",a?{authorization:"Bearer "+a}:{},{model:s,max_tokens:o||300,temperature:.2,messages:[...e?[{role:"system",content:e}]:[],{role:"user",content:n}]},12e4),c=l.choices&&l.choices[0];return{text:c&&c.message&&c.message.content||c&&c.text||"",usage:l.usage?{inputTokens:l.usage.prompt_tokens,outputTokens:l.usage.completion_tokens}:null}}var jh,B0,K0,G0,ua,da,Ph,gt,xs=V(()=>{ke();ve();jh={haiku:"claude-haiku-5-5",sonnet:"claude-sonnet-5-5",opus:"claude-opus-5-5"},B0=["haiku","sonnet","opus",""],K0="https://ollama.com",G0="gpt-oss:20b",ua={ollama:{baseUrl:"http://localhost:11434",model:"llama3.2"},"ollama-cloud":{baseUrl:K0,model:G0},"openai-compatible":{baseUrl:"http://localhost:1234/v1",model:"local-model"}},da={anthropic:{env:"ANTHROPIC_API_KEY",file:".secret/anthropic-api-key"},"ollama-cloud":{env:"OLLAMA_API_KEY",file:".secret/ollama-api-key"}},Ph={stub:"heuristic",none:"heuristic",heuristic:"heuristic",api:"anthropic",anthropic:"anthropic",claude:"claude",ollama:"ollama","ollama-cloud":"ollama-cloud","ollama.com":"ollama-cloud",cloud:"ollama-cloud","openai-compatible":"openai-compatible",openai:"openai-compatible",lmstudio:"openai-compatible",llamacpp:"openai-compatible",vllm:"openai-compatible"};gt=class{constructor(e,{fetchImpl:n,root:s}={}){this.cfg=e,this.root=s||null,this.fetchImpl=n||((...o)=>fetch(...o)),this.mode=Y0(e)}get model(){let e=String(this.cfg.summarizer&&this.cfg.summarizer.model||"");return ua[this.mode]&&B0.includes(e)?ua[this.mode].model:e||"haiku"}get baseUrl(){return this.cfg.summarizer&&this.cfg.summarizer.baseUrl||ua[this.mode]&&ua[this.mode].baseUrl||null}describe(){return{provider:this.mode,model:this.mode==="heuristic"?null:this.model,baseUrl:this.baseUrl}}async complete({system:e,prompt:n,maxTokens:s}){let o=this.cfg.summarizer||{},r={system:e,prompt:n,maxTokens:s,model:this.model,baseUrl:this.baseUrl,apiKeyEnv:o.apiKeyEnv,apiKeyFile:o.apiKeyFile,root:this.root};if(this.mode==="claude")return W0(r);if(this.mode==="anthropic")return J0(this.fetchImpl,r);if(this.mode==="ollama")return Fh(this.fetchImpl,r);if(this.mode==="ollama-cloud"){let i=yn("ollama-cloud",{apiKeyEnv:o.apiKeyEnv,apiKeyFile:o.apiKeyFile,root:this.root});if(!i)throw new Error((o.apiKeyEnv||"OLLAMA_API_KEY")+" is not set (create a key at https://ollama.com/settings/keys)");return Fh(this.fetchImpl,{...r,key:i,cloud:!0})}if(this.mode==="openai-compatible")return q0(this.fetchImpl,r);throw new Error("no model in heuristic mode")}async checkpoint({system:e,prompt:n,messages:s,session:o,maxTokens:r=300,length:i="short"}){if(this.mode!=="heuristic")try{let a=await this.complete({system:e,prompt:n,maxTokens:r}),l=Yf(String(a.text||"").replace(/<think>[\s\S]*?<\/think>/gi,"").trim()),c={by:"daemon",provider:this.mode,model:this.model};return a.usage&&a.usage.inputTokens!=null&&(c.inputTokens=a.usage.inputTokens),a.usage&&a.usage.outputTokens!=null&&(c.outputTokens=a.usage.outputTokens),{...l,generator:c}}catch(a){return{...Uc(s,o,{length:i}),generator:{by:"heuristic",fallbackFrom:this.mode,error:String(a.message||a).slice(0,160)}}}return{...Uc(s,o,{length:i}),generator:{by:"heuristic"}}}async title(e){if(this.mode!=="heuristic"&&e.firstPrompt)try{let n=await this.complete({system:zf,prompt:`First prompt:
|
|
82
|
+
`+e.firstPrompt,maxTokens:30}),s=Jf(n.text);if(s)return{title:s,titleSource:"generated"}}catch{}return{title:Wf(e),titleSource:"fallback"}}async paragraph(e,n){if(this.mode==="heuristic")return null;try{return(await this.complete({system:e,prompt:n,maxTokens:200})).text.trim()}catch{return null}}}});function au(t){return String(t||"").split(/[\\/]+/).filter(Boolean)}function V0(t){let e=au(t);return e.length?e[e.length-1]:""}function lu(t){if(!t)return"no-repo";if(t.repoKey)return t.repoKey;let e=t.repo||{};return t.repoRoot||e.root||t.cwd||e.name||t.repoName||"no-repo"}function Zt(t){let e=[...new Set([...t].filter(Boolean))],n=new Map(e.map(o=>[o,1])),s=o=>{if(o==="no-repo")return"no repo";let r=au(o);return r.length?r.slice(-Math.min(n.get(o),r.length)).join("/"):o};for(let o=0;o<12;o++){let r=new Map;for(let a of e){let l=s(a);r.set(l,[...r.get(l)||[],a])}let i=!1;for(let a of r.values())if(!(a.length<2))for(let l of a)n.get(l)<au(l).length&&(n.set(l,n.get(l)+1),i=!0);if(!i)break}return new Map(e.map(o=>[o,s(o)]))}function oo(t,e,{loose:n=!1}={}){let s=String(e||"").trim().toLowerCase().replace(/\/+$/,"");if(!s)return!1;let o=lu(t),r=[o,t.repoLabel,t.repoName,V0(o)].filter(Boolean).map(i=>String(i).toLowerCase());return r.includes(s)||s.includes("/")&&String(o).toLowerCase().endsWith("/"+s)?!0:n&&r.some(i=>i.includes(s))}function fa(t,e){if(!e)return t;let n=t.filter(s=>oo(s,e));return n.length?n:t.filter(s=>oo(s,e,{loose:!0}))}var As=V(()=>{});function jt(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||"UTC"}catch{return"UTC"}}function ro(t){if(!t||typeof t!="string")return!1;try{return new Intl.DateTimeFormat("en-US",{timeZone:t}),!0}catch{return!1}}function X0(t){return cu.has(t)||cu.set(t,new Intl.DateTimeFormat("en-US",{timeZone:t,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",weekday:"short",hourCycle:"h23"})),cu.get(t)}function Pt(t,e){let n={};for(let c of X0(e).formatToParts(new Date(t)))n[c.type]=c.value;let s=Number(n.year),o=Number(n.month),r=Number(n.day),i=Number(n.hour)%24,a=Number(n.minute),l=Xt.indexOf(String(n.weekday).slice(0,3).toLowerCase());return{y:s,mo:o,d:r,hh:i,mm:a,ss:Number(n.second),dow:l,day:Xt[l],date:s+"-"+String(o).padStart(2,"0")+"-"+String(r).padStart(2,"0")}}function Uh(t,e){let n=Pt(t,e);return Date.UTC(n.y,n.mo-1,n.d,n.hh,n.mm,n.ss)-Math.floor(t/1e3)*1e3}function Ft(t,e,n,s,o,r){let i=Date.UTC(t,e-1,n,s,o),a=Uh(i,r),l=i-a,c=Uh(l,r);return c!==a&&(l=i-c),l}function uu(t,e){try{let n=new Intl.DateTimeFormat("en-US",{timeZone:e,timeZoneName:"short"}).formatToParts(new Date(t)).find(s=>s.type==="timeZoneName");return n?n.value:""}catch{return""}}function io(t){if(!Array.isArray(t))return Xt.slice();let e=Xt.filter(n=>t.map(s=>String(s).toLowerCase().slice(0,3)).includes(n));return e.length?e:Xt.slice()}function pa(t={},e=jt()){let n=t||{},s=n.time===null?null:/^\d{1,2}:\d{2}$/.test(String(n.time||""))?String(n.time):"07:00",o=ro(n.timeZone)?n.timeZone:e;return{time:s,tz:o,days:io(n.days),skipEmpty:n.skipEmpty!==!1,zoneOverride:ro(n.timeZone)?n.timeZone:null}}function ao(t,e){let n=Pt(t,e.tz),s=e.days.includes(n.day),o=e.time?Ft(n.y,n.mo,n.d,Math.floor(ha(e.time)/60),ha(e.time)%60,e.tz):null;return{date:n.date,day:n.day,isDay:s,at:o,past:o!=null&&t>=o}}function Hh(t,e,{doneToday:n=!1}={}){if(!e.time)return null;let s=Pt(t,e.tz),o=Math.floor(ha(e.time)/60),r=ha(e.time)%60;for(let i=0;i<=8;i++){let a=new Date(Date.UTC(s.y,s.mo-1,s.d+i)),l=Xt[a.getUTCDay()];if(!e.days.includes(l))continue;let c=Ft(a.getUTCFullYear(),a.getUTCMonth()+1,a.getUTCDate(),o,r,e.tz);if(i===0){if(n)continue;return c<=t?t:c}return c}return null}function Bh(t,e,n=Date.now()){if(!t)return"off";let[s,o]=t.split(":").map(Number),r=(s%12||12)+":"+String(o).padStart(2,"0")+" "+(s<12?"AM":"PM"),i=uu(n,e);return r+" · "+e+(i&&i!==e?" ("+i+")":"")}function Kh(t){let e=io(t);return e.length===7?"Every day":e.length===5&&Ts.every(n=>e.includes(n))?"Weekdays":e.length===2&&e.includes("sat")&&e.includes("sun")?"Weekends":e.map(n=>Z0[n]).join(", ")}function Gh({machines:t=[],me:e,slotAt:n=null,graceMs:s=20*6e4}={}){let o=[...new Set([...t,e].filter(Boolean))].sort(),r=o.indexOf(e);return{order:o,rank:r,first:o[0]||e,graceMs:s,at:n==null?null:n+r*s}}var Xt,Z0,Ts,cu,ha,Cr=V(()=>{Xt=["sun","mon","tue","wed","thu","fri","sat"],Z0={sun:"Sun",mon:"Mon",tue:"Tue",wed:"Wed",thu:"Thu",fri:"Fri",sat:"Sat"},Ts=["mon","tue","wed","thu","fri"];cu=new Map;ha=t=>{let[e,n]=String(t).split(":").map(Number);return(e||0)*60+(n||0)}});var ap={};zt(ap,{CATCHUP_LENGTHS:()=>pu,CATCHUP_NAME_RE:()=>Q0,CATCHUP_SCHEMA:()=>Wh,CATCHUP_SYSTEM:()=>ep,availableFrom:()=>Su,buildRecap:()=>Xh,catchupFileName:()=>rp,catchupLength:()=>mu,catchupOptions:()=>ba,catchupZone:()=>Sa,collectSessions:()=>wu,createCatchup:()=>Nr,heuristicSummary:()=>Or,lastWorkday:()=>yu,modelLabel:()=>op,parseBullets:()=>np,parseCatchupMd:()=>ku,parseDuration:()=>zh,parseWhen:()=>hu,pickRepos:()=>Vh,recapSession:()=>Zh,renderCatchupMd:()=>ip,resolveWindow:()=>ka,stillOpenLists:()=>Qh,summarize:()=>sp,summaryPrompt:()=>tp,windowChoices:()=>Jh,workdayLabel:()=>tS,workdaysOf:()=>gu});function mu(t=ye,e){let n=Pc(t||ye,e);return{key:n,...pu[n]}}function Sa(t=ye){let e=t&&t.digest&&t.digest.timeZone;return ro(e)?e:jt()}function gu(t=ye){let e=t&&t.catchup&&t.catchup.workdays,n=Array.isArray(e)?io(e):null;return n&&n.length?n:Ts}function du(t){let e=new Error(t);return e.badWindow=!0,e}function ya(t,e,n){let s=Pt(t,n),o=new Date(Date.UTC(s.y,s.mo-1,s.d-e));return Ft(o.getUTCFullYear(),o.getUTCMonth()+1,o.getUTCDate(),0,0,n)}function yu(t,e,n=Ts){for(let s=1;s<=7;s++){let o=ya(t,s,e),r=Pt(o+2*Ir,e);if(n.includes(r.day))return{from:o,day:r.day,daysAgo:s}}return{from:ya(t,1,e),day:Pt(t-ga,e).day,daysAgo:1}}function tS(t,e,n=Ts){return"Since "+ma[yu(t,e,n).day]}function nS(t){let e=t/Ir;if(e<1||e<3&&!Number.isInteger(e))return"last "+Math.round(t/6e4)+" minutes";if(e===1)return"last hour";if(e<48&&Number.isInteger(e))return"last "+e+" hours";let n=e/24;return n===7?"last 7 days":Number.isInteger(n)?"last "+n+" days":"last "+Math.round(e)+" hours"}function zh(t){let e=String(t??"").trim().toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(m|min|mins|minutes?|h|hr|hrs|hours?|d|days?|w|wk|weeks?)$/);if(!e)return null;let n=Number(e[1]),s=e[2][0],o=n*(s==="m"?6e4:s==="h"?Ir:s==="d"?ga:7*ga);return o>0?o:null}function hu(t,e){let n=String(t??"").trim(),s=n.match(/^(\d{4})-(\d{2})-(\d{2})$/);if(s)return Ft(+s[1],+s[2],+s[3],0,0,e);if(s=n.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{1,2}):(\d{2})(?::\d{2}(?:\.\d+)?)?$/),s)return Ft(+s[1],+s[2],+s[3],+s[4],+s[5],e);if(/^\d{4}-\d{2}-\d{2}T.*(Z|[+-]\d{2}:?\d{2})$/i.test(n)){let o=Date.parse(n);return isNaN(o)?null:o}return null}function ka(t,{now:e=Date.now(),tz:n=jt(),lastAt:s=null,to:o=null,workdays:r=Ts}={}){let i=o!=null&&o!==""?typeof o=="number"?o:hu(o,n):e;if(i==null||isNaN(i))throw du(`can't read the end of the window "`+o+'" (use a date like 2026-10-09 or 2026-10-09 18:00)');let a=t==null||t===!0?"":String(t).trim(),l=a.toLowerCase().replace(/[\s_]+/g,"-"),c=(p,h,m,y={})=>{if(!(p<i))throw du("the window starts after it ends ("+new Date(p).toISOString()+" → "+new Date(i).toISOString()+")");return{from:p,to:i,spec:a||"last",kind:h,label:m,...y}};if(!l||l==="last"||l==="since-last"||l==="last-catchup"||l==="since-last-catchup")return s!=null&&!isNaN(s)&&s<i?c(s,"last","Since your last catch-up"):c(i-ga,"last","Last 24 hours",{fallback:s!=null&&s>=i?"last catch-up is at or after the end of the window":"no earlier catch-up"});if(["workday","last-workday","lastworkday","since-workday"].includes(l)){let p=yu(i,n,r);return c(p.from,"workday","Since "+ma[p.day])}if(l==="today")return c(eS(i,n),"day","Today");if(l==="yesterday")return c(ya(i,1,n),"day","Since yesterday");let u=l.replace(/^since-/,"").slice(0,3);if(/^(since-)?(sun|mon|tue|wed|thu|fri|sat)[a-z]*$/.test(l)&&Xt.includes(u)&&ma[u].toLowerCase().startsWith(l.replace(/^since-/,"")))for(let p=1;p<=7;p++){let h=ya(i,p,n);if(Pt(h+2*Ir,n).day===u)return c(h,"day","Since "+ma[u])}let d=zh(l);if(d)return c(i-d,"duration",nS(d).replace(/^l/,"L"));let f=hu(a,n);if(f!=null)return c(f,"custom","Since "+wa(f,n));throw du(`can't read the window "`+a+'". Try: 24h, 3d, 1w, workday, yesterday, friday, or a date like 2026-10-09')}function Jh({now:t=Date.now(),tz:e=jt(),lastAt:n=null,workdays:s=Ts}={}){let o=c=>{try{return ka(c,{now:t,tz:e,lastAt:n,workdays:s})}catch{return null}},r=o("last"),i=o("24h"),a=o("workday"),l=o("7d");return[{key:"last",label:n!=null?"Since I last caught up":"Since I last caught up (none yet: 24h)",from:r&&G(r.from)},{key:"24h",label:"Last 24 hours",from:i&&G(i.from)},{key:"workday",label:a?a.label:"Since the last workday",from:a&&G(a.from)},{key:"7d",label:"Last 7 days",from:l&&G(l.from)},{key:"custom",label:"Custom…",from:null}]}function wu(t,e={}){let{from:n,to:s,now:o=Date.now(),cfg:r=ye,machine:i=null}=e,a=e.repoKeyFor||(c=>lu(c)),l=[];for(let{session:c,checkpoints:u=[]}of t){if(!c||!c.id||c.archived||c.ignored||!c.agent&&(ht(c,u.length)||Ws(c,u.length)))continue;let d=u.filter(v=>v&&v.createdAt).slice().sort((v,A)=>R(v.createdAt)-R(A.createdAt)),f=d.filter(v=>$h(R(v.createdAt),n,s)),p=i&&c.machine&&c.machine!==i,h=c.agent?c.agentStatus:Rt(c,o,p?{...r,staleAfterSec:Math.max(r.staleAfterSec||180,900)}:r),m=[c.lastActivityAt,c.startedAt,c.endedAt].some(v=>$h(R(v),n,s)),y=c.agent?sS(c):(e.needsOf?e.needsOf(c,h):oS(c,h))||null,w=d[d.length-1]||null,k=w&&/^YOU:/i.test(w.next||"")&&h!=="ended"&&h!=="stale"&&!c.endedAt,S=s>=o-6e4;!f.length&&!m&&!(S&&(y||k))||l.push({s:c,cps:d,inWin:f,latest:w,status:h,needs:y,youNext:k,repoKey:a(c)})}return l}function sS(t){let e=t.agentNeeds;return!e||!e.items||!e.items.length?null:{kind:"agent",message:e.items.map(n=>n.text.replace(/\s*\n\s*/g," ")).join("; ")+(t.agent.reply?" ("+t.agent.reply.label+")":t.agent.replyHint?" ("+t.agent.replyHint+")":"")}}function oS(t,e){return e==="ended"||e==="stale"||t.endedAt?null:t.needsInput?{kind:"terminal",message:t.lastNotificationMessage||(t.lastNotification?String(t.lastNotification).replace(/_/g," "):"waiting in the terminal")}:t.question&&!t.inTurn?{kind:"reply",message:typeof t.question=="string"?t.question:t.question.text||"asked you a question"}:null}function Su(t,{labels:e}={}){let n=new Map;for(let i of t){let a=n.get(i.repoKey)||{key:i.repoKey,count:0,needsYou:0,last:0};a.count++,(i.needs||i.youNext)&&a.needsYou++,a.last=Math.max(a.last,qh(i)),n.set(i.repoKey,a)}let s=e||Zt(n.keys()),o=[...n.values()].map(i=>({...i,label:s.get(i.key)||i.key,last:i.last?G(i.last):null})).sort((i,a)=>(a.needsYou>0)-(i.needsYou>0)||(R(a.last)||0)-(R(i.last)||0)||i.label.localeCompare(a.label)),r=[...new Set(t.map(i=>i.s.machine).filter(Boolean))].sort();return{repos:o,machines:r}}function Vh(t,e=[],n){let s=(e||[]).flatMap(a=>String(a).split(",")).map(a=>a.trim()).filter(Boolean);if(!s.length)return{keys:null,unmatched:[]};let o=new Set,r=[],i=n||Zt(t.map(a=>a.repoKey));for(let a of s){let l=u=>t.filter(d=>oo({...d.s,repoKey:d.repoKey,repoLabel:i.get(d.repoKey),repoName:fe(d.s)},a,{loose:u})),c=l(!1);c.length||(c=l(!0)),c.length||r.push(a);for(let u of c)o.add(u.repoKey)}return{keys:o,unmatched:r}}function iS(t){return t.lastPreview?"no checkpoint yet; last reply: "+F(t.lastPreview,220):t.firstPrompt?"no checkpoint yet; started with “"+F(t.firstPrompt,200)+"”":"no checkpoint yet"+(t.turns?" ("+t.turns+" turn"+(t.turns===1?"":"s")+")":"")}function aS(t,e){let n=t.s;return t.needs?"needs you":t.status==="ended"?"closed"+(n.endedAt?" "+wa(R(n.endedAt),e):""):t.status==="stale"?"gone quiet":t.status==="dormant"?"idle":t.status==="needs_input"?"needs you":t.status}function Zh(t,{length:e,tz:n,patterns:s,labels:o}={}){let r=e||pu.informative,i=m=>m==null||m===""?m:Gi(String(m),s),a=t.s,l=t.latest,c=fu([...t.inWin.map(m=>m.doing),...r.done>4&&l&&l.recap&&t.inWin.includes(l)?[l.recap]:[]]).slice(-r.done),u=l&&(R(a.lastActivityAt)||0)>R(l.createdAt)+5*6e4,d=co(l&&l.next).toLowerCase().replace(/[.!?…]+$/,""),f=fu([l&&l.openQuestion,...l&&l.questions||[]]).filter(m=>co(m).toLowerCase().replace(/[.!?…]+$/,"")!==d).slice(0,r.questions),p=fu((l&&l.nextSteps||[]).map(co)).filter(m=>m.toLowerCase()!==co(l&&l.next).toLowerCase()).slice(0,Math.max(0,r.steps-1)),h=a.repo&&a.repo.branch;return{id:a.id,title:i(a.title||"Untitled session"),repoKey:t.repoKey,repo:o&&o.get(t.repoKey)||fe(a),branch:h||null,machine:a.machine||null,cwd:a.cwd||null,status:t.status,state:aS(t,n),lastActivityAt:G(qh(t))||null,checkpoints:t.inWin.length,done:c.map(m=>i(F(m,240))),stoppedAt:i(l?F(l.stoppedAt,280):iS(a)),next:l?i(F(l.next,240)):null,nextSteps:p.map(m=>i(F(m,200))),questions:f.map(m=>i(F(m,240))),needsYou:t.needs?i(F(t.needs.message||t.needs.question||"waiting on you",200)):t.youNext?i(F(co(l.next),200)):null,needsKind:t.needs?t.needs.kind||"terminal":t.youNext?"next":null,behind:!!u,lastReply:u&&a.lastPreview&&!rS(a.lastPreview,l&&l.stoppedAt)?i(F(a.lastPreview,200)):null,resume:a.agent?null:Ys(a),agent:a.agent?{name:a.agent.name,display:a.agent.display,url:a.agent.url||null,reply:a.agent.reply||null}:null}}function Xh(t,e={}){let{now:n=Date.now(),cfg:s=ye,machine:o=null}=e,r=e.tz||Sa(s),i=e.window,a=mu(s,e.lengthOverride),l=s.redact&&s.redact.patterns||[],c=wu(t,{...e,from:i.from,to:i.to,now:n,cfg:s,machine:o}),u=(e.machines||[]).flatMap(S=>String(S).split(",")).map(S=>S.trim()).filter(Boolean);u.length&&(c=c.filter(S=>u.includes(S.s.machine||o)));let d=Zt(c.map(S=>S.repoKey)),f=Su(c,{labels:d}),{keys:p,unmatched:h}=Vh(c,e.repos,d);p&&(c=c.filter(S=>p.has(S.repoKey)));let m=c.map(S=>Zh(S,{length:a,tz:r,patterns:l,labels:d})).sort((S,v)=>!!v.needsYou-!!S.needsYou||(R(v.lastActivityAt)||0)-(R(S.lastActivityAt)||0)),y=new Map;for(let S of m)y.has(S.repoKey)||y.set(S.repoKey,{key:S.repoKey,label:S.repo,sessions:[]}),y.get(S.repoKey).sessions.push(S);let w=[...y.values()],k=[...new Set(m.map(S=>S.machine).filter(Boolean))].sort();return{window:{from:G(i.from),to:G(i.to),spec:i.spec,kind:i.kind,label:i.label,fallback:i.fallback||null,tz:r},length:a.key,repos:w,sessions:m,needsYou:m.filter(S=>S.needsYou),stillOpen:Qh(e.lists,{keys:p,labels:w.map(S=>S.label),from:R(i.from),to:R(i.to)}),machines:k,selection:{repos:p?w.map(S=>S.label):"all",repoKeys:p?[...p]:null,machines:u.length?u:"all",unmatched:h},available:f,madeOn:o}}function Qh(t,{keys:e=null,labels:n=[],from:s=0,to:o=1/0}={}){let r=[];for(let i of t||[]){if(!i||i.archived||!(i.total>i.done)||e&&!(e.has(i.repoKey)||n.includes(i.repo)))continue;let a=(i.items||[]).filter(c=>!c.done),l=(i.items||[]).filter(c=>c.done&&c.at&&R(c.at)>=s&&R(c.at)<=o);r.push({ref:i.ref,name:i.name,repo:i.repo||"no repo",done:i.done,total:i.total,next:a.slice(0,3).map(c=>({n:c.n,text:c.text})),ticked:l.map(c=>({n:c.n,text:c.text})),sourceSession:i.sourceSession||null,updated:i.updated})}return r.sort((i,a)=>(R(a.updated)||0)-(R(i.updated)||0)).slice(0,20)}function tp(t,{maxChars:e=24e3}={}){let n=["Window: "+t.window.label+" ("+t.window.from+" to "+t.window.to+")",""];for(let o of t.repos){n.push("Repo "+o.label+":");for(let r of o.sessions)n.push('- "'+r.title+'" ['+r.state+"]"+(r.needsYou?" NEEDS YOU: "+r.needsYou+".":"")+(r.done.length?" Done: "+r.done.map(wn).join("; ")+".":"")+(r.stoppedAt?" Stopped at: "+wn(r.stoppedAt)+".":"")+(r.next?" Next: "+wn(r.next)+".":"")+(r.questions.length?" Open: "+r.questions.map(wn).join("; ")+".":""))}for(let o of t.stillOpen||[])n.push('Open list "'+o.name+'" ('+o.repo+"): "+o.done+"/"+o.total+" done"+(o.ticked.length?", "+o.ticked.length+" ticked off in this window":"")+"; next: "+o.next.map(r=>wn(r.text)).join("; ")+".");let s=n.join(`
|
|
83
|
+
`);return s.length>e&&(s=s.slice(0,e)+`
|
|
84
|
+
…`),s}function Or(t,e=5){let n=t.sessions;if(!n.length)return["Nothing happened "+t.window.label.toLowerCase().replace(/^last/,"in the last")+": no Claude Code sessions with activity."];let s=[],o=t.repos.slice().sort((d,f)=>f.sessions.length-d.sessions.length).slice(0,4).map(d=>d.label+" ("+d.sessions.length+")").join(", ")+(t.repos.length>4?", +"+(t.repos.length-4)+" more":"");s.push(n.length+" session"+(n.length===1?"":"s")+" in "+t.repos.length+" repo"+(t.repos.length===1?"":"s")+": "+o+".");let r=t.needsYou;r.length===1?s.push("Waiting on you: "+lo(r[0].title)+" ("+r[0].repo+"), "+wn(r[0].needsYou)+"."):r.length>1&&s.push(r.length+" sessions are waiting on you: "+r.slice(0,3).map(d=>lo(d.title)).join(", ")+(r.length>3?" and more":"")+".");let i=n.filter(d=>d.done.length&&!d.needsYou);for(let d of i.slice(0,2))s.push("In "+d.repo+", "+lo(d.title)+": "+wn(d.done[d.done.length-1])+(d.status==="ended"?" (closed).":"."));let a=n.find(d=>!d.needsYou&&d.status!=="ended"&&d.next)||n.find(d=>!d.needsYou&&d.next);a&&s.push("Pick up first: "+lo(a.title)+", next: "+wn(co(a.next))+".");let l=n.filter(d=>["active","background"].includes(d.status));l.length&&s.push("Still running: "+l.slice(0,3).map(d=>lo(d.title)).join(", ")+(l.length>3?" +"+(l.length-3):"")+".");let c=t.stillOpen||[];c.length&&s.length<e&&s.push("Still open: "+c.slice(0,2).map(d=>lo(d.name)+" ("+d.done+"/"+d.total+", next: "+wn(d.next[0]?d.next[0].text:"")+")").join("; ")+(c.length>2?" and "+(c.length-2)+" more list"+(c.length>3?"s":""):"")+".");let u=n.filter(d=>d.status==="ended").length;return u&&s.length<e&&s.push(u+" session"+(u===1?"":"s")+" closed in this stretch."),s.slice(0,Math.max(1,e))}async function sp(t,{summarizer:e=null,cfg:n=ye,given:s=null}={}){let o=mu(n,t.length),r=n.redact&&n.redact.patterns||[],i=a=>a.map(l=>Gi(F(l,400),r));if(!t.sessions.length)return{bullets:Or(t,o.bullets),by:{by:"heuristic",reason:"empty"}};if(s&&Array.isArray(s.bullets)&&s.bullets.length)return{bullets:i(s.bullets.map(String).filter(Boolean).slice(0,6)),by:{by:"model",provider:s.provider||"claude-in-session",model:s.model||null}};if(e&&e.mode&&e.mode!=="heuristic")try{let a=n.summarizer&&n.summarizer.maxInputChars||24e3,l=await e.complete({system:ep.replace("{n}",String(o.bullets)),prompt:tp(t,{maxChars:a}),maxTokens:o.maxTokens}),c=np(l&&l.text,Math.max(o.bullets,3));return c?{bullets:i(c),by:{by:"model",provider:e.mode,model:e.model||null}}:{bullets:Or(t,o.bullets),by:{by:"heuristic",fallbackFrom:e.mode,error:"the model reply had no bullets"}}}catch(a){return{bullets:Or(t,o.bullets),by:{by:"heuristic",fallbackFrom:e.mode,error:String(a&&a.message||a).slice(0,160)}}}return{bullets:Or(t,o.bullets),by:{by:"heuristic"}}}function op(t){return!t||t.by!=="model"?"heuristic":(t.provider||"model")+(t.model?"/"+t.model:"")}function rp(t,e,n=0){let s=Pt(t,e);return s.date+"T"+String(s.hh).padStart(2,"0")+String(s.mm).padStart(2,"0")+(n?"-"+(n+1):"")+".md"}function ip(t,e,{now:n=Date.now()}={}){let s=t.window.tz||jt(),o=R(t.window.from),r=R(t.window.to),i={kind:"catchup",generated_at:G(n),window_from:t.window.from,window_to:t.window.to,window_label:t.window.label,window_spec:t.window.spec,time_zone:s,repos:t.selection.repos==="all"?"all":t.selection.repos,repo_keys:t.repos.map(c=>c.key),machines:t.machines,machine_filter:t.selection.machines,made_on:t.madeOn||null,model:op(e.by),summary_by:e.by,length:t.length,sessions:t.sessions.length,session_ids:t.sessions.map(c=>c.id),needs_you:t.needsYou.map(c=>c.id),schema:Wh},a=t.machines.length>1,l=[];l.push("# Catch me up: "+t.window.label.replace(/^\w/,c=>c.toLowerCase()).replace(/^since/,"since"),""),l.push("_"+wa(o,s)+" → "+wa(r,s)+" · "+t.sessions.length+" session"+(t.sessions.length===1?"":"s")+" in "+t.repos.length+" repo"+(t.repos.length===1?"":"s")+(t.machines.length?" · "+t.machines.join(", "):"")+"_",""),l.push("## Summary","");for(let c of e.bullets)l.push("- "+Le(c));if(l.push(""),t.needsYou.length){l.push("## Needs you ("+t.needsYou.length+")","");for(let c of t.needsYou)l.push("- **"+Le(c.title)+"** ("+c.repo+(a&&c.machine?" · "+c.machine:"")+") — "+Le(c.needsYou)+" <!-- session: "+c.id+" -->");l.push("")}if((t.stillOpen||[]).length){l.push("## Still open ("+t.stillOpen.length+")","");for(let c of t.stillOpen)l.push("- **"+Le(c.name)+"** ("+Le(c.repo)+" · "+c.done+"/"+c.total+" done) — next: "+c.next.map(u=>"#"+u.n+" "+Le(u.text)).join("; ")+" <!-- list: "+c.ref+" -->"),c.ticked.length&&l.push(" - Ticked off in this window: "+c.ticked.map(u=>"#"+u.n+" "+Le(u.text)).join("; "));l.push("")}for(let c of t.repos){l.push("## "+Le(c.label)+" ("+c.sessions.length+")","");for(let u of c.sessions){if(l.push("### "+Le(u.title),"<!-- session: "+u.id+" -->"),l.push("`"+[u.repo+(u.branch?" · "+u.branch:""),a||u.machine?u.machine:null,u.state].filter(Boolean).join(" · ")+"`",""),u.needsYou&&u.needsKind!=="next"&&l.push("**Needs you:** "+Le(u.needsYou)),u.done.length===1)l.push("**Done:** "+Le(u.done[0]));else if(u.done.length){l.push("**Done:**");for(let d of u.done)l.push("- "+Le(d))}u.stoppedAt&&l.push((u.agent?"**Latest:** ":"**Stopped at:** ")+Le(u.stoppedAt)),u.lastReply&&l.push("**Since then:** "+Le(u.lastReply)),u.next&&l.push("**Next:** "+Le(u.next));for(let d of u.nextSteps)l.push("- "+Le(d));if(u.questions.length){l.push("**Open questions:**");for(let d of u.questions)l.push("- "+Le(d))}u.agent?l.push("","From "+Le(u.agent.display)+"’s feed (read-only)"+(u.agent.reply&&/^[^<>\s]+$/.test(u.agent.reply.url)?" · "+Le(u.agent.reply.label).replace(/[<>→]/g,"")+" → <"+u.agent.reply.url+">":""),""):l.push("","Resume: `"+u.resume+"`","")}}return t.sessions.length||l.push("_No Claude Code sessions with activity in this window._",""),ms(i,lS)+l.join(`
|
|
85
|
+
`).replace(/\n+$/,"")+`
|
|
86
|
+
`}function ku(t){let{data:e,body:n}=Be(t),s={generatedAt:e.generated_at?G(e.generated_at):null,window:{from:e.window_from?G(e.window_from):null,to:e.window_to?G(e.window_to):null,label:e.window_label||null,spec:e.window_spec||null,tz:e.time_zone||null},repos:e.repos==="all"||e.repos==null?"all":[].concat(e.repos).map(String),repoKeys:Array.isArray(e.repo_keys)?e.repo_keys.map(String):[],machines:Array.isArray(e.machines)?e.machines.map(String):[],machineFilter:e.machine_filter==="all"||e.machine_filter==null?"all":[].concat(e.machine_filter).map(String),madeOn:e.made_on==null?null:String(e.made_on),model:e.model==null?null:String(e.model),summaryBy:e.summary_by&&typeof e.summary_by=="object"?e.summary_by:null,length:e.length||null,sessions:typeof e.sessions=="number"?e.sessions:null,sessionIds:Array.isArray(e.session_ids)?e.session_ids.map(String):[],needsYou:Array.isArray(e.needs_you)?e.needs_you.map(String):[]},o=n.split(`
|
|
87
|
+
`),r=[],i=[],a=[],l=[],c=null,u=null,d=null,f=null,p=h=>{let m=h.match(/<!--\s*session:\s*([^\s>]+)\s*-->/);return m?m[1]:null};for(let h of o){let m=h.replace(/\s+$/,""),y;if(y=m.match(/^#\s+(.*)$/)){s.title=y[1];continue}if(y=m.match(/^##\s+(.*?)(?:\s+\((\d+)\))?$/)){d=null,f=null,/^summary$/i.test(y[1])?c="summary":/^needs you$/i.test(y[1])?c="needs":/^still open$/i.test(y[1])?c="open":(c="repo",u={label:y[1],sessions:[]},a.push(u));continue}if(c==="summary"){(y=m.match(/^-\s+(.*)$/))&&r.push(y[1]);continue}if(c==="needs"){(y=m.match(/^-\s+\*\*(.+?)\*\*\s+\((.*?)\)\s+—\s+(.*?)(?:\s*<!--.*-->)?$/))&&i.push({id:p(m),title:y[1],repo:y[2].split(" · ")[0],needsYou:y[3]});continue}if(c==="open"){if(y=m.match(/^-\s+\*\*(.+?)\*\*\s+\((.*?) · (\d+)\/(\d+) done\)\s+—\s+next:\s*(.*?)(?:\s*<!--\s*list:\s*(\S+)\s*-->)?$/)){let w=k=>k.split(/;\s+(?=#\d+ )/).map(S=>{let v=S.match(/^#(\d+)\s+(.*)$/);return v?{n:Number(v[1]),text:v[2]}:null}).filter(Boolean);l.push({name:y[1],repo:y[2],done:Number(y[3]),total:Number(y[4]),next:w(y[5]),ref:y[6]||null,ticked:[]})}else(y=m.match(/^\s+-\s+Ticked off in this window:\s*(.*)$/))&&l.length&&(l[l.length-1].ticked=y[1].split(/;\s+(?=#\d+ )/).map(w=>{let k=w.match(/^#(\d+)\s+(.*)$/);return k?{n:Number(k[1]),text:k[2]}:null}).filter(Boolean));continue}if(c==="repo"){if(y=m.match(/^###\s+(.*)$/)){d={id:null,title:y[1],repo:u.label,done:[],nextSteps:[],questions:[],stoppedAt:null,next:null,needsYou:null,lastReply:null,resume:null,line:null},u.sessions.push(d),f=null;continue}if(d){if(p(m)&&!d.id){d.id=p(m);continue}if(!d.line&&(y=m.match(/^`([^`]+)`$/))){d.line=y[1];continue}if(y=m.match(/^Resume:\s+`(.*)`$/)){d.resume=y[1],f=null;continue}if(y=m.match(/^From (.+?)’s feed \(read-only\) · ([^<>→]{1,60}?) → <([^>\s]+)>$/)){d.agent={display:y[1],url:null,reply:{label:y[2],url:y[3]}},f=null;continue}if(y=m.match(/^From (.+?)’s feed \(read-only\)(?:\. Reply: <(https:\/\/[^>\s]+)>)?$/)){d.agent={display:y[1],url:y[2]||null},f=null;continue}if(y=m.match(/^\*\*([A-Za-z ]+):\*\*\s*(.*)$/)){let w=y[1].toLowerCase(),k=y[2].trim();f=null,w==="done"?k?d.done.push(k):f=d.done:w==="stopped at"?d.stoppedAt=k:w==="latest"?(d.stoppedAt=k,d.agent=d.agent||{display:null,url:null}):w==="next"?(d.next=k,f=d.nextSteps):w==="open questions"?f=d.questions:w==="needs you"?d.needsYou=k:w==="since then"&&(d.lastReply=k);continue}if((y=m.match(/^-\s+(.*)$/))&&f){f.push(y[1]);continue}m.trim()}}}return{meta:s,summary:r,needsYou:i,stillOpen:l,repos:a}}async function Nr(t,e={}){let n=e.now!=null?e.now:Date.now(),s=e.cfg||ye,o=Sa(s),r=ka(e.since,{now:n,tz:o,lastAt:e.lastAt==null?null:e.lastAt,to:e.to||null,workdays:gu(s)}),i=Xh(t,{...e,now:n,cfg:s,tz:o,window:r,lengthOverride:e.length}),a=await sp(i,{summarizer:e.summarizer||null,cfg:s,given:e.given||null}),l=ip(i,a,{now:n});return{recap:i,summary:a,markdown:l,fileName:rp(n,o)}}function ba(t,e={}){let n=e.now!=null?e.now:Date.now(),s=e.cfg||ye,o=Sa(s),r=gu(s),i=e.lastAt==null?null:e.lastAt,a=ka(e.since,{now:n,tz:o,lastAt:i,to:e.to||null,workdays:r}),l=wu(t,{...e,from:a.from,to:a.to,now:n,cfg:s}),c=Su(l),u=[...new Set(t.map(d=>d.session&&d.session.machine).filter(Boolean))].sort();return{tz:o,now:G(n),window:{from:G(a.from),to:G(a.to),spec:a.spec,kind:a.kind,label:a.label,fallback:a.fallback||null},windows:Jh({now:n,tz:o,lastAt:i,workdays:r}),lastAt:i!=null?G(i):null,repos:c.repos,machines:c.machines,allMachines:u,sessions:l.length}}var Wh,Q0,Ir,ga,ma,pu,eS,wa,$h,qh,fu,Yh,rS,co,wn,ep,np,lo,lS,Le,va=V(()=>{ve();As();Cr();Wh=1,Q0=/^\d{4}-\d{2}-\d{2}T\d{4}(?:-\d+)?$/,Ir=36e5,ga=24*Ir,ma={sun:"Sunday",mon:"Monday",tue:"Tuesday",wed:"Wednesday",thu:"Thursday",fri:"Friday",sat:"Saturday"},pu={short:{bullets:3,done:2,steps:1,questions:1,maxTokens:350},informative:{bullets:5,done:4,steps:3,questions:3,maxTokens:600},long:{bullets:6,done:8,steps:5,questions:5,maxTokens:900}};eS=(t,e)=>{let n=Pt(t,e);return Ft(n.y,n.mo,n.d,0,0,e)};wa=(t,e)=>new Date(t).toLocaleString("en-US",{timeZone:e,weekday:"short",month:"short",day:"numeric",hour:"numeric",minute:"2-digit"}).replace(/,(?= \w{3} \d)/,"");$h=(t,e,n)=>!isNaN(t)&&t>=e&&t<=n;qh=t=>Math.max(R(t.s.lastActivityAt)||0,R(t.s.endedAt)||0,t.latest?R(t.latest.createdAt):0,R(t.s.startedAt)||0);fu=t=>{let e=new Set;return t.filter(n=>{let s=String(n||"").trim().toLowerCase();return!s||e.has(s)?!1:(e.add(s),!0)})},Yh=t=>String(t||"").toLowerCase().replace(/[^a-z0-9]+/g," ").trim(),rS=(t,e)=>{let n=Yh(t),s=Yh(e);return!!n&&!!s&&(n.includes(s)||s.includes(n))},co=t=>String(t||"").replace(/^YOU:\s*/i,""),wn=t=>String(t||"").replace(/[.!?…]+$/,"");ep='You catch a developer up after time away from their Claude Code sessions ("WTF was I working on?"). From the per-session notes, write {n} short bullets, most important first: what is waiting on them, what got done, where things stopped, and the one thing to pick up first. Name the session or repo in each bullet. Plain sentences, no headings, no preamble, no bold. Start every line with "- ".';np=qf,lo=t=>"“"+t+"”";lS=["kind","generated_at","window_from","window_to","window_label","window_spec","time_zone","repos","repo_keys","machines","machine_filter","made_on","model","summary_by","length","sessions","session_ids","needs_you","schema"],Le=t=>String(t??"").replace(/\s*\n\s*/g," ").trim()});import rt from"node:fs";import be from"node:path";function bu(t){let e=be.basename(t),n=e.match(/^(.+?)\.sync-conflict-[\w-]+\.md$/)||e.match(/^(.+?) \([^)]*conflicted copy[^)]*\)\.md$/i)||e.match(/^(.+?) \d{1,2}\.md$/);return n?n[1]+".md":null}function fS(t){return zi(t||"no-repo",60)||"no-repo"}function xa(t){try{return rt.accessSync(t),!0}catch{return!1}}function it(t,{items:e=!0}={}){let n=eh(t);return{ref:t.ref,slug:t.slug,repoDir:t.repoDir,name:t.meta.name,repo:t.meta.repo,repoKey:t.meta.repoKey,archived:!!t.meta.archived,sourceSession:t.meta.sourceSession,sourceMachine:t.meta.sourceMachine,sourceTitle:t.meta.sourceTitle,created:t.meta.created,updated:t.meta.updated,done:n.done,total:n.total,progress:n.label,open:n.total-n.done,...e?{items:t.items.map(s=>({n:s.id,text:s.text,done:!!s.done,at:s.at||null}))}:{}}}function vu(t,{cli:e="leftoff"}={}){let n=(t||[]).filter(o=>!o.archived);if(!n.length)return"";let s=["Leftoff is tracking "+(n.length===1?"a checklist":n.length+" checklists")+" for the user from this session (their own list, separate from your internal todo list):"];for(let o of n.slice(0,4)){s.push("",'"'+o.name+'" ('+o.progress+" done; id "+o.ref+"):");for(let r of(o.items||[]).slice(0,40))s.push(" "+r.n+". ["+(r.done?"x":" ")+"] "+F(r.text,140))}return s.push("",'When you finish one of these steps, or the user says a step is done ("step 3 is done"), tick it by running: '+e+" todo done "+n[0].ref+" <n>"+(n.length>1?" (use that list's id)":"")+". To add a step: "+e+" todo add "+n[0].ref+` "<text>". Item numbers never change. Don't rewrite the list file yourself.`),s.join(`
|
|
88
|
+
`)}var cS,uS,dS,Gn,Aa=V(()=>{ve();gn();cS=1e4,uS=4e3,dS=t=>{try{Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,t)}catch{}};Gn=class{constructor(e,n="unknown",{now:s}={}){this.root=e,this.machine=n,this.now=s||(()=>Date.now()),this.dir=be.join(e,"lists"),this.lockDir=be.join(e,".locks",String(n).replace(/[^\w.-]/g,"_"))}files(){let e=[],n=[];try{n=rt.readdirSync(this.dir,{withFileTypes:!0}).filter(s=>s.isDirectory()&&!s.name.startsWith(".")).map(s=>s.name)}catch{return e}for(let s of n){let o=[];try{o=rt.readdirSync(be.join(this.dir,s)).filter(r=>r.endsWith(".md")&&!r.startsWith("."))}catch{}for(let r of o)e.push(be.join(this.dir,s,r))}return e}refOf(e){return be.basename(be.dirname(e))+"/"+be.basename(e,".md")}fileOf(e){let[n,s]=String(e).split("/");return be.join(this.dir,n,s+".md")}read(e){let n;try{n=rt.readFileSync(e,"utf8")}catch{return null}let s=Hc(n);return zs(s),this.decorate(s,e)}decorate(e,n){return e.file=n,e.ref=this.refOf(n),e.slug=be.basename(n,".md"),e.repoDir=be.basename(be.dirname(n)),e}healConflicts(){let e=0;for(let n of this.files()){let s=bu(n);if(!s)continue;let o=be.join(be.dirname(n),s);if(xa(o))try{this.update(this.refOf(o),i=>{let a=Hc(rt.readFileSync(n,"utf8")),l=Qf(i,a);i.meta=l.meta,i.items=l.items,i.segs=l.segs},{touch:!1});let r=be.join(this.dir,".merged",be.basename(be.dirname(n)));rt.mkdirSync(r,{recursive:!0}),rt.renameSync(n,be.join(r,be.basename(n))),e++}catch{}}return e}all({includeArchived:e=!1,repo:n=null,session:s=null}={}){this.healConflicts();let o=[];for(let r of this.files()){if(bu(r)&&xa(be.join(be.dirname(r),bu(r))))continue;let i=this.read(r);i&&(i.meta.archived&&!e||n&&i.repoDir!==n&&i.meta.repo!==n&&i.meta.repoKey!==n||s&&i.meta.sourceSession!==s||o.push(i))}return o.sort((r,i)=>(R(i.meta.updated)||0)-(R(r.meta.updated)||0))}resolve(e,{includeArchived:n=!0}={}){let s=String(e||"").trim();if(!s)throw Object.assign(new Error("which list? (give its name or repo/slug; `leftoff todo` lists them)"),{code:"no_list"});if(/^[\w.-]+\/[\w.-]+$/.test(s)&&xa(this.fileOf(s)))return this.read(this.fileOf(s));if(/^\d+$/.test(s)){let c=this.all()[Number(s)-1];if(c)return c}let o=this.all({includeArchived:n}),r=s.toLowerCase(),i=zi(s),a=[l=>l.slug===s||l.ref===s,l=>String(l.meta.name||"").toLowerCase()===r,l=>l.slug===i,l=>l.slug.startsWith(i)||String(l.meta.name||"").toLowerCase().startsWith(r),l=>l.slug.includes(i)];for(let l of a){let c=o.filter(l),u=c.filter(d=>!d.meta.archived);if(u.length===1||c.length===1)return u[0]||c[0];if(c.length>1)throw Object.assign(new Error('"'+s+'" matches '+c.length+" lists: "+c.map(d=>d.ref).join(", ")+". Use repo/slug."),{code:"ambiguous_list"})}throw Object.assign(new Error('no list called "'+s+'" (`leftoff todo --all` lists them)'),{code:"no_list"})}lock(e){rt.mkdirSync(this.lockDir,{recursive:!0});let n=be.join(this.lockDir,e.replace(/[^\w.-]+/g,"_")+".lock"),s=Date.now();for(;;){try{return rt.writeFileSync(n,String(process.pid),{flag:"wx"}),()=>{try{rt.unlinkSync(n)}catch{}}}catch(o){if(o.code!=="EEXIST")throw o}try{if(Date.now()-rt.statSync(n).mtimeMs>cS){rt.unlinkSync(n);continue}}catch{continue}if(Date.now()-s>uS)throw Object.assign(new Error("the list is busy (locked by another writer); try again"),{code:"list_busy"});dS(15)}}update(e,n,{touch:s=!0}={}){let o=this.fileOf(typeof e=="string"?e:e.ref),r=this.lock(this.refOf(o));try{if(!xa(o))throw Object.assign(new Error("that list is gone"),{code:"no_list"});let i=this.read(o);return n(i)===!1?i:(s&&(i.meta.updated=G(this.now())),Kn(o,Bc(i)),this.read(o))}finally{r()}}create({name:e,items:n=[],repo:s=null,repoKey:o=null,sourceSession:r=null,sourceMachine:i=null,sourceTitle:a=null}){let l=ps(e)||"Checklist",c=Xf({name:l,items:n,repo:s,repoKey:o,sourceSession:r,sourceMachine:i||(r?this.machine:null),sourceTitle:a,now:G(this.now())});if(!c.items.length)throw Object.assign(new Error("a list needs at least one item"),{code:"empty_list"});let u=be.join(this.dir,fS(s));rt.mkdirSync(u,{recursive:!0});let d=zi(l);for(let f=1;f<200;f++){let p=be.join(u,(f===1?d:d+"-"+f)+".md");try{return rt.writeFileSync(p,Bc(c),{flag:"wx"}),this.read(p)}catch(h){if(h.code!=="EEXIST")throw h}}throw new Error("no free file name for that list")}setDone(e,n,s=!0){let o=[],r=this.update(e,i=>{let a=!1;for(let l of n){let c=i.items.find(u=>u.id===Number(l));if(!c){o.push(l);continue}c.done!==!!s&&(c.done=!!s,c.at=G(this.now()),a=!0)}if(!a)return!1});if(o.length&&o.length===n.length)throw Object.assign(new Error("no item "+o.join(", ")+' on "'+r.meta.name+'" (it has '+r.items.map(i=>i.id).join(", ")+")"),{code:"no_item",list:r});return{list:r,missing:o}}findItem(e,n){let s=new Set(["the","and","for","step","item","done","with","that","this","from","into","its"]),o=String(n||"").toLowerCase().split(/[^a-z0-9]+/).filter(a=>a.length>2&&!s.has(a)),r=null,i=0;for(let a of e.items){let l=a.text.toLowerCase(),c=o.filter(u=>l.includes(u)).length/Math.max(1,o.length);c>i&&(r=a,i=c)}return i>=.6?r:null}addItem(e,n){let s=String(n||"").replace(/\s+/g," ").trim();if(!s)throw Object.assign(new Error("the item is empty"),{code:"empty_item"});let o=null,r=this.update(e,i=>{o={id:null,text:F(s,500),done:!1,at:G(this.now())},i.items.push(o),zs(i)});return{list:r,item:r.items.find(i=>i.text===o.text&&i.id===o.id)||r.items[r.items.length-1]}}editItem(e,n,{text:s,done:o}={}){let r=null,i=this.update(e,a=>{let l=a.items.find(c=>c.id===Number(n));if(!l)return!1;r=l,s!=null&&String(s).trim()&&(l.text=F(String(s).replace(/\s+/g," ").trim(),500)),o!=null&&(l.done=!!o),l.at=G(this.now())});if(!r)throw Object.assign(new Error("no item "+n+' on "'+i.meta.name+'"'),{code:"no_item"});return i}removeItem(e,n){let s=!1,o=this.update(e,r=>{let i=r.items.length;if(r.items=r.items.filter(a=>a.id!==Number(n)),s=r.items.length<i,!s)return!1});if(!s)throw Object.assign(new Error("no item "+n),{code:"no_item"});return o}rename(e,n){let s=ps(n);if(!s)throw Object.assign(new Error("the name is empty"),{code:"empty_name"});return this.update(e,o=>{o.meta.name=s})}archive(e,n=!0){return this.update(e,s=>{s.meta.archived=!!n})}}});import uo from"node:fs";import hS from"node:os";import Ta from"node:path";import{execFileSync as Yx,spawn as pS,spawnSync as mS}from"node:child_process";import{fileURLToPath as gS}from"node:url";async function Dr({baseUrl:t=process.env.CHECKPOINTS_CLOUD_URL||_s,fetchImpl:e=fetch,timeoutMs:n=5e3}={}){let s=t.replace(/\/$/,"");try{let o=await Ea(e,s+"/api/tags",n);return{ok:!0,baseUrl:s,models:(o.models||[]).map(r=>r.name||r.model).filter(Boolean),error:null}}catch(o){return{ok:!1,baseUrl:s,models:[],error:o.cause&&o.cause.code||o.message}}}function Ca(t=[],{curated:e=Lr,current:n=null}={}){let s=r=>t.some(i=>Ge(i,r)),o=e.map(r=>({...r,curated:!0,available:t.length?s(r.tag):null,current:!!n&&Ge(n,r.tag)}));for(let r of t)o.some(i=>Ge(i.tag,r))||o.push({tag:r,name:r,curated:!1,available:!0,recommended:!1,current:!!n&&Ge(n,r)});return o}function _a(t){let e=String(t||"").trim().toLowerCase();return e?e.includes(":")?e:e+":latest":""}function Ge(t,e){return _a(t)===_a(e)}function ho(t=[],{curated:e=Mr,current:n=null}={}){let s=t.map(i=>typeof i=="string"?{name:i}:i),o=i=>s.some(a=>Ge(a.name||a.model,i)),r=e.map(i=>({...i,curated:!0,installed:o(i.tag),current:!!n&&Ge(n,i.tag)}));for(let i of s){let a=i.name||i.model;!a||r.some(l=>Ge(l.tag,a))||/embed/i.test(a)||r.push({tag:a,name:a,sizeBytes:i.size||null,curated:!1,installed:!0,recommended:!1,current:!!n&&Ge(n,a)})}return r}function cp(t,{totalMemBytes:e=hS.totalmem()}={}){let n=t.find(r=>r.current&&r.installed);if(n)return n.tag;let s=t.find(r=>r.recommended);if(s&&s.installed)return s.tag;let o=t.find(r=>r.curated&&r.installed);if(o)return o.tag;if(e&&e<85e8){let r=t.filter(i=>i.curated).sort((i,a)=>i.ramGB-a.ramGB)[0];if(r)return r.tag}return s?s.tag:t[0]&&t[0].tag}function Oa(t,e){let n=[];return t.sizeBytes&&n.push(e(t.sizeBytes)),t.ramGB&&n.push("~"+t.ramGB+" GB RAM"),t.speed&&n.push(t.speed),n.join(" · ")}function Ia(t,e=process.env){let n=String(e.PATH||"").split(Ta.delimiter).filter(Boolean),s=process.platform==="win32"?[".exe",".cmd",".bat",""]:[""];for(let o of n)for(let r of s){let i=Ta.join(o,t+r);try{if(uo.statSync(i).isFile())return uo.accessSync(i,uo.constants.X_OK),i}catch{}}return null}function Rr(t=process.env){let e=Ia("ollama",t);if(e)return e;if(t.CHECKPOINTS_TEST_NO_SYSTEM_BINS)return null;for(let n of["/opt/homebrew/bin/ollama","/usr/local/bin/ollama","/Applications/Ollama.app/Contents/Resources/ollama"])if(uo.existsSync(n))return n;return null}async function Ea(t,e,n=1500){let s=await t(e,{signal:AbortSignal.timeout(n)});if(!s.ok)throw new Error(e+" -> HTTP "+s.status);return s.json()}async function Ut({baseUrl:t=at,fetchImpl:e=fetch,env:n=process.env}={}){let s=t.replace(/\/$/,""),o={kind:"ollama",running:!1,baseUrl:s,version:null,models:[],binary:Rr(n),error:null};try{let r=await Ea(e,s+"/api/tags");o.running=!0,o.models=(r.models||[]).map(i=>({name:i.name||i.model,size:i.size,modifiedAt:i.modified_at,parameterSize:i.details&&i.details.parameter_size,quantization:i.details&&i.details.quantization_level}));try{o.version=(await Ea(e,s+"/api/version")).version||null}catch{}}catch(r){o.error=r.cause&&r.cause.code||r.name||r.message}return o}async function Sn({baseUrl:t=Qt,fetchImpl:e=fetch}={}){let n=t.replace(/\/$/,""),s={kind:"openai-compatible",running:!1,baseUrl:n,models:[],error:null};try{let o=await Ea(e,n+"/models");s.running=!0,s.models=(o.data||[]).map(r=>r.id).filter(r=>r&&!/embed/i.test(r))}catch(o){s.error=o.cause&&o.cause.code||o.name||o.message}return s}async function po({baseUrl:t=at,fetchImpl:e=fetch,timeoutMs:n=2e4}={}){let s=Date.now()+n;for(;Date.now()<s;){let o=await Ut({baseUrl:t,fetchImpl:e});if(o.running)return o;await wS(500)}return Ut({baseUrl:t,fetchImpl:e})}function SS(t,e){let n=t||{layers:{},status:"",total:0,completed:0,fraction:0,done:!1,error:null};if(e.error)return{...n,error:String(e.error)};let s={...n.layers};e.digest&&e.total&&(s[e.digest]={total:e.total,completed:Math.min(e.completed||0,e.total)});let o=Object.values(s),r=o.reduce((c,u)=>c+u.total,0),i=o.reduce((c,u)=>c+u.completed,0),a=e.status==="success",l=a?1:r?Math.min(.999,i/r):n.fraction;return{layers:s,status:e.status||n.status,total:r,completed:i,fraction:Math.max(n.fraction,l),done:a,error:null}}async function mo({tag:t,baseUrl:e=at,fetchImpl:n=fetch,onProgress:s=()=>{},signal:o}={}){let r=await n(e.replace(/\/$/,"")+"/api/pull",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({model:t,stream:!0}),signal:o});if(!r.ok||!r.body){let u="HTTP "+r.status;try{let d=await r.json();d.error&&(u=d.error)}catch{}throw new Error("pull "+t+" failed: "+u)}let i=null,a="",l=new TextDecoder,c=u=>{if(!u.trim())return;let d;try{d=JSON.parse(u)}catch{return}if(i=SS(i,d),i.error)throw new Error("pull "+t+" failed: "+i.error);s(i)};for await(let u of r.body){a+=l.decode(u,{stream:!0});let d;for(;(d=a.indexOf(`
|
|
89
|
+
`))>=0;)c(a.slice(0,d)),a=a.slice(d+1)}if(c(a),!i||!i.done)throw new Error("pull "+t+" ended early (last status: "+(i&&i.status||"none")+")");return i}async function Es(t,{fetchImpl:e,transcriptFile:n=yS,root:s}={}){let o=$s(ye,{summarizer:t}),{messages:r,meta:i}=to(uo.readFileSync(n,"utf8")),a={id:i.sessionId||"sample",cwd:i.cwd||"/work/larry",title:i.summary||"Sample session",repo:{root:i.cwd,branch:i.gitBranch}},l=$i({session:a,messages:r,cfg:o}),c=new gt(o,{...e?{fetchImpl:e}:{},...s?{root:s}:{}}),u=Date.now(),d=await c.checkpoint({...l,messages:r,session:a}),f=Date.now()-u,p=d.generator&&d.generator.by==="heuristic"&&d.generator.fallbackFrom;return{ok:!p,doing:d.doing,stoppedAt:d.stoppedAt,next:d.next,parseOk:d.parseOk!==!1,ms:f,generator:d.generator,error:p?d.generator.error:null}}function up(t=process.platform,e=process.env){let n={id:"script",label:"Official install script",shell:"curl -fsSL https://ollama.com/install.sh | sh",cmd:["sh","-c","curl -fsSL https://ollama.com/install.sh | sh"]};if(t==="darwin"){let s=[];return Ia("brew",e)&&s.push({id:"brew",label:"Homebrew",shell:"brew install ollama",cmd:["brew","install","ollama"]}),s.push({...n,note:"installs the Ollama app"}),s.push({id:"manual",label:"Download the app myself",shell:"open https://ollama.com/download",cmd:null,url:"https://ollama.com/download"}),s}return t==="linux"?[{...n,note:"asks for sudo; sets up a systemd service"},{id:"manual",label:"Install it myself",shell:"see https://ollama.com/download/linux",cmd:null,url:"https://ollama.com/download/linux"}]:t==="win32"?[{id:"manual",label:"PowerShell installer",shell:"irm https://ollama.com/install.ps1 | iex",cmd:null,url:"https://ollama.com/download/windows"}]:[{id:"manual",label:"Install it myself",shell:"see https://ollama.com/download",cmd:null,url:"https://ollama.com/download"}]}function jr(t=process.platform,e=Rr()){return t==="darwin"&&uo.existsSync("/Applications/Ollama.app")?{shell:"open -a Ollama",cmd:["open","-a","Ollama"],detached:!1}:e?{shell:e+" serve",cmd:[e,"serve"],detached:!0}:null}function Na({platform:t=process.platform,binary:e,env:n=process.env}={}){if(n.CHECKPOINTS_OLLAMA_START_CMD)try{let s=JSON.parse(n.CHECKPOINTS_OLLAMA_START_CMD);if(Array.isArray(s)&&s.length)return{shell:s.join(" "),cmd:s,detached:!0}}catch{}return e?jr(t,e):null}function dp(t,{spawnImpl:e=pS,spawnSyncImpl:n=mS}={}){if(!t||!Array.isArray(t.cmd))return{started:!1,shell:null,error:"no start command"};try{if(t.detached){let s=e(t.cmd[0],t.cmd.slice(1),{detached:!0,stdio:"ignore"});s.on("error",()=>{}),s.unref()}else{let s=n(t.cmd[0],t.cmd.slice(1),{stdio:"ignore",timeout:15e3});if(s&&s.status)return{started:!1,shell:t.shell,error:t.shell+" exited "+s.status}}return{started:!0,shell:t.shell}}catch(s){return{started:!1,shell:t.shell,error:String(s.message||s)}}}var at,Qt,yS,fo,Mr,_s,lp,Lr,wS,go=V(()=>{br();ve();xs();at="http://localhost:11434",Qt="http://localhost:1234/v1",yS=Ta.resolve(Ta.dirname(gS(import.meta.url)),"..","assets","sample-transcript.jsonl"),fo="2026-10-09",Mr=[{tag:"qwen2.5:3b",name:"Qwen 2.5 · 3B",sizeBytes:19e8,ramGB:4,speed:"fast",note:"Sticks to the 3-line format, good with code and file names",recommended:!0},{tag:"llama3.2:3b",name:"Llama 3.2 · 3B",sizeBytes:2e9,ramGB:4,speed:"fast",note:"Solid all-rounder from Meta"},{tag:"gemma3:4b",name:"Gemma 3 · 4B",sizeBytes:34e8,ramGB:6,speed:"medium",note:"Best-written summaries of the four, slower without a GPU"},{tag:"llama3.2:1b",name:"Llama 3.2 · 1B",sizeBytes:13e8,ramGB:2.5,speed:"fastest",note:'For 8 GB machines; terser, sometimes vague on "Next"'}],_s="https://ollama.com",lp="2026-10-09",Lr=[{tag:"gpt-oss:20b",name:"gpt-oss · 20B",note:"OpenAI's open-weight model; quick, plenty for 3 lines",recommended:!0},{tag:"gemma4:31b",name:"Gemma 4 · 31B",note:"Google; the model Ollama's own cloud docs use"},{tag:"nemotron-3-nano:30b",name:"Nemotron 3 Nano · 30B",note:"NVIDIA; small and fast"},{tag:"gpt-oss:120b",name:"gpt-oss · 120B",note:"Bigger and slower; more than a checkpoint needs"}];wS=t=>new Promise(e=>setTimeout(e,t))});function fp(t){let e={indent:-1,children:[]},n=[e],s=null;for(let r of String(t||"").split(/\r?\n/)){let i=r.match(/^(\s*)[-*+]\s+(.*)$/);if(i){let a=i[1].replace(/\t/g," ").length;for(;n.length>1&&a<=n[n.length-1].indent;)n.pop();s={indent:a,text:i[2].trim(),children:[]},n[n.length-1].children.push(s),n.push(s)}else if(s&&/^\s+\S/.test(r))s.text+=" "+r.trim();else if(r.trim())s=null,n.length=1;else continue}let o=r=>r.map(({text:i,children:a})=>({text:i,children:o(a)}));return o(e.children)}var hp=V(()=>{});import kS from"node:fs";import pp from"node:path";import{fileURLToPath as bS}from"node:url";function mp(t){let e=String(t||"").trim().replace(/^v/i,"").match(/^(\d+)\.(\d+)\.(\d+)/);return e?[Number(e[1]),Number(e[2]),Number(e[3])]:null}function Ye(t,e){let n=mp(t),s=mp(e);if(!n||!s)return n?1:s?-1:0;for(let o=0;o<3;o++)if(n[o]!==s[o])return n[o]>s[o]?1:-1;return 0}function xu(t){let e=[],n=null,s=null;for(let r of String(t||"").split(/\r?\n/)){let i=r.match(/^##\s+\[?v?(\d+\.\d+\.\d+[^\]\s]*)\]?(?:\s*[-–—]\s*(\d{4}-\d{2}-\d{2}))?/);if(i){n={version:i[1],date:i[2]||null,sections:[],lines:[]},e.push(n),s=null;continue}if(!n)continue;if(/^##\s/.test(r)){n=null;continue}n.lines.push(r);let a=r.match(/^###\s+(.+?)\s*$/);if(a){s={title:a[1],items:[],lines:[]},n.sections.push(s);continue}s&&s.lines.push(r);let l=r.match(/^\s*[-*]\s+(.*)$/);l?(s||(s={title:"Changes",items:[],lines:[r]},n.sections.push(s)),s.items.push(l[1].trim())):s&&s.items.length&&/^\s{2,}\S/.test(r)&&(s.items[s.items.length-1]+=" "+r.trim())}let o=({lines:r,...i})=>({...i,list:fp(r.join(`
|
|
90
|
+
`))});return e.map(({lines:r,...i})=>({...i,sections:i.sections.map(o),markdown:r.join(`
|
|
91
|
+
`).trim()}))}function yo(t=vS){try{return xu(kS.readFileSync(t,"utf8"))}catch{return[]}}function wo(t,e,n){return t.filter(s=>(!e||Ye(s.version,e)>0)&&(!n||Ye(s.version,n)<=0))}function gp(t,e,n){return t.map(s=>({...s,isNew:(!n||Ye(s.version,n)<=0)&&(!e||Ye(s.version,e)>0)}))}function Ma(t,{colors:e=null,width:n=78}={}){let s=e||{bold:i=>i,dim:i=>i,cyan:i=>i,green:i=>i},o=[],r=(i,a)=>{let l=String(i).replace(/\*\*(.+?)\*\*/g,"$1").split(/\s+/),c=[],u="";for(let d of l)(u+" "+d).trim().length>n-a.length?(c.push(u),u=d):u=(u+" "+d).trim();return u&&c.push(u),c.map((d,f)=>(f?a:"")+d)};for(let i of t){o.push(s.bold(s.cyan("v"+i.version))+(i.date?s.dim(" "+i.date):"")+(i.isNew?" "+s.green("new"):""));for(let a of i.sections){o.push(" "+s.bold(a.title));for(let l of a.items)o.push(" • "+r(l," ").join(`
|
|
92
|
+
`))}o.push("")}return o.join(`
|
|
93
|
+
`)}var vS,La=V(()=>{hp();vS=pp.resolve(pp.dirname(bS(import.meta.url)),"..","CHANGELOG.md")});var Pe,z,Ze,yp,Pr,Cs,Au,wp,Os,Tu,Sp,kp,bp,yt,vp,wt=V(()=>{Pe="Leftoff",z="leftoff",Ze="OneSillyApp/leftoff",yp=["joelash/checkpoints","joelash/leftoff"],Pr="leftoff-hook",Cs="checkpoints-hook",Au="dev.leftoff.daemon",wp="dev.checkpoints.daemon",Os="leftoff.service",Tu="checkpoints.service",Sp="checkpoints",kp="leftoff",bp="checkpoints",yt="leftoff.localhost",vp="checkpoints.localhost"});var Lp={};zt(Lp,{CHECK_EVERY_MS:()=>Ou,DEFAULT_SOURCE:()=>Fr,GITHUB_REPO:()=>Ze,NPM_PACKAGE:()=>Xe,PACKAGE_DIRS:()=>Ur,REPO_DIR:()=>$n,STAMP_FILE:()=>Cu,checkForUpdate:()=>Br,defaultCacheDir:()=>Mu,fetchRemoteFiles:()=>Mp,installInfo:()=>Hr,isNpmBuild:()=>Ep,isNpxCacheDir:()=>Eu,lsRemote:()=>Np,normalizeSource:()=>ko,npmLatest:()=>Op,npmPackageUrl:()=>Ra,npmRegistry:()=>Da,npmUpgradeCommand:()=>Nu,packageManagerOf:()=>Cp,readPkg:()=>_p,readStamp:()=>Ap,registryPath:()=>Tp,renamedSource:()=>Is,runGit:()=>St,sshFallback:()=>Ip,writeStamp:()=>Iu});import So from"node:fs";import xS from"node:os";import kn from"node:path";import{execFile as AS}from"node:child_process";import{fileURLToPath as TS}from"node:url";function ko(t){let e=String(t||"").trim();if(!e)return Fr;let n=e.match(/^(?:github:)?([\w.-]+)\/([\w.-]+?)(?:\.git)?(?:#.*)?$/);return n&&!e.includes("://")&&!e.startsWith("/")&&!e.startsWith(".")?Is("https://github.com/"+n[1]+"/"+n[2]+".git"):Is(e)}function Is(t){let e=String(t||"");for(let n of yp){let s=n.replace(/[.]/g,"\\."),o=new RegExp("^(https://github\\.com/|git@github\\.com:|ssh://git@github\\.com/)"+s+"(?:\\.git)?/?$","i"),r=e.match(o);if(r)return r[1]+Ze+".git"}return e}function St(t,{cwd:e,timeoutMs:n=15e3,env:s=process.env}={}){return new Promise(o=>{let r={...s,GIT_TERMINAL_PROMPT:"0",GIT_ASKPASS:s.GIT_ASKPASS||"echo",SSH_ASKPASS:"echo",GIT_SSH_COMMAND:s.GIT_SSH_COMMAND||"ssh -o BatchMode=yes"};AS(s.CHECKPOINTS_GIT||"git",t,{cwd:e,timeout:n,env:r,maxBuffer:4*1024*1024},(i,a,l)=>{o({code:i?typeof i.code=="number"?i.code:-1:0,stdout:String(a||""),stderr:String(l||(i&&!a?i.message:"")||"")})})})}function Ap(t=$n){try{return JSON.parse(So.readFileSync(kn.join(t,Cu),"utf8"))}catch{return null}}function Iu(t,e=$n){let n={version:t.version||J,sha:t.sha||null,source:t.source||null,at:t.at||new Date().toISOString()};return So.writeFileSync(kn.join(e,Cu),JSON.stringify(n,null,2)+`
|
|
94
|
+
`),n}function Eu(t){return/[\\/]_npx[\\/]/.test(t)||/[\\/]\.npm[\\/]/.test(t)}function Tp(t=Xe){return t.startsWith("@")?"@"+t.slice(1).split("/").map(encodeURIComponent).join("%2f"):encodeURIComponent(t)}function Ra(t=process.env,e=Xe){return Da(t)+"/"+Tp(e)}function _p(t=$n){try{return JSON.parse(So.readFileSync(kn.join(t,"package.json"),"utf8"))}catch{return null}}function Ep(t=$n){let e=_p(t);return!!(e&&e.leftoffBuild==="npm")}function Cp(t,e=process.env){if(e.LEFTOFF_PM)return e.LEFTOFF_PM;let n=String(t||"");return/[\\/]\.?pnpm[\\/]/i.test(n)||/[\\/]pnpm-global[\\/]/i.test(n)?"pnpm":/[\\/]\.bun[\\/]/.test(n)?"bun":"npm"}function Nu(t,e=Xe+"@latest"){let n=process.platform==="win32";return t==="pnpm"?[n?"pnpm.cmd":"pnpm","add","-g",e]:t==="bun"?["bun","add","-g",e]:[n?"npm.cmd":"npm","install","-g",e,"--no-fund","--no-audit","--loglevel=error"]}function Da(t=process.env){return String(t.LEFTOFF_NPM_REGISTRY||t.npm_config_registry||"https://registry.npmjs.org").replace(/\/+$/,"")}async function Op({env:t=process.env,fetchImpl:e=fetch,timeoutMs:n=15e3}={}){let s=Ra(t);try{let o=await e(s,{headers:{accept:"application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8"},signal:AbortSignal.timeout(n)});if(o.status===404)return{ok:!1,version:null,error:Xe+" isn’t on "+Da(t)+" (404)"};if(!o.ok)return{ok:!1,version:null,error:Da(t)+" answered "+o.status};let r=await o.json(),i=r&&(r["dist-tags"]&&r["dist-tags"].latest||r.version);return i?{ok:!0,version:i,error:null}:{ok:!1,version:null,error:"no version in the registry answer"}}catch(o){return{ok:!1,version:null,error:o.message}}}async function Hr({repoDir:t=$n,env:e=process.env,globalRoot:n}={}){let s={version:J,sha:null,kind:"local",source:null,repoDir:t};if(So.existsSync(kn.join(t,".git"))){s.kind="clone";let r=await St(["rev-parse","HEAD"],{cwd:t,env:e});r.code===0&&(s.sha=r.stdout.trim());let i=await St(["remote","get-url","origin"],{cwd:t,env:e});return s.source=i.code===0?i.stdout.trim():null,s}if(Ep(t))return s.kind="npm",s.source="npm:"+Xe,s.pm=Cp(t,e),s.npx=Eu(t),s;let o=Ap(t);return o&&(s.sha=o.sha||null,s.source=o.source||null),Eu(t)?s.kind="npx":(n?t.startsWith(n):/[\\/]node_modules[\\/](checkpoints|leftoff|@leftoff[\\/]cli)$/.test(t))&&(s.kind="global"),s}function Ip(t){let e=String(t||"").match(/^https:\/\/github\.com\/([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/);return e?"git@github.com:"+e[1]+"/"+e[2]+".git":null}async function xp(t,{env:e,timeoutMs:n}){let s=await St(["ls-remote",t,"HEAD"],{env:e,timeoutMs:n}),o=s.code===0?(s.stdout.match(/^([0-9a-f]{40})\s+HEAD$/m)||[])[1]:null;return o?{ok:!0,sha:o,error:null,source:t}:{ok:!1,sha:null,error:_u(s.stderr)||"git ls-remote exited "+s.code,source:t}}async function Np(t,{env:e=process.env,timeoutMs:n=15e3}={}){let s=ko(t),o=await xp(s,{env:e,timeoutMs:n});if(o.ok)return o;let r=Ip(s);if(!r)return o;let i=await xp(r,{env:e,timeoutMs:n});return i.ok?i:{ok:!1,sha:null,source:s,error:o.error+" (also tried "+r+": "+i.error+")"}}async function Mp(t,{cacheDir:e=Mu(),env:n=process.env,timeoutMs:s=3e4}={}){try{if(So.mkdirSync(e,{recursive:!0}),!So.existsSync(kn.join(e,"HEAD"))){let c=await St(["init","--bare","-q",e],{env:n});if(c.code!==0)return{ok:!1,error:_u(c.stderr)}}let o=await St(["fetch","-q","--depth=1",ko(t),"HEAD"],{cwd:e,env:n,timeoutMs:s});if(o.code!==0)return{ok:!1,error:_u(o.stderr)||"git fetch exited "+o.code};let r=(await St(["rev-parse","FETCH_HEAD"],{cwd:e,env:n})).stdout.trim()||null,i=await St(["show","FETCH_HEAD:CHANGELOG.md"],{cwd:e,env:n}),a=await St(["show","FETCH_HEAD:package.json"],{cwd:e,env:n}),l=null;try{l=JSON.parse(a.stdout).version||null}catch{}return{ok:!0,sha:r,version:l,changelog:i.code===0?i.stdout:null,error:null}}catch(o){return{ok:!1,error:o.message}}}function Mu(t=process.env){return kn.join(t.XDG_CACHE_HOME||kn.join(xS.homedir(),".cache"),"checkpoints","remote.git")}async function Br({info:t,source:e,env:n=process.env,cacheDir:s,now:o=Date.now(),fetchFiles:r=!0}={}){let i=t||await Hr({env:n});if(i.kind==="npm"){let d={checkedAt:new Date(o).toISOString(),source:"npm:"+Xe,current:{version:i.version,sha:null},remote:{sha:null,version:null},available:!1,incoming:[],error:null},f=await Op({env:n});return f.ok?(d.remote.version=f.version,d.available=Ye(f.version,i.version)===1,d):(d.error=f.error,d)}let a=ko(e||i.source||Fr),l={checkedAt:new Date(o).toISOString(),source:a,current:{version:i.version,sha:i.sha},remote:{sha:null,version:null},available:!1,incoming:[],error:null},c=await Np(a,{env:n});if(!c.ok)return l.error=c.error,l;if(l.source=c.source,l.remote.sha=c.sha,i.sha&&i.sha===c.sha)return l;if(r){let d=await Mp(l.source,{cacheDir:s||Mu(n),env:n});d.ok?(l.remote.version=d.version,d.changelog&&(l.incoming=wo(xu(d.changelog),i.version,d.version).map(f=>({version:f.version,date:f.date,sections:f.sections})))):l.fetchError=d.error}let u=l.remote.version?Ye(l.remote.version,i.version):null;return l.available=u===1||u===0&&!!i.sha||u===null&&!!i.sha,u===-1&&(l.available=!1),l}var $n,Fr,Cu,Ou,_u,Xe,Ur,Kr=V(()=>{wt();ke();La();$n=kn.resolve(kn.dirname(TS(import.meta.url)),".."),Fr="https://github.com/"+Ze+".git",Cu=".install.json",Ou=24*3600*1e3;_u=t=>String(t||"").trim().split(`
|
|
95
|
+
`).filter(Boolean).pop()||"";Xe="@leftoff/cli",Ur=[Xe,"leftoff","checkpoints"]});import jp from"node:crypto";function Fp(){return"rq_"+jp.randomBytes(9).toString("base64url")}function Gr(t,e){let n=s=>Array.isArray(s)?"["+s.map(n).join(",")+"]":s&&typeof s=="object"?"{"+Object.keys(s).filter(o=>o!=="answers").sort().map(o=>JSON.stringify(o)+":"+n(s[o])).join(",")+"}":JSON.stringify(s===void 0?null:s);return jp.createHash("sha256").update(String(t||"")+`
|
|
96
|
+
`+n(e||{})).digest("hex").slice(0,16)}function ES(t,e={}){let n=e&&typeof e=="object"?e:{};switch(t){case"Bash":return{verb:"Run",detail:We(n.command,400),note:n.description?We(n.description,160):null};case"Edit":case"MultiEdit":return{verb:"Edit",detail:We(n.file_path,300),note:null};case"Write":return{verb:"Write",detail:We(n.file_path,300),note:n.content?We(String(n.content).split(`
|
|
97
|
+
`).length+" lines",40):null};case"NotebookEdit":return{verb:"Edit notebook",detail:We(n.notebook_path,300),note:null};case"Read":return{verb:"Read",detail:We(n.file_path,300),note:null};case"WebFetch":return{verb:"Fetch",detail:We(n.url,300),note:null};case"WebSearch":return{verb:"Search the web for",detail:We(n.query,300),note:null};default:{let s=Object.entries(n).find(([,o])=>typeof o=="string");return{verb:"Use "+t,detail:s?We(s[1],300):"",note:null}}}}function CS(t={}){return(Array.isArray(t&&t.questions)?t.questions:[]).slice(0,6).map(n=>({question:String(n.question||""),header:n.header?String(n.header):null,kind:n.kind==="text"||n.kind==="number"?n.kind:"choice",multiSelect:!!n.multiSelect,options:(Array.isArray(n.options)?n.options:[]).slice(0,8).map(s=>({label:String(s.label||s),description:s.description?String(s.description):null}))})).filter(n=>n.question)}function OS(t,e,n={}){let s=Array.isArray(e)?e.filter(r=>r&&(r.type==="addRules"&&Array.isArray(r.rules)&&r.behavior!=="deny"||r.type==="addDirectories"&&Array.isArray(r.directories))):[];return s.length?s.map(r=>r.type==="addRules"?{type:"addRules",rules:r.rules,behavior:"allow",destination:"session"}:{type:"addDirectories",directories:r.directories,destination:"session"}):[{type:"addRules",rules:[t==="Bash"&&n&&typeof n.command=="string"?{toolName:"Bash",ruleContent:n.command}:{toolName:t}],behavior:"allow",destination:"session"}]}function IS(t,e={}){let n=r=>{let i=new Error(r);throw i.status=400,i.code="bad_answer",i},s=e.action;Dp.includes(s)||n("action is one of "+Dp.join(", "));let o=e.message?We(e.message,500):"";if(s==="deny")return{behavior:"deny",message:o||"Denied from the Leftoff board."};if(t.kind==="question"){s!=="answer"&&n('a question takes action "answer" (or "deny")');let r=e.answers&&typeof e.answers=="object"?e.answers:{},i={};for(let a of t.questions){let l=r[a.question];if(Array.isArray(l)&&(l=l.map(String).filter(Boolean)),!(l==null||l===""||Array.isArray(l)&&!l.length))if(a.kind==="choice"&&a.options.length){let c=Array.isArray(l)?l:[String(l)],u=c.filter(d=>!a.options.some(f=>f.label===d));u.length&&!e.allowOther&&n('"'+u[0]+'" isn’t one of the options for "'+a.question+'"'),i[a.question]=a.multiSelect?c.join(", "):c[0]}else i[a.question]=Array.isArray(l)?l.join(", "):String(l)}return Object.keys(i).length||n("no answer given"),{behavior:"allow",updatedInput:{...t.input||{},answers:i}}}return s==="answer"&&n("a permission takes allow, allow_session or deny"),s==="allow_session"?{behavior:"allow",updatedPermissions:OS(t.tool,t.suggestions,t.input)}:{behavior:"allow"}}function Up(t){return t?{hookSpecificOutput:{hookEventName:"PermissionRequest",decision:t}}:{}}function $r(t,e={}){if(e.action==="deny")return"Denied"+(e.message?" (“"+We(e.message,160)+"”)":"");if(e.action==="allow")return"Allowed once";if(e.action==="allow_session")return"Allowed for this session";if(e.action==="answer"){let n=e.answers&&typeof e.answers=="object"?e.answers:{};return"Answered "+Object.entries(n).map(([s,o])=>"“"+We(s,80)+"” → "+We(Array.isArray(o)?o.join(", "):o,120)).join("; ")}return e.action==="reply"?"Replied “"+We(e.text,200)+"”":String(e.action||"")}function Lu({via:t,ip:e,host:n,who:s,userAgent:o,peer:r,transport:i}={}){if(t==="peer")return"from "+(r||"another machine")+"’s board"+(i?" ("+(i==="relay"?"through the relay":"direct")+")":"");let a=String(o||""),l=/Edg\//.test(a)?"Edge":/Chrome\//.test(a)?"Chrome":/Firefox\//.test(a)?"Firefox":/Safari\//.test(a)?"Safari":a?"a browser":null,c=/iPhone|iPad/.test(a)?"iPhone/iPad":/Android/.test(a)?"Android":null;return t==="remote"?"over Tailscale"+(s?" as "+s:"")+(n?" ("+n+")":"")+(c||l?" · "+[c,l].filter(Boolean).join(" "):""):"on this machine"+(l?" · "+l:"")+(e?" ("+e+")":"")}function Hp({max:t=30,windowMs:e=6e4,now:n=()=>Date.now()}={}){let s=new Map;return{take(o="all"){let r=n(),i=(s.get(o)||[]).filter(a=>r-a<e);return i.length>=t?(s.set(o,i),!1):(i.push(r),s.set(o,i),!0)}}}var Dp,_S,Pp,ja,Rp,We,Pa,Fa,Yr=V(()=>{Dp=["allow","allow_session","deny","answer"],_S=9.5*60*1e3,Pp=45*1e3,ja=25*1e3,Rp=4e3;We=(t,e)=>{let n=String(t??"").replace(/\s+/g," ").trim();return n.length>e?n.slice(0,e-1)+"…":n};Pa=class{constructor({now:e=()=>Date.now(),timeoutMs:n=_S,noListenerMs:s=Pp,keep:o=200,onChange:r=()=>{}}={}){this.now=e,this.timeoutMs=n,this.noListenerMs=s,this.keep=o,this.onChange=r,this.items=new Map}open({sessionId:e,tool:n,input:s,suggestions:o,capture:r,cwd:i,permissionMode:a}={}){if(!e||typeof e!="string"){let f=new Error("sessionId is required");throw f.status=400,f.code="bad_ask",f}if(!n||typeof n!="string"){let f=new Error("tool is required");throw f.status=400,f.code="bad_ask",f}let l=Gr(n,s);for(let f of this.items.values())if(f.state==="waiting"&&f.sessionId===e&&f.key===l)return f.lastPollAt=this.now(),f.captures.add(r||"hooks"),f;let c=this.now(),u=n==="AskUserQuestion"?"question":"permission",d={id:Fp(),sessionId:e,kind:u,tool:n,key:l,input:s&&typeof s=="object"?s:{},suggestions:Array.isArray(o)?o:[],captures:new Set([r||"hooks"]),cwd:i||null,permissionMode:a||null,questions:u==="question"?CS(s):null,describe:u==="permission"?ES(n,s):null,createdAt:c,expiresAt:c+this.timeoutMs,lastPollAt:c,state:"waiting",reason:null,decision:null,answer:null,answeredAt:null,settledAt:null,by:null,delivered:!1,waiters:new Set};return this.items.set(d.id,d),this.trim(),this.onChange(d,"opened"),d}get(e){return this.items.get(e)||null}waiting({sessionId:e}={}){return this.sweep(),[...this.items.values()].filter(n=>n.state==="waiting"&&(!e||n.sessionId===e)).sort((n,s)=>n.createdAt-s.createdAt)}wait(e,n=ja,s=null){let o=this.items.get(e);if(!o)return Promise.resolve({id:e,state:"gone",reason:"unknown"});if(o.lastPollAt=this.now(),o.state!=="waiting")return Promise.resolve(this.forHook(o,!0));let r=Math.max(0,Math.min(Number(n)||0,ja,o.expiresAt-this.now()));return new Promise(i=>{let a={resolve:null,timer:null};a.resolve=l=>{clearTimeout(a.timer),o.waiters.delete(a),o.lastPollAt=this.now(),i(l)},a.timer=setTimeout(()=>{this.sweep(),a.resolve(this.forHook(o,!1))},r),a.timer.unref&&a.timer.unref(),o.waiters.add(a),s&&s.addEventListener("abort",()=>{o.waiters.has(a)&&(clearTimeout(a.timer),o.waiters.delete(a),i(null))},{once:!0})})}forHook(e,n){return e.state==="answered"&&n&&(e.delivered=!0),{id:e.id,state:e.state,reason:e.reason,...e.state==="answered"?{decision:e.decision}:{}}}listening(e){return e.waiters.size>0||this.now()-e.lastPollAt<this.noListenerMs}answer(e,n={},s={}){let o=this.items.get(e);if(!o){let a=new Error("no such request");throw a.status=404,a.code="not_found",a}if(this.sweep(),o.state!=="waiting"){let a=new Error(o.state==="answered"?"already answered"+(o.by&&o.by.where?" "+o.by.where:""):o.reason==="answered_in_terminal"?"already answered in the terminal":"this request is no longer waiting ("+(o.reason||o.state)+")");throw a.status=409,a.code="not_waiting",a.state=o.state,a}let r=IS(o,n);o.state="answered",o.decision=r,o.answer={action:n.action,message:n.message?We(n.message,500):null,answers:n.answers||null},o.answeredAt=o.settledAt=this.now(),o.by=s;let i=this.forHook(o,!1);if(o.waiters.size){o.delivered=!0;for(let a of[...o.waiters])a.resolve(i)}return this.onChange(o,"answered"),o}delivery(e,n=3e3){let s=this.items.get(e);return s?s.delivered?Promise.resolve(!0):new Promise(o=>{let r=this.now(),i=()=>{if(s.delivered)return o(!0);if(this.now()-r>=n)return o(!1);setTimeout(i,50)};i()}):Promise.resolve(!1)}settle(e,n="answered_in_terminal"){let s=this.items.get(e);if(!s||s.state!=="waiting")return s||null;s.state="gone",s.reason=String(n||"gone").slice(0,40),s.settledAt=this.now();let o=this.forHook(s,!1);for(let r of[...s.waiters])r.resolve(o);return this.onChange(s,"settled"),s}settleSession(e,n,{key:s,tool:o}={}){let r=[];for(let i of this.items.values())i.state!=="waiting"||i.sessionId!==e||s&&i.key!==s||o&&i.tool!==o||r.push(this.settle(i.id,n));return r}sweep(){let e=this.now();for(let n of this.items.values())if(n.state==="waiting")if(e>=n.expiresAt){n.state="expired",n.reason="timeout",n.settledAt=e;let s=this.forHook(n,!1);for(let o of[...n.waiters])o.resolve(s);this.onChange(n,"expired")}else this.listening(n)||this.settle(n.id,"no_listener")}trim(){if(!(this.items.size<=this.keep))for(let e of[...this.items.values()].sort((n,s)=>n.createdAt-s.createdAt)){if(this.items.size<=this.keep)break;e.state!=="waiting"&&this.items.delete(e.id)}}publicAsk(e){return e?{id:e.id,sessionId:e.sessionId,kind:e.kind,tool:e.tool,describe:e.describe,questions:e.questions,canAllowSession:e.kind==="permission",state:e.state,reason:e.reason,createdAt:new Date(e.createdAt).toISOString(),expiresAt:new Date(e.expiresAt).toISOString(),answeredAt:e.answeredAt?new Date(e.answeredAt).toISOString():null,settledAt:e.settledAt?new Date(e.settledAt).toISOString():null,by:e.by?{where:e.by.where||null,via:e.by.via||null}:null,summary:e.answer?$r(e,e.answer):null,capture:[...e.captures].includes("mod")?"mod":"hooks"}:null}},Fa=class t{constructor({now:e=()=>Date.now(),ttlMs:n=600*1e3}={}){this.now=e,this.ttlMs=n,this.queues=new Map,this.waiters=new Map,this.lastPoll=new Map}static check(e){let n=String(e??"").replace(/\r\n?/g,`
|
|
98
|
+
`).trim(),s=o=>{let r=new Error(o);throw r.status=400,r.code="bad_reply",r};return n||s("the reply is empty"),n.startsWith("/")&&s("a reply can’t start with “/” (slash commands aren’t sent from the board)"),n.length>Rp&&s("the reply is over "+Rp+" characters"),n}listening(e){return(this.waiters.get(e)||new Set).size>0||this.now()-(this.lastPoll.get(e)||0)<Pp}put(e,n,s={}){let o={id:Fp().replace("rq_","rp_"),text:t.check(n),at:this.now(),by:s},r=this.waiters.get(e);if(r&&r.size)return r.values().next().value.resolve(o),{...o,delivered:!0};let i=this.queues.get(e)||[];return i.push(o),this.queues.set(e,i),{...o,delivered:!1}}pending(e){let n=(this.queues.get(e)||[]).filter(s=>this.now()-s.at<this.ttlMs);return this.queues.set(e,n),n}take(e,n=ja,s=null){this.lastPoll.set(e,this.now());let o=this.pending(e);if(o.length)return Promise.resolve(o.shift());let r=Math.max(0,Math.min(Number(n)||0,ja));return new Promise(i=>{let a=this.waiters.get(e)||new Set;this.waiters.set(e,a);let l={resolve:null,timer:null};l.resolve=c=>{clearTimeout(l.timer),a.delete(l),this.lastPoll.set(e,this.now()),i(c)},l.timer=setTimeout(()=>l.resolve(null),r),l.timer.unref&&l.timer.unref(),a.add(l),s&&s.addEventListener("abort",()=>{a.has(l)&&l.resolve(null)},{once:!0})})}}});function Bp(t,e={},n=Date.now()){let s=e.session_id;if(!s)return[];let o=new Date(n).toISOString(),r={v:1,sessionId:s,at:o,capture:"hooks"},i={};e.cwd&&(i.cwd=e.cwd),e.transcript_path&&(i.transcriptPath=e.transcript_path),e.session_title&&(i.sessionTitle=e.session_title),e.model&&(i.model=typeof e.model=="string"?e.model:e.model.id||void 0);let a=String(t||e.hook_event_name||"");switch(a){case"SessionStart":{let l={},c=e._checkpoints;return c&&typeof c=="object"&&(c.proc&&typeof c.proc=="object"&&(l.proc={...c.proc,source:"hook"}),c.termEnv&&typeof c.termEnv=="object"&&(l.termEnv=c.termEnv)),[{...r,type:"session.started",data:{...i,source:e.source||"startup",...l}}]}case"UserPromptSubmit":return[{...r,type:"activity",data:{...i,kind:"prompt",prompt:F(e.prompt||"",300)}}];case"PreToolUse":case"PostToolUse":case"PostToolUseFailure":return[{...r,type:"activity",data:{...i,kind:"tool",tool:e.tool_name||null,phase:a==="PreToolUse"?"pre":"post",inputKey:e.tool_name?Gr(e.tool_name,e.tool_input):null}}];case"Stop":return[{...r,type:"turn_completed",data:{...i,lastAssistantPreview:F(e.last_assistant_message||"",280),question:jc(e.last_assistant_message||""),...(()=>{let l=Zf(e.last_assistant_message||"");return l?{listHint:l}:{}})(),backgroundTasks:Array.isArray(e.background_tasks)?e.background_tasks.length:Number(e.background_tasks||0),sessionCrons:Array.isArray(e.session_crons)?e.session_crons.length:Number(e.session_crons||0)}}];case"Notification":return[{...r,type:"notification",data:{...i,notificationType:e.notification_type||null,message:F(e.message||"",200)}}];case"SessionEnd":return[{...r,type:"session.ended",data:{...i,reason:e.reason||"other"}}];default:return[]}}var Kp=V(()=>{ve();Yr()});import{execFile as NS}from"node:child_process";import Wr from"node:fs";import Ns from"node:path";function Gp(t,e){return new Promise(n=>{NS("git",["-C",t,...e],{timeout:1500},(s,o)=>n(s?null:String(o).trim()))})}async function $p(t){if(!t||!Wr.existsSync(t))return null;let e=await Gp(t,["rev-parse","--show-toplevel"]);if(!e)return null;let n=await Gp(t,["symbolic-ref","--short","-q","HEAD"]);return{root:e,branch:n||null,name:Ns.basename(e)}}function Yp(t,e){if(!t)return!1;if((e.ignoreRepos||[]).includes(Ns.basename(t))||(e.ignoreRepos||[]).includes(t))return!0;try{return Wr.existsSync(Ns.join(t,".leftoff-ignore"))||Wr.existsSync(Ns.join(t,".checkpoints-ignore"))}catch{return!1}}function Wp(t){if(!t)return null;let e=Ns.resolve(String(t));try{if(!Wr.statSync(e).isDirectory())return null}catch{return null}for(let n=0;n<64;n++){try{if(Wr.existsSync(Ns.join(e,".git")))return e}catch{}let s=Ns.dirname(e);if(s===e)return null;e=s}return null}var zp=V(()=>{});import ze from"node:fs";import MS from"node:os";import lt from"node:path";import Zp from"node:crypto";function Ya(t,{multiline:e=!1}={}){let n=String(t).replace(/\r\n?/g,`
|
|
99
|
+
`).replace(RS,"");return n=e?n.replace(/[\u0000-\u0008\u000B-\u001F\u007F]/g,""):n.replace(/[\u0000-\u001F\u007F]+/g," "),n.replace(/[ \t]+\n/g,`
|
|
100
|
+
`).replace(/\n{3,}/g,`
|
|
101
|
+
|
|
102
|
+
`).trim()}function em(t){if(typeof t!="string"||!t||t.length>de.url)return null;let e;try{e=new URL(t)}catch{return null}return e.protocol!=="https:"||!e.hostname||e.username||e.password?null:e.href}function Ru(t){if(typeof t!="string"||!t||t.length>de.url||/[\s\u0000-\u001F\u007F<>"'`\\\u202A-\u202E\u2066-\u2069\u200E\u200F]/.test(t))return null;let e=/^([a-z][a-z0-9+.-]*:)(.*)$/i.exec(t);if(!e)return null;let n=e[1].toLowerCase();if(!jS.includes(n))return null;if(n==="https:")return em(t);let s=e[2].replace(/^\/\//,"");if(!s||s.startsWith("?")||s.startsWith("/"))return null;try{new URL(t)}catch{return null}return n+e[2]}function Fu(t){let e;try{e=new URL(String(t||"").trim())}catch{return{ok:!1,message:"not a URL"}}return e.username||e.password?{ok:!1,message:"no user:password@ in the URL; use --key"}:e.searchParams.has("key")?{ok:!1,message:"take key=… out of the URL and pass it with --key (the URL syncs; the key stays on this Mac)"}:e.protocol==="https:"?{ok:!0,url:e.href}:e.protocol==="http:"&&PS.has(e.hostname)?{ok:!0,url:e.href,loopback:!0}:{ok:!1,message:"the feed must be https (plain http only on localhost)"}}function bn(t,e,n,s){for(let o of Object.keys(t))!e.includes(o)&&!o.startsWith("x-")&&s.add(n,'unknown field "'+o.slice(0,40)+'"')}function Ht(t){return t&&typeof t=="object"&&!Array.isArray(t)}function kt(t,e,n,s,{required:o=!1,max:r,multiline:i=!1}={}){let a=t[e];if(a==null||a==="")return o&&s.add(n+"."+e,"required"),null;if(typeof a!="string")return s.add(n+"."+e,"must be a string"),null;let l=Ya(a,{multiline:i});return l?l.length>r?(s.add(n+"."+e,"longer than "+r+" characters"),null):l:(o&&s.add(n+"."+e,"required"),null)}function Du(t,e,n,s){let o=t[e];if(typeof o!="string"&&typeof o!="number")return s.add(n+"."+e,"required"),null;let r=String(o);return r.length>de.id||!LS.test(r)?(s.add(n+"."+e,"letters, digits and ._:@#/- only, up to "+de.id),null):r}function zr(t,e,n,s,{required:o=!1}={}){let r=t[e];return r==null||r===""?(o&&s.add(n+"."+e,"required"),null):typeof r!="string"||!DS.test(r)||isNaN(Date.parse(r))?(s.add(n+"."+e,"must be an ISO 8601 time with a zone, like 2026-10-10T15:00:00Z"),null):new Date(Date.parse(r)).toISOString()}function vo(t,e,n,s,o){let r=t[e];return r==null?[]:Array.isArray(r)?r.length>o?(s.add(n+"."+e,"more than "+o+" entries"),[]):r:(s.add(n+"."+e,"must be a list"),[])}function tm(t,e,n,s,{required:o=!1}={}){let r=t[e];if(r==null||r==="")return o&&s.add(n+"."+e,"required"),null;let i=em(r);return i||s.add(n+"."+e,"must be an https:// link"),i}function nm(t,e,n){if(!Ht(t))return n.add(e,"must be an object"),null;bn(t,["id","title","project","status","startedAt","updatedAt","checkpoints","needsYou","lists","links"],e,n);let s={id:Du(t,"id",e,n),title:kt(t,"title",e,n,{required:!0,max:de.title}),project:kt(t,"project",e,n,{max:de.project}),status:null,startedAt:zr(t,"startedAt",e,n,{required:!0}),updatedAt:zr(t,"updatedAt",e,n,{required:!0}),checkpoints:[],needsYou:[],lists:[],links:[]};Jp.includes(t.status)?s.status=t.status:n.add(e+".status","must be one of "+Jp.join(", "));let o=new Set;vo(t,"checkpoints",e,n,de.checkpoints).forEach((a,l)=>{let c=e+".checkpoints["+l+"]";if(!Ht(a))return n.add(c,"must be an object");bn(a,["id","at","summary","done","next"],c,n);let u={id:Du(a,"id",c,n),at:zr(a,"at",c,n,{required:!0}),summary:kt(a,"summary",c,n,{required:!0,max:de.summary,multiline:!0}),done:kt(a,"done",c,n,{max:de.summary,multiline:!0}),next:kt(a,"next",c,n,{max:de.text,multiline:!0})};u.id&&o.has(u.id)&&n.add(c+".id",'duplicate "'+u.id+'"'),o.add(u.id),s.checkpoints.push(u)});let r=new Set;vo(t,"needsYou",e,n,de.needsYou).forEach((a,l)=>{let c=e+".needsYou["+l+"]";if(!Ht(a))return n.add(c,"must be an object");bn(a,["id","text","since"],c,n);let u={id:Du(a,"id",c,n),text:kt(a,"text",c,n,{required:!0,max:de.text,multiline:!0}),since:zr(a,"since",c,n)};u.id&&r.has(u.id)&&n.add(c+".id",'duplicate "'+u.id+'"'),r.add(u.id),s.needsYou.push(u)});let i=new Set;return vo(t,"lists",e,n,de.lists).forEach((a,l)=>{let c=e+".lists["+l+"]";if(!Ht(a))return n.add(c,"must be an object");bn(a,["name","items"],c,n);let u=kt(a,"name",c,n,{required:!0,max:de.title});u&&i.has(u.toLowerCase())&&n.add(c+".name",'two lists named "'+u+'" in one session'),u&&i.add(u.toLowerCase());let d=[],f=new Set;Array.isArray(a.items)||n.add(c+".items","required (a list)"),vo(a,"items",c,n,de.items).forEach((p,h)=>{let m=c+".items["+h+"]";if(!Ht(p))return n.add(m,"must be an object");bn(p,["n","text","done"],m,n),!Number.isInteger(p.n)||p.n<1||p.n>de.n?n.add(m+".n","must be a whole number from 1 (stable: the same item keeps its n)"):f.has(p.n)&&n.add(m+".n","duplicate "+p.n),f.add(p.n),p.done!=null&&typeof p.done!="boolean"&&n.add(m+".done","must be true or false"),d.push({n:p.n,text:kt(p,"text",m,n,{required:!0,max:de.text}),done:p.done===!0})}),s.lists.push({name:u,items:d})}),vo(t,"links",e,n,de.links).forEach((a,l)=>{let c=e+".links["+l+"]";if(!Ht(a))return n.add(c,"must be an object");bn(a,["label","url"],c,n),s.links.push({label:kt(a,"label",c,n,{required:!0,max:de.label}),url:tm(a,"url",c,n,{required:!0})})}),s}function FS(t){let e=new Wa;if(!Ht(t))return{ok:!1,errors:["feed: must be a JSON object"],feed:null};bn(t,["version","agent","generatedAt","sessions"],"feed",e),t.version!==Ka&&e.add("feed.version",'must be "'+Ka+'"');let n=null;if(!Ht(t.agent))e.add("feed.agent","required ({ name, icon, url })");else{bn(t.agent,["name","icon","url","reply","replyHint"],"feed.agent",e),n={name:kt(t.agent,"name","feed.agent",e,{required:!0,max:de.agentName}),icon:kt(t.agent,"icon","feed.agent",e,{max:de.icon}),url:tm(t.agent,"url","feed.agent",e)};let i=t.agent.reply;if(i!=null)if(!Ht(i))e.add("feed.agent.reply","must be an object ({ label, url })");else{bn(i,["label","url"],"feed.agent.reply",e);let l=kt(i,"label","feed.agent.reply",e,{required:!0,max:de.replyLabel}),c=null;i.url==null||i.url===""?e.add("feed.agent.reply.url","required"):(c=Ru(i.url))||e.add("feed.agent.reply.url","must be an https:, sms:, imessage:, mailto: or tel: link"),l&&c&&(n.reply={label:l,url:c})}let a=kt(t.agent,"replyHint","feed.agent",e,{max:de.replyHint});a&&(n.replyHint=a)}let s=zr(t,"generatedAt","feed",e,{required:!0});Array.isArray(t.sessions)||e.add("feed.sessions","required (a list, may be empty)");let o=[],r=new Set;return vo(t,"sessions","feed",e,de.sessions).forEach((i,a)=>{let l=nm(i,"sessions["+a+"]",e);l&&l.id&&(r.has(l.id)&&e.add("sessions["+a+"].id",'duplicate "'+l.id+'"'),r.add(l.id)),l&&o.push(l)}),e.list.length?{ok:!1,errors:e.list,feed:null}:{ok:!0,errors:[],feed:{version:Ka,agent:n,generatedAt:s,sessions:o}}}function za(t,{maxBytes:e=de.bytes}={}){let n=Buffer.isBuffer(t)?t:Buffer.from(String(t??""),"utf8");if(n.length>e)return{ok:!1,code:"too_large",errors:["feed is "+n.length+" bytes; the limit is "+e]};let s;try{s=JSON.parse(n.toString("utf8").replace(/^\uFEFF/,""))}catch(r){return{ok:!1,code:"not_json",errors:["not JSON: "+String(r.message).slice(0,120)]}}let o=FS(s);return o.ok?{ok:!0,feed:o.feed,bytes:n.length}:{ok:!1,code:"invalid",errors:o.errors}}async function US(t,e){let n=Number(t.headers.get("content-length"));if(n&&n>e){try{await t.body?.cancel()}catch{}return{tooLarge:!0,bytes:n}}if(!t.body)return{buf:Buffer.from(await t.arrayBuffer())};let s=[],o=0;for await(let r of t.body){if(o+=r.length,o>e){try{await t.body.cancel()}catch{}return{tooLarge:!0,bytes:o}}s.push(Buffer.from(r))}return{buf:Buffer.concat(s)}}function HS(t,e){let n=new URL(t);return n.searchParams.set("key",e),n.href}async function BS({url:t,key:e=null,etag:n=null,auth:s="bearer",fetchImpl:o=globalThis.fetch,timeoutMs:r=15e3,maxBytes:i=de.bytes,userAgent:a="Leftoff"}={}){let l=e&&s==="query"?["query"]:e?["bearer","query"]:["none"],c=null;for(let u of l){let d=await KS({url:t,key:e,etag:n,mode:u,fetchImpl:o,timeoutMs:r,maxBytes:i,userAgent:a});if(d.result==="error"&&(d.httpStatus===401||d.httpStatus===403)&&u==="bearer"){c=d;continue}return{...d,auth:u==="none"?null:u}}return{...c,auth:"bearer"}}async function KS({url:t,key:e,etag:n,mode:s,fetchImpl:o,timeoutMs:r,maxBytes:i,userAgent:a}){let l=new URL(t).origin,c=s==="query"?HS(t,e):t,u={accept:"application/json","user-agent":a};s==="bearer"&&(u.authorization="Bearer "+e),n&&(u["if-none-match"]=n);let d=new AbortController,f=setTimeout(()=>d.abort(),r);try{for(let p=0;p<4;p++){let h;try{h=await o(c,{headers:u,redirect:"manual",signal:d.signal})}catch(w){let k=d.signal.aborted?"no answer in "+Math.round(r/1e3)+" s":w&&w.cause&&(w.cause.code||w.cause.message)||w&&w.message||"network error";return{result:"error",error:{code:"unreachable",message:Ua("can’t reach the feed: "+k,e)}}}if([301,302,303,307,308].includes(h.status)){let w=h.headers.get("location"),k=null;try{k=w&&new URL(w,c)}catch{}if(!k||k.origin!==l)return{result:"error",httpStatus:h.status,error:{code:"redirect",message:"the feed redirects to another site; Leftoff won’t send the key there. Use the final URL."}};s==="query"&&!k.searchParams.has("key")&&k.searchParams.set("key",e),c=k.href;continue}if(h.status===304)return{result:"unchanged",etag:n,httpStatus:304};if(h.status===401||h.status===403)return{result:"error",httpStatus:h.status,error:{code:"unauthorized",message:"the feed refused the key (HTTP "+h.status+")"}};if(h.status===404)return{result:"error",httpStatus:404,error:{code:"not_found",message:"no feed at that URL (HTTP 404)"}};if(h.status<200||h.status>=300)return{result:"error",httpStatus:h.status,error:{code:"http",message:"the feed answered HTTP "+h.status}};let m=await US(h,i);if(m.tooLarge)return{result:"error",httpStatus:h.status,error:{code:"too_large",message:"the feed is bigger than "+Math.round(i/1024)+" KB"}};let y=za(m.buf,{maxBytes:i});return y.ok?{result:"ok",feed:y.feed,bytes:y.bytes,etag:h.headers.get("etag")||null,httpStatus:h.status}:{result:"error",httpStatus:h.status,error:{code:y.code,message:y.code==="invalid"?"the feed doesn’t match "+Ka+" ("+y.errors.length+" problem"+(y.errors.length===1?"":"s")+")":Ua(y.errors[0],e),details:y.errors.slice(0,10).map(w=>Ua(w,e))}}}return{result:"error",error:{code:"redirect",message:"too many redirects"}}}catch(p){return{result:"error",error:{code:"unreachable",message:Ua("can’t read the feed: "+(p&&p.message||p),e)}}}finally{clearTimeout(f)}}function sm(t=process.env){return lt.join(t.XDG_CONFIG_HOME||lt.join(t.HOME||MS.homedir(),".config"),"checkpoints","agents")}function Uu(t,e=process.env){return $a(t),lt.join(sm(e),t+".key")}function Ga(t,e=process.env){try{return ze.readFileSync(Uu(t,e),"utf8").trim()||null}catch{return null}}function GS(t,e,n=process.env){let s=sm(n);ze.mkdirSync(s,{recursive:!0,mode:448});try{ze.chmodSync(s,448)}catch{}let o=Uu(t,n),r=o+"."+process.pid+".tmp";return ze.writeFileSync(r,String(e).trim()+`
|
|
103
|
+
`,{mode:384}),ze.chmodSync(r,384),ze.renameSync(r,o),o}function $S(t,e=process.env){try{return ze.unlinkSync(Uu(t,e)),!0}catch{return!1}}function $a(t){if(typeof t!="string"||!Qp.test(t)){let e=new Error('agent name: lowercase letters, digits and -, up to 31 (e.g. "tab")');throw e.code="bad_name",e}return t}function bo(t,...e){let n=lt.resolve(t),s=lt.resolve(n,...e);if(s!==n&&!s.startsWith(n+lt.sep))throw new Error("refusing to write outside "+n);return s}function YS(t){return(String(t).toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,48)||"session")+"-"+Zp.createHash("sha256").update(String(t)).digest("hex").slice(0,8)+".md"}function WS(t){return(String(t).toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)||"list")+"-"+Zp.createHash("sha256").update(String(t)).digest("hex").slice(0,6)}function zS(t){let e=t.session,n=["---","kind: leftoff-agent-session","schema: 1","agent: "+Ha(t.agent),"id: "+Ha(e.id),"title: "+Ha(e.title)];if(e.project&&n.push("project: "+Ha(e.project)),n.push("status: "+e.status,"started_at: "+e.startedAt,"updated_at: "+e.updatedAt,"in_feed: "+(t.inFeed!==!1),"---",""),n.push("# "+e.title.replace(/\n/g," "),""),n.push("From the **"+(t.agentName||t.agent)+"** feed"+(e.project?" · project "+e.project.replace(/\n/g," "):"")+" · "+e.status+(t.inFeed===!1?" · no longer in the feed":"")+". Read-only: Leftoff rewrites this file on each pull.",""),e.needsYou.length){n.push("## Needs you","");for(let o of e.needsYou)n.push(Ba(o.text+(o.since?" (since "+o.since+")":"")),"")}if(e.lists.length){n.push("## Lists","");for(let o of e.lists){n.push("### "+o.name.replace(/\n/g," "),"");for(let r of o.items)n.push("- ["+(r.done?"x":" ")+"] "+r.n+". "+r.text.replace(/\n/g," "));n.push("")}}if(e.links.length){n.push("## Links","");for(let o of e.links)n.push("- "+o.label.replace(/\n/g," ")+": <"+o.url+">");n.push("")}if(e.checkpoints.length){n.push("## Checkpoints","");for(let o of e.checkpoints.slice().reverse())n.push("### "+o.at,"",Ba(o.summary)),o.done&&n.push(">",Ba("Done: "+o.done)),o.next&&n.push(">",Ba("Next: "+o.next)),n.push("")}let s={schema:1,agent:t.agent,agentName:t.agentName||null,inFeed:t.inFeed!==!1,firstSeenAt:t.firstSeenAt||null,goneAt:t.goneAt||null,pulledAt:t.pulledAt||null,session:e};return n.push("<!-- Leftoff reads the block below. Edits above this line are overwritten on the next pull. -->",ju,JSON.stringify(s,null,1),"```",""),n.join(`
|
|
104
|
+
`)}function JS(t){let e=String(t||""),n=e.lastIndexOf(`
|
|
105
|
+
`+ju+`
|
|
106
|
+
`);if(n<0)return null;let s=e.slice(n+ju.length+1),o=s.indexOf("\n```");if(o<0)return null;let r;try{r=JSON.parse(s.slice(0,o))}catch{return null}if(!Ht(r)||!Ht(r.session))return null;let i=new Wa,a=nm(r.session,"session",i);return i.list.length||!a?null:{agent:typeof r.agent=="string"?r.agent:null,agentName:typeof r.agentName=="string"?Ya(r.agentName).slice(0,de.agentName):null,inFeed:r.inFeed!==!1,firstSeenAt:r.firstSeenAt||null,goneAt:r.goneAt||null,pulledAt:r.pulledAt||null,session:a}}function qS(t,e){if(!t)return{...e,checkpoints:e.checkpoints.slice().sort((o,r)=>o.at.localeCompare(r.at)).slice(-de.stored)};let n=new Map(t.checkpoints.map(o=>[o.id,o]));for(let o of e.checkpoints)n.set(o.id,o);let s=[...n.values()].sort((o,r)=>o.at.localeCompare(r.at)).slice(-de.stored);return{...e,checkpoints:s}}function Vp(t={}){return{ok:t.ok===!0?!0:t.ok===!1?!1:null,lastAttemptAt:t.lastAttemptAt||null,lastSuccessAt:t.lastSuccessAt||null,lastChangeAt:t.lastChangeAt||null,by:t.by||null,sessions:t.sessions==null?null:t.sessions,bytes:t.bytes||null,generatedAt:t.generatedAt||null,auth:t.auth||null,error:t.lastError?{at:t.lastError.at,code:t.lastError.code,message:t.lastError.message,details:t.lastError.details||null}:null}}function VS(t){let e={reply:null,replyHint:null};if(!t||typeof t!="object")return e;let n=t.reply;if(n&&typeof n=="object"&&typeof n.label=="string"&&Ru(n.url)){let s=Ya(n.label).slice(0,de.replyLabel);s&&(e.reply={label:s,url:Ru(n.url)})}return typeof t.replyHint=="string"&&(e.replyHint=Ya(t.replyHint).slice(0,de.replyHint)||null),e}function Jr(t){let e=t.session.status;return!t.inFeed||e==="done"?"ended":e==="waiting"?"waiting":"active"}function Bu(t,e){return"agents/"+t.name+"/"+(e.project?e.project.toLowerCase().replace(/[\\/]+/g,"-"):t.name)}function Pu(t,e,n){return{id:n.id,sessionId:Ja(t.name,e.session.id),createdAt:n.at,doing:n.done||n.summary,stoppedAt:n.summary,next:n.next||"",machine:t.display,trigger:"agent",summary:n.summary,done:n.done||null}}function Wn(t,e,n=Date.now()){if(!e.inFeed||e.session.status==="done"||!e.session.needsYou.length)return null;let s=e.session.needsYou.map(r=>({id:r.id,text:r.text,since:r.since||e.session.updatedAt})),o=s.reduce((r,i)=>Yn(i.since)<Yn(r.since)?i:r,s[0]);return{kind:"agent",blocking:!1,agent:t.display,url:t.url||null,reply:t.reply||null,replyHint:t.replyHint||null,items:s,message:s[0].text,since:o.since,waitSec:Math.max(0,Math.round((n-Yn(o.since))/1e3))}}function qa(t,e,n=Date.now(),{full:s=!1}={}){let o=e.session,r=Jr(e),i=o.checkpoints,a=i[i.length-1]||null,l=Bu(t,o),c={id:Ja(t.name,o.id),feedId:o.id,title:o.title,titleSource:"agent",agent:{...Hu(t),puller:t.puller,pullsHere:t.pullsHere,ok:t.status.ok,lastSuccessAt:t.status.lastSuccessAt},machine:t.display,isLocal:!1,cwd:null,repo:{name:o.project||t.display,root:l},repoName:o.project||t.display,repoKey:l,repoLabel:o.project||t.display,project:o.project||null,feedStatus:o.status,inFeed:e.inFeed,goneAt:e.goneAt||null,capture:"agent",status:r,startedAt:o.startedAt,lastActivityAt:o.updatedAt,endedAt:r==="ended"?e.goneAt||o.updatedAt:null,turns:0,turnsSinceCheckpoint:0,idleSec:Math.max(0,Math.round((n-Yn(o.updatedAt))/1e3)),checkpointCount:i.length,latestCheckpoint:a?Pu(t,e,a):null,resumeCommand:null,jump:null,live:null,peer:null,pinned:!1,archived:!1,ignored:!1,empty:!1,ghost:!1,needsYou:Wn(t,e,n),lists:o.lists.map(u=>{let d=u.items.filter(f=>f.done).length;return{ref:om(t.name,o.id,u.name),name:u.name,done:d,total:u.items.length,progress:d+"/"+u.items.length,readOnly:!0}}),links:o.links,file:e.file||null};return s&&(c.checkpoints=i.slice().reverse().map(u=>({...Pu(t,e,u)}))),c}function om(t,e,n){return"~"+t+"/"+WS(e+"\0"+n)}function Va(t){let e=[];for(let n of t.agents)for(let s of n.records)if(s.inFeed)for(let o of s.session.lists){let r=o.items.filter(i=>i.done).length;e.push({ref:om(n.name,s.session.id,o.name),name:o.name,repo:s.session.project||n.display,repoKey:Bu(n,s.session),repoDir:null,items:o.items.map(i=>({n:i.n,text:i.text,done:i.done})),done:r,total:o.items.length,open:o.items.length-r,progress:r+"/"+o.items.length,archived:s.session.status==="done",readOnly:!0,agent:Hu(n),sourceSession:Ja(n.name,s.session.id),sourceTitle:s.session.title,sourceMachine:n.display,updated:s.session.updatedAt,created:s.firstSeenAt||s.session.startedAt})}return e}function Za(t){let e=[];for(let n of t.agents)for(let s of n.records){let o=s.session,r=Jr(s);e.push({session:{id:Ja(n.name,o.id),title:o.title,machine:n.display,capture:"agent",repo:{name:o.project||n.display,root:Bu(n,o)},startedAt:o.startedAt,lastActivityAt:o.updatedAt,endedAt:r==="ended"?s.goneAt||o.updatedAt:null,turns:o.checkpoints.length,agent:Hu(n),agentStatus:r,agentNeeds:Wn(n,s)},checkpoints:o.checkpoints.map(i=>Pu(n,s,i))})}return e}function Ku(t,e,n,{heading:s="##"}={}){let o=[];for(let a of t.agents)for(let l of a.records){let c=l.session,u=Wn(a,l);!(Yn(c.updatedAt)>=e&&Yn(c.updatedAt)<=n)&&!u||o.push({a,rec:l,n:u})}if(!o.length)return[];o.sort((a,l)=>!!l.n-!!a.n||Yn(l.rec.session.updatedAt)-Yn(a.rec.session.updatedAt));let r=(a,l=220)=>{let c=String(a||"").replace(/\s*\n\s*/g," ");return c.length>l?c.slice(0,l-1)+"…":c},i=[s+" From your agents ("+o.length+")"];for(let{a,rec:l,n:c}of o.slice(0,15)){let u=l.session,d=u.checkpoints[u.checkpoints.length-1];i.push("- **"+r(u.title,120)+"** ("+(u.project?r(u.project,60)+" · ":"")+a.display+") — "+(c?"needs you":Jr(l)==="ended"?"done":u.status)),c&&i.push(" Needs you: "+c.items.slice(0,3).map(f=>r(f.text,140)).join("; ")+(a.reply?" ("+a.reply.label+": "+a.reply.url+")":a.replyHint?" ("+r(a.replyHint,120)+")":"")),d&&i.push(" Latest: "+r(d.summary)+(d.next?" Next: "+r(d.next,160):""))}return i}var Ka,Xp,de,Jp,Qp,LS,DS,RS,jS,PS,Wa,Ua,Ha,Ba,ju,qp,bt,Yn,Hu,Ja,xo,zn=V(()=>{gn();Ka="leftoff-agent-feed/1",Xp=12e4,de={bytes:1024*1024,sessions:200,checkpoints:200,needsYou:50,lists:30,items:300,links:20,id:120,agentName:60,icon:16,title:200,project:120,summary:4e3,text:2e3,label:200,url:2048,replyLabel:40,replyHint:120,n:1e5,stored:500},Jp=["active","waiting","done"],Qp=/^[a-z0-9][a-z0-9-]{0,30}$/,LS=/^[A-Za-z0-9][A-Za-z0-9._:@#\/-]*$/,DS=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}:?\d{2})$/,RS=/[\u202A-\u202E\u2066-\u2069\u200E\u200F]/g;jS=["https:","sms:","imessage:","mailto:","tel:"];PS=new Set(["127.0.0.1","localhost","[::1]","::1"]);Wa=class{constructor(){this.list=[]}add(e,n){this.list.length<50&&this.list.push(e+": "+n)}};Ua=(t,e)=>e?String(t).split(e).join("…"):String(t);Ha=t=>t==null?"":JSON.stringify(String(t)),Ba=t=>String(t).split(`
|
|
107
|
+
`).map(e=>"> "+e).join(`
|
|
108
|
+
`),ju="```json leftoff-agent-session";qp=t=>{try{return JSON.parse(ze.readFileSync(t,"utf8"))}catch{return null}},bt=class{constructor(e,n,{now:s=()=>Date.now(),env:o=process.env,log:r=()=>{},fetchImpl:i=null,userAgent:a="Leftoff"}={}){this.root=e,this.machine=n,this.now=s,this.env=o,this.log=r,this.fetchImpl=i,this.userAgent=a,this.cache=null,this.inflight=new Map}get dir(){return lt.join(this.root,"agents")}agentDir(e){return bo(this.dir,$a(e))}sources(){let e=[];try{e=ze.readdirSync(this.dir,{withFileTypes:!0}).filter(s=>s.isDirectory()&&Qp.test(s.name)).map(s=>s.name).sort()}catch{}let n=[];for(let s of e){let o=qp(lt.join(this.dir,s,"source.json"));o&&o.name===s&&typeof o.url=="string"&&n.push(o)}return n}source(e){return $a(e),this.sources().find(n=>n.name===e)||null}status(e){return qp(lt.join(this.agentDir(e),"status.json"))||{}}writeStatus(e,n){Kn(bo(this.agentDir(e),"status.json"),JSON.stringify(n,null,2)+`
|
|
109
|
+
`),this.cache=null}add({name:e,url:n,key:s}){$a(e);let o=Fu(n);if(!o.ok){let l=new Error(o.message);throw l.code="bad_url",l}if(s!=null&&typeof s!="string")throw Object.assign(new Error("key must be text"),{code:"bad_key"});let r=s?String(s).trim():"";if(r&&(r.length>4096||/[\s\u0000-\u001F]/.test(r)))throw Object.assign(new Error("the key has spaces or control characters in it"),{code:"bad_key"});let i=this.source(e),a={name:e,url:o.url,addedAt:i?i.addedAt:new Date(this.now()).toISOString(),addedOn:i?i.addedOn:this.machine,puller:this.machine,updatedAt:new Date(this.now()).toISOString()};if(Kn(bo(this.agentDir(e),"source.json"),JSON.stringify(a,null,2)+`
|
|
110
|
+
`),r&&GS(e,r,this.env),i&&i.url!==a.url){let l=this.status(e);delete l.etag,delete l.auth,this.writeStatus(e,l)}return this.cache=null,{source:a,replaced:!!i,previousPuller:i&&i.puller!==this.machine?i.puller:null,hasKey:!!Ga(e,this.env)}}remove(e,{purge:n=!1}={}){let s=this.agentDir(e);if(!ze.existsSync(s)){let i=new Error("no agent feed named "+e);throw i.code="not_found",i}let o=null;if(n)ze.rmSync(s,{recursive:!0,force:!0});else{let i=new Date(this.now()).toISOString().replace(/[:.]/g,"-"),a=bo(lt.join(this.dir,".removed"),e+"-"+i);ze.mkdirSync(lt.dirname(a),{recursive:!0}),ze.renameSync(s,a),o=lt.relative(this.root,a)}let r=$S(e,this.env);return this.cache=null,{removed:e,movedTo:o,purged:!!n,keyDeleted:r}}hasKey(e){return!!Ga(e,this.env)}pullsHere(e){return e&&e.puller===this.machine&&this.hasKey(e.name)}loadRecords(e){let n=lt.join(this.agentDir(e),"sessions"),s=[],o=[];try{o=ze.readdirSync(n).filter(r=>r.endsWith(".md")&&!r.startsWith("."))}catch{}for(let r of o){let i=null;try{i=JS(ze.readFileSync(lt.join(n,r),"utf8"))}catch{}i&&(i.file="agents/"+e+"/sessions/"+r,s.push(i))}return s}apply(e,n){let s=this.agentDir(e),o=bo(s,"sessions"),r=new Date(this.now()).toISOString(),i=new Map(this.loadRecords(e).map(p=>[p.session.id,p])),a=0,l=0,c=0,u=n.agent&&n.agent.name||e,d=p=>{let h=bo(o,YS(p.session.id)),m=zS(p),y=null;try{y=ze.readFileSync(h,"utf8")}catch{}y!==m&&(Kn(h,m),a++)},f=new Set;for(let p of n.sessions){f.add(p.id);let h=i.get(p.id);h||l++;let m=qS(h&&h.session,p),y=h&&h.inFeed&&JSON.stringify(h.session)===JSON.stringify(m)&&h.agentName===u;d({agent:e,agentName:u,inFeed:!0,firstSeenAt:h&&h.firstSeenAt||r,goneAt:null,pulledAt:y?h.pulledAt:r,session:m})}for(let[p,h]of i)f.has(p)||!h.inFeed||(c++,d({...h,inFeed:!1,goneAt:r,session:{...h.session,needsYou:[]}}));return this.cache=null,{sessions:n.sessions.length,written:a,added:l,gone:c}}pull(e,{force:n=!1}={}){if(this.inflight.has(e))return this.inflight.get(e);let s=this.pullNow(e,{force:n}).finally(()=>this.inflight.delete(e));return this.inflight.set(e,s),s}async pullNow(e,{force:n=!1}={}){let s=this.source(e);if(!s){let u=new Error("no agent feed named "+e);throw u.code="not_found",u}if(s.puller!==this.machine){let u=new Error(e+" is pulled by "+s.puller+", not this Mac. Add it here with its key to pull from this Mac instead.");throw u.code="not_puller",u.puller=s.puller,u}let o=Ga(e,this.env),r=this.status(e),i=new Date(this.now()).toISOString(),a=await BS({url:s.url,key:o,etag:n?null:r.etag,auth:r.auth||"bearer",fetchImpl:this.fetchImpl||globalThis.fetch,userAgent:this.userAgent}),l={...r,lastAttemptAt:i,by:this.machine},c=null;return a.result==="ok"?(c=this.apply(e,a.feed),Object.assign(l,{lastSuccessAt:i,lastChangeAt:i,lastError:null,etag:a.etag||null,auth:a.auth,agent:a.feed.agent,generatedAt:a.feed.generatedAt,sessions:a.feed.sessions.length,bytes:a.bytes,ok:!0}),this.log("agents: pulled "+e+" ("+a.feed.sessions.length+" session"+(a.feed.sessions.length===1?"":"s")+", "+c.written+" file"+(c.written===1?"":"s")+" written)")):a.result==="unchanged"?Object.assign(l,{lastSuccessAt:i,lastError:null,ok:!0,unchangedAt:i}):(l.ok=!1,l.lastError={at:i,code:a.error.code,message:a.error.message,details:a.error.details||null,httpStatus:a.httpStatus||null},this.log("agents: "+e+": "+a.error.message)),this.writeStatus(e,l),{name:e,result:a.result,applied:c,status:Vp(l),error:l.lastError||null}}view({fresh:e=!1}={}){if(!e&&this.cache&&this.now()-this.cache.at<3e3)return this.cache.v;let n=[];for(let o of this.sources()){let r=this.status(o.name),i=this.loadRecords(o.name),a=r.agent&&r.agent.name||i[0]&&i[0].agentName||o.name;n.push({name:o.name,display:a,icon:r.agent&&r.agent.icon||null,url:r.agent&&r.agent.url||null,...VS(r.agent),source:o,puller:o.puller,pullsHere:this.pullsHere(o),hasKey:this.hasKey(o.name),status:Vp(r),records:i})}let s={agents:n};return this.cache={at:this.now(),v:s},s}};Yn=t=>{let e=Date.parse(t||"");return isNaN(e)?0:e};Hu=t=>({name:t.name,display:t.display,icon:t.icon||null,url:t.url||null,reply:t.reply||null,replyHint:t.replyHint||null}),Ja=(t,e)=>"agent:"+t+":"+e,xo=t=>typeof t=="string"&&t.startsWith("agent:")});function ZS(t=Date.now()){let e=new Date(t);return e.getFullYear()+"-"+String(e.getMonth()+1).padStart(2,"0")+"-"+String(e.getDate()).padStart(2,"0")}function XS(t){let e=Math.round(t/6e4);if(e<60)return e+"m";let n=Math.round(e/60);return n<48?n+"h":Math.round(n/24)+"d"}function rm(t){return new Date(t).toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"})}function Gu(t,e,n=ye,s,o){let r={needsYou:[],active:[],dormant:[],finished:[]};for(let{session:a,checkpoints:l}of t){if(a.archived||ht(a,l.length))continue;let c=l.slice().sort((h,m)=>R(m.createdAt)-R(h.createdAt))[0]||null,u=o&&a.machine&&a.machine!==o,d=Rt(a,e,u?{...n,staleAfterSec:Math.max(n.staleAfterSec,900)}:n),f=Math.max(R(a.lastActivityAt)||0,R(a.endedAt)||0,c?R(c.createdAt):0,R(a.startedAt)||0);if(d==="ended"&&f<s||d!=="ended"&&f<s&&!c)continue;let p={session:a,latest:c,status:d};d==="needs_input"||c&&/^YOU:/i.test(c.next||"")&&d!=="ended"?r.needsYou.push(p):d==="ended"?r.finished.push(p):d==="dormant"||d==="stale"?r.dormant.push(p):r.active.push(p)}let i=(a,l)=>(R(l.session.lastActivityAt)||0)-(R(a.session.lastActivityAt)||0);for(let a of Object.values(r))a.sort(i);return r}function ek(t,e,n,s,o){let r=t.session,i=t.latest,a=r.repo&&r.repo.branch,l=(o&&o.get(Ao(r))||fe(r))+(a?" · "+a:"")+(s&&r.machine?" · "+r.machine:""),c;n==="needsYou"?c=r.needsInput?"waiting on "+String(r.lastNotification||"input").replace(/_/g," ")+" since "+rm(r.lastEventAt||r.lastActivityAt):"blocked on you":n==="finished"?c="closed "+rm(r.endedAt):n==="active"?c=t.status:c="idle "+XS(e-(R(r.lastActivityAt)||e));let u=["- **"+(r.title||"Untitled session")+"** ("+l+") — "+c];return i?n==="dormant"||n==="finished"?u.push(" Stopped at: "+F(i.stoppedAt,220)+" Next: "+F(i.next,200)):u.push(" Next: "+F(i.next,220)):r.lastPreview&&u.push(" Last reply: "+F(r.lastPreview,200)),u}function tk(t){let e=t.needsYou[0]||t.dormant[0]||t.active[0];if(!e)return"Nothing open. Clean slate.";let n=e.session.title||"your most recent session",s=t.needsYou[0]?"it is blocked on you":t.dormant[0]?"it went quiet mid-task":"it is still running",o="Start with “"+n+"”: "+s+(e.latest?" (next: "+F(e.latest.next.replace(/^YOU:\s*/i,""),120).replace(/[.!?…]+$/,"")+").":"."),r=t.needsYou.length+t.dormant.length-1;return r>0&&(o+=" "+r+" more session"+(r===1?"":"s")+" waiting after that."),o}function nk(t,e,n){let s=[],o=0,r=(t||[]).filter(a=>!a.archived),i=[];for(let a of r){let l=(a.items||[]).filter(c=>c.done&&c.at&&R(c.at)>=e&&R(c.at)<=n);!l.length&&!(a.total>a.done)||(o+=l.length,i.push({l:a,got:l}))}if(!i.length)return s;i.sort((a,l)=>l.got.length-a.got.length),s.push("## Lists"+(o?" ("+o+" item"+(o===1?"":"s")+" done)":""));for(let{l:a,got:l}of i.slice(0,12)){let c=(a.items||[]).filter(u=>!u.done);s.push("- **"+F(a.name,80)+"** ("+(a.repo||"no repo")+"): "+a.done+"/"+a.total+" done"+(l.length?". Done: "+l.slice(0,4).map(u=>F(u.text,80)).join("; ")+(l.length>4?" +"+(l.length-4):""):"")+(c.length?". Next: "+F(c[0].text,100):". All done."))}return s.ticked=o,s}function $u(t,e){return Bn(t||ks(),e,{repoKey:Ao(e),repoName:fe(e)}).space}async function Xa(t,{now:e=Date.now(),cfg:n=ye,date:s,summarizer:o,machine:r,lists:i=[],agents:a=null,spaces:l=null,agentSpace:c=null}={}){let u=(n.digest&&n.digest.lookbackHours||24)*36e5,d=e-u,f=Gu(t,e,n,d,r),p=[...new Set(t.map(O=>O.session.machine).filter(Boolean))].sort(),h=s||ZS(e),m=new Date(h+"T12:00:00").toLocaleDateString("en-US",{weekday:"short",month:"short",day:"numeric"}).replace(",",""),y=f.needsYou.length+f.active.length+f.dormant.length+f.finished.length,w={date:h,generatedAt:G(e),window:{from:G(d),to:G(e)},sessions:y,needsYou:f.needsYou.map(O=>O.session.id),path:"digests/"+h+".md",machine:r||null,shared:!0,machines:p},k=Zt(Object.values(f).flat().map(O=>Ao(O.session))),S=new Map,v=O=>(S.has(O)||S.set(O,{needsYou:[],active:[],dormant:[],finished:[],agents:[]}),S.get(O));for(let[O,H]of Object.entries(f))for(let W of H)v($u(l,W.session))[O].push(W);let A=new Map;if(a&&a.agents)for(let O of a.agents){let H=c?c(O):Bn(l||ks(),{id:"agent:"+O.name+":x",agent:{name:O.name,puller:O.puller}}).space;Ku({agents:[O]},d,e,{heading:"###"}).length&&(v(H),A.set(H,[...A.get(H)||[],O]))}let b=so([...S.keys()]);w.spaces=b;let g=[];g.push("# Where you left off — "+m,"");let _=0;for(let O of b){let H=S.get(O),W=H.needsYou.length+H.active.length+H.dormant.length+H.finished.length,P=new Set(Object.values(H).flat().map($=>$.session&&$.session.machine).filter(Boolean)).size>1;g.push("## "+O,"");let Y=($,X,ne)=>{if(X.length){g.push("### "+$+" ("+X.length+")");for(let B of X)g.push(...ek(B,e,ne,P,k));g.push("")}};Y("Needs you",H.needsYou,"needsYou"),Y("Still running",H.active,"active"),Y("Dormant, ready to pick up",H.dormant,"dormant"),Y("Finished or closed",H.finished,"finished");let ue=A.get(O);if(ue){let $=Ku({agents:ue},d,e,{heading:"###"});_+=$.filter(X=>X.startsWith("- ")).length,g.push(...$,"")}else W||g.push("_Nothing here in the last "+Math.round(u/36e5)+" hours._","")}y||g.push("_No Claude Code sessions in the last "+Math.round(u/36e5)+" hours._",""),w.agentSessions=_;let x=nk(i,d,e);x.length&&g.push(...x,""),w.listsTicked=x.ticked||0;let C=null,E=null;if(y&&(!n.digest||n.digest.topOfMind!==!1)){if(o){let O=[...f.needsYou,...f.dormant,...f.active].slice(0,15).map(H=>"- "+(H.session.title||"Untitled")+" ["+H.status+"]"+(H.latest?" Doing: "+H.latest.doing+" Stopped at: "+H.latest.stoppedAt+" Next: "+H.latest.next:""));C=await o.paragraph(QS,O.join(`
|
|
111
|
+
`)),C&&(E="model")}C||(C=tk(f),E="heuristic"),g.push("## Top of mind","",C,"")}w.topOfMindBy=E;let L=ms({date:h,machine:r||null,shared:!0,machines:p,spaces:b,generated_at:w.generatedAt,window_from:w.window.from,window_to:w.window.to,sessions:y,needs_you:w.needsYou,schema:2});return{meta:w,markdown:L+g.join(`
|
|
112
|
+
`).replace(/\n+$/,"")+`
|
|
113
|
+
`}}var QS,Ao,Qa=V(()=>{ve();As();zn();vs();QS="You rank what a developer should pick up first this morning across their coding-agent sessions. Given the checkpoints, write 2-3 plain sentences naming the first thing to do and why. No preamble, no markdown, no lists.";Ao=t=>t.repo&&t.repo.root||t.cwd||fe(t)});function ok(t){let[e,n,s]=String(t).split("-").map(Number),o=new Date(Date.UTC(e,n-1,s,12));for(let r=0;r<7&&(o.setUTCDate(o.getUTCDate()-1),!sk.includes(Xt[o.getUTCDay()]));r++);return o.toISOString().slice(0,10)}function rk({date:t,now:e,tz:n="UTC",digestTime:s="07:00",prevDigestAt:o=null}){let r=ok(t),i=o?Date.parse(o):NaN;if(!Number.isFinite(i)){let[a,l,c]=r.split("-").map(Number),[u,d]=String(s||"07:00").split(":").map(Number);i=Ft(a,l,c,u||0,d||0,n)}return{prev:r,from:i,to:e}}function el(t,{now:e=Date.now(),date:n,space:s,spaces:o=null,cfg:r={},tz:i="UTC",digestTime:a="07:00",prevDigestAt:l=null,agents:c=null,agentSpace:u=null,maxDormant:d=3}={}){let f=rk({date:n,now:e,tz:i,digestTime:a,prevDigestAt:l}),p=t.filter(g=>!s||$u(o,g.session)===s),h=Zt(p.map(g=>Ao(g.session))),m=g=>"`"+(h.get(Ao(g))||fe(g)||"no repo").replace(/`/g,"'")+"`",y=g=>Bt(g.title||"Untitled session",80),w=[],k=[],S=[],v=[];for(let{session:g,checkpoints:_}of p){if(g.archived||ht(g,_.length))continue;let x=O=>O&&O>=f.from&&O<=f.to,C=_.filter(O=>x(R(O.createdAt))).sort((O,H)=>R(H.createdAt)-R(O.createdAt)),E=x(R(g.endedAt));if(!C.length&&!E&&!x(R(g.lastActivityAt)))continue;let L=E?"done":C[0]?Bt(C[0].stoppedAt||C[0].doing,110):"worked on it";v.push({t:Math.max(R(g.lastActivityAt)||0,R(g.endedAt)||0),line:"• "+m(g)+" "+y(g)+(L==="done"?" (done)":" — "+L)})}v.sort((g,_)=>_.t-g.t),w.push(...v.slice(0,8).map(g=>g.line)),v.length>8&&w.push("• +"+(v.length-8)+" more");let A=Gu(p,e,r&&r.staleAfterSec?r:{...r,staleAfterSec:120},f.from);for(let g of A.needsYou){let _=g.session,x=g.latest,C=x&&/^YOU:/i.test(x.next||"")?Bt(x.next.replace(/^YOU:\s*/i,""),110):_.needsInput?"waiting on "+String(_.lastNotification||"input").replace(/_/g," "):"waiting on me";S.push("• "+m(_)+" "+y(_)+" — "+C)}for(let g of[...A.active,...A.dormant.slice(0,d)]){let _=g.session,x=g.latest;k.push("• "+m(_)+" "+y(_)+(x&&x.next?" — next: "+Bt(x.next,110):""))}if(c&&c.agents)for(let g of c.agents){let _=u?u(g):Bn(o||ks(),{id:"agent:"+g.name+":x",agent:{name:g.name,puller:g.puller}}).space;if(!(s&&_!==s))for(let x of g.records||[]){let C=x.session,E="`"+Bt(C.project||g.display,40)+"`",L=C.checkpoints&&C.checkpoints[C.checkpoints.length-1],O=Wn(g,x,e),H=!!(O&&O.items&&O.items.length),W=Jr(x),I=Date.parse(C.updatedAt);H?S.push("• "+E+" "+Bt(C.title,80)+" — "+Bt(O.items[0].text,110)):W!=="ended"&&k.push("• "+E+" "+Bt(C.title,80)+(L&&L.next?" — next: "+Bt(L.next,110):"")),I>=f.from&&I<=f.to&&!H&&w.push("• "+E+" "+Bt(C.title,80)+(W==="ended"?" (done)":L?" — "+Bt(L.summary,110):""))}}return{text:["Yesterday:",...w.length?w:["• Nothing logged"],"Today:",...k.length?k:["• Nothing open"],"Blocked:",...S.length?S:["• None"]].join(`
|
|
114
|
+
`),space:s||null,counts:{yesterday:v.length,today:k.length,blocked:S.length},window:{from:new Date(f.from).toISOString(),to:new Date(f.to).toISOString(),previousWorkday:f.prev}}}var sk,Bt,Yu=V(()=>{ve();As();Qa();vs();zn();Cr();sk=["mon","tue","wed","thu","fri"];Bt=(t,e)=>F(String(t||"").replace(/\s*\n\s*/g," ").replace(/`/g,"'").trim(),e).replace(/[\s.]+$/,"")});function ck(t){return t?t>=1e9?(t/1e9).toFixed(1)+" GB":Math.round(t/1e6)+" MB":""}async function im(t,{current:e=null,baseUrl:n=null,fetchImpl:s=fetch,pulls:o={},env:r=process.env,platform:i=process.platform}={}){let a=String(t||"auto").toLowerCase();if(a==="ollama"){let l=await Ut({baseUrl:n||at,fetchImpl:s,env:r}),c=ho(l.models,{current:e}).map(f=>({tag:f.tag,name:f.name||f.tag,note:f.note||(f.curated?"":"already on this machine"),hint:[ck(f.sizeBytes),f.ramGB?"~"+f.ramGB+" GB RAM":null].filter(Boolean).join(" · "),recommended:!!f.recommended,current:!!f.current,installed:!!f.installed,curated:!!f.curated,canDownload:!!f.curated&&!f.installed&&l.running,pull:o[f.tag]||null})),u=l.running?null:Na({platform:i,binary:l.binary,env:r}),d=l.running?{ok:!0,text:"Ollama "+(l.version?l.version+" ":"")+"is running"}:u?{ok:!1,text:"Ollama is installed but not running. Start it here, or run: "+u.shell,code:"ollama_down",canStart:!0,startShell:u.shell}:l.binary?{ok:!1,text:"Ollama is installed but not running. Start it with: "+l.binary+" serve",code:"ollama_down",canStart:!1}:{ok:!1,text:"Ollama isn’t running here. Install it from ollama.com, then start it, to download these.",code:"ollama_down",canStart:!1};return{provider:a,choices:c,status:d,checked:fo}}if(a==="ollama-cloud")return{provider:a,choices:Lr.map(l=>tl({tag:l.tag,name:l.name,note:l.note,hint:"runs on ollama.com · needs an API key",recommended:!!l.recommended},e)),status:null,checked:lp};if(a==="anthropic"){let l={haiku:"claude-haiku-5-5",sonnet:"claude-sonnet-5-5"}[e]||e;return{provider:a,choices:ak.map(c=>tl({tag:c.tag,name:c.name,note:c.note,hint:c.cost,recommended:!!c.recommended},l)),status:null,checked:ik}}if(a==="claude"||a==="auto")return{provider:a,choices:lk.map(l=>tl({tag:l.tag,name:l.name,note:l.note,hint:l.cost,recommended:!!l.recommended},e||"haiku")),status:null,checked:null};if(a==="openai-compatible"){let l=await Sn({baseUrl:n||Qt,fetchImpl:s});return{provider:a,choices:l.models.map(c=>tl({tag:c,name:c,note:"loaded in your local server",hint:"",recommended:!1},e)),status:l.running?{ok:!0,text:l.models.length+" model"+(l.models.length===1?"":"s")+" at "+l.baseUrl}:{ok:!1,text:"No server answered at "+l.baseUrl+". Start LM Studio’s server (or another OpenAI-compatible one).",code:"server_down"},checked:null}}return{provider:a,choices:[],status:null,checked:null}}function uk(t){return am.some(e=>Ge(e,t))}function dk(t,e,n){if(!t||t.at==null)return{at:n,completed:e,bps:0};let s=(n-t.at)/1e3;if(s<.25)return t;let o=Math.max(0,(e-t.completed)/s);return{at:n,completed:e,bps:t.bps?t.bps*.6+o*.4:o}}var ik,ak,lk,tl,am,nl,lm=V(()=>{go();ik="2026-10-10",ak=[{tag:"claude-haiku-5-5",name:"Claude Haiku 5.5",note:"Fastest and cheapest; plenty for a 3-line checkpoint",cost:"from $0.10 in / $0.50 out per M tokens",recommended:!0},{tag:"claude-sonnet-5-5",name:"Claude Sonnet 5.5",note:"Sharper summaries of long, messy sessions",cost:"$2 in / $10 out per M tokens"}],lk=[{tag:"haiku",name:"Haiku",note:"Quick; uses your Claude Code plan, no key",cost:"your Claude plan",recommended:!0},{tag:"sonnet",name:"Sonnet",note:"Slower, more careful summaries",cost:"your Claude plan"}];tl=(t,e)=>({...t,current:!!e&&(Ge(e,t.tag)||e===t.tag)});am=Mr.map(t=>t.tag);nl=class{constructor({baseUrl:e=at,fetchImpl:n=fetch,onChange:s=()=>{},pull:o=mo,now:r=()=>Date.now()}={}){Object.assign(this,{baseUrl:e,fetchImpl:n,onChange:s,pull:o,now:r}),this.jobs=new Map}view(e){return{tag:e.tag,status:e.status,fraction:e.fraction,completed:e.completed,total:e.total,bps:Math.round(e.rate.bps||0),done:e.done,error:e.error,cancelled:e.cancelled,use:e.use,startedAt:e.startedAt,finishedAt:e.finishedAt||null}}list(){let e={};for(let[n,s]of this.jobs)e[n]=this.view(s);return e}start(e,{use:n=!1,onDone:s=()=>{}}={}){if(!uk(e)){let l=new Error("Only the suggested models can be downloaded from the board: "+am.join(", "));throw l.status=400,l.code="not_allowed",l}let o=this.jobs.get(e);if(o&&!o.done&&!o.error&&!o.cancelled)return n&&(o.use=!0),this.view(o);let r=new AbortController,i={tag:e,status:"starting",fraction:0,completed:0,total:0,rate:{},done:!1,error:null,cancelled:!1,use:n,startedAt:new Date(this.now()).toISOString(),ac:r};this.jobs.set(e,i);let a=0;return this.pull({tag:e,baseUrl:this.baseUrl,fetchImpl:this.fetchImpl,signal:r.signal,onProgress:l=>{i.status=l.status,i.fraction=l.fraction,i.completed=l.completed,i.total=l.total,i.rate=dk(i.rate,l.completed,this.now());let c=this.now();c-a>250&&(a=c,this.onChange(this.view(i)))}}).then(async()=>{i.done=!0,i.fraction=1,i.status="success",i.finishedAt=new Date(this.now()).toISOString();try{await s(i)}catch{}this.onChange(this.view(i))},l=>{r.signal.aborted?(i.cancelled=!0,i.status="cancelled"):(i.error=String(l&&l.message||l).replace(/^pull \S+ failed: /,""),i.status="failed"),i.finishedAt=new Date(this.now()).toISOString(),this.onChange(this.view(i))}),this.onChange(this.view(i)),this.view(i)}cancel(e){let n=this.jobs.get(e);return!n||n.done||n.error||n.cancelled?n?this.view(n):null:(n.ac.abort(),this.view(n))}}});import qr from"node:fs";import cm from"node:path";import To from"node:crypto";function dm(t){let e=String(t||"").trim().toLowerCase().replace(/\.$/,"").replace(/\.(?=:\d+$)/,"");if(!e)return null;let n=e.match(/^(\[[^\]]+\]|[^:\s/]+)(?::(\d+))?$/);return n?{name:n[1],port:n[2]?Number(n[2]):null}:null}function sl(t){let e=dm(String(t||"").replace(/^https?:\/\//i,"").replace(/\/.*$/,""));return!e||Ju.includes(e.name)||!/^[a-z0-9.-]+$/.test(e.name)||!e.name.includes(".")?null:e}function Mt(t){let e=sl(t);return e?e.name+(e.port&&e.port!==443?":"+e.port:""):null}function Vr(t,e,n=[]){let s=dm(t);if(!s)return null;if(Ju.includes(s.name))return s.port===Number(e)?"loopback":null;for(let o of n||[]){let r=sl(o);if(!(!r||r.name!==s.name)){if(r.port&&r.port!==443){if(s.port===r.port)return"remote"}else if(s.port===null||s.port===443||s.port===Number(e))return"remote"}}return null}function fm(t,e,n=[]){if(!t)return!0;let s=String(t).toLowerCase(),o=s.match(/^http:\/\/([^/]+)$/);return o?Vr(o[1],e,[])==="loopback":(o=s.match(/^https:\/\/([^/]+)$/),o?Vr(o[1],e,n)==="remote":!1)}function ol(t=[]){return(t||[]).map(Mt).filter(Boolean).map(e=>"https://"+e+"/")}function hm(t){return cm.join(we(t).secret,"remote.json")}function Co(t){try{let e=JSON.parse(qr.readFileSync(hm(t),"utf8"));return{links:Array.isArray(e.links)?e.links:[],sessions:Array.isArray(e.sessions)?e.sessions:[],failures:Array.isArray(e.failures)?e.failures:[]}}catch{return{links:[],sessions:[],failures:[]}}}function Eo(t,e){let n=hm(t);qr.mkdirSync(cm.dirname(n),{recursive:!0,mode:448});let s=n+".tmp-"+process.pid;qr.writeFileSync(s,JSON.stringify(e,null,2)+`
|
|
115
|
+
`,{mode:384}),qr.renameSync(s,n);try{qr.chmodSync(n,384)}catch{}}function hk(t){let e=String(t||"").toUpperCase().replace(/[\s-]+/g,"");if(e.length!==um)return null;for(let n of e)if(!zu.includes(n))return null;return e}function pk(){return Array.from(To.randomBytes(um),t=>zu[t%zu.length]).join("")}function Qr(t,{now:e=Date.now(),ttlMs:n=Ls,note:s=null,withCode:o=!0,codeOnly:r=!1}={}){let i=Co(t),a=new Date(e+n).toISOString();i.links=i.links.filter(u=>Ms(u,e));let l=null,c=null;return r||(l=To.randomBytes(24).toString("base64url"),i.links.push({kind:"link",hash:_o(l),createdAt:new Date(e).toISOString(),expiresAt:a,note:s})),(o||r)&&(c=pk(),i.links.push({kind:"code",hash:_o(c),createdAt:new Date(e).toISOString(),expiresAt:a,note:s})),Eo(t,i),{code:l,signInCode:c,expiresAt:a}}function pm(t,{now:e,userAgent:n,who:s,how:o}){let r=To.randomBytes(32).toString("base64url"),i=To.randomBytes(6).toString("hex"),a=new Date(e).toISOString();return t.sessions.push({id:i,hash:_o(r),createdAt:a,lastUsedAt:a,how:o,userAgent:String(n||"").slice(0,160),who:s?String(s).slice(0,120):null}),{id:i,secret:r}}function mm(t,e,{now:n=Date.now(),userAgent:s="",who:o=null}={}){if(!e||String(e).length>200)return null;let r=Co(t),i=_o(e),a=r.links.find(c=>c.hash===i&&c.kind!=="code");if(!a||!Ms(a,n))return null;a.usedAt=new Date(n).toISOString();let l=pm(r,{now:n,userAgent:s,who:o,how:"link"});return r.links=r.links.filter(c=>Ms(c,n)),r.sessions=r.sessions.filter(c=>Zr(c,n)),Eo(t,r),l}function gm(t,e,{now:n=Date.now(),userAgent:s="",who:o=null}={}){let r=Co(t);if(r.failures=(r.failures||[]).filter(u=>n-Date.parse(u)<Ls),r.failures.length>=Wu)return{ok:!1,reason:"locked"};let i=hk(e),a=i?_o(i):null,l=a?r.links.find(u=>u.kind==="code"&&u.hash===a&&Ms(u,n)):null;if(!l)return r.failures.push(new Date(n).toISOString()),r.failures.length>=Wu&&(r.links=r.links.filter(u=>u.kind!=="code")),Eo(t,r),{ok:!1,reason:r.failures.length>=Wu?"locked":"bad_code"};l.usedAt=new Date(n).toISOString();let c=pm(r,{now:n,userAgent:s,who:o,how:"code"});return r.links=r.links.filter(u=>Ms(u,n)),r.sessions=r.sessions.filter(u=>Zr(u,n)),Eo(t,r),{ok:!0,...c}}function Zr(t,e){return t.lastUsedAt?e-Date.parse(t.lastUsedAt)<qu*1e3:!0}function il(t,e,{now:n=Date.now(),touch:s=!1}={}){if(!e)return null;let o=Buffer.from(_o(e)),r=Co(t),i=r.sessions.find(c=>{let u=Buffer.from(c.hash);return u.length===o.length&&To.timingSafeEqual(u,o)});if(!i||!Zr(i,n))return null;if(s&&(!i.lastUsedAt||n-Date.parse(i.lastUsedAt)>fk)){i.lastUsedAt=new Date(n).toISOString(),r.sessions=r.sessions.filter(c=>Zr(c,n));try{Eo(t,r)}catch{}}let{hash:a,...l}=i;return{...l,expiresAt:new Date(Date.parse(i.lastUsedAt||new Date(n).toISOString())+qu*1e3).toISOString()}}function al(t,{maxAgeSec:e=qu}={}){return Xr+"="+t+"; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age="+e}function ei(t){let n=Co(t).sessions.length;return Eo(t,{links:[],sessions:[],failures:[]}),n}function ll(t){let e=Co(t),n=Date.now();return{sessions:e.sessions.filter(s=>Zr(s,n)).map(s=>({id:s.id,createdAt:s.createdAt,lastUsedAt:s.lastUsedAt||null,how:s.how||"link",userAgent:s.userAgent,who:s.who})),pendingLinks:e.links.filter(s=>Ms(s,n)&&s.kind!=="code").length,pendingCodes:e.links.filter(s=>Ms(s,n)&&s.kind==="code").length}}var Ju,Ls,Xr,qu,fk,zu,um,Wu,_o,rl,Ms,cl=V(()=>{ke();wt();Ju=["127.0.0.1","[::1]","localhost",yt,vp],Ls=900*1e3,Xr="cp_remote",qu=720*3600,fk=3600*1e3,zu="ABCDEFGHJKLMNPQRSTUVWXYZ23456789",um=6,Wu=10;_o=t=>To.createHash("sha256").update(String(t)).digest("hex");rl=t=>t&&t.slice(0,3)+"-"+t.slice(3);Ms=(t,e)=>!t.usedAt&&Date.parse(t.expiresAt)>e});import{execFile as mk}from"node:child_process";function yk(t,e=process.kill.bind(process)){if(!Number.isInteger(t)||t<=1)return!1;try{return e(t,0),!0}catch(n){return n&&n.code==="EPERM"}}function wk(t,{run:e=Sk,now:n=Date.now()}={}){let s=[...new Set(t.filter(o=>Number.isInteger(o)&&o>1))];return s.length?e(s).then(o=>{let r=new Map;for(let i of String(o||"").split(`
|
|
116
|
+
`)){let[a,l,c]=i.trim().split(/\s+/);if(a&&/^Z/.test(l||"")){r.set(Number(a),-1);continue}let u=rr(c);a&&u!=null&&r.set(Number(a),n-u*1e3)}return r}).catch(()=>new Map):Promise.resolve(new Map)}function Sk(t){return new Promise((e,n)=>{mk("ps",["-o","pid=,stat=,etime=","-p",t.join(",")],{env:{...process.env,LC_ALL:"C"},timeout:5e3},(s,o)=>{if(s&&!o)return s.code===1?e(""):n(s);e(o)})})}async function ym(t,{kill:e,run:n,now:s=Date.now()}={}){let o=new Map,r=t.filter(c=>Number.isInteger(c.pid)&&c.pid>1),i=r.filter(c=>yk(c.pid,e));for(let c of r)i.includes(c)||o.set(c.id,{alive:!1,reason:"process_gone"});let a=i.filter(c=>c.pidStartedAt),l=a.length?await wk(a.map(c=>c.pid),{run:n,now:s}):new Map;for(let c of i){let u=l.get(c.pid);if(u===-1){o.set(c.id,{alive:!1,reason:"process_gone"});continue}let d=R(c.pidStartedAt);u!=null&&!isNaN(d)&&Math.abs(u-d)>gk?o.set(c.id,{alive:!1,reason:"pid_reused"}):o.set(c.id,{alive:!0,reason:"running"})}return o}var gk,wm=V(()=>{ve();gk=15e3});import Vu from"node:fs";import fl from"node:path";import Am from"node:os";import{execFile as kk}from"node:child_process";function Zu({tty:t=!1}={}){return["on run argv",' if application "Ghostty" is not running then return "notrunning"'," set RS to character id 30"," set US to character id 31",' tell application "Ghostty"'," set ids to id of every terminal"," set nms to name of every terminal"," set wds to working directory of every terminal",...t?[" set ttys to {}"," try"," set ttys to «property Gtty» of every terminal"," end try"]:[]," end tell"," set n to count of ids",' if (count of nms) is not n or (count of wds) is not n then return "changed"',...t?[" set haveTty to ((count of ttys) is n)"]:[],' set out to "ok" & RS'," repeat with i from 1 to n",' set t to ""',...t?[" if haveTty then set t to item i of ttys"]:[]," set out to out & (item i of ids) & US & (item i of nms) & US & (item i of wds) & US & t & RS"," end repeat"," return out","end run"].join(`
|
|
117
|
+
`)}function Xu(t){let e=String(t||"").replace(/\n$/,"");if(e==="notrunning")return{state:"notrunning",terminals:[]};if(e==="changed")return{state:"changed",terminals:[]};if(!e.startsWith("ok"+Sm)&&e!=="ok")return{state:"bad",terminals:[]};let n=[];for(let s of e.slice(3).split(Sm)){if(!s)continue;let o=s.split(vk);o.length!==4||!o[0]||n.push({id:o[0],name:o[1],cwd:o[2],tty:o[3]||null})}return{state:"ok",terminals:n}}function km(t,{realpath:e=!0}={}){let n=String(t||"").trim();if(!n)return"";if(n.startsWith("file://"))try{n=decodeURIComponent(new URL(n).pathname)}catch{}if((n==="~"||n.startsWith("~/"))&&(n=fl.join(Am.homedir(),n.slice(1))),n.length>1&&(n=n.replace(/\/+$/,"")),e)try{n=Vu.realpathSync.native(n)}catch{}return n}function _m(t){return/^\s*[\u2733\u273b\u273d\u2736\u2722\u00b7*\u2800-\u28ff]\s/.test(String(t||""))}function Em(t,e,{realpath:n=!0}={}){let s=t||{},o=e||[];if(s.tty&&o.some(c=>c.tty)){let c=o.find(u=>u.tty&&u.tty===s.tty);if(c)return{id:c.id,how:"tty"}}let r=s.marker||(s.id?sr(s.id):"");if(r){let c=o.find(u=>String(u.name||"").includes(r));if(c)return{id:c.id,how:"title"}}let i=bm(s.name),a=i?o.filter(c=>bm(c.name)===i):[];if(a.length===1)return{id:a[0].id,how:"name"};let l=km(s.cwd,{realpath:n});if(l){let c=o.filter(u=>u.cwd&&km(u.cwd,{realpath:n})===l);if(c.length>1&&a.length>1){let u=c.filter(d=>a.includes(d));if(u.length===1)return{id:u[0].id,how:"name+cwd"}}if(c.length===1)return{id:c[0].id,how:"cwd"};if(c.length>1){let u=c.filter(d=>_m(d.name));return u.length===1?{id:u[0].id,how:"cwd+claude-title"}:{ambiguous:c.length}}}return null}function bm(t){let e=String(t||"").trim();return _m(e)&&(e=e.replace(/^\s*\S+\s+/,"")),hs(e).replace(/\s+/g," ").trim().toLowerCase()}function ul(t){let e=String(t||"").match(/(\d+)\.(\d+)(?:\.(\d+))?/);if(!e)return null;let[n,s,o]=[Number(e[1]),Number(e[2]),Number(e[3]||0)];return n!==1?n>1:s!==3?s>3:o>1}function vm({resourcesDir:t,home:e=Am.homedir()}={}){let n=[];if(t){let s=String(t).match(/^(.*?\.app)(?:\/|$)/);s&&n.push(s[1])}return n.push("/Applications/Ghostty.app",fl.join(e,"Applications","Ghostty.app")),[...new Set(n)]}function xm(t){try{let n=Vu.readFileSync(fl.join(t,"Contents","Info.plist"),"utf8").match(/<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/);return n?n[1].trim():null}catch{return null}}function Qu({resourcesDir:t,version:e,home:n,readFile:s=o=>Vu.readFileSync(o,"utf8")}={}){for(let o of vm({resourcesDir:t,home:n})){let r=null;try{r=s(fl.join(o,"Contents","Resources","Ghostty.sdef"))}catch{}if(r&&/class name="terminal"/.test(r))return{tty:/code="Gtty"/.test(r),version:xm(o)||e||null,source:"sdef",bundle:o}}for(let o of vm({resourcesDir:t,home:n})){let r=xm(o);if(r)return{tty:ul(r)===!0,version:r,source:"Info.plist",bundle:o}}return e?{tty:ul(e)===!0,version:e,source:"TERM_PROGRAM_VERSION"}:{tty:!1,version:null,source:"unknown"}}function _k(t=process.env){let e=t.LC_ALL||t.LC_CTYPE||t.LANG||"";if(/utf-?8/i.test(e))return t;let n={...t,LC_CTYPE:"en_US.UTF-8"};return(!t.LANG||!/utf-?8/i.test(t.LANG))&&(n.LANG="en_US.UTF-8"),t.LC_ALL&&(n.LC_ALL="en_US.UTF-8"),n}function Kt(t,{env:e,timeoutMs:n=Oo}={}){let s=Date.now(),o=e?{...process.env,...e}:process.env;return/(^|\/)osascript$/.test(String(t[0]))&&(o=_k(o)),new Promise(r=>{kk(t[0],t.slice(1),{env:o,timeout:n,killSignal:"SIGKILL",encoding:"utf8"},(i,a,l)=>{let c=!!(i&&(i.killed||i.signal==="SIGKILL"));r({code:i?c?"timeout":typeof i.code=="number"?i.code:i.code||1:0,stdout:String(a||""),stderr:String(l||(c?"timed out after "+n+" ms":i&&i.code==="ENOENT"?"not found":"")),missing:!!(i&&i.code==="ENOENT"),ms:Date.now()-s})})})}function ed(t,e,n){return{ok:!1,code:"timeout",message:t+" didn't answer within "+Math.round(e/1e3)+" s"+(n?" ("+n+")":"")+". It may still switch in a moment. If macOS is showing a permission prompt, answer it and try again; `leftoff doctor` times each step."}}function Ck(t){return Ek[t]||t}async function Cm(t,{exec:e=Kt,caps:n,capsFn:s=Qu,timeoutMs:o=Oo,env:r,explain:i,onFound:a,onStale:l,activate:c="auto",realpath:u=!0}={}){let d=t.ghostty||{},f=Date.now(),p=[],h=async(g,_,x={})=>{let C=Date.now(),E=await e(_,{env:r,timeoutMs:x.timeoutMs||o}),L={step:g,ms:E.ms!=null?E.ms:Date.now()-C,result:E.code===0?String(E.stdout).trim().split(/[\x1e\n]/)[0]||"ok":String(E.code)};return p.push(L),{...E,step:L}},m=g=>({...g,steps:p,elapsedMs:Date.now()-f}),y=g=>g.code==="timeout"?m(ed("Ghostty",o,g.step.step)):g.missing?m({ok:!1,code:"missing_osascript",message:"Couldn't run `osascript`."}):m({ok:!1,...i?i(g.stderr,"Ghostty"):{code:"script_failed",message:String(g.stderr).trim()}}),w=()=>m({ok:!1,code:"not_running",message:"Ghostty isn't running."}),k=async g=>{let _=await h("focus by id",["osascript","-e",xk,g]);if(_.code!==0)return{fail:y(_)};let x=_.stdout.trim();return x==="ok"?{ok:!0}:x==="notrunning"?{fail:w()}:x.startsWith("stale")?(_.step.result="stale",{stale:!0}):{fail:m({ok:!1,code:"script_failed",message:'Ghostty answered "'+x.slice(0,80)+'"'})}},S=d.terminalId||null,v=null;if(S){let g=await k(S);if(g.fail)return g.fail;g.ok?v="cached id":(l&&l(S),S=null)}if(!v){let g=n||s({resourcesDir:d.resourcesDir,version:d.version}),_=null;for(let E=0;E<2;E++){let L=await h("list terminals"+(g.tty?" (with tty)":""),["osascript","-e",Zu({tty:!!g.tty})]);if(L.code!==0)return y(L);if(_=Xu(L.stdout),L.step.result=_.state==="ok"?Tm(_.terminals.length):_.state,_.state!=="changed")break}if(_.state==="notrunning")return w();if(_.state!=="ok")return m({ok:!1,code:"script_failed",message:"Ghostty's terminal list came back unreadable"+(_.state==="changed"?" (tabs were opening or closing); try again":"")+"."});let x=Em({id:d.sessionId,marker:d.marker,name:d.name,cwd:d.cwd,tty:g.tty?d.tty:null},_.terminals,{realpath:u});if(!x)return m({ok:!1,code:"not_found",message:"Couldn't find the tab in Ghostty. It may have been closed, or its title was changed by hand."});if(x.ambiguous)return m({ok:!1,code:"ambiguous",message:x.ambiguous+" Ghostty tabs are in this folder and none has this session's name. Give it a name only it has (/cp-name), then try again."});a&&a(x.id,x.how,_.terminals);let C=await k(x.id);if(C.fail)return C.fail;if(C.stale)return m({ok:!1,code:"not_found",message:"The Ghostty tab closed while switching."});S=x.id,v=x.how}let A=null,b=c||"auto";if(b==="auto"){await bk(150);let g=await h("frontmost check",["osascript","-e",Tk],{timeoutMs:3e3});A=g.code===0?g.stdout.trim()==="true":null,A===!1&&(await h("open -a Ghostty",["open","-a","Ghostty"],{timeoutMs:3e3})).code===0&&(A=null)}else b==="open"?await h("open -a Ghostty",["open","-a","Ghostty"],{timeoutMs:3e3}):(b==="applescript"||b==="always")&&await h("activate",["osascript","-e",Ak],{timeoutMs:3e3});return m({ok:!0,how:v,terminalId:S,message:"Switched to Ghostty ("+(v==="cached id"?"remembered tab":"matched by "+Ck(v))+")"})}async function hl({exec:t=Kt,caps:e,capsFn:n=Qu,timeoutMs:s=Oo,hint:o={}}={}){let r=e||n(o),i=[],a=await t(["osascript","-e",'return "ok"'],{timeoutMs:s});i.push({step:"osascript start",ms:a.ms,result:a.code===0?"ok":String(a.code)});let l=await t(["osascript","-e",Zu({tty:!!r.tty})],{timeoutMs:s}),c=l.code===0?Xu(l.stdout):null;i.push({step:"list terminals"+(r.tty?" (with tty)":""),ms:l.ms,result:c?c.state==="ok"?Tm(c.terminals.length):c.state:String(l.code)});let u=!!(c&&c.state==="ok");return{ok:u,steps:i,terminals:u?c.terminals.length:null,caps:r,stderr:u?"":(l.stderr||"").trim(),code:l.code}}function pl(t){return(t||[]).map(e=>e.step+" "+e.ms+" ms"+(e.result&&e.result!=="ok"?" ("+e.result+")":"")).join(" · ")}var Oo,bk,Tm,Sm,vk,xk,Ak,Tk,Ek,Ok,dl,Io=V(()=>{ve();Oo=Number(process.env.CHECKPOINTS_JUMP_STEP_TIMEOUT_MS)||8e3,bk=t=>new Promise(e=>setTimeout(e,t)),Tm=t=>t+(t===1?" terminal":" terminals"),Sm="",vk="";xk=`on run argv
|
|
118
|
+
set termId to item 1 of argv
|
|
119
|
+
if application "Ghostty" is not running then return "notrunning"
|
|
120
|
+
tell application "Ghostty"
|
|
121
|
+
try
|
|
122
|
+
focus (terminal id termId)
|
|
123
|
+
on error errMsg number errNum
|
|
124
|
+
if errNum is -1728 or errNum is -1719 or errNum is -10000 then return "stale " & errNum
|
|
125
|
+
error errMsg number errNum
|
|
126
|
+
end try
|
|
127
|
+
end tell
|
|
128
|
+
return "ok"
|
|
129
|
+
end run`,Ak='tell application "Ghostty" to activate',Tk=`if application "Ghostty" is not running then return "notrunning"
|
|
130
|
+
tell application "Ghostty" to return frontmost as text`;Ek={"cached id":"its remembered tab",tty:"tty",title:"title",name:"name","name+cwd":"name and folder",cwd:"folder","cwd+claude-title":"folder and Claude title"};Ok=600*1e3,dl=class{constructor({exec:e=Kt,log:n=()=>{},now:s=()=>Date.now(),capsFn:o=Qu,timeoutMs:r=Oo,realpath:i=!0}={}){this.exec=e,this.log=n,this.now=s,this.capsFn=o,this.timeoutMs=r,this.realpath=i,this.map=new Map,this.caps=null,this.capsAt=0,this.refreshing=null,this.last=null,this.refreshCount=0}getCaps(e={}){return(!this.caps||this.now()-this.capsAt>Ok)&&(this.caps=this.capsFn(e),this.capsAt=this.now()),this.caps}get(e){let n=this.map.get(e);return n?n.id:null}entry(e){return this.map.get(e)||null}set(e,n,s){this.map.set(e,{id:n,how:s,at:this.now()})}forget(e){this.map.delete(e)}applyListing(e,n){let s=new Set((n||[]).map(i=>i.id));for(let[i,a]of this.map)s.has(a.id)||this.map.delete(i);let o=!!(this.caps&&this.caps.tty),r=0;for(let i of e||[]){let a=Em({id:i.id,name:i.title||null,cwd:i.cwd,tty:o?i.tty:null},n,{realpath:this.realpath});a&&a.id&&(this.set(i.id,a.id,a.how),r++)}return r}refresh(e,{reason:n="manual"}={}){return this.refreshing?this.refreshing:(this.refreshing=this._refresh(e||[],n).finally(()=>{this.refreshing=null}),this.refreshing)}async _refresh(e,n){let s=this.now(),o=e[0]&&e[0].term?{resourcesDir:e[0].term.resourcesDir,version:e[0].term.version}:{},r=this.getCaps(o),i=null,a=null;for(let c=0;c<2&&(a=await this.exec(["osascript","-e",Zu({tty:!!r.tty})],{timeoutMs:this.timeoutMs}),!(a.code!==0||(i=Xu(a.stdout),i.state!=="changed")));c++);if(this.refreshCount++,!i||i.state!=="ok"){let c=i?i.state:a.code==="timeout"?"timeout":String(a.stderr||a.code).trim().split(`
|
|
131
|
+
`).pop();return this.last={at:new Date(s).toISOString(),ms:this.now()-s,reason:n,error:c,stderr:a?a.stderr:""},i&&i.state==="notrunning"&&this.map.clear(),this.log("ghostty cache ("+n+"): "+c+" after "+this.last.ms+" ms"),this.last}let l=this.applyListing(e,i.terminals);return this.last={at:new Date(s).toISOString(),ms:this.now()-s,reason:n,terminals:i.terminals.length,sessions:e.length,matched:l},this.log("ghostty cache ("+n+"): "+i.terminals.length+" terminals, "+l+"/"+e.length+" sessions matched in "+this.last.ms+" ms"),this.last}}});import ml from"node:fs";import ti from"node:path";function Nk(t){return'"'+String(t??"").replace(/[\x00-\x08\x0b-\x1f\x7f]/g," ").replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\r?\n/g,"\\n")+'"'}function gl(t){let e=[];return t.cwd&&e.push(t.cwd),t.tty&&e.push(t.tty),e.join(" on ")}function No(t,{platform:e=nd()}={}){let n=t||{},s=n.term||n.terminal||Ln({}),o=s.kind||"unknown",r=s.label||o,i=e==="darwin",a=c=>({supported:!1,kind:o,label:r,reason:c,steps:[]}),l=(c,u,d={})=>({argv:["osascript","-e",c,...u.map(f=>String(f??""))],parse:"osascript",...d});switch(o){case"ghostty":return i?{supported:!0,kind:o,label:r,app:"Ghostty",steps:[],ghostty:{sessionId:n.id||null,marker:n.id?sr(n.id):"",name:n.title||null,cwd:n.cwd||"",tty:n.tty||"",terminalId:n.ghosttyTerminalId||null,resourcesDir:s.resourcesDir||null,version:s.version||null}}:a("Ghostty jump uses AppleScript, which is macOS only");case"iterm2":return i?s.itermSessionId?{supported:!0,kind:o,label:r,app:"iTerm2",steps:[l(Mk,[s.itermSessionId])]}:a("no ITERM_SESSION_ID was recorded for this session"):a("iTerm2 is macOS only");case"apple-terminal":return i?n.tty?{supported:!0,kind:o,label:r,app:"Terminal",steps:[l(Lk,[n.tty])]}:a("no tty was recorded for this session"):a("Terminal.app is macOS only");case"wezterm":{if(!s.weztermPane)return a("no WEZTERM_PANE was recorded for this session");let c=s.weztermSocket?{WEZTERM_UNIX_SOCKET:s.weztermSocket}:void 0,u=[{argv:["wezterm","cli","activate-pane","--pane-id",String(s.weztermPane)],env:c,bin:"wezterm"}];return i&&u.push({argv:["open","-a","WezTerm"],optional:!0}),{supported:!0,kind:o,label:r,app:"WezTerm",steps:u}}case"kitty":{if(!s.kittyWindowId)return a("no KITTY_WINDOW_ID was recorded for this session");if(!s.kittyListenOn)return a("kitty remote control is off. Add `allow_remote_control socket-only` and `listen_on unix:/tmp/kitty` to kitty.conf, restart kitty, then start a new session");let c=[{argv:["kitten","@","--to",s.kittyListenOn,"focus-window","--match","id:"+s.kittyWindowId],bin:"kitten"}];return i&&c.push({argv:["open","-a","kitty"],optional:!0}),{supported:!0,kind:o,label:r,app:"kitty",steps:c}}case"tmux":{if(!s.tmuxPane)return a("no TMUX_PANE was recorded for this session");let c=f=>({argv:["tmux",...s.tmuxSocket?["-S",s.tmuxSocket]:[],...f],bin:"tmux"}),u=[c(["select-window","-t",s.tmuxPane]),c(["select-pane","-t",s.tmuxPane]),{...c(["switch-client","-t",s.tmuxPane]),optional:!0}],d=s.host&&Im[s.host];return i&&d&&u.push({argv:["open","-a",d],optional:!0}),{supported:!0,kind:o,label:r,app:d||null,steps:u}}case"vscode":return a(r+" terminals can't be focused from outside the editor"+(gl(n)?". It's in "+gl(n):""));default:return a("jumping isn't supported for "+(r||"this terminal")+" yet"+(gl(n)?". It's in "+gl(n):""))}}function td(t,e="the terminal"){let n=String(t||"");return/-1743|not authori[sz]ed to send apple events|not allowed to send apple events/i.test(n)?{code:"automation_denied",message:"macOS blocked the request. In System Settings → Privacy & Security → Automation, allow "+(e==="the terminal"?"it":"control of "+e)+' for the app that asked (your terminal for `leftoff go`, "node" for the board\'s Go button), then try again.'}:/-600|isn.t running|not running/i.test(n)?{code:"not_running",message:e+" isn't running."}:e==="Ghostty"&&/-2741|-2740|-1728|-1708|expected class name|expected end of line|can.t get every terminal|doesn.t understand/i.test(n)?{code:"ghostty_no_applescript",message:"This Ghostty has no AppleScript support. Jump needs Ghostty 1.3 or newer (Ghostty → Check for Updates), with macos-applescript left on (it is by default)."}:/-1712|timed? ?out/i.test(n)?{code:"timeout",message:e+" didn't answer. If macOS is showing a permission prompt, answer it and try again."}:{code:"script_failed",message:"The "+e+" script failed: "+F(n.trim().split(`
|
|
132
|
+
`).pop(),200)}}function Dk(t,{env:e=process.env,platform:n=process.platform}={}){for(let s of String(e.PATH||"").split(ti.delimiter).filter(Boolean)){let o=ti.join(s,t);try{return ml.accessSync(o,ml.constants.X_OK),o}catch{}}if(n==="darwin")for(let s of Ik[t]||[])try{return ml.accessSync(s,ml.constants.X_OK),s}catch{}return null}function Nm(t,e={}){return Kt(t,e)}async function yl(t,{exec:e=Nm,env:n=process.env,platform:s=nd(),timeoutMs:o=Oo,ghostty:r={}}={}){let i=Date.now();if(!t||!t.supported)return{ok:!1,code:"unsupported",message:t&&t.reason||"jump is not supported here",steps:[],elapsedMs:0};if(t.kind==="ghostty"&&t.ghostty)return Cm(t,{exec:e,timeoutMs:o,explain:td,...r});let a=[],l=u=>({...u,steps:a,elapsedMs:Date.now()-i}),c=null;for(let u of t.steps){let d=u.argv;if(u.bin){let h=Dk(u.bin,{env:n,platform:s});if(!h&&!u.optional)return l({ok:!1,code:"missing_"+u.bin,message:"Couldn't find `"+u.bin+"` on PATH"+(t.kind==="kitty"?" (it ships with kitty)":"")+"."});h&&(d=[h,...d.slice(1)])}let f=Date.now(),p=await e(d,{env:u.env,timeoutMs:o});if(a.push({step:u.name||(ti.basename(d[0])+(d[0].endsWith("osascript")?"":" "+(d[1]||""))).trim(),ms:p.ms!=null?p.ms:Date.now()-f,result:p.code===0?"ok":String(p.code)}),!u.optional){if(p.code==="timeout")return l(ed(t.app||t.label||"The terminal",o));if(p.missing)return l({ok:!1,code:"missing_"+d[0],message:"Couldn't run `"+ti.basename(d[0])+"`."});if(u.parse==="osascript"){if(p.code!==0)return l({ok:!1,...td(p.stderr,t.app)});let h=p.stdout.trim();if(h.startsWith("ok")){c=h.slice(2).trim()||null;continue}return h.startsWith("ambiguous")?l({ok:!1,code:"ambiguous",message:h.slice(9).trim()+" "+t.app+" tabs are in this folder and none has this session's name. Give it a name only it has (/cp-name), then try again."}):l({ok:!1,code:"not_found",message:"Couldn't find the tab in "+t.app+". It may have been closed, or its title was changed by hand."})}if(p.code!==0)return l({ok:!1,code:"command_failed",message:"`"+[ti.basename(d[0]),...d.slice(1,3)].join(" ")+"` failed: "+F((p.stderr||p.stdout).trim(),200)})}}return l({ok:!0,how:c,message:"Switched to "+(t.label||t.kind)+(c?" (matched by "+c+")":"")})}function Om(t){return String(t||"").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)}function Rk(t,e){let n=0;for(let s of e)s===t[n]&&n++;return n===t.length}function sd(t,e){let n=String(t||"").trim().toLowerCase();if(!n)return 1;let s=String(e.id||"").toLowerCase(),o=Ke(e.id);if(n===o||n==="cp:"+o||n.length>=4&&s.startsWith(n.replace(/^cp:/,"")))return 1e3;let r=[[e.title,5],[e.repoName||e.repo&&e.repo.name,4],[e.repo&&e.repo.branch,2],[Fi(e.cwd),3],[e.cwd,1],[e.machine,1]],i=0;for(let a of n.split(/\s+/).filter(Boolean)){let l=0;for(let[c,u]of r){if(!c)continue;let d=String(c).toLowerCase();Om(d).includes(a)?l=Math.max(l,u*4):Om(d).some(f=>f.startsWith(a))?l=Math.max(l,u*3):d.includes(a)?l=Math.max(l,u*2):a.length>=2&&Rk(a,d.replace(/[^a-z0-9]/g,""))&&(l=Math.max(l,u))}if(!l)return 0;i+=l}return i}function Mm(t,e){return e.map(n=>({s:n,score:sd(t,n)})).filter(n=>n.score>0).sort((n,s)=>s.score-n.score||Date.parse(s.s.lastActivityAt||s.s.startedAt||0)-Date.parse(n.s.lastActivityAt||n.s.startedAt||0)).map(n=>n.s)}function jk(t){let e=String(t||"").match(/(\d+)\.(\d+)/);if(!e)return!0;let[n,s]=[Number(e[1]),Number(e[2])];return n>1||n===1&&s>=3}async function wl(t,{exec:e=Nm,platform:n=nd(),timeoutMs:s=8e3}={}){let o=Im[t];if(n!=="darwin")return{kind:t,ok:null,message:"not macOS"};if(!o||t==="wezterm"||t==="kitty")return{kind:t,ok:null,message:"no AppleScript needed"};let r=await e(["osascript","-e","tell application "+Nk(o)+" to get version"],{timeoutMs:s});if(r.code===0){let a=r.stdout.trim();return t==="ghostty"&&!jk(a)?{kind:t,ok:!1,version:a,code:"ghostty_old",message:"Ghostty "+a+" has no AppleScript; jump needs 1.3 or newer (Ghostty → Check for Updates)"}:{kind:t,ok:!0,version:a,message:o+" "+a+", automation allowed"}}let i=td(r.stderr,o);return{kind:t,ok:!1,code:i.code,message:i.message}}var Im,Ik,Mk,Lk,nd,ni=V(()=>{ve();Io();Im={ghostty:"Ghostty",iterm2:"iTerm2","apple-terminal":"Terminal",wezterm:"WezTerm",kitty:"kitty"},Ik={wezterm:["/Applications/WezTerm.app/Contents/MacOS/wezterm","/opt/homebrew/bin/wezterm","/usr/local/bin/wezterm"],kitten:["/Applications/kitty.app/Contents/MacOS/kitten","/opt/homebrew/bin/kitten","/usr/local/bin/kitten"],tmux:["/opt/homebrew/bin/tmux","/usr/local/bin/tmux","/usr/bin/tmux"]};Mk=`on run argv
|
|
133
|
+
set sid to item 1 of argv
|
|
134
|
+
tell application "iTerm2"
|
|
135
|
+
repeat with w in windows
|
|
136
|
+
repeat with t in tabs of w
|
|
137
|
+
repeat with s in sessions of t
|
|
138
|
+
if (unique id of s) is sid then
|
|
139
|
+
select w
|
|
140
|
+
tell t to select
|
|
141
|
+
tell s to select
|
|
142
|
+
activate
|
|
143
|
+
return "ok session"
|
|
144
|
+
end if
|
|
145
|
+
end repeat
|
|
146
|
+
end repeat
|
|
147
|
+
end repeat
|
|
148
|
+
end tell
|
|
149
|
+
return "notfound"
|
|
150
|
+
end run`,Lk=`on run argv
|
|
151
|
+
set ttyPath to item 1 of argv
|
|
152
|
+
tell application "Terminal"
|
|
153
|
+
repeat with w in windows
|
|
154
|
+
repeat with t in tabs of w
|
|
155
|
+
if (tty of t) is ttyPath then
|
|
156
|
+
set selected tab of w to t
|
|
157
|
+
set index of w to 1
|
|
158
|
+
activate
|
|
159
|
+
return "ok tty"
|
|
160
|
+
end if
|
|
161
|
+
end repeat
|
|
162
|
+
end repeat
|
|
163
|
+
end tell
|
|
164
|
+
return "notfound"
|
|
165
|
+
end run`;nd=()=>process.env.CHECKPOINTS_JUMP_PLATFORM||process.platform});import Pk from"node:http";import Fk from"node:https";import si from"node:crypto";import{EventEmitter as Dm}from"node:events";function Lm(t,{opcode:e=1,mask:n=!1}={}){let s=Buffer.isBuffer(t)?t:Buffer.from(String(t)),o=s.length,r=[128|e],i=Buffer.alloc(0);if(o<126?r.push((n?128:0)|o):o<65536?(r.push((n?128:0)|126),i=Buffer.alloc(2),i.writeUInt16BE(o)):(r.push((n?128:0)|127),i=Buffer.alloc(8),i.writeBigUInt64BE(BigInt(o))),!n)return Buffer.concat([Buffer.from(r),i,s]);let a=si.randomBytes(4),l=Buffer.alloc(o);for(let c=0;c<o;c++)l[c]=s[c]^a[c&3];return Buffer.concat([Buffer.from(r),i,a,l])}function Bk(t,{timeoutMs:e=5e3,hostHeader:n=null}={}){return new Promise((s,o)=>{let r=new URL(t),i=r.protocol==="wss:"||r.protocol==="https:",a=si.randomBytes(16).toString("base64"),l=(i?Fk:Pk).request({host:r.hostname,port:r.port||(i?443:80),path:(r.pathname||"/")+(r.search||""),method:"GET",headers:{connection:"Upgrade",upgrade:"websocket","sec-websocket-version":"13","sec-websocket-key":a,"user-agent":"leftoff-peer",...n?{host:n}:{}},timeout:e,...i&&n?{servername:n.replace(/:\d+$/,"")}:{}});l.on("upgrade",(c,u,d)=>{let f=si.createHash("sha1").update(a+Uk).digest("base64");if(c.headers["sec-websocket-accept"]!==f)return u.destroy(),o(new Error("bad websocket handshake"));u.setNoDelay(!0),s(new rd(u,{client:!0,head:d}))}),l.on("response",c=>{o(new Error("relay answered HTTP "+c.statusCode+" instead of upgrading")),c.resume()}),l.on("timeout",()=>{l.destroy(new Error("timed out"))}),l.on("error",o),l.end()})}var Uk,Hk,od,rd,Mo,id=V(()=>{Uk="258EAFA5-E914-47DA-95CA-C5AB0DC85B11",Hk=1024*1024;od=class{constructor(e){this.buf=Buffer.alloc(0),this.onFrame=e,this.parts=[],this.partOp=0}push(e){for(this.buf=Buffer.concat([this.buf,e]);;){if(this.buf.length<2)return;let n=this.buf[0],s=this.buf[1],o=!!(n&128),r=n&15,i=!!(s&128),a=s&127,l=2;if(a===126){if(this.buf.length<4)return;a=this.buf.readUInt16BE(2),l=4}else if(a===127){if(this.buf.length<10)return;a=Number(this.buf.readBigUInt64BE(2)),l=10}if(a>Hk)throw new Error("frame too large");let c=l+(i?4:0)+a;if(this.buf.length<c)return;let u=this.buf.subarray(l+(i?4:0),c);if(i){let d=this.buf.subarray(l,l+4),f=Buffer.alloc(a);for(let p=0;p<a;p++)f[p]=u[p]^d[p&3];u=f}else u=Buffer.from(u);if(this.buf=this.buf.subarray(c),r===0){this.parts.push(u),o&&(this.onFrame({opcode:this.partOp,payload:Buffer.concat(this.parts)}),this.parts=[]);continue}if(!o&&(r===1||r===2)){this.partOp=r,this.parts=[u];continue}this.onFrame({opcode:r,payload:u})}}},rd=class extends Dm{constructor(e,{client:n,head:s=null}){super(),this.socket=e,this.client=n,this.open=!0;let o=new od(({opcode:r,payload:i})=>{r===1?this.emit("message",i.toString("utf8")):r===8?this.close():r===9&&this.raw(i,10)});e.on("data",r=>{try{o.push(r)}catch{this.close()}}),s&&s.length&&setImmediate(()=>{try{o.push(s)}catch{this.close()}}),e.on("close",()=>{this.open&&(this.open=!1,this.emit("close"))}),e.on("error",()=>{})}raw(e,n){this.open&&this.socket.write(Lm(e,{opcode:n,mask:this.client}))}send(e){this.raw(Buffer.from(typeof e=="string"?e:JSON.stringify(e)),1)}ping(){this.raw(Buffer.alloc(0),9)}close(){if(this.open){try{this.socket.write(Lm(Buffer.alloc(0),{opcode:8,mask:this.client}))}catch{}this.open=!1;try{this.socket.end()}catch{}setTimeout(()=>{try{this.socket.destroy()}catch{}},200).unref(),this.emit("close")}}};Mo=class extends Dm{constructor({url:e,id:n,log:s=()=>{},onEnvelope:o,accepts:r=()=>!0,user:i=null,hostHeader:a=null,helloExtra:l=()=>({})}){super(),this.url=e,this.id=n,this.log=s,this.onEnvelope=o,this.accepts=r,this.user=i,this.hostHeader=a,this.helloExtra=l,this.conn=null,this.connected=!1,this.closed=!1,this.backoff=1e3,this.waiting=new Map,this.calls=new Map,this.latencyMs=null,this.connectedAt=null,this.lastError=null,this.seq=0}async connect(){if(this.closed)return;let e;try{e=await Bk(this.url,{hostHeader:this.hostHeader})}catch(n){return this.lastError=n.message,this.emit("state"),this.retry()}if(this.closed){e.close();return}this.conn=e,e.on("message",n=>this.onMessage(n)),e.on("close",()=>{let n=this.connected;this.connected=!1,this.conn=null,this.connectedAt=null;for(let[,s]of this.waiting)s.reject(Object.assign(new Error("relay connection closed"),{code:"relay_closed"}));this.waiting.clear();for(let[,s]of this.calls)s.reject(Object.assign(new Error("relay connection closed"),{code:"relay_closed"}));this.calls.clear(),n&&(this.log("peer: relay connection closed"),this.emit("state")),this.retry()}),clearInterval(this.keep),this.keep=setInterval(()=>this.ping(),Number(process.env.LEFTOFF_RELAY_PING_MS)||25e3),this.keep.unref()}ping(){this.conn&&(this.user?(this.pingAt=Date.now(),this.conn.send('{"t":"ping"}')):this.conn.ping())}retry(){this.closed||(clearInterval(this.keep),clearTimeout(this.retryTimer),this.retryTimer=setTimeout(()=>this.connect(),this.backoff),this.retryTimer.unref(),this.backoff=Math.min(3e4,this.backoff*2))}hello(){}send(e){return this.conn&&this.connected?(this.conn.send(e),!0):!1}ask(e,n=8e3){if(!this.conn||!this.connected)return Promise.reject(Object.assign(new Error("not connected to the relay"),{code:"relay_down"}));let s="c"+ ++this.seq+"."+si.randomBytes(4).toString("hex");return new Promise((o,r)=>{let i=setTimeout(()=>{this.calls.delete(s),r(Object.assign(new Error("the relay didn’t answer"),{code:"relay_timeout"}))},n);this.calls.set(s,{resolve:a=>{clearTimeout(i),o(a)},reject:a=>{clearTimeout(i),r(a)}}),this.conn.send({...e,id:s})})}onMessage(e){let n;try{n=JSON.parse(e)}catch{return}if(n.t==="pong"){this.pingAt&&(this.latencyMs=Date.now()-this.pingAt,this.pingAt=0,this.emit("latency",this.latencyMs));return}if(n.t==="challenge"){if(this.user&&n.user&&n.user!==this.user)return this.lastError="wrong_user",this.log("relay: that relay is for "+n.user+", not "+this.user),this.conn.close();let s=n.user?n.user+"|":"",o=si.sign(null,Buffer.from("leftoff-relay-hello|"+s+this.id.machine+"|"+n.nonce),this.id.signPriv).toString("base64url");this.conn.send({t:"hello",machine:this.id.machine,sign:this.id.signX,nonce:n.nonce,sig:o,...this.helloExtra()})}else if(n.t==="welcome")this.connected=!0,this.backoff=1e3,this.lastError=null,this.connectedAt=Date.now(),this.relayMachines=n.machines||null,this.log("peer: connected to the relay "+(this.hostHeader||this.url)),this.emit("state"),this.emit("welcome",n),this.user&&setTimeout(()=>this.ping(),50).unref();else if((n.t==="ack"||n.t==="devices"||n.t==="status")&&n.id&&this.calls.has(n.id)){let s=this.calls.get(n.id);this.calls.delete(n.id),s.resolve(n)}else if(n.t==="error"&&n.ref&&this.waiting.has(n.ref)){let s=this.waiting.get(n.ref);this.waiting.delete(n.ref),s.reject(Object.assign(new Error("relay: "+n.code),{code:"relay_"+n.code}))}else if(n.t==="error")this.lastError=n.code,this.log("peer: relay said "+n.code),this.emit("state");else if(n.t==="deliver"&&n.env){let s=n.env;if(s.kind==="res"||s.kind==="err"){let o=this.waiting.get(s.re||n.ref);o&&(this.waiting.delete(s.re||n.ref),o.resolve(s));return}if(!this.onEnvelope)return this.conn&&this.conn.send({t:"send",to:n.from,ref:s.id,env:{kind:"err",code:"not_allowed",re:s.id}});Promise.resolve(this.onEnvelope(s)).then(o=>{o&&this.conn&&this.conn.send({t:"send",to:n.from,ref:s.id,env:o.kind==="err"?{...o,re:s.id}:o})}).catch(()=>{})}else n.t&&this.emit("frame",n)}request(e,n,s=4e3){return!this.conn||!this.connected?Promise.reject(Object.assign(new Error("not connected to the relay"),{code:"relay_down"})):new Promise((o,r)=>{let i=setTimeout(()=>{this.waiting.delete(n.id),r(Object.assign(new Error("no answer through the relay"),{code:"relay_timeout"}))},s);this.waiting.set(n.id,{resolve:a=>{clearTimeout(i),o(a)},reject:a=>{clearTimeout(i),r(a)}}),this.conn.send({t:"send",to:e,ref:n.id,env:n})})}close(){this.closed=!0,clearInterval(this.keep),clearTimeout(this.retryTimer),this.conn&&this.conn.close()}}});var dd={};zt(dd,{ACTIONS:()=>$k,MAX_TTL_MS:()=>jm,PROTOCOL:()=>Lo,PeerError:()=>me,PeerHub:()=>ri,ReplayGuard:()=>Al,SKEW_MS:()=>Pm,TTL_MS:()=>Rm,canonical:()=>vt,cardFile:()=>Hm,fingerprint:()=>cd,identityDir:()=>Tl,loadIdentity:()=>xn,localAddresses:()=>$m,machinesDir:()=>bl,makeCard:()=>Bm,open:()=>ad,publishCard:()=>Km,readCards:()=>Ds,saveIdentity:()=>Sl,seal:()=>kl,trustOf:()=>vn,verifyCard:()=>ud});import Gt from"node:fs";import ld from"node:os";import en from"node:path";import Kk from"node:http";import De from"node:crypto";import{EventEmitter as Gk}from"node:events";function vt(t){return t===null||typeof t!="object"?JSON.stringify(t===void 0?null:t):Array.isArray(t)?"["+t.map(vt).join(",")+"]":"{"+Object.keys(t).filter(e=>t[e]!==void 0).sort().map(e=>JSON.stringify(e)+":"+vt(t[e])).join(",")+"}"}function Tl(t=process.env){return t.LEFTOFF_IDENTITY_DIR?t.LEFTOFF_IDENTITY_DIR:en.join(t.XDG_CONFIG_HOME||en.join(t.HOME||ld.homedir(),".config"),"checkpoints","identity")}function cd(t,e){return De.createHash("sha256").update(Jn(t)).update(Jn(e)).digest("hex").slice(0,16).toUpperCase().match(/.{4}/g).join("-")}function Yk(){let t=De.generateKeyPairSync("ed25519"),e=De.generateKeyPairSync("x25519");return{sign:t.privateKey.export({format:"jwk"}),box:e.privateKey.export({format:"jwk"})}}function Wk(t,e){let n=De.createPrivateKey({key:t.sign,format:"jwk"}),s=De.createPrivateKey({key:t.box,format:"jwk"});return{machine:t.machine,file:e,state:t,signPriv:n,boxPriv:s,signX:t.sign.x,boxX:t.box.x,fingerprint:cd(t.sign.x,t.box.x),get allowIncoming(){return!!t.allowIncoming},get pins(){return t.pins||(t.pins={})}}}function xn(t,{dir:e=Tl()}={}){let n=en.join(e,Fm(t)+".json"),s=null;try{s=JSON.parse(Gt.readFileSync(n,"utf8"))}catch{}(!s||!s.sign||!s.box)&&(s={v:1,machine:t,created:new Date().toISOString(),...Yk(),allowIncoming:!1,pins:{}},Gt.mkdirSync(e,{recursive:!0,mode:448}),Um(n,s));try{Gt.chmodSync(n,384)}catch{}return Wk(s,n)}function Um(t,e){let n=t+"."+process.pid+".tmp";Gt.writeFileSync(n,JSON.stringify(e,null,2)+`
|
|
166
|
+
`,{mode:384}),Gt.renameSync(n,t)}function Sl(t){Um(t.file,t.state)}function bl(t){return en.join(t,"machines")}function Hm(t,e){return en.join(bl(t),Fm(e)+".json")}function Bm(t,{addresses:e=[],relay:n=!1,version:s=null,hostname:o=ld.hostname(),now:r=Date.now(),anywhere:i=null}={}){let a={v:Lo,app:"leftoff",machine:t.machine,hostname:o,version:s,sign:t.signX,box:t.boxX,fingerprint:t.fingerprint,accepts:t.allowIncoming,addresses:t.allowIncoming?e:[],relay:!!(n&&t.allowIncoming),...i?{anywhere:i}:{},updatedAt:new Date(r).toISOString()};return a.sig=oi(De.sign(null,Buffer.from(vt(a)),t.signPriv)),a}function ud(t){if(!t||typeof t!="object"||!t.sign||!t.box||!t.sig||!t.machine)return!1;try{let{sig:e,...n}=t,s=De.createPublicKey({key:{kty:"OKP",crv:"Ed25519",x:t.sign},format:"jwk"});return De.verify(null,Buffer.from(vt(n)),s,Jn(e))&&cd(t.sign,t.box)===t.fingerprint}catch{return!1}}function Km(t,e){let n=Hm(t,e.machine);try{let o=JSON.parse(Gt.readFileSync(n,"utf8")),r=i=>{let{updatedAt:a,sig:l,...c}=i;return vt(c)};if(r(o)===r(e)&&ud(o))return{file:n,changed:!1}}catch{}Gt.mkdirSync(en.dirname(n),{recursive:!0});let s=en.join(en.dirname(n),"."+en.basename(n)+"."+process.pid+".tmp");return Gt.writeFileSync(s,JSON.stringify(e,null,2)+`
|
|
167
|
+
`),Gt.renameSync(s,n),{file:n,changed:!0}}function Ds(t){let e={},n=[];try{n=Gt.readdirSync(bl(t)).filter(s=>s.endsWith(".json")&&!s.startsWith("."))}catch{return e}for(let s of n)try{let o=JSON.parse(Gt.readFileSync(en.join(bl(t),s),"utf8"));e[o.machine||s.slice(0,-5)]={card:o,valid:ud(o)}}catch{}return e}function vn(t,e,{pinNew:n=!0}={}){if(!e||!e.valid)return"invalid";let s=t.pins[e.card.machine];return s?s===e.card.fingerprint?"trusted":"key_changed":n?(t.pins[e.card.machine]=e.card.fingerprint,"new"):"unpinned"}function Gm(t,e,n,s){let o=De.createPublicKey({key:{kty:"OKP",crv:"X25519",x:e},format:"jwk"}),r=De.diffieHellman({privateKey:t,publicKey:o});return Buffer.from(De.hkdfSync("sha256",r,n,Buffer.from(s),32))}function kl({me:t,to:e,kind:n="req",body:s,re:o=null,ttlMs:r=Rm,now:i=Date.now()}){if(!e||!e.box||!e.machine)throw new me("unknown_target","no published key for that machine",400);let a=De.generateKeyPairSync("x25519"),l=oi(De.randomBytes(16)),c=De.randomBytes(12),u={v:Lo,kind:n,from:t.machine,to:e.machine,id:l,iat:i,exp:i+r,re:o||void 0,epk:a.publicKey.export({format:"jwk"}).x,iv:oi(c)},d=Gm(a.privateKey,e.box,Jn(l),"leftoff-peer-v1|"+u.from+"|"+u.to),f=De.createCipheriv("aes-256-gcm",d,c);f.setAAD(Buffer.from(vt(xl(u,vl))));let p=Buffer.concat([f.update(Buffer.from(JSON.stringify(s))),f.final(),f.getAuthTag()]);return u.ct=oi(p),u.sig=oi(De.sign(null,Buffer.from(vt(xl(u,[...vl,"ct"]))),t.signPriv)),u}function xl(t,e){let n={};for(let s of e)t[s]!==void 0&&(n[s]=t[s]);return n}function ad(t,{me:e,cardOf:n,replay:s,now:o=Date.now(),expectKind:r=null,expectRe:i=null}){if(!t||typeof t!="object"||t.v!==Lo||!t.id||!t.from||!t.ct||!t.sig||!t.epk||!t.iv)throw new me("bad_envelope","not a Leftoff peer envelope",400);if(r&&t.kind!==r)throw new me("bad_envelope","unexpected envelope kind",400);if(t.to!==e.machine)throw new me("wrong_target","this envelope is for "+t.to+", not "+e.machine,400);let a=n(t.from);if(!a||!a.card)throw new me("unknown_sender",t.from+" has no published key in this folder (has the sync caught up?)");if(a.trust==="key_changed")throw new me("key_changed",t.from+"’s key changed since this Mac first saw it; confirm it in Settings → Privacy & security");if(a.trust==="invalid")throw new me("unknown_sender",t.from+"’s published key file doesn’t verify");let l=!1;try{let u=De.createPublicKey({key:{kty:"OKP",crv:"Ed25519",x:a.card.sign},format:"jwk"});l=De.verify(null,Buffer.from(vt(xl(t,[...vl,"ct"]))),u,Jn(t.sig))}catch{}if(!l)throw new me("bad_signature","the signature doesn’t match "+t.from+"’s key (tampered, or signed by someone else)");if(typeof t.exp!="number"||typeof t.iat!="number"||t.exp-t.iat>jm||t.exp<t.iat)throw new me("bad_envelope","bad lifetime",400);if(o>t.exp)throw new me("expired","the command expired before it arrived ("+Math.round((o-t.exp)/1e3)+" s late; are both clocks right?)");if(t.iat>o+Pm)throw new me("not_yet_valid","the command is from the future (check both Macs’ clocks)");if(i&&t.re!==i)throw new me("bad_envelope","reply to a different request",400);if(!s.check(t.from,t.id,t.exp,o))throw new me("replayed","this command was already used once");let c;try{let u=Gm(e.boxPriv,t.epk,Jn(t.id),"leftoff-peer-v1|"+t.from+"|"+t.to),d=Jn(t.ct),f=De.createDecipheriv("aes-256-gcm",u,Jn(t.iv));f.setAAD(Buffer.from(vt(xl(t,vl)))),f.setAuthTag(d.subarray(d.length-16)),c=JSON.parse(Buffer.concat([f.update(d.subarray(0,d.length-16)),f.final()]).toString("utf8"))}catch{throw new me("decrypt_failed","couldn’t decrypt (wrong key?)",400)}return{env:t,body:c,sender:a}}function $m(t,e=ld.networkInterfaces()){let n=[];for(let[s,o]of Object.entries(e||{}))for(let r of o||[])r.internal||r.family!=="IPv4"&&r.family!==4||/^169\.254\./.test(r.address)||n.push({url:"http://"+r.address+":"+t,kind:/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(r.address)||/^(tailscale|utun)/.test(s)&&/^100\./.test(r.address)?"tailscale":"lan"});return n}var Lo,Rm,jm,Pm,$k,oi,Jn,me,Fm,vl,Al,ri,qn=V(()=>{id();Lo=1,Rm=60*1e3,jm=120*1e3,Pm=30*1e3,$k=["ping","needs.list","ask.answer","session.reply","session.go"],oi=t=>Buffer.from(t).toString("base64url"),Jn=t=>Buffer.from(String(t||""),"base64url"),me=class extends Error{constructor(e,n,s=403){super(n||e),this.code=e,this.status=s}};Fm=t=>String(t).replace(/[^\w.-]/g,"_");vl=["v","kind","from","to","id","iat","exp","re","epk","iv"];Al=class{constructor({max:e=2e4}={}){this.seen=new Map,this.max=e}check(e,n,s,o=Date.now()){if(this.seen.size>this.max||this.seen.size%256===0)for(let[i,a]of this.seen)a<o&&this.seen.delete(i);let r=e+"|"+n;return this.seen.has(r)?!1:(this.seen.set(r,s),!0)}};ri=class extends Gk{constructor({engine:e,root:n,machine:s,cfg:o={},identity:r=null,idDir:i,fetchImpl:a=(...d)=>fetch(...d),now:l=()=>Date.now(),log:c=()=>{},version:u=null}){super(),this.engine=e,this.root=n,this.machine=s,this.cfg=o,this.fetch=a,this.now=l,this.log=c,this.version=u,this.id=r||xn(s,{dir:i}),this.replay=new Al,this.status=new Map,this.remoteNeeds=new Map,this.cards={},this.cardsAt=0,this.server=null,this.relay=null,this.timer=null,this.stopped=!1}pcfg(){return this.engine&&this.engine.cfg&&this.engine.cfg.peers||this.cfg.peers||{}}get accepts(){return this.id.allowIncoming}listenPort(){let e=this.pcfg();if(process.env.LEFTOFF_PEER_PORT)return Number(process.env.LEFTOFF_PEER_PORT);if(e.port)return Number(e.port);let n=this.engine&&this.engine.cfg&&this.engine.cfg.port||7820;return Number(n)+1}relayUrl(){return process.env.LEFTOFF_RELAY||this.pcfg().relay||(this.hosted()?this.hosted().url():null)}hosted(){let e=this.engine&&this.engine.relayHost;return e&&e.active()?e:null}direct(){return process.env.LEFTOFF_PEER_DIRECT?process.env.LEFTOFF_PEER_DIRECT!=="0":this.pcfg().direct!==!1}async start(){this.publish(),this.accepts&&this.direct()&&await this.listen().catch(()=>{}),this.connectRelay();let e=Number(process.env.LEFTOFF_PEER_POLL_MS||this.pcfg().pollMs||4e3);this.timer=setInterval(()=>{this.poll().catch(()=>{})},e),this.timer.unref(),setTimeout(()=>this.poll().catch(()=>{}),300).unref()}async stop(){this.stopped=!0,this.timer&&clearInterval(this.timer),this.relay&&!this.relayShared&&this.relay.close(),this.server&&await new Promise(e=>this.server.close(()=>e())),this.server=null}addresses(){if(!this.direct())return[];let e=this.pcfg(),n=this.listenPort();return Array.isArray(e.addresses)&&e.addresses.length?e.addresses.map(s=>({url:String(s),kind:"configured"})):process.env.LEFTOFF_PEER_ADDRESSES?process.env.LEFTOFF_PEER_ADDRESSES.split(",").filter(Boolean).map(s=>({url:s.trim(),kind:"configured"})):$m(n)}publish(){let e=Bm(this.id,{addresses:this.addresses(),relay:!!this.relayUrl(),version:this.version,now:this.now(),anywhere:this.engine&&this.engine.relayHost?this.engine.relayHost.cardUser():null});try{let n=Km(this.root,e);return this.card=e,n}catch(n){return this.log("peer: could not publish machines/"+this.machine+".json: "+n.message),null}}async setAllowIncoming(e){if(this.id.state.allowIncoming=!!e,Sl(this.id),this.publish(),e&&!this.server&&this.direct()&&await this.listen(),!e&&this.server){let n=this.server;this.server=null,await new Promise(s=>n.close(()=>s()))}return this.relay&&this.relay.hello(),this.emit("changed"),this.overview()}trust(e){let n=this.readCards()[e];if(!n||!n.valid)throw new me("unknown_target","no valid key published for "+e,404);return this.id.pins[e]=n.card.fingerprint,Sl(this.id),this.status.delete(e),this.emit("changed"),this.overview()}readCards({fresh:e=!1}={}){if(!e&&this.now()-this.cardsAt<1500)return this.cards;this.cards=Ds(this.root),this.cardsAt=this.now();let n=!1;for(let[s,o]of Object.entries(this.cards))s!==this.machine&&vn(this.id,o)==="new"&&(n=!0);if(n)try{Sl(this.id)}catch{}return this.cards}cardOf(e){let n=this.readCards()[e];return n?{card:n.card,trust:vn(this.id,n,{pinNew:!1})==="unpinned"?"new":vn(this.id,n,{pinNew:!1})}:null}usable(e){if(!e||e===this.machine)return{ok:!1,reason:"this machine"};let n=this.cardOf(e);if(!n)return{ok:!1,code:"no_key",reason:e+" hasn’t published a machine key yet (it needs Leftoff 0.5.2, and the sync has to catch up)"};if(n.trust==="invalid")return{ok:!1,code:"bad_key",reason:e+"’s key file doesn’t verify"};if(n.trust==="key_changed")return{ok:!1,code:"key_changed",reason:e+"’s key changed; confirm it in Settings → Privacy & security"};if(!n.card.accepts)return{ok:!1,code:"not_allowed",reason:e+" doesn’t allow other machines to answer or Go (Settings → Privacy & security, on "+e+")"};let s=this.status.get(e);return!n.card.addresses.length&&!(n.card.relay&&this.relayUrl())?{ok:!1,code:"no_route",reason:"no way to reach "+e+": it has no address and no relay is set"}:s&&s.reachable===!1?{ok:!1,code:"unreachable",reason:"can’t reach "+e+" right now"+(s.lastError?" ("+s.lastError+")":"")}:s?{ok:!0,via:s.via,reason:null}:{ok:!1,code:"checking",reason:"checking whether "+e+" is reachable…"}}overview(){let e=this.readCards({fresh:!0}),n=[];for(let[s,o]of Object.entries(e)){if(s===this.machine)continue;let r=vn(this.id,o,{pinNew:!1}),i=this.status.get(s)||null;n.push({machine:s,hostname:o.card.hostname||null,version:o.card.version||null,fingerprint:o.card.fingerprint||null,valid:o.valid,trust:r==="unpinned"?"new":r,accepts:!!o.card.accepts,addresses:o.card.addresses||[],relay:!!o.card.relay,updatedAt:o.card.updatedAt||null,reachable:i?i.reachable:null,via:i?i.via:null,lastOkAt:i&&i.lastOkAt?new Date(i.lastOkAt).toISOString():null,lastError:i?i.lastError:null,usable:this.usable(s)})}return{machine:this.machine,fingerprint:this.id.fingerprint,allowIncoming:this.accepts,listening:this.server?this.listenPort():null,addresses:this.accepts?this.addresses():[],relay:this.relayUrl()?{url:this.relayUrl(),connected:!!(this.relay&&this.relay.connected)}:null,peers:n.sort((s,o)=>s.machine.localeCompare(o.machine))}}async receive(e,n){let s;try{s=ad(e,{me:this.id,cardOf:a=>this.cardOf(a),replay:this.replay,now:this.now(),expectKind:"req"})}catch(a){this.log("peer: refused an envelope from "+(e&&e.from)+" via "+n+": "+a.code);let l=e&&e.from?this.cardOf(e.from):null;if(l&&l.card&&l.trust!=="invalid"&&a.code!=="bad_signature"&&a.code!=="replayed")try{return kl({me:this.id,to:l.card,kind:"res",re:e.id,body:{ok:!1,error:{code:a.code,message:a.message,status:a.status}},now:this.now()})}catch{}return{v:Lo,kind:"err",code:a.code||"refused",message:a.message}}let{body:o,sender:r}=s,i;if(!this.accepts&&o.action!=="ping")i={ok:!1,error:{code:"not_allowed",status:403,message:this.machine+" doesn’t allow other machines to answer or Go here. Turn it on in Settings → Privacy & security on "+this.machine+"."}};else try{i={ok:!0,result:await this.execute(o,{from:e.from,transport:n})}}catch(a){i={ok:!1,error:{code:a.code||"error",status:a.status||500,message:a.message}}}return this.log("peer: "+o.action+" from "+e.from+" via "+n+" → "+(i.ok?"ok":i.error.code)),kl({me:this.id,to:r.card,kind:"res",re:e.id,body:i,now:this.now()})}async execute(e,{from:n,transport:s}){let o=this.engine,r={via:"peer",peer:n,transport:s};switch(e.action){case"ping":return{machine:this.machine,version:this.version,accepts:this.accepts};case"needs.list":return{items:o.needsList().filter(i=>i.session&&i.session.isLocal).map(i=>({...i,session:{id:i.session.id,title:i.session.title,machine:this.machine}}))};case"ask.answer":return await o.answerAsk(String(e.askId||""),e.body||{},r);case"session.reply":return await o.postReply(String(e.sessionId||""),e.text,r);case"session.go":return await o.jump(String(e.sessionId||""));default:throw new me("unknown_action","unknown action "+e.action,400)}}async listen(){if(this.server)return;let e=process.env.LEFTOFF_PEER_BIND||this.pcfg().bind||"0.0.0.0",n=Kk.createServer(async(s,o)=>{let r=(l,c)=>{let u=JSON.stringify(c);o.writeHead(l,{"content-type":"application/json","cache-control":"no-store"}),o.end(u)};if(s.method!=="POST"||s.url!=="/peer/v1/envelope")return r(404,{error:"not_found"});let i=0,a=[];s.on("data",l=>{i+=l.length,i>256*1024?s.destroy():a.push(l)}),s.on("end",async()=>{let l;try{l=JSON.parse(Buffer.concat(a).toString("utf8"))}catch{return r(400,{v:Lo,kind:"err",code:"bad_envelope"})}let c=await this.receive(l,"direct");r(c.kind==="err"?403:200,c)})});await new Promise((s,o)=>{n.once("error",o),n.listen(this.listenPort(),e,()=>{n.off("error",o),s()})}).catch(s=>{throw this.log("peer: could not listen on "+e+":"+this.listenPort()+": "+s.message),s}),this.server=n,this.log("peer: listening for other machines on "+e+":"+this.listenPort())}connectRelay(){let e=this.relayUrl();if(!(!e||this.relay)){if(!process.env.LEFTOFF_RELAY&&!this.pcfg().relay&&this.hosted()){this.hosted().client&&this.useRelay(this.hosted().client);return}this.relayShared=!1,this.relay=new Mo({url:e,id:this.id,log:this.log,onEnvelope:n=>this.receive(n,"relay"),accepts:()=>this.accepts}),this.relay.on("state",()=>this.emit("changed")),this.relay.connect()}}useRelay(e){this.relay&&!this.relayShared&&this.relay.close(),this.relay=e,this.relayShared=!0,e.onEnvelope=n=>this.receive(n,"relay"),this.onRelayState||(this.onRelayState=()=>this.emit("changed")),e.on("state",this.onRelayState),this.publish(),this.emit("changed")}dropRelay(e){this.relay===e&&(e.off("state",this.onRelayState),e.onEnvelope=null,this.relay=null,this.relayShared=!1,this.publish(),this.emit("changed"),this.connectRelay())}async request(e,n,s={},{timeoutMs:o=4e3,onlyVia:r=null}={}){let i=this.cardOf(e);if(!i)throw new me("no_key",e+" hasn’t published a machine key yet",409);if(i.trust==="key_changed")throw new me("key_changed",e+"’s key changed; confirm it in Settings → Privacy & security",409);if(i.trust==="invalid")throw new me("bad_key",e+"’s key file doesn’t verify",409);let a=[],l=[];if(r!=="relay")for(let c of i.card.addresses||[])l.push({via:"direct",url:c.url||c});if(r!=="direct"&&this.relay&&i.card.relay&&l.push({via:"relay"}),!l.length)throw this.mark(e,!1,null,"no address and no relay"),new me("no_route","no way to reach "+e+" (no address, no relay)",503);for(let c of l){let u=kl({me:this.id,to:i.card,kind:"req",body:{action:n,...s},now:this.now()}),d;try{c.via==="direct"?d=await(await this.fetch(String(c.url).replace(/\/$/,"")+"/peer/v1/envelope",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(u),signal:AbortSignal.timeout(o)})).json():d=await this.relay.request(e,u,o)}catch(p){a.push(c.via+": "+(p.code||p.cause&&p.cause.code||p.name||p.message));continue}if(d&&d.kind==="err")throw this.mark(e,!0,c.via,null),new me(d.code||"refused",e+" refused the command: "+(d.message||d.code),403);let f=ad(d,{me:this.id,cardOf:p=>this.cardOf(p),replay:this.replay,now:this.now(),expectKind:"res",expectRe:u.id});if(this.mark(e,!0,c.via,null),!f.body.ok){let p=f.body.error||{};throw new me(p.code||"error",p.message||"failed on "+e,p.status||409)}return{result:f.body.result,via:c.via}}throw this.mark(e,!1,null,a.join("; ")),new me("unreachable","couldn’t reach "+e+" ("+a.join("; ")+")",503)}mark(e,n,s,o){let r=this.status.get(e),i={reachable:n,via:s,lastError:o,lastCheckedAt:this.now(),lastOkAt:n?this.now():r&&r.lastOkAt||null};this.status.set(e,i),(!r||r.reachable!==n||r.via!==s)&&this.emit("changed")}async poll(){if(!(this.stopped||this.polling)){this.polling=!0;try{let e=this.readCards({fresh:!0});for(let[n,s]of Object.entries(e))if(n!==this.machine){if(!s.valid||!s.card.accepts||vn(this.id,s,{pinNew:!1})==="key_changed"){this.remoteNeeds.has(n)&&(this.remoteNeeds.delete(n),this.emit("needs",n));continue}try{let{result:o}=await this.request(n,"needs.list",{},{timeoutMs:2500}),r=this.remoteNeeds.get(n),i=JSON.stringify((o.items||[]).map(a=>[a.session.id,a.kind,a.ask&&a.ask.id,a.since]));this.remoteNeeds.set(n,{at:this.now(),items:o.items||[],sig:i}),(!r||r.sig!==i)&&this.emit("needs",n)}catch{this.remoteNeeds.has(n)&&(this.remoteNeeds.delete(n),this.emit("needs",n))}}}finally{this.polling=!1}}}needsFor(e,n){let s=this.remoteNeeds.get(n);return!s||this.now()-s.at>3e4?null:s.items.find(o=>o.session&&o.session.id===e)||null}machineForAsk(e){for(let[n,s]of this.remoteNeeds)if(s.items.some(o=>o.ask&&o.ask.id===e))return n;return null}}});import{execFile as zk,execFileSync as Jk}from"node:child_process";import _l from"node:process";function qk(){if(ii!==void 0)return ii;try{ii=Jk("/bin/sh",["-c","command -v terminal-notifier"],{encoding:"utf8",timeout:1500}).trim()||null}catch{ii=null}return ii}function Ym({title:t,subtitle:e,message:n,sessionId:s,goBin:o,notifier:r=null}={}){let i=El(t||"Leftoff",80),a=El(e,120),l=El(n,240);if(r){let c=["-title",i,"-subtitle",a,"-message",l||" ","-group","leftoff-"+El(s,64)];return o&&/^[A-Za-z0-9_-]{6,64}$/.test(String(s||""))&&/^[\w./ -]+$/.test(o)&&c.push("-execute","'"+o.replace(/'/g,"")+"' go "+s),{cmd:r,args:c}}return{cmd:"osascript",args:["-e","on run argv","-e","display notification (item 1 of argv) with title (item 2 of argv) subtitle (item 3 of argv)","-e","end run",l||" ",i,a]}}function Wm(t,e){let n=e||"A Claude Code session";if(t.kind==="permission"&&t.ask){let s=t.ask.describe||{};return{subtitle:n+" needs your OK",message:(s.verb?s.verb+": ":"")+(s.detail||t.ask.tool)}}return t.kind==="question"&&t.ask?{subtitle:n+" has a question",message:((t.ask.questions||[])[0]||{}).question||"Claude is asking you something"}:t.kind==="terminal"?{subtitle:n+" needs you",message:t.message||"Waiting in the terminal"}:{subtitle:n+" asked you",message:t.question||""}}function zm(t,{platform:e=_l.platform,run:n=zk}={}){if(e!=="darwin"&&!_l.env.CHECKPOINTS_NOTIFY_CMD)return{ok:!1,skipped:"not macOS"};let s;_l.env.CHECKPOINTS_NOTIFY_CMD?s={cmd:_l.env.CHECKPOINTS_NOTIFY_CMD,args:Ym({...t}).args}:s=Ym({...t,notifier:t.notifier!==void 0?t.notifier:qk()});try{n(s.cmd,s.args,{timeout:5e3},()=>{})}catch(o){return{ok:!1,error:o.message}}return{ok:!0,via:s.cmd}}var ii,El,Jm=V(()=>{El=(t,e)=>String(t??"").replace(/[\u0000-\u001f]+/g," ").replace(/\s+/g," ").trim().slice(0,e)});import Re from"node:fs";import Vn from"node:os";import Ie from"node:path";import{fileURLToPath as Vk}from"node:url";import{spawnSync as Zk}from"node:child_process";function tn(){return process.env.CLAUDE_SETTINGS_PATH||Ie.join(process.env.CLAUDE_CONFIG_DIR||Ie.join(Vn.homedir(),".claude"),"settings.json")}function Cl(t){return/^[A-Za-z0-9_./-]+$/.test(t)?t:JSON.stringify(t)}function nb(t,e=Fe){return Cl(process.execPath)+" "+Cl(e)+" hook "+t+" --tag="+eb}function sb(t=Fe){let e={};for(let n of Xk)e[n]=[{hooks:[{type:"command",command:nb(n,t),timeout:5,...Qk[n]||{}}]}];return e}function Ol(t=tn()){try{let e=Re.readFileSync(t,"utf8");return e.trim()?JSON.parse(e):{}}catch{return null}}function li(t=tn()){let e=Ol(t),n=e?JSON.stringify(e.hooks||{}):"";return qm.some(s=>n.includes(s))}function ci(t,e,n=tn()){let s=Ol(n),o=s||{},r=o.env?o.env[t]:void 0;return e==null&&r===void 0||r===e?!1:(s&&Re.existsSync(n)?Re.writeFileSync(n+".leftoff-backup-"+new Date().toISOString().replace(/[:.]/g,"-"),Re.readFileSync(n,"utf8")):Re.mkdirSync(Ie.dirname(n),{recursive:!0}),e==null?(delete o.env[t],Object.keys(o.env).length||delete o.env):o.env={...o.env||{},[t]:e},Re.writeFileSync(n,JSON.stringify(o,null,2)+`
|
|
168
|
+
`),!0)}function Do(t=tn(),{bin:e=Fe}={}){let n={};if(Re.existsSync(t)){let s=Re.readFileSync(t,"utf8");n=s.trim()?JSON.parse(s):{};let o=t+".leftoff-backup-"+new Date().toISOString().replace(/[:.]/g,"-");Re.writeFileSync(o,s)}else Re.mkdirSync(Ie.dirname(t),{recursive:!0});Vm(n),n.hooks=n.hooks||{};for(let[s,o]of Object.entries(sb(e)))n.hooks[s]=[...n.hooks[s]||[],...o];return Re.writeFileSync(t,JSON.stringify(n,null,2)+`
|
|
169
|
+
`),t}function Vm(t){if(!t.hooks)return 0;let e=0;for(let n of Object.keys(t.hooks)){let s=(t.hooks[n]||[]).map(o=>{let r=(o.hooks||[]).length,i=(o.hooks||[]).filter(a=>!(a.command&&tb(a.command)));return e+=r-i.length,{...o,hooks:i}}).filter(o=>o.hooks.length);s.length?t.hooks[n]=s:delete t.hooks[n]}return Object.keys(t.hooks).length||delete t.hooks,e}function Il(t=tn()){if(!Re.existsSync(t))return 0;let e=JSON.parse(Re.readFileSync(t,"utf8")||"{}"),n=Vm(e);return n&&Re.writeFileSync(t,JSON.stringify(e,null,2)+`
|
|
170
|
+
`),n}function An(){return process.env.CHECKPOINTS_SERVICE_PLATFORM||process.platform}function xt(t=An()){return t==="darwin"?Ie.join(Vn.homedir(),"Library","LaunchAgents",Au+".plist"):Ie.join(process.env.XDG_CONFIG_HOME||Ie.join(Vn.homedir(),".config"),"systemd","user",Os)}function Ro(t=An()){return t==="darwin"?Ie.join(Vn.homedir(),"Library","LaunchAgents",wp+".plist"):Ie.join(process.env.XDG_CONFIG_HOME||Ie.join(Vn.homedir(),".config"),"systemd","user",Tu)}function ai(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}function Tn({root:t=Ie.join(Vn.homedir(),"checkpoints"),bin:e=Fe,platform:n=An()}={}){let s=Ie.resolve(t)!==Ie.join(Vn.homedir(),"checkpoints"),o=Ie.join(t,".daemon.log");if(n==="darwin"){let r=s?` <key>EnvironmentVariables</key><dict><key>CHECKPOINTS_DIR</key><string>${ai(t)}</string></dict>
|
|
171
|
+
`:"";return`<?xml version="1.0" encoding="UTF-8"?>
|
|
172
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
173
|
+
<plist version="1.0"><dict>
|
|
174
|
+
<key>Label</key><string>${Au}</string>
|
|
175
|
+
<key>ProgramArguments</key><array><string>${ai(process.execPath)}</string><string>${ai(e)}</string><string>serve</string></array>
|
|
176
|
+
${r} <key>ProcessType</key><string>Interactive</string>
|
|
177
|
+
<key>RunAtLoad</key><true/>
|
|
178
|
+
<key>KeepAlive</key><true/>
|
|
179
|
+
<key>StandardOutPath</key><string>${ai(o)}</string>
|
|
180
|
+
<key>StandardErrorPath</key><string>${ai(o)}</string>
|
|
181
|
+
</dict></plist>
|
|
182
|
+
`}return`[Unit]
|
|
183
|
+
Description=Leftoff daemon (Claude Code session tracker)
|
|
184
|
+
|
|
185
|
+
[Service]
|
|
186
|
+
ExecStart=${Cl(process.execPath)} ${Cl(e)} serve
|
|
187
|
+
${s?"Environment="+JSON.stringify("CHECKPOINTS_DIR="+t)+`
|
|
188
|
+
`:""}Restart=on-failure
|
|
189
|
+
|
|
190
|
+
[Install]
|
|
191
|
+
WantedBy=default.target
|
|
192
|
+
`}function Zm(){return An()==="darwin"?"launchctl load -w "+xt():"systemctl --user daemon-reload && systemctl --user enable --now "+Os}function Rs(t=An()){let e=xt(t);return t==="darwin"?[["launchctl","unload",e],["launchctl","load","-w",e]]:[["systemctl","--user","daemon-reload"],["systemctl","--user","enable",Os],["systemctl","--user","restart",Os]]}function ob(t=An()){return t==="darwin"?[["launchctl","unload","-w",Ro(t)]]:[["systemctl","--user","disable","--now",Tu]]}function fd(t){let e=String(t||""),n=e.match(/<key>CHECKPOINTS_DIR<\/key>\s*<string>([^<]*)<\/string>/)||e.match(/^Environment="?CHECKPOINTS_DIR=([^"\n]*)"?$/m);return n?n[1].replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&"):null}function Nl({root:t,bin:e=Fe,platform:n=An(),dryRun:s=!1,run:o=Zk}={}){let r=Ro(n);if(!Re.existsSync(r))return null;let i=xt(n),a=Re.readFileSync(r,"utf8"),l=fd(a)||t||Ie.join(Vn.homedir(),"checkpoints"),c=[];if(!s)for(let u of ob(n)){let d=o(u[0],u.slice(1),{encoding:"utf8"});c.push({cmd:u,code:d.status,stderr:String(d.stderr||"").trim()})}return Re.mkdirSync(Ie.dirname(i),{recursive:!0}),Re.writeFileSync(i,Tn({root:l,bin:e,platform:n})),Re.unlinkSync(r),{from:r,to:i,root:l,steps:c,dryRun:s}}var Fe,CT,Xk,Qk,eb,qm,tb,Zn=V(()=>{wt();Fe=Ie.resolve(Ie.dirname(Vk(import.meta.url)),"..","bin","leftoff.js"),CT=Ie.join(Ie.dirname(Fe),"checkpoints.js"),Xk=["SessionStart","UserPromptSubmit","Stop","Notification","SessionEnd","PermissionRequest","PostToolUse"],Qk={SessionEnd:{timeout:2},PermissionRequest:{timeout:600},PostToolUse:{timeout:5,async:!0}},eb=Pr,qm=[Pr,Cs],tb=t=>qm.some(e=>String(t).includes(e))});import Ml from"node:fs";import Qm from"node:path";import{execFileSync as rb}from"node:child_process";function Ll(t){return Math.max(Xn(t.lastHeartbeatAt),Xn(t.lastEventAt),Xn(t.lastActivityAt),Xn(t.pidAliveAt),Xn(t.startedAt))}function ub(t,e){return Math.max(0,...[t.lastHeartbeatAt,t.lastEventAt,t.lastActivityAt,t.pidAliveAt,t.startedAt].map(Xn).filter(n=>n&&n<=e))}function eg(t){if(t==null||t===!0||t==="")return null;let e=String(t).trim().toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(m|min|h|hr|hours?|d|days?)?$/);if(!e)return null;let n=Number(e[1]),s=(e[2]||"h")[0],o=s==="m"?n/60:s==="d"?n*24:n;return o>0?o:null}function tg({platform:t=process.platform,exec:e=rb,readFile:n=Ml.readFileSync}={}){try{if(t==="darwin"){let o=String(e("/usr/sbin/sysctl",["-n","kern.boottime"],{encoding:"utf8",timeout:3e3})).match(/sec\s*=\s*(\d+)/);return o?Number(o[1])*1e3:null}if(t==="linux"){let s=String(n("/proc/stat","utf8")).match(/^btime\s+(\d+)/m);return s?Number(s[1])*1e3:null}}catch{}return null}function gd(t,e){try{return(JSON.parse(Ml.readFileSync(ng(t,e),"utf8")).starts||[]).map(Xn).filter(Boolean).sort((s,o)=>s-o)}catch{return[]}}function sg(t,e,n=Date.now()){let s=[...gd(t,e),n].sort((r,i)=>r-i).slice(-20),o=ng(t,e);try{Ml.mkdirSync(Qm.dirname(o),{recursive:!0}),Ml.writeFileSync(o,JSON.stringify({machine:e,starts:s.map(r=>G(r))},null,2)+`
|
|
193
|
+
`)}catch{}return s}function og({bootAt:t=null,starts:e=[],daemonStartedAt:n=null}={}){let s=[];if(t){let o=e.filter(r=>r>=t-6e4).sort((r,i)=>r-i)[0];s.push({kind:"boot",restartAt:t,upAt:Math.max(t,o||n||t)})}return n&&!s.some(o=>Math.abs(o.upAt-n)<6e4)&&s.push({kind:"daemon",restartAt:n,upAt:n}),s}function rg(t,{machine:e,moments:n=[],sinceHours:s=md,isRunning:o=()=>!1,restored:r=new Map,now:i=Date.now(),repoFilter:a=null}={}){let l=t.filter(f=>f&&f.id&&(f.machine||e)===e),c=new Map,u=null;for(let f of n){let p=l.map(w=>ub(w,f.restartAt)).filter(Boolean),h=p.length?Math.max(...p):f.restartAt,m=Math.min(h,f.restartAt)-ib,y=f.upAt+ab;for(let w of l){if(c.has(w.id)||w.archived||w.ignored||!w.cwd||ht(w)||w.surface&&w.surface!=="terminal"||o(w))continue;let k=r.get(w.id);if(k&&i-k<lb)continue;let S=Ll(w);if(!S||S<f.restartAt-s*36e5||S>f.upAt+12e4)continue;let v=Xn(w.endedAt),A;if(!v)A="never ended";else{if(w.endReason&&cb.has(w.endReason)||v<m||v>y)continue;A=w.endReason==="closed"?"process gone after the restart":w.endReason?"ended ("+w.endReason+") as the Mac shut down":"ended at the restart"}a&&!a(w)||(c.set(w.id,{s:w,lastAliveAt:S,endedAt:v||null,why:A,moment:f.kind}),u=u||f)}}let d=[...c.values()].sort((f,p)=>p.lastAliveAt-f.lastAliveAt);return{moment:u||n[0]||null,sessions:d}}function hd(t){let e=String(t);return/^[A-Za-z0-9_./~@%+=:,-]+$/.test(e)?e:"'"+e.replace(/'/g,"'\\''")+"'"}function db(t,e={}){let n=t.title&&t.titleSource!=="none"?t.title:null;return e.tabTitle===!1?n:Lc(e.tabTitleFormat,{name:n,id:t.id,repo:t.repo&&t.repo.name||null,marker:!!e.tabMarker})}function ig(t,e={}){let n=db(t,e),s="claude --resume "+hd(t.id)+(n?" --name "+hd(n):"");return{cwd:t.cwd,name:n,claude:s,line:"cd "+hd(t.cwd)+" && "+s}}function fb(){return["on u8(t)",' if t does not start with "'+pd+'" then return t'," set h to text "+(pd.length+1)+" thru -1 of t",' if h is "" then return ""',' return run script ((character id 171) & "data utf8" & h & (character id 187) & " as text")',"end u8","on run argv",' set out to ""',' tell application "Ghostty"'," set win to missing value"," try"," if (count of windows) > 0 then set win to front window"," end try"," set i to 1"," repeat while i < (count of argv)"," try"," set d to my u8(item i of argv)"," set c to my u8(item (i + 1) of argv)"," set cfg to new surface configuration"," set initial working directory of cfg to d"," set initial input of cfg to c & linefeed"," if win is missing value then"," set win to new window with configuration cfg"," else"," new tab in win with configuration cfg"," end if",' set out to out & "ok" & linefeed'," on error e",' set out to out & "error " & e & linefeed'," end try"," set i to i + 2"," end repeat"," activate"," end tell"," return out","end run"].join(`
|
|
194
|
+
`)}async function Dl(t,{exec:e,platform:n=process.platform,timeoutMs:s=3e4}={}){if(!t.length)return{ok:!0,how:"ghostty",results:[],message:"nothing to restore"};if(n!=="darwin")return{ok:!1,how:"print",results:[],message:"Ghostty tabs can only be opened on macOS"};let o=["osascript","-e",fb(),...t.flatMap(c=>[Xm(c.cwd),Xm(c.claude)])],r=await e(o,{timeoutMs:s});if(r.missing)return{ok:!1,how:"print",results:[],message:"osascript isn't available"};if(r.code!==0){let c=String(r.stderr||"").trim(),u=/-1743|not allowed|Not authorized/i.test(c)?"macOS didn't let this app control Ghostty (System Settings → Privacy & Security → Automation)":/-2741|Expected|syntax error/i.test(c)?"this Ghostty has no AppleScript support (Ghostty 1.3.0 or newer has it)":/-1728|Can.t get application|-10814|Application isn.t running/i.test(c)?"Ghostty isn't installed":"Ghostty said: "+(c||"exit "+r.code);return{ok:!1,how:"print",results:[],message:u,stderr:c}}let i=String(r.stdout||"").split(/\r?\n/).filter(Boolean),a=t.map((c,u)=>{let d=i[u]||"";return d==="ok"?{ok:!0}:{ok:!1,error:d.replace(/^error\s*/,"")||"no answer from Ghostty"}}),l=a.filter(c=>c.ok).length;return{ok:l===t.length,how:"ghostty",results:a,message:l+" of "+t.length+" opened in Ghostty"}}var md,ib,ab,lb,cb,Xn,ng,pd,Xm,yd=V(()=>{ve();ke();md=24,ib=900*1e3,ab=600*1e3,lb=600*1e3,cb=new Set(["prompt_input_exit","clear","logout","bypass_permissions_disabled","resume"]),Xn=t=>{let e=R(t);return isNaN(e)?0:e};ng=(t,e)=>Qm.join(we(t).secret,"daemon-starts-"+String(e||"this").replace(/[^A-Za-z0-9._-]/g,"_")+".json");pd="u8:",Xm=t=>pd+Buffer.from(String(t),"utf8").toString("hex").toUpperCase()});import{EventEmitter as hb}from"node:events";import At from"node:fs";import pb from"node:os";import jo from"node:path";function gb(t){try{return t()}catch(e){throw e&&e.badWindow?M(400,"bad_window",e.message):e}}async function yb(t){try{return await t()}catch(e){throw e&&e.badWindow?M(400,"bad_window",e.message):e}}function ag(t={}){let e=(o,r,i=400)=>(Array.isArray(o)?o:[]).map(a=>String(a??"").replace(/\s+/g," ").trim().slice(0,i)).filter(Boolean).slice(0,r),n=(Array.isArray(t.files)?t.files:[]).slice(0,10).map(o=>o&&typeof o=="object"?{path:String(o.path||"").trim().slice(0,300),why:String(o.why||"").replace(/\s+/g," ").trim().slice(0,300)}:{path:"",why:String(o||"").trim().slice(0,300)}).filter(o=>o.path||o.why);return{length:cn(t.length,{strict:!0})||null,decisions:e(t.decisions,6),questions:e(t.questions,5),files:n,didntWork:e(t.didntWork,5),recap:String(t.recap||"").replace(/\s+/g," ").trim().slice(0,1600),nextSteps:e(t.nextSteps,5)}}function M(t,e,n){let s=new Error(n);return s.status=t,s.code=e,s}var mb,Rl,wd=V(()=>{eo();ve();nu();gn();br();xs();va();Aa();go();La();Kr();Kp();zp();As();Qa();vs();Yu();lm();Cr();ke();cl();wm();ni();Io();Yr();qn();zn();Jm();Zn();yd();mb=18e4;Rl=class extends hb{constructor({root:e,cfg:n,summarizer:s,now:o,log:r,digestSettleMs:i,pullImpl:a,pullFetch:l,tickMs:c,settleMs:u,machine:d,updateChecker:f,updateStartDelayMs:p,liveChecker:h,jumpRunner:m,liveCheckMs:y,jumpExec:w,ghosttyRefreshMs:k,notifier:S,askTimeoutMs:v,askNoListenerMs:A,bootTime:b,restoreExec:g,platform:_,healer:x,agentFetch:C,agentEnv:E,agentPollMs:L}={}){super(),this.root=e,this.cfg=n||Z(e),this.machine=d||le(this.cfg),this.folder=new mt(e,this.machine),this.lastRemoteScan=0,this.updateChecker=f||null,this.updateStartDelayMs=p,this.summarizer=s||new gt(this.cfg,{root:this.root}),this.now=o||(()=>Date.now()),this.log=r||(()=>{}),this.pullImpl=a||null,this.pullFetch=l||null,this.tickMs=c||Number(process.env.CHECKPOINTS_TICK_MS)||15e3,this.settleMs=u??Number(process.env.CHECKPOINTS_SETTLE_MS||5e3),this.sessions=new Map,this.checkpoints=new Map,this.seen=new Map,this.generating=new Set,this.persistTimers=new Map,this.lastHbPersist=new Map,this.startedAt=Date.now(),this.bootTimeOpt=b,this.restoreExec=g||w||null,this.platform=_||process.env.CHECKPOINTS_JUMP_PLATFORM||process.platform,this.restored=new Map,this.restoring=!1,this.timers=[],this.liveChecker=h||ym,this.jumpRunner=m||yl,this.jumpExec=w||null,this.ghostty=new dl({exec:w||Kt,log:O=>this.log(O)}),this.ghosttyRefreshMs=k||Number(process.env.CHECKPOINTS_GHOSTTY_REFRESH_MS)||6e4,this.boardClients=0,this.lastGoAt=0,this.jumping=null,this.liveCheckMs=y||Number(process.env.CHECKPOINTS_LIVE_CHECK_MS)||(Number(this.cfg.liveCheckSec)||30)*1e3,this.asks=new Pa({now:this.now,onChange:(O,H)=>this.onAskChange(O,H),...v||process.env.CHECKPOINTS_ASK_TIMEOUT_MS?{timeoutMs:v||Number(process.env.CHECKPOINTS_ASK_TIMEOUT_MS)}:{},...A||process.env.CHECKPOINTS_ASK_NO_LISTENER_MS?{noListenerMs:A||Number(process.env.CHECKPOINTS_ASK_NO_LISTENER_MS)}:{}}),this.replies=new Fa({now:this.now}),this.notifier=S||zm,this.notified=new Map,this.healer=x||(process.env.CHECKPOINTS_HEAL==="off"?null:Ch),this.healTries=new Map,this.healed=0,this.openStateTimer=null,this.lists=new Gn(this.root,this.machine,{now:this.now}),this.listCache=null,this.listHints=new Map(Object.entries(this.readState("list-hints")||{})),this.transcriptDirs=null,this.agents=new bt(this.root,this.machine,{now:this.now,log:O=>this.log(O),fetchImpl:C||null,env:E||process.env,userAgent:"Leftoff/"+J}),this.agentPollMs=L||Number(process.env.LEFTOFF_AGENTS_POLL_MS)||Xp,this.spaces=new aa(this.root,this.machine,{now:this.now}),this.digestSettleMs=i??Number(process.env.LEFTOFF_DIGEST_SETTLE_MS||9e4),this.awakeSince=this.now()}isMine(e){return!e.machine||e.machine===this.machine}cfgFor(e){return this.isMine(e)?this.cfg:{...this.cfg,staleAfterSec:Math.max(this.cfg.staleAfterSec,900)}}load(){for(let{session:o,checkpoints:r}of this.folder.loadAll()){if(this.sessions.get(o.id))continue;o.statusChanges=[],this.sessions.set(o.id,o),this.checkpoints.set(o.id,r.sort((l,c)=>R(l.createdAt)-R(c.createdAt)));let a=r[r.length-1];a&&(!o.lastCheckpointAt||R(a.createdAt)>R(o.lastCheckpointAt))&&(o.lastCheckpointAt=a.createdAt,o.lastCheckpointId=a.id),o.needsRewrite&&(delete o.needsRewrite,this.isMine(o)&&this.persist(o,!0))}let e=this.loadOpenState(),n=this.drainSpool(),s=this.archiveGhosts();return this.log("loaded "+this.sessions.size+" sessions ("+e+" open without turns yet); drained "+n+" spooled items"+(s?"; archived "+s+" empty sessions with no folder":"")),this.loadUiState(),this}stateFile(e){return jo.join(this.root,"."+e+"."+this.machine.replace(/[^\w.-]/g,"_")+".json")}readState(e){try{return JSON.parse(At.readFileSync(this.stateFile(e),"utf8"))}catch{return null}}writeState(e,n){try{At.writeFileSync(this.stateFile(e),JSON.stringify(n,null,2)+`
|
|
195
|
+
`)}catch{}}loadUiState(){let e=this.readState("ui-state"),n=e;if(n)n.lastRunVersion!==J&&(n={...n,upgradedFrom:n.lastRunVersion||null,upgradeNoticeFor:J,lastRunVersion:J});else{let s=this.sessions.size===0;n={lastRunVersion:J,lastSeenVersion:s?J:null,upgradedFrom:s?null:"an earlier version",upgradeNoticeFor:s?null:J}}this.uiState=n,JSON.stringify(e)!==JSON.stringify(n)&&this.writeState("ui-state",n)}changelog(){let e=this.uiState||{},n=gp(yo(),e.lastSeenVersion,J);return{current:J,lastSeen:e.lastSeenVersion||null,upgradedFrom:e.upgradedFrom||null,upgradeNotice:e.upgradeNoticeFor===J,entries:n}}markChangelogSeen(e){let n=e||J,s={...this.uiState||{}};return(!s.lastSeenVersion||Ye(n,s.lastSeenVersion)>0)&&(s.lastSeenVersion=Ye(n,J)>0?J:n),s.upgradeNoticeFor=null,this.uiState=s,this.writeState("ui-state",s),this.changelog()}dismissUpgradeNotice(){return this.uiState={...this.uiState||{},upgradeNoticeFor:null},this.writeState("ui-state",this.uiState),{ok:!0}}updateChecksOn(){return!(this.cfg.updates&&this.cfg.updates.check===!1)&&!process.env.CHECKPOINTS_NO_UPDATE_CHECK}scheduleUpdateCheck(){if(this._updateTimer&&clearTimeout(this._updateTimer),!this.updateChecksOn())return;let e=this.readState("update-state"),n=e&&e.checkedAt?Date.now()-Date.parse(e.checkedAt):1/0,s=n<36e5?Ou-n:this.updateStartDelayMs??2e4;this._updateTimer=this.later(Math.max(1e3,s),async()=>{try{await this.runUpdateCheck()}catch(o){this.log("update check failed: "+o.message)}this.stopped||this.scheduleUpdateCheck()})}async runUpdateCheck({force:e=!1}={}){if(!e&&!this.updateChecksOn())return this.updateState();let n=this.cfg.updates&&this.cfg.updates.source||process.env.CHECKPOINTS_UPDATE_SOURCE||null,s=await(this.updateChecker||Br)({source:n});return this.writeState("update-state",s),s.available&&this.log("update available: "+(s.remote.version?"v"+s.remote.version:s.remote.sha.slice(0,7))),this.emit("session.updated",{update:!0}),this.updateState()}updateState(){let e=this.readState("update-state");if(!e)return{checked:!1,enabled:this.updateChecksOn()};let n=e.current&&e.current.version!==J;return{...e,checked:!0,enabled:this.updateChecksOn(),available:!!e.available&&!n}}start(){let e=setInterval(()=>this.tick().catch(i=>this.log("tick error: "+i.message)),this.tickMs);e.unref&&e.unref(),this.timers.push(e);let n=setInterval(()=>this.checkLiveness().catch(i=>this.log("live check error: "+i.message)),this.liveCheckMs);n.unref&&n.unref(),this.timers.push(n),this.later(500,()=>this.checkLiveness().catch(()=>{}));for(let i of this.sessions.values())this.isMine(i)&&i.capture==="mod"&&!i.cwd&&!i.endedAt&&!i.archived&&this.scheduleHeal(i,2e3);this.daemonStarts=sg(this.root,this.machine,this.startedAt);let s=setInterval(()=>{(this.boardClients>0||Date.now()-this.lastGoAt<10*6e4)&&this.scheduleGhosttyRefresh("periodic",0)},this.ghosttyRefreshMs);s.unref&&s.unref(),this.timers.push(s),this.scheduleDigest(),this.scheduleUpdateCheck();let o=setInterval(()=>this.syncthingTick().catch(i=>this.log("syncthing: "+i.message)),Number(process.env.LEFTOFF_SYNCTHING_POLL_MS)||1500);o.unref&&o.unref(),this.timers.push(o);let r=setInterval(()=>this.agentsTick().catch(i=>this.log("agents: "+i.message)),this.agentPollMs);return r.unref&&r.unref(),this.timers.push(r),this.later(Number(process.env.LEFTOFF_AGENTS_FIRST_MS)||3e3,()=>this.agentsTick().catch(()=>{})),this}async agentsTick(){if(this.stopped)return[];let e=[];for(let n of this.agents.sources())if(this.agents.pullsHere(n))try{e.push(await this.pullAgent(n.name))}catch(s){this.log("agents: "+n.name+": "+s.message)}return e}agentError(e){if(e&&e.status)return e;let n=e&&e.code||"agent_error";return M(n==="not_found"?404:n==="not_puller"?409:n==="bad_name"||n==="bad_url"||n==="bad_key"?400:500,n,e&&e.message||String(e))}async pullAgent(e,{force:n=!1}={}){let s;try{s=await this.agents.pull(e,{force:n})}catch(o){throw this.agentError(o)}return s.result==="ok"&&s.applied&&s.applied.written&&(this.listCache=null,this.emit("session.updated",{agent:e}),this.emit("needs.updated",{agent:e}),this.emit("lists.updated",{agent:e})),this.emit("agents.updated",{name:e,result:s.result}),s}agentView(){return this.agents.view()}agentsOverview({local:e=!0}={}){let n=this.agents.view({fresh:!0});return{machine:this.machine,pollSec:Math.round(this.agentPollMs/1e3),agents:n.agents.map(s=>({name:s.name,display:s.display,icon:s.icon,url:s.url,reply:s.reply||null,replyHint:s.replyHint||null,feedUrl:e?s.source.url:(()=>{try{return new URL(s.source.url).host}catch{return null}})(),addedAt:s.source.addedAt,addedOn:s.source.addedOn,puller:s.puller,pullsHere:s.pullsHere,hasKey:s.hasKey,status:s.status,sessions:s.records.length,open:s.records.filter(o=>o.inFeed&&o.session.status!=="done").length,needsYou:s.records.reduce((o,r)=>o+(Wn(s,r)||{items:[]}).items.length,0)}))}}async addAgent(e={}){let n;try{n=this.agents.add({name:String(e.name||"").trim().toLowerCase(),url:e.url,key:e.key==null||e.key===""?null:e.key})}catch(o){throw this.agentError(o)}this.log("agents: "+(n.replaced?"updated ":"added ")+n.source.name+" ("+n.source.url+")"+(n.previousPuller?"; now pulled here instead of "+n.previousPuller:""));let s=null;if(e.pull!==!1)try{s=await this.pullAgent(n.source.name,{force:!0})}catch(o){s={result:"error",error:{message:o.message}}}return this.emit("agents.updated",{name:n.source.name,result:"added"}),{...n,pull:s}}removeAgent(e,{purge:n=!1}={}){let s;try{s=this.agents.remove(e,{purge:n})}catch(o){throw this.agentError(o)}return this.log("agents: removed "+e+(s.movedTo?" (history kept in "+s.movedTo+")":" (purged)")),this.listCache=null,this.emit("session.updated",{agent:e}),this.emit("needs.updated",{agent:e}),this.emit("lists.updated",{agent:e}),this.emit("agents.updated",{name:e,result:"removed"}),s}agentSessions(e=this.now()){let n=[];for(let s of this.agentView().agents)for(let o of s.records)n.push(this.withSpace(qa(s,o,e)));return n}agentSession(e,{full:n=!0}={}){for(let s of this.agentView().agents)for(let o of s.records)if("agent:"+s.name+":"+o.session.id===e)return this.withSpace(qa(s,o,this.now(),{full:n}));return null}assertNotAgent(e,n="that"){if(xo(e))throw M(409,"agent_session","this session comes from an external agent’s feed and is read-only here; "+n+" happens with the agent itself")}async syncthingTick(){if(this.stBusy||this.stopped)return;let e=It();if(!(!e||e.used||e.expiredLogged)){this.stBusy=!0;try{let n=ot();if(!n.ok)return;await gr({client:n.client,log:s=>this.log(s)})}finally{this.stBusy=!1}}}needsTabMarker(e,n=[]){if(!e||!this.isMine(e))return!1;let s=e.term||{};if(s.kind!=="ghostty"||ul(s.version)===!0)return!1;let o=c=>hs(c.title||"").trim().toLowerCase(),r=c=>String(c.cwd||"").replace(/\/+$/,""),i=o(e),a=r(e);return[...n,...[...this.sessions.values()].filter(c=>this.isMine(c)&&!c.endedAt&&!["ended","stale"].includes(c.status))].some(c=>c&&c.id!==e.id&&(i&&o(c)===i||a&&r(c)===a))}later(e,n){let s=setTimeout(n,e);return s.unref&&s.unref(),this.timers.push(s),s}stop(){this.stopped=!0;for(let e of this.timers)clearInterval(e),clearTimeout(e);for(let e of this.persistTimers.values())clearTimeout(e);for(let e of this.sessions.values())this.persistNow(e);this.openStateTimer&&(clearTimeout(this.openStateTimer),this.openStateTimer=null),this.writeOpenState()}openStateRows(){let e=["id","machine","capture","cwd","repo","transcriptPath","model","surface","pid","tty","pidStartedAt","pidSource","term","startedAt","lastEventAt","lastHeartbeatAt","lastActivityAt","parentSessionId","startSource","historyTurns","healedAt","healedVia","inTurn","needsInput"],n=[];for(let s of this.sessions.values()){if(!this.isMine(s)||s.endedAt||s.archived||this.worthPersisting(s))continue;let o={};for(let r of e)s[r]!==void 0&&s[r]!==null&&(o[r]=s[r]);n.push(o)}return n}scheduleOpenState(){this.openStateTimer||this.stopped||(this.openStateTimer=setTimeout(()=>{this.openStateTimer=null,this.writeOpenState()},1e3),this.openStateTimer.unref&&this.openStateTimer.unref())}writeOpenState(){let e=this.openStateRows(),n=this.lastOpenState,s=JSON.stringify({savedAt:G(Date.now()),sessions:e},null,2)+`
|
|
196
|
+
`;if(n!==JSON.stringify(e)){this.lastOpenState=JSON.stringify(e);try{At.writeFileSync(this.stateFile("open-sessions"),s)}catch{}}}loadOpenState(){let e=this.readState("open-sessions");if(!e||!Array.isArray(e.sessions))return 0;let n=this.now(),s=0;for(let o of e.sessions){if(!o||!o.id||this.sessions.has(o.id))continue;let r=Math.max(R(o.lastHeartbeatAt)||0,R(o.lastEventAt)||0,R(o.startedAt)||0);if(!o.pid&&n-r>24*36e5)continue;let i={turns:0,turnsSinceCheckpoint:0,backgroundTasks:0,pinned:!1,archived:!1,...o,machine:o.machine||this.machine,statusChanges:[]};this.sessions.set(i.id,i),this.checkpoints.set(i.id,[]),s++}return this.lastOpenState=null,s}archiveGhosts(){let e=this.now(),n=0;for(let s of this.sessions.values()){if(!this.isMine(s)||s.archived||!s.folder||!Ws(s,(this.checkpoints.get(s.id)||[]).length))continue;let o=R(s.lastHeartbeatAt||s.lastEventAt)||0;!s.endedAt&&e-o<(this.cfg.staleAfterSec||600)*1e3||(s.archived=!0,s.archivedReason="empty session with no folder or process (v0.4.8 cleanup)",this.persist(s,!0),n++)}if(n){let s=this.readState("cleanup")||{};this.writeState("cleanup",{...s,ghostsArchived:(s.ghostsArchived||0)+n,lastArchivedAt:G(e),lastArchived:n})}return n}emptyCounts(){let e=0,n=0;for(let o of this.sessions.values()){if(o.archived||!this.isMine(o))continue;let r=(this.checkpoints.get(o.id)||[]).length;ht(o,r)&&(e++,Ws(o,r)&&n++)}let s=this.readState("cleanup")||{};return{empty:e,ghosts:n,healed:this.healed,archivedGhosts:s.ghostsArchived||0,lastArchivedAt:s.lastArchivedAt||null}}noteHistory(e){let n=tu(e.transcriptPath);n&&(e.historyTurns=n.prompts||0,e.historyTurns>0&&this.persist(e))}scheduleHeal(e,n=1e3){if(!this.healer||this.stopped||e.healTimer||e.healing)return;let s=this.healTries.get(e.id)||{n:0,at:0},o=[0,6e4,3e5,9e5][s.n]??18e5,r=Math.max(n,s.at+o-Date.now());e.healTimer=setTimeout(()=>{e.healTimer=null,this.healSession(e).catch(i=>this.log("heal "+e.id.slice(0,8)+": "+i.message))},r),e.healTimer.unref&&e.healTimer.unref()}async healSession(e){if(!this.healer||e.healing||e.archived||e.endedAt||!this.isMine(e)||e.cwd&&e.pid)return!1;let n=this.healTries.get(e.id)||{n:0,at:0};this.healTries.set(e.id,{n:n.n+1,at:Date.now()}),e.healing=!0;try{let s=new Set([...this.sessions.values()].filter(i=>i!==e&&i.pid&&!i.endedAt).map(i=>i.pid)),o=await this.healer(e.id,{cwd:e.cwd||null,claimedPids:s});if(!o)return!1;let r=[];return o.cwd&&!e.cwd&&(e.cwd=o.cwd,r.push("folder")),o.transcriptPath&&!e.transcriptPath&&(e.transcriptPath=o.transcriptPath),o.prompts!=null&&e.historyTurns==null&&(e.historyTurns=Math.max(0,o.prompts-(e.turns||0))),o.customTitle&&!e.title&&!or(o.customTitle).id&&(e.title=F(o.customTitle,120),e.titleSource="custom",r.push("name")),o.proc&&o.proc.pid>1&&!e.pid&&(e.pid=o.proc.pid,e.tty=o.proc.tty||null,e.pidStartedAt=o.proc.pidStartedAt||null,e.pidSource="daemon",e.pidAliveAt=this.now(),r.push("process "+e.pid)),o.term&&!e.term&&(e.term=o.term,r.push("terminal "+(o.term.label||o.term.kind))),r.length?(e.healedAt=G(this.now()),e.healedVia=(o.via||[]).join(","),this.healed++,this.log("session "+e.id.slice(0,8)+" was known only by id; found its "+r.join(", ")+" ("+e.healedVia+")"),r.includes("folder")&&await this.enrichRepo(e),this.refreshStatus(e),this.persist(e,!0),this.emit("session.updated",this.publicSession(e)),!0):!1}finally{e.healing=!1,!e.endedAt&&!e.archived&&(!e.cwd||!e.pid)&&!this.stopped&&this.scheduleHeal(e,5e3)}}drainSpool(){let e=0;for(let n of this.folder.readSpool())try{let s=JSON.parse(n.body);s&&s.hook&&s.payload?this.ingestHook(s.hook,s.payload,s.at?R(s.at):n.t):s&&Array.isArray(s.events)?this.ingest(s.events):s&&s.type&&s.sessionId?this.ingest([s]):s&&s.checkpoint&&s.sessionId&&this.addCheckpoint(s.sessionId,s.checkpoint),At.unlinkSync(n.file),e++}catch(s){this.log("spool item skipped: "+n.file+" "+s.message);try{At.renameSync(n.file,n.file+".bad")}catch{}}return e}ingest(e){let n=this.now(),s=0,o=0,r=new Set;for(let u of e||[]){if(!u||!u.sessionId||!Of.includes(u.type))throw M(400,"bad_event","each event needs sessionId and a known type");if(xo(String(u.sessionId)))throw M(400,"bad_event","agent:… ids belong to external agents’ feeds");let d=u.type+"|"+(u.at||""),f=this.seen.get(u.sessionId);if(f||this.seen.set(u.sessionId,f=new Set),u.at&&f.has(d)){o++;continue}if(u.at&&(f.add(d),f.size>2e3&&f.delete(f.values().next().value)),process.env.CHECKPOINTS_IGNORE_SESSION&&u.sessionId===process.env.CHECKPOINTS_IGNORE_SESSION)continue;let p=this.sessions.get(u.sessionId),h=!p;p||(p={id:u.sessionId,machine:this.machine,turns:0,turnsSinceCheckpoint:0,backgroundTasks:0,pinned:!1,archived:!1,statusChanges:[]},this.sessions.set(p.id,p),this.checkpoints.set(p.id,[]));let m=p.title,y=p.turns;Bf(p,{...u,at:u.at||G(n)}),(h||u.data&&u.data.cwd&&!(p.repo&&p.repo.root))&&this.enrichRepo(p),u.type==="session.started"&&p.startSource==="resume"&&p.historyTurns==null&&p.transcriptPath&&this.noteHistory(p),!p.cwd&&p.capture==="mod"&&this.isMine(p)&&this.scheduleHeal(p),p.turns===1&&y===0&&!p.title&&this.autoTitle(p),p.title!==m&&(p.titleChangedAt=G(n)),u.type==="session.ended"&&this.onEnded(p),u.type==="turn_completed"&&u.data&&u.data.listHint&&typeof u.data.listHint=="object"&&this.noteListHint(p,u.data.listHint,u.at||G(n)),this.settleAsksFor(p,u),p.term&&p.term.kind==="ghostty"&&this.isMine(p)&&(u.type==="session.opened"?this.scheduleGhosttyRefresh("session started",1500):p.title!==m&&this.scheduleGhosttyRefresh("renamed",3e3)),s++,r.add(p)}let i;for(let u of r){this.refreshStatus(u,n);let d=e.every(f=>f.type==="heartbeat");if(this.persist(u,!1,d),this.emit("session.updated",this.publicSession(u)),!i&&u.capture==="mod"&&!["ended","stale"].includes(u.status)){let f=Rc(u,n,this.cfg);f&&(u.dueSince||(u.dueSince=n),i={sessionId:u.id,reason:f})}}let a=r.size===1?[...r][0]:null,l=!!(a&&a.capture==="mod"&&this.isMine(a)&&!a.cwd&&!a.endedAt),c=a?this.listSignature(a.id):null;return{ok:!0,accepted:s,duplicate:o,...i?{checkpointDue:i}:{},...a?{session:{id:a.id,title:a.title||null,repoName:fe(a),tabMarker:this.needsTabMarker(a)}}:{},...l?{hello:!0}:{},...c?{lists:c}:{}}}ingestHook(e,n,s){if(n&&n.session_id&&process.env.CHECKPOINTS_IGNORE_SESSION===n.session_id)return{ok:!0,accepted:0};let o=Bp(e,n,s||this.now());return o.length?this.ingest(o):{ok:!0,accepted:0,ignored:!0}}async enrichRepo(e){if(e.cwd)try{let n=await $p(e.cwd);n&&(e.repo={...n,...e.repo||{},root:e.repo&&e.repo.root||n.root,branch:e.repo&&e.repo.branch||n.branch}),Yp(e.repo&&e.repo.root||e.cwd,this.cfg)&&(e.ignored=!0),this.persist(e)}catch{}}onEnded(e){e.endSettleUntil=this.now()+this.settleMs,this.later(this.settleMs+20,()=>this.tick().catch(()=>{}))}refreshRemote(){this.lastRemoteScan=this.now();for(let{session:e,checkpoints:n}of this.folder.loadAll()){if(this.isMine(e))continue;let s=this.sessions.get(e.id),o=this.checkpoints.get(e.id)||[];if(s&&!this.isMine(s)){if(s.lastEventAt===e.lastEventAt&&s.lastActivityAt===e.lastActivityAt&&s.title===e.title&&o.length===n.length&&s.endedAt===e.endedAt)continue;e.statusChanges=s.statusChanges||[],e.status=s.status}else{if(s)continue;e.statusChanges=[]}this.sessions.set(e.id,e),this.checkpoints.set(e.id,n.sort((r,i)=>R(r.createdAt)-R(i.createdAt))),this.refreshStatus(e),this.emit("session.updated",this.publicSession(e))}}refreshStatus(e,n=this.now()){let s=Rt(e,n,this.cfgFor(e));return s!==e.status?(e.status=s,e.statusChanges=[...e.statusChanges||[],{at:G(n),status:s}].slice(-50),!0):!1}bootTime(){return this.bootTimeOpt!==void 0?typeof this.bootTimeOpt=="function"?this.bootTimeOpt():this.bootTimeOpt:(this.bootTimeCache===void 0&&(this.bootTimeCache=process.env.CHECKPOINTS_BOOT_TIME?Date.parse(process.env.CHECKPOINTS_BOOT_TIME)||null:tg()),this.bootTimeCache)}isRunningNow(e,n=this.now()){return this.liveInfo(e,Rt(e,n,this.cfgFor(e)),n).live==="open"}restoreView(e,n,s,o=[]){let r=this.publicSession(e,s),i=ig(e,{...this.cfg,tabMarker:this.needsTabMarker(e,o)});return{id:e.id,shortId:r.shortId,title:r.title,repoLabel:r.repoLabel,repoKey:r.repoKey,branch:e.repo&&e.repo.branch||null,cwd:e.cwd,lastAliveAt:n?new Date(n.lastAliveAt).toISOString():Ll(e)?new Date(Ll(e)).toISOString():null,endedAt:e.endedAt||null,why:n?n.why:null,name:i.name,command:i.line,claude:i.claude}}restoreCandidates({since:e,repo:n}={},s=this.now()){let o=eg(e)||md,r=this.bootTime(),i=og({bootAt:r,starts:this.daemonStarts||gd(this.root,this.machine),daemonStartedAt:this.startedAt}),a=n?String(n).toLowerCase():null,l=a?d=>[this.repoKeyFor(d),d.repo&&d.repo.name,d.cwd].some(f=>f&&String(f).toLowerCase().includes(a)):null,c=rg([...this.sessions.values()],{machine:this.machine,moments:i,sinceHours:o,isRunning:d=>this.isRunningNow(d,s),restored:this.restored,now:s,repoFilter:l}),u=c.moment;return{machine:this.machine,sinceHours:o,restartAt:u?new Date(u.restartAt).toISOString():null,restartKind:u?u.kind:null,bootAt:r?new Date(r).toISOString():null,daemonStartedAt:new Date(this.startedAt).toISOString(),sessions:c.sessions.map(d=>this.restoreView(d.s,d,s,c.sessions.map(f=>f.s))),ghostty:this.platform==="darwin"}}restoreTargets(e,n=this.now()){let s=[];for(let o of[...new Set((e||[]).map(String))].slice(0,50)){let r=this.sessions.get(o);if(!r)throw M(404,"not_found","no such session: "+o);if(!this.isMine(r))throw M(409,"other_machine",'"'+(r.title||o)+'" ran on '+(r.machine||"another machine")+"; restore it there");if(!r.cwd)throw M(409,"no_folder",'"'+(r.title||o)+'" has no folder on record');if(this.isRunningNow(r,n))throw M(409,"running",'"'+(r.title||o)+'" is already running (use Go)');s.push(r)}return s}markRestored(e,n=this.now()){for(let s of e)this.restored.set(s,n);this.emit("restore.updated",{ids:e})}async restore(e,{print:n=!1}={},s=this.now()){let o=this.restoreTargets(e,s);if(!o.length)throw M(400,"nothing","pick at least one session");if(this.restoring)throw M(409,"busy","a restore is already running");this.restoring=!0;try{let r=o.map(c=>this.restoreView(c,null,s,o));if(n)return{ok:!1,how:"print",opened:0,failed:0,sessions:r,message:"the commands, to run yourself"};let i=await Dl(r.map(c=>({cwd:c.cwd,claude:c.claude})),{exec:this.restoreExec||Kt,platform:this.platform}),a=r.map((c,u)=>({...c,ok:i.how==="ghostty"?!!(i.results[u]&&i.results[u].ok):!1,error:i.results[u]&&i.results[u].error?i.results[u].error:null})),l=a.filter(c=>c.ok);return l.length&&this.markRestored(l.map(c=>c.id),s),this.log("restore: "+i.message+(i.how==="print"?" ("+o.length+" commands printed)":"")),{ok:i.ok,how:i.how,opened:l.length,failed:a.length-l.length,sessions:a,message:i.message}}finally{this.restoring=!1}}async checkLiveness(e=this.now()){if(this.checkingLive)return 0;let n=[...this.sessions.values()].filter(o=>this.isMine(o)&&o.pid&&!o.endedAt);if(!n.length)return 0;this.checkingLive=!0;let s=0;try{let o=await this.liveChecker(n,{now:e});for(let r of n){let i=o.get(r.id);if(i){if(i.alive){r.pidAliveAt=e,this.refreshStatus(r,e)&&this.emit("session.updated",this.publicSession(r,e));continue}r.endedAt||(r.endedAt=G(e),r.closedAt=r.endedAt,r.endReason="closed",r.inTurn=!1,r.pidAliveAt=null,this.log("session "+r.id.slice(0,8)+" closed (Claude Code pid "+r.pid+" "+(i.reason==="pid_reused"?"was reused":"is gone")+")"),this.refreshStatus(r,e),this.persist(r,!0),this.emit("session.updated",this.publicSession(r,e)),this.onEnded(r),s++)}}}finally{this.checkingLive=!1}return s}liveInfo(e,n,s=this.now()){return e.endedAt?{live:"closed",via:e.closedAt?"pid":"event",since:e.endedAt}:n==="stale"?{live:"closed",via:"heartbeat",since:e.lastHeartbeatAt||e.lastEventAt||null}:this.isMine(e)&&e.pid?{live:"open",via:"pid",since:e.pidStartedAt||e.startedAt||null}:e.capture==="mod"?{live:"open",via:"heartbeat",since:e.startedAt||null}:{live:"unknown",via:"hooks",since:null}}jumpInfo(e,n){if(!this.isMine(e)){if(!this.peers)return{supported:!1,reason:"runs on "+(e.machine||"another machine")};if(n==="closed")return{supported:!1,reason:"closed"};let o=this.peerUsable(e.machine);return o.ok?{supported:!0,reason:null,peer:e.machine,via:o.via}:{supported:!1,reason:"runs on "+(e.machine||"another machine")+(o.reason?": "+o.reason:""),peer:e.machine,code:o.code||null}}if(n!=="open"&&n!=="unknown")return{supported:!1,reason:"closed"};if(!e.term)return{supported:!1,reason:"no terminal recorded (sessions started before v0.4, or hooks without the mod)"};let s=No(e);return{supported:s.supported,reason:s.reason||null}}async jump(e){this.assertNotAgent(e,"Go");let n=this.sessions.get(e);if(!n)throw M(404,"not_found","no such session");if(!this.isMine(n)){let a=await this.viaPeer(n.machine,"session.go",{sessionId:n.id},"Go");return{...a.result,peer:n.machine,peerVia:a.via,label:(a.result&&a.result.label||"its terminal")+" on "+n.machine}}let s=this.publicSession(n);if(s.live==="closed")throw M(409,"closed","That session is closed"+(s.resumeCommand?". Resume it with: "+s.resumeCommand:""));if(!n.term)return{ok:!1,code:"unsupported",message:"No terminal was recorded for this session (it started before v0.4, or without the mod).",steps:[],elapsedMs:0};if(this.jumping)return this.jumping.id===n.id?this.jumping.p:{ok:!1,code:"busy",message:"Still switching to another session; try again in a moment.",steps:[],elapsedMs:0};this.lastGoAt=Date.now();let o=No({...n,ghosttyTerminalId:this.ghostty.get(n.id)}),r={...this.jumpExec?{exec:this.jumpExec}:{}};o.kind==="ghostty"&&(r.ghostty={caps:this.ghostty.getCaps({resourcesDir:n.term.resourcesDir,version:n.term.version}),activate:this.cfg.ghostty&&this.cfg.ghostty.activate||"auto",onStale:()=>this.ghostty.forget(n.id),onFound:(a,l,c)=>{this.ghostty.set(n.id,a,l),this.ghostty.applyListing(this.ghosttyOpenSessions(),c,"go")}});let i=(async()=>{let a=await this.jumpRunner(o,r);return o.kind==="ghostty"&&this.noteGhosttyAutomation(a),this.log("go "+n.id.slice(0,8)+" ("+o.kind+"): "+(a.ok?"ok"+(a.how?" by "+a.how:""):a.code+" "+a.message)+(a.elapsedMs!=null?" in "+a.elapsedMs+" ms":"")+(a.steps&&a.steps.length?" ["+pl(a.steps)+"]":"")),{...a,kind:o.kind,label:o.label}})();this.jumping={id:n.id,p:i};try{return await i}finally{this.jumping=null}}ghosttyOpenSessions(){return[...this.sessions.values()].filter(e=>this.isMine(e)&&!e.endedAt&&e.term&&e.term.kind==="ghostty")}ghosttyAutoOk(){if((process.env.CHECKPOINTS_JUMP_PLATFORM||process.platform)!=="darwin")return!1;let e=this.readState("ghostty-automation");return!!(e&&e.ok)}noteGhosttyAutomation(e){let n=this.readState("ghostty-automation"),s=null;e&&(e.ok||["not_found","ambiguous"].includes(e.code))?s={ok:!0}:e&&e.code==="automation_denied"&&(s={ok:!1,code:e.code}),s&&(!n||n.ok!==s.ok)&&this.writeState("ghostty-automation",{...s,at:new Date().toISOString()})}scheduleGhosttyRefresh(e,n=1500){return this.stopped||this.ghosttyTimer||!this.ghosttyAutoOk()||!this.ghosttyOpenSessions().length?!1:(this.ghosttyTimer=this.later(n,()=>{this.ghosttyTimer=null,this.refreshGhostty(e).catch(s=>this.log("ghostty cache error: "+s.message))}),!0)}async refreshGhostty(e="manual"){let n=this.ghosttyOpenSessions();if(!n.length)return null;let s=await this.ghostty.refresh(n,{reason:e});return s&&s.error&&/-1743|not authori[sz]ed/i.test(s.stderr||"")&&this.noteGhosttyAutomation({code:"automation_denied"}),s}boardOpened(){this.boardClients++,this.scheduleGhosttyRefresh("board opened",300)}boardClosed(){this.boardClients=Math.max(0,this.boardClients-1)}async jumpSpeed(){let e=this.ghosttyOpenSessions()[0],n=this.ghostty.getCaps(e?{resourcesDir:e.term.resourcesDir,version:e.term.version}:{}),s=await hl({exec:this.jumpExec||Kt,caps:n});return s.ok&&this.noteGhosttyAutomation({ok:!0}),{...s,cache:{sessions:this.ghosttyOpenSessions().length,remembered:this.ghostty.map.size,last:this.ghostty.last},pid:process.pid,launchd:process.env.XPC_SERVICE_NAME&&process.env.XPC_SERVICE_NAME!=="0"?process.env.XPC_SERVICE_NAME:null}}async jumpCheck(e){let n=await wl(e,this.jumpExec?{exec:this.jumpExec}:{});return e==="ghostty"&&n.ok&&this.noteGhosttyAutomation({ok:!0}),n}where(e){this.assertNotAgent(e,"Go");let n=this.sessions.get(e);if(!n)throw M(404,"not_found","no such session");return{id:n.id,title:n.title||null,isLocal:this.isMine(n),machine:n.machine||this.machine,cwd:n.cwd||null,tty:n.tty||null,pid:n.pid||null,term:n.term||null,ghosttyTerminalId:this.ghostty.get(n.id)}}worthPersisting(e){let n=(this.checkpoints.get(e.id)||[]).length;return!!(e.title||e.turns>0||n||e.folder||e.historyTurns>0||e.endedAt&&!ht(e,n))}persist(e,n=!1,s=!1){if(s){let r=this.lastHbPersist.get(e.id)||0;if(Date.now()-r<6e4)return;this.lastHbPersist.set(e.id,Date.now())}if(n)return this.persistNow(e);if(this.persistTimers.has(e.id))return;let o=setTimeout(()=>{this.persistTimers.delete(e.id),this.persistNow(e)},300);o.unref&&o.unref(),this.persistTimers.set(e.id,o)}persistNow(e){if(!this.isMine(e))return;if(!this.worthPersisting(e)){this.scheduleOpenState();return}let n=this.persistTimers.get(e.id);n&&(clearTimeout(n),this.persistTimers.delete(e.id));try{this.folder.writeSession(e,this.checkpoints.get(e.id)||[])}catch(s){this.log("write session failed: "+s.message)}}async autoTitle(e){if(!(e.title||e.titling)){e.titling=!0;try{let n=await this.summarizer.title(e);!e.title&&n.title&&(e.title=n.title,e.titleSource=n.titleSource,this.persist(e,!0),this.emit("session.updated",this.publicSession(e)))}finally{e.titling=!1}}}setTitle(e,n,s="custom"){let o=this.sessions.get(e);if(!o)throw M(404,"not_found","no such session");return o.title=F(n,120),o.titleSource=s,this.persist(o,!0),this.emit("session.updated",this.publicSession(o)),o}patchSession(e,n){this.assertNotAgent(e,"renaming, pinning and archiving");let s=this.sessions.get(e);if(!s)throw M(404,"not_found","no such session");if(this.assertMine(s),n.title!=null){if(typeof n.title!="string"||!n.title.trim())throw M(400,"bad_title","title must be a non-empty string");s.title=F(n.title,120),s.titleSource="custom",s.term&&s.term.kind==="ghostty"&&this.scheduleGhosttyRefresh("renamed",5e3)}return n.pinned!=null&&(s.pinned=!!n.pinned),n.archived!=null&&(s.archived=!!n.archived),this.persist(s,!0),this.emit("session.updated",this.publicSession(s)),this.publicSession(s)}assertMine(e){if(!this.isMine(e))throw M(409,"other_machine",'this session belongs to machine "'+e.machine+'"; change it from that machine')}addCheckpoint(e,n,{by:s}={}){this.assertNotAgent(e,"checkpointing");let o=this.sessions.get(e);o&&this.assertMine(o),o||(o={id:e,machine:this.machine,turns:0,turnsSinceCheckpoint:0,backgroundTasks:0,capture:"mod",startedAt:n.createdAt||G(this.now()),statusChanges:[]},this.sessions.set(e,o),this.checkpoints.set(e,[]));let r=n.trigger||"manual";if(!Ic.includes(r))throw M(400,"bad_trigger","trigger must be one of "+Ic.join(", "));for(let u of["doing","stoppedAt","next"])if(typeof n[u]!="string")throw M(400,"bad_checkpoint",u+" is required");let i=this.checkpoints.get(e);if(n.id&&i.some(u=>u.id===n.id))return i.find(u=>u.id===n.id);let a=G(n.createdAt||this.now()),l={id:n.id||Bi(e,a),sessionId:e,machine:o.machine||this.machine,createdAt:a,trigger:r,sessionTitle:o.title||null,repo:o.repo?fe(o):null,branch:o.repo&&o.repo.branch||null,cwd:o.cwd||null,doing:n.doing,stoppedAt:n.stoppedAt,next:n.next,openQuestion:n.openQuestion||(/^YOU:/i.test(n.next)?n.next.replace(/^YOU:\s*/i,""):null),userNote:n.userNote||null,filesTouched:Array.isArray(n.filesTouched)?n.filesTouched.map(String).slice(0,50):[],turnsSinceLast:o.turnsSinceCheckpoint||0,parseOk:n.parseOk!==!1,generator:n.generator||{by:s||"mod"},...ag(n)},c=this.folder.writeCheckpoint(o,l);return i.push(c),o.lastCheckpointId=c.id,o.lastCheckpointAt=c.createdAt,o.turnsSinceCheckpoint=0,o.dueSince=null,(r==="dormant"||r==="long_run")&&(o.dormantStretch=o.lastActivityAt),(r==="session_end"||r==="clear")&&(o.finalCheckpointDone=!0),(o.endedAt||o.status==="stale")&&(o.finalCheckpointDone=!0),this.persist(o,!0),this.emit("checkpoint.created",c),this.emit("session.updated",this.publicSession(o)),c}async generateCheckpoint(e,n,{userNote:s,length:o,save:r=!0}={}){this.assertNotAgent(e,"checkpointing");let i=this.sessions.get(e);if(!i)throw M(404,"not_found","no such session");if(this.assertMine(i),this.generating.has(e))throw M(409,"busy","a checkpoint is already being generated for this session");this.generating.add(e);try{let{messages:a,meta:l}=i.transcriptPath?kr(i.transcriptPath):{messages:[],meta:{}};l&&l.gitBranch&&!(i.repo&&i.repo.branch)&&(i.repo={...i.repo||{},branch:l.gitBranch});let c=this.checkpoints.get(e)||[],u=c[c.length-1]||null,d=$i({session:i,previous:u,messages:a,cfg:this.cfg,userNote:s,length:o}),f;i.ignored?f={doing:"(repo opted out of summaries)",stoppedAt:i.status||"",next:"Open the session to see where it is.",parseOk:!0,generator:{by:"ignored"}}:f=await this.summarizer.checkpoint({system:d.system,prompt:d.prompt,messages:a,session:i,maxTokens:d.maxTokens,length:d.length});let p={trigger:n,doing:f.doing,stoppedAt:f.stoppedAt,next:f.next,openQuestion:f.openQuestion,userNote:s,filesTouched:d.filesTouched,parseOk:f.parseOk,generator:f.generator,length:d.length,decisions:f.decisions,questions:f.questions,files:f.files,didntWork:f.didntWork,recap:f.recap,nextSteps:f.nextSteps};return r?this.addCheckpoint(e,p,{by:"daemon"}):{...p,...ag(p),sessionId:e,preview:!0,sessionTitle:i.title||null,createdAt:G(this.now())}}finally{this.generating.delete(e)}}async tick(){if(this.stopped)return;let e=this.now();this.folder.spoolCount()&&this.drainSpool(),e-this.lastRemoteScan>(Number(process.env.CHECKPOINTS_REMOTE_SCAN_MS)||3e4)&&this.refreshRemote();for(let n of this.sessions.values()){if(this.refreshStatus(n,e)&&(this.persist(n),this.emit("session.updated",this.publicSession(n))),!this.isMine(n)||this.generating.has(n.id)||n.endSettleUntil&&e<n.endSettleUntil)continue;let s=Rc(n,e,this.cfg);if(!s){n.dueSince=null;continue}if(!(n.capture==="mod"&&!["ended","stale"].includes(n.status)&&(n.dueSince||(n.dueSince=e),e-n.dueSince<mb)))try{await this.generateCheckpoint(n.id,s)}catch(r){this.log("checkpoint failed for "+n.id+": "+r.message)}}await this.maybeScheduledDigest()}async makeDigest({date:e,by:n="manual"}={}){for(let r of this.sessions.values())this.persistNow(r);let s=this.now(),o;try{o=await Xa(this.folder.loadAll(),{now:s,cfg:this.cfg,machine:this.machine,date:e&&e!=="today"?e:this.digestDate(s),summarizer:this.summarizer,lists:this.listIndex({fresh:!0}).concat(this.agentListIndex()),agents:this.agents.view({fresh:!0}),spaces:this.spaces.map({fresh:!0})}),this.folder.writeDigest(o.meta.date,o.markdown)}catch(r){throw this.digestState={...this.digestState||{},lastError:{at:G(s),by:n,message:String(r&&r.message||r)}},this.log("digest failed ("+n+"): "+(r&&r.stack||r)),r}return this.digestState={...this.digestState||{},lastMadeAt:G(s),lastBy:n,lastSessions:o.meta.sessions,lastError:null},this.log("digest made ("+n+") for "+o.meta.date+": digests/"+o.meta.date+".md"),this.emit("digest.created",o.meta),o}standup({space:e=null,date:n=null}={}){for(let f of this.sessions.values())this.persistNow(f);let s=this.now(),o=this.digestSchedule(),r=this.digestDate(s),i=n&&n!=="today"?n:r;if(!/^\d{4}-\d{2}-\d{2}$/.test(i))throw M(400,"bad_date","date is YYYY-MM-DD");let a=s;if(i!==r){let f=this.folder.readDigest(i),p=f?Be(f.markdown).data||{}:{},[h,m,y]=i.split("-").map(Number),[w,k]=String(o.time||"07:00").split(":").map(Number);a=Date.parse(p.generated_at)||Ft(h,m,y,w,k,o.tz)}let l=this.spaces.map({fresh:!0}),c=null;if(e&&e!=="all")try{c=Vt(l,this.knownMachines()).find(f=>f.toLowerCase()===String(e).toLowerCase())||Er(e)}catch(f){throw M(400,"bad_space",f.message)}let u=null;try{u=this.agents.view({fresh:!0})}catch{}return{...el(this.folder.loadAll(),{now:a,date:i,space:c,spaces:l,cfg:this.cfg,tz:o.tz,digestTime:o.time||"07:00",agents:u}),date:i}}knownMachines(){let e=new Set(this.folder.machines());try{for(let n of this.folder.aliveMachines())e.add(n.machine)}catch{}try{for(let n of At.readdirSync(jo.join(this.root,"machines")))if(n.endsWith(".json")&&!n.startsWith("."))try{let s=JSON.parse(At.readFileSync(jo.join(this.root,"machines",n),"utf8"));s&&s.machine&&e.add(s.machine)}catch{}}catch{}for(let n of Object.keys(Nt(this.spaces.map(),"machines")))e.add(n);return[...e].filter(Boolean).sort()}spacesView(){let e=this.spaces.map({fresh:!0}),n=this.knownMachines(),s=a=>Object.entries(e[a]||{}).filter(([,l])=>l.space!=null).map(([l,c])=>({key:l,space:c.space,at:c.at,by:c.by||null})).sort((l,c)=>l.key.localeCompare(c.key)),o=Lh([...this.sessions.values()].filter(a=>!a.archived).map(a=>{let l=this.repoKeyFor(a);return{key:l==="no-repo"?"":l,name:this.repoLabels().get(l)||fe(a),at:a.lastActivityAt||a.startedAt}}),{home:pb.homedir()}),r=o.map(a=>a.name),i=[];try{i=this.agentView().agents.map(a=>({name:a.name,display:a.display,puller:a.puller||null,space:Bn(e,{id:"agent:"+a.name+":x",agent:{name:a.name,puller:a.puller}}).space}))}catch{}return{machine:this.machine,quickPicks:no,spaces:so([...Vt(e,n),...no]),inUse:Vt(e,n),machines:n.map(a=>{let l=e.machines[a];return{machine:a,space:Hn(e,a),set:!!(l&&l.space!=null),at:l&&l.space!=null?l.at:null,by:l&&l.space!=null?l.by:null,here:a===this.machine}}),repos:s("repos"),sessions:s("sessions"),agentOverrides:s("agents"),agents:i,knownRepos:r,repoSuggestions:o,file:"spaces.json",conflicts:this.spaces.cache&&this.spaces.cache.conflicts.length||0}}setSpace(e={}){let n;try{n=Tr(e.kind)}catch(r){throw M(400,"bad_kind",r.message)}let s=String(e.key==null?"":e.key).trim();if(!s)throw M(400,"bad_key","which "+n.replace(/s$/,"")+"?");let o=e.space;if((o===""||o===void 0)&&(o=null),o!=null)try{o=Er(o)}catch(r){throw M(400,"bad_space",r.message)}try{this.spaces.update((r,i)=>{r[n][s]={space:o==null?null:Vt(r,this.knownMachines()).concat(no).find(a=>a.toLowerCase()===o.toLowerCase())||o,at:i.at,by:i.by}})}catch(r){throw M(400,r.code||"bad_space",r.message)}return this.emit("spaces.updated",{by:this.machine}),this.spacesView()}listIndex({fresh:e=!1}={}){let n=Date.now();if(!e&&this.listCache&&n-this.listCache.at<2e3)return this.listCache.lists;let s=[];try{s=this.lists.all({includeArchived:!0}).map(o=>it(o))}catch(o){this.log("lists: "+(o&&o.message||o))}return this.listCache={at:n,lists:s},s}listSignature(e){let n=this.listIndex().filter(s=>s.sourceSession===e&&!s.archived);return n.length?n.map(s=>s.ref+":"+s.total+":"+s.items.filter(o=>o.done).map(o=>o.n).join(".")).sort().join("|"):null}listsChanged(e,n){this.listCache=null,this.emit("lists.updated",{ref:e&&e.ref||null,what:n})}sessionListInfo(e){let n=this.listIndex().filter(r=>r.sourceSession===e.id&&!r.archived),s=this.listHints.get(e.id),o=s&&!s.dismissed&&!s.savedRef?{count:s.count,name:s.name,kind:s.kind,at:s.at,fp:s.fp}:null;return{lists:n.map(r=>({ref:r.ref,name:r.name,done:r.done,total:r.total,progress:r.progress})),listHint:o}}saveListHints(){let e={},n=[...this.listHints.entries()].sort((s,o)=>(R(o[1].at)||0)-(R(s[1].at)||0)).slice(0,300);for(let[s,o]of n)e[s]=o;this.writeState("list-hints",e)}noteListHint(e,n,s){let o=(Array.isArray(n.items)?n.items:[]).slice(0,80).map(l=>({text:F(String(l&&l.text||l||""),500),done:!!(l&&l.done)})).filter(l=>l.text);if(o.length<3)return;let i=this.listIndex().filter(l=>l.sourceSession===e.id).find(l=>Wi(o,l.items)>=.6),a=this.listHints.get(e.id);a&&a.fp&&a.fp===n.fp&&(a.dismissed||a.savedRef)||(this.listHints.set(e.id,{count:Number(n.count)||o.length,name:F(String(n.name||ar({heading:n.heading},{fallback:e.title})),80),kind:n.kind||null,items:o,at:s,fp:n.fp||null,...i?{savedRef:i.ref}:{}}),this.saveListHints())}dismissListHint(e){let n=this.listHints.get(e);n&&(n.dismissed=!0,this.saveListHints());let s=this.sessions.get(e);return s&&this.emit("session.updated",this.publicSession(s)),{ok:!0}}findListsInSession(e){this.assertNotAgent(e,"saving lists");let n=this.sessions.get(e);if(!n)throw M(404,"not_found","no such session");let s=(n.transcriptPath&&At.existsSync(n.transcriptPath)?n.transcriptPath:null)||vr(e,this.transcriptDirs?{dirs:this.transcriptDirs}:{});if(!s)throw this.isMine(n)?M(404,"no_transcript","Claude Code's transcript for this session isn't on this machine (it looked in ~/.claude/projects). Type /cp-todo in the session instead."):M(409,"other_machine","This session ran on "+n.machine+", and its transcript is on that machine. Open Leftoff there (or type /cp-todo in the session) to find its lists.");let{messages:o}=kr(s,{maxBytes:32*1024*1024}),r=Yi(o,{minItems:2,fallback:n.title}),i=this.listIndex().filter(a=>a.sourceSession===e);return{transcript:s,messages:o.length,candidates:r.reverse().map(a=>{let l=i.find(c=>Wi(a.items,c.items)>=.6);return{n:a.n,name:a.name,kind:a.kind,heading:a.heading,count:a.items.length,items:a.items,message:a.message,savedAs:l?{ref:l.ref,name:l.name}:null}})}}assertMayEditLists(e={}){if(e.via==="remote"&&!(this.cfg.remote&&this.cfg.remote.allowAnswers))throw M(403,"lists_remote_off","Changing lists from another device follows the same switch as answering. On the machine running Leftoff, turn on Settings → Privacy & security → “Allow answering permission prompts and questions from other devices”.")}listError(e){if(e&&e.status)return e;let n=e&&e.code;return n==="no_list"?M(404,"no_list",e.message):n==="no_item"?M(404,"no_item",e.message):n==="ambiguous_list"?M(409,"ambiguous_list",e.message):n==="list_busy"?M(503,"list_busy",e.message):n&&/^empty_/.test(n)?M(400,n,e.message):e}saveList(e={},n={}){this.assertMayEditLists(n);let s=e.sessionId?this.sessions.get(e.sessionId):null;if(e.sessionId&&!s&&!Array.isArray(e.items))throw M(404,"not_found","no such session");let o=Array.isArray(e.items)?e.items:null,r=e.name?String(e.name):null,i=null;if(!o&&e.fromHint&&s){if(i=this.listHints.get(s.id),!i)throw M(404,"no_hint","Claude hasn't written a list in this session lately; use Find lists in this session");o=i.items,r=r||i.name}if(!o&&e.fromTranscript!=null&&s){let u=this.findListsInSession(s.id).candidates.find(d=>d.n===Number(e.fromTranscript));if(!u)throw M(404,"no_candidate","no list "+e.fromTranscript+" in that session's transcript");o=u.items,r=r||u.name}if(!o||!o.length)throw M(400,"empty_list","a list needs items (or fromHint / fromTranscript with a sessionId)");let a=s?fe(s):e.repo||null,l=s?this.repoKeyFor(s):e.repo||null,c;try{c=this.lists.create({name:r||ar({heading:null},{fallback:s&&s.title}),items:o.map(u=>typeof u=="string"?{text:u}:u),repo:a&&a!=="no-repo"?a:null,repoKey:l,sourceSession:s?s.id:e.sessionId||null,sourceMachine:s?s.machine||this.machine:null,sourceTitle:s&&s.title||null})}catch(u){throw this.listError(u)}if(s){let u=this.listHints.get(s.id);u&&(i||Wi(o,u.items)>=.6)&&(u.savedRef=c.ref,this.saveListHints())}return this.log("list saved: "+c.ref+" ("+c.items.length+" items"+(s?", from session "+Ke(s.id):"")+")"),this.listsChanged(c,"created"),s&&this.emit("session.updated",this.publicSession(s)),{ok:!0,list:it(c),context:vu([it(c)])}}getList(e){if(String(e||"").startsWith("~")){let n=this.agentListIndex().find(s=>s.ref===e);if(!n)throw M(404,"not_found","no such list");return n}try{return it(this.lists.resolve(e))}catch(n){throw this.listError(n)}}changeList(e,n={},s={}){if(String(e||"").startsWith("~"))throw M(409,"read_only","this list comes from an external agent’s feed and is read-only here; tell the agent instead");this.assertMayEditLists(s);let o,r={};try{if(o=this.lists.resolve(e),n.text&&!n.done&&!n.undone){let a=this.lists.findItem(o,n.text);if(!a)throw M(404,"no_item",'no item on "'+o.meta.name+'" matches "'+n.text+'"; give its number');n={...n,[n.undo?"undone":"done"]:[a.id]}}if(n.done||n.undone){let a=this.lists.setDone(o,[].concat(n.done||n.undone).map(Number),!!n.done);o=a.list,r.missing=a.missing}if(n.add){let a=this.lists.addItem(o,n.add);o=a.list,r.added=a.item.id}n.edit&&(o=this.lists.editItem(o,n.edit.n,n.edit)),n.remove!=null&&(o=this.lists.removeItem(o,n.remove)),n.name&&(o=this.lists.rename(o,n.name)),n.archived!=null&&(o=this.lists.archive(o,!!n.archived))}catch(a){throw this.listError(a)}this.listsChanged(o,"changed");let i=o.meta.sourceSession&&this.sessions.get(o.meta.sourceSession);return i&&this.emit("session.updated",this.publicSession(i)),{ok:!0,list:it(o),...r}}agentListIndex(){try{return Va(this.agentView())}catch(e){return this.log("agents: "+e.message),[]}}listsOverview({all:e=!1,repo:n=null}={}){let s=this.listIndex({fresh:!0}).concat(this.agentListIndex());e||(s=s.filter(r=>!r.archived)),n&&(s=s.filter(r=>r.repoDir===n||r.repo===n||r.repoKey===n));let o=new Map;for(let r of s){let i=r.repo||"no repo";o.has(i)||o.set(i,{repo:i,repoDir:r.repoDir,lists:[]}),o.get(i).lists.push(r)}for(let r of o.values())r.lists.sort((i,a)=>(i.open===0)-(a.open===0)||(R(a.updated)||0)-(R(i.updated)||0));return{lists:s,repos:[...o.values()].sort((r,i)=>r.repo.localeCompare(i.repo)),archived:this.listIndex().filter(r=>r.archived).length}}sessionLists(e){if(xo(e))return{lists:this.agentListIndex().filter(i=>i.sourceSession===e),hint:null,context:null,signature:null,readOnly:!0};if(!this.sessions.get(e))throw M(404,"not_found","no such session");let s=this.listIndex({fresh:!0}).filter(r=>r.sourceSession===e),o=this.listHints.get(e);return{lists:s,hint:o&&!o.dismissed&&!o.savedRef?o:null,context:vu(s),signature:this.listSignature(e)}}catchupEntries(){for(let n of this.sessions.values())this.persistNow(n);let e=[];try{e=Za(this.agents.view({fresh:!0}))}catch(n){this.log("agents: "+n.message)}return this.folder.loadAll().concat(e)}catchupNeeds(e,n){let s=this.sessions.get(e.id)||e,o=this.needsItem(s,n);if(!o)return null;let r="";return o.kind==="permission"&&o.ask&&o.ask.describe?r="needs your OK: "+o.ask.describe.verb+" "+o.ask.describe.detail:o.kind==="question"&&o.ask?r="asks: "+(((o.ask.questions||[])[0]||{}).question||"a question"):o.kind==="terminal"?r=o.message||"waiting in the terminal":o.kind==="reply"&&(r="asked you: "+(typeof o.question=="string"?o.question:"a question")),{kind:o.kind,message:r}}catchupArgs(e={}){let n=s=>(Array.isArray(s)?s:s==null||s===""?[]:String(s).split(",")).map(o=>String(o).trim()).filter(Boolean);return{since:e.since==null||e.since===""?void 0:String(e.since),to:e.to?String(e.to):null,repos:n(e.repos),machines:n(e.machines),length:e.length||void 0}}catchupOptions(e={}){let n=this.catchupArgs(e);return gb(()=>ba(this.catchupEntries(),{...n,now:this.now(),cfg:this.cfg,machine:this.machine,lastAt:this.folder.lastCatchupAt(),repoKeyFor:s=>this.repoKeyFor(s),needsOf:(s,o)=>this.catchupNeeds(s,o)}))}async makeCatchup(e={}){let n=this.catchupArgs(e),s=e.summary&&Array.isArray(e.summary.bullets)?e.summary:null,o=await yb(()=>Nr(this.catchupEntries(),{...n,now:this.now(),cfg:this.cfg,machine:this.machine,lastAt:this.folder.lastCatchupAt(),summarizer:e.useModel===!1?null:this.summarizer,given:s,repoKeyFor:i=>this.repoKeyFor(i),needsOf:(i,a)=>this.catchupNeeds(i,a),lists:this.listIndex({fresh:!0}).concat(this.agentListIndex())})),r=null;return e.save!==!1&&(r=this.folder.writeCatchup(o.fileName,o.markdown),this.log("catch-up "+r.name+": "+o.recap.sessions.length+" session(s), "+o.recap.window.label.toLowerCase()+", summary by "+(o.summary.by.by==="model"?o.summary.by.provider:"heuristic")),this.emit("catchup.created",{name:r.name,sessions:o.recap.sessions.length})),{name:r?r.name:null,path:r?"catchups/"+r.name+".md":null,saved:!!r,recap:o.recap,summary:o.summary,markdown:o.markdown}}readCatchup(e){let n=this.folder.readCatchup(e);return n?{name:n.name,markdown:n.markdown,parsed:ku(n.markdown)}:null}digestSchedule(){return pa(this.cfg.digest,jt())}digestTime(){return this.digestSchedule().time}digestDate(e=this.now()){return ao(e,this.digestSchedule()).date}digestGraceMs(){let e=Number(this.cfg.digest&&this.cfg.digest.graceMin);return(Number.isFinite(e)&&e>=0?e:20)*6e4}digestMachines(e=this.now()){let s=[];try{s=this.folder.aliveMachines().filter(o=>e-(Date.parse(o.seenAt)||0)<6048e5&&o.digest!==!1).map(o=>o.machine)}catch{}return[...new Set([...s,this.machine])].sort()}digestTurnNow(e=this.now()){let n=this.digestSchedule(),s=ao(e,n);return{slot:s,...Gh({machines:this.digestMachines(e),me:this.machine,slotAt:s.at,graceMs:this.digestGraceMs()})}}sharedDigest(e){return this.folder.readDigest(e,"all")}nextDigestAt(e=this.now()){let n=this.digestSchedule(),s=this.digestTurnNow(e),o=s.slot,r=!!this.sharedDigest(o.date),i=this.digestDue(e);if(!r&&o.isDay&&s.at!=null&&e<s.at)return G(s.at);let a=Hh(e,n,{doneToday:r&&!i||o.isDay&&o.past&&!i});return a==null?null:G(a>e+1e3&&a!==o.at?a+s.rank*this.digestGraceMs():a)}hasRecentSessions(e=this.now()){let n=e-(this.cfg.digest&&this.cfg.digest.lookbackHours||24)*36e5;for(let s of this.sessions.values())if(!s.archived&&(this.worthPersisting(s)||!this.isMine(s))&&Math.max(R(s.lastActivityAt)||0,R(s.startedAt)||0)>=n)return!0;return!1}digestDue(e=this.now()){let n=this.digestSchedule();if(!n.time)return!1;let s=this.digestTurnNow(e),o=s.slot;if(!o.isDay||!o.past)return!1;let r=this.sharedDigest(o.date);if(!r)return e<s.at||e>=o.at+this.digestGraceMs()&&e<this.awakeSince+this.digestSettleMs*(s.rank+1)&&(s.order.length>1||this.knownMachines().length>1)||n.skipEmpty&&!this.hasRecentSessions(e)?!1:"missing";let i=Be(r.markdown).data||{};return(!i.machine||i.machine===this.machine||!s.order.includes(i.machine))&&Number(i.sessions)===0&&e-(R(i.generated_at)||0)>=5*6e4&&this.hasRecentSessions(e)?"empty":!1}async maybeScheduledDigest(){let e=this.now();if(this.lastDigestCheck&&e-this.lastDigestCheck>12e4&&(this.awakeSince=e),this.lastDigestCheck=e,!this.digestTime()||this.digesting)return;if(!this.aliveAt||e-this.aliveAt>30*6e4)try{this.folder.stampAlive({version:J,digest:!0},e),this.aliveAt=e}catch(o){this.log("digest: could not stamp digests/.machines: "+o.message)}let n=this.digestState&&this.digestState.lastError;if(n&&n.by==="schedule"&&e-R(n.at)<10*6e4)return;let s=this.digestDue(e);if(s){this.digesting=!0;try{await this.makeDigest({by:"schedule"}),s==="empty"&&this.log("digest remade: the morning one had no sessions")}catch{}finally{this.digesting=!1}}}digestStatus(e=this.now()){let n=this.digestState||{},s=this.digestSchedule(),o=this.digestTurnNow(e),r=o.slot,i=this.sharedDigest(r.date),a=i?Be(i.markdown).data||{}:null,l=null;return!i&&s.time&&(r.isDay?r.past&&s.skipEmpty&&!this.hasRecentSessions(e)?l="no activity":r.past&&o.at!=null&&e<o.at&&(l="waiting for "+o.order[0]):l="not a digest day"),{time:s.time,timeZone:s.tz,zoneOverride:s.zoneOverride,daemonZone:jt(),zoneAbbr:uu(e,s.tz),label:Bh(s.time,s.tz,e),days:s.days,daysLabel:Kh(s.days),skipEmpty:s.skipEmpty,nextAt:this.nextDigestAt(e),todayDate:r.date,todayDay:r.day,todayNote:l,today:a?{date:r.date,generatedAt:a.generated_at||null,sessions:a.sessions!=null?Number(a.sessions):null,by:a.machine||null,duplicates:i.duplicates||0}:null,shared:!0,order:o.order,rank:o.rank,first:o.first,graceMin:this.digestGraceMs()/6e4,lastMadeAt:n.lastMadeAt||null,lastBy:n.lastBy||null,lastError:n.lastError||null}}scheduleDigest(){this.later(1e3,()=>this.maybeScheduledDigest().catch(()=>{}))}repoKeyFor(e){return e.repo&&e.repo.root?e.repo.root:e.cwd?this.isMine(e)?(this.gitRootCache||(this.gitRootCache=new Map),this.gitRootCache.has(e.cwd)||(this.gitRootCache.size>2e3&&this.gitRootCache.clear(),this.gitRootCache.set(e.cwd,Wp(e.cwd))),this.gitRootCache.get(e.cwd)||e.cwd):e.cwd:e.repo&&e.repo.name||"no-repo"}repoLabels(){let e=[...new Set([...this.sessions.values()].map(s=>this.repoKeyFor(s)))].sort(),n=e.join(`
|
|
197
|
+
`);return(!this.repoLabelMemo||this.repoLabelMemo.sig!==n)&&(this.repoLabelMemo={sig:n,map:Zt(e)}),this.repoLabelMemo.map}publicSession(e,n=this.now()){let s=this.repoKeyFor(e),o=this.checkpoints.get(e.id)||[],r=o[o.length-1]||null,i=Rt(e,n,this.cfgFor(e));return{id:e.id,title:e.title||null,titleSource:e.titleSource||null,machine:e.machine||this.machine,isLocal:this.isMine(e),cwd:e.cwd||null,repo:e.repo||null,repoName:fe(e),repoKey:s,repoLabel:this.repoLabels().get(s)||fe(e),transcriptPath:e.transcriptPath||null,model:e.model||null,surface:e.surface||null,capture:e.capture||null,status:i,startedAt:e.startedAt||null,lastActivityAt:e.lastActivityAt||null,lastHeartbeatAt:e.lastHeartbeatAt||null,endedAt:e.endedAt||null,endReason:e.endReason||null,turns:e.turns||0,turnsSinceCheckpoint:e.turnsSinceCheckpoint||0,lastCheckpointId:e.lastCheckpointId||null,parentSessionId:e.parentSessionId||null,pinned:!!e.pinned,archived:!!e.archived,ignored:!!e.ignored,needsInput:!!e.needsInput,lastNotification:e.lastNotification||null,lastPreview:e.lastPreview||null,idleSec:e.lastActivityAt?Math.max(0,Math.round((n-R(e.lastActivityAt))/1e3)):null,checkpointCount:o.length,latestCheckpoint:r,resumeCommand:Ys(e),statusChanges:e.statusChanges||[],folder:e.folder||null,empty:ht(e,o.length),ghost:Ws(e,o.length),historyTurns:e.historyTurns||0,startSource:e.startSource||null,healedVia:e.healedVia||null,archivedReason:e.archivedReason||null,question:e.question||null,answers:(e.answers||[]).slice(-10),...this.livePublic(e,i,n),needsYou:this.needsItem(e,i,n),peer:this.isMine(e)?null:{machine:e.machine,...this.peerUsable(e.machine)},...this.sessionListInfo(e),...this.spaceOf({id:e.id,machine:e.machine||this.machine,repoKey:s,repoName:fe(e),repoLabel:this.repoLabels().get(s)||fe(e)})}}spaceOf(e){try{let n=this.spaces.resolve(e);return{space:n.space,spaceSource:n.source}}catch{return{space:e.machine||this.machine,spaceSource:"default"}}}withSpace(e){return e&&Object.assign(e,this.spaceOf(e))}livePublic(e,n,s){let o=this.liveInfo(e,n,s),r=e.term||null;return{live:o.live,liveVia:o.via,liveSince:o.since,closedAt:o.live==="closed"&&(e.closedAt||e.endedAt||o.since)||null,pid:e.pid||null,tty:e.tty||null,shortId:Ke(e.id),terminal:r?{kind:r.kind,label:r.label||r.kind}:null,jump:this.jumpInfo(e,o.live)}}listSessions({status:e,repo:n,q:s,machine:o,limit:r=50,includeArchived:i=!1}={}){let a=e?String(e).split(",").map(u=>u.trim()).filter(Boolean):null,l=s?String(s).toLowerCase():null,c=[...this.sessions.values()].map(u=>this.publicSession(u));try{c.push(...this.agentSessions().filter(u=>!l||this.agentMatches(u,l)))}catch(u){this.log("agents: "+u.message)}if(i||(c=c.filter(u=>!u.archived)),a&&(c=c.filter(u=>a.includes(u.status))),n){let u=String(n).split(",").map(d=>d.trim()).filter(Boolean);c=c.filter(d=>u.some(f=>oo(d,f)))}return o&&(c=c.filter(u=>u.machine===o)),l&&(c=c.filter(u=>this.matches(u,l))),c.sort((u,d)=>d.pinned-u.pinned||(R(d.lastActivityAt||d.startedAt)||0)-(R(u.lastActivityAt||u.startedAt)||0)),c.slice(0,Math.min(Number(r)||50,500))}agentMatches(e,n){let s=this.agentSession(e.id)||e;return[s.title,s.project,s.machine,...(s.checkpoints||[]).flatMap(r=>[r.summary,r.done,r.next]),...(s.needsYou&&s.needsYou.items||[]).map(r=>r.text)].some(r=>r&&String(r).toLowerCase().includes(n))}matches(e,n){if(e.agent)return this.agentMatches(e,n);let s=[e.title,e.repoName,e.machine,e.cwd,e.repo&&e.repo.branch,e.lastPreview];for(let o of this.checkpoints.get(e.id)||[])s.push(o.doing,o.stoppedAt,o.next,o.userNote,...o.filesTouched||[]);return s.some(o=>o&&String(o).toLowerCase().includes(n))}getSession(e){if(xo(e)){let o=this.agentSession(e);if(!o)throw M(404,"not_found","no such session");return o}let n=this.sessions.get(e);if(!n)throw M(404,"not_found","no such session");let s=this.checkpoints.get(e)||[];return{...this.publicSession(n),checkpoints:s.slice(-20).reverse()}}sessionCheckpoints(e,{before:n,limit:s=50}={}){if(xo(e)){let r=this.agentSession(e);if(!r)throw M(404,"not_found","no such session");return r.checkpoints.filter(i=>!n||R(i.createdAt)<R(n)).slice(0,Math.min(Number(s)||50,500))}if(!this.sessions.has(e))throw M(404,"not_found","no such session");let o=(this.checkpoints.get(e)||[]).slice().reverse();return n&&(o=o.filter(r=>R(r.createdAt)<R(n))),o.slice(0,Math.min(Number(s)||50,500))}timeline({since:e,q:n,session:s,limit:o=100}={}){let r=[];if(s)r.push(...this.checkpoints.get(s)||[]);else for(let i of this.checkpoints.values())r.push(...i);if(e&&(r=r.filter(i=>R(i.createdAt)>=R(e))),n){let i=String(n).toLowerCase();r=r.filter(a=>[a.sessionTitle,a.doing,a.stoppedAt,a.next,a.repo,a.userNote,...a.filesTouched||[]].some(l=>l&&String(l).toLowerCase().includes(i)))}return r.sort((i,a)=>R(a.createdAt)-R(i.createdAt)),r.slice(0,Math.min(Number(o)||100,1e3))}search(e){let n=String(e||"").toLowerCase().trim();return n?{sessions:this.listSessions({q:n,includeArchived:!0,limit:50}),checkpoints:this.timeline({q:n,limit:50})}:{sessions:[],checkpoints:[]}}health(){let e=0;for(let n of this.sessions.values())["ended","stale"].includes(Rt(n,this.now(),this.cfgFor(n)))||e++;return{ok:!0,version:J,pid:process.pid,folder:this.root,machine:this.machine,machines:this.folder.machines(),peers:this.peers?{allowIncoming:this.peers.accepts,fingerprint:this.peers.id.fingerprint}:null,uptimeSec:Math.round((Date.now()-this.startedAt)/1e3),sessionsOpen:e,sessionsTotal:this.sessions.size,needsYou:this.needsList().length,answering:this.answeringInfo(),spoolPending:this.folder.spoolCount(),summarizer:this.summarizer.mode,summarizerDetail:this.summarizer.describe(),listsOpen:this.listIndex().filter(n=>!n.archived&&n.done<n.total).length,agents:this.agentsHealth(),mod:this.modHealth(),remote:this.remoteHealth(),digest:this.digestStatus(),sessions:this.emptyCounts(),...this.versionHealth()}}agentsHealth(){try{return this.agentView().agents.map(e=>({name:e.name,display:e.display,icon:e.icon,ok:e.status.ok,lastSuccessAt:e.status.lastSuccessAt,error:e.status.error?e.status.error.message:null,puller:e.puller,pullsHere:e.pullsHere,sessions:e.records.length}))}catch{return[]}}settleAsksFor(e,n){let s=n.data||{};n.type==="activity"&&s.kind==="tool"&&s.phase==="post"&&s.inputKey?this.asks.settleSession(e.id,"answered_in_terminal",{key:s.inputKey}):n.type==="activity"&&s.kind==="prompt"?this.asks.settleSession(e.id,"new_prompt"):n.type==="turn_completed"?this.asks.settleSession(e.id,"turn_ended"):n.type==="session.ended"&&this.asks.settleSession(e.id,"session_ended"),(n.type==="turn_started"||n.type==="activity"&&s.kind==="prompt")&&(e.replyPending=null)}openAsk(e={}){let n=e.sessionId;if(n&&process.env.CHECKPOINTS_IGNORE_SESSION===n)return{id:null,state:"gone",reason:"ignored"};if(this.cfg.answers&&this.cfg.answers.enabled===!1)return{id:null,state:"gone",reason:"answering_off"};let s=this.asks.open({sessionId:n,tool:e.tool,input:e.input,suggestions:e.suggestions,capture:e.capture==="mod"?"mod":"hooks",cwd:e.cwd,permissionMode:e.permissionMode});return{id:s.id,state:s.state}}onAskChange(e,n){let s=this.sessions.get(e.sessionId);s&&(n==="opened"?(s.needsInput||(s.needsInputAt=new Date(e.createdAt).toISOString()),s.needsInput=!0):!this.asks.waiting({sessionId:s.id}).length&&s.needsInput&&(!s.lastNotification||s.lastNotification==="permission_prompt")&&(s.needsInput=!1),this.refreshStatus(s),this.persist(s),this.emit("session.updated",this.publicSession(s)),n==="opened"&&this.scheduleNotify(s)),this.log("ask "+e.id+" "+n+" ("+e.kind+" "+e.tool+(e.reason?", "+e.reason:"")+") session "+String(e.sessionId).slice(0,8)),this.emit("needs.updated",{sessionId:e.sessionId,id:e.id,state:e.state,what:n})}async startPeers(e={}){if(this.peers||process.env.LEFTOFF_PEERS==="0"||this.cfg.peers&&this.cfg.peers.enabled===!1)return this.peers;this.peers=new ri({engine:this,root:this.root,machine:this.machine,log:this.log,version:J,now:this.now,...e}),this.peers.on("needs",n=>{for(let s of this.sessions.values())s.machine===n&&!this.isMine(s)&&this.emit("session.updated",this.publicSession(s));this.emit("needs.updated",{machine:n,what:"peer"})}),this.peers.on("changed",()=>this.emit("peers.updated",{}));try{await this.peers.start()}catch(n){this.log("peer: "+n.message)}return this.peers}async stopPeers(){this.peers&&(await this.peers.stop(),this.peers=null)}peerUsable(e){return this.peers?this.peers.usable(e):{ok:!1,code:"off",reason:"cross-machine actions are off on this machine"}}async viaPeer(e,n,s,o){if(!this.peers)throw M(409,"other_machine","That session runs on "+e+"; cross-machine actions are off here");let r=this.peers.usable(e);if(!r.ok&&!["unreachable","checking"].includes(r.code))throw M(409,"peer_"+(r.code||"unavailable"),o+" on "+e+" isn’t available: "+r.reason);try{return await this.peers.request(e,n,s)}catch(i){throw M(i.status&&i.status<500?i.status===403?403:409:503,i.code&&i.code.startsWith("peer_")?i.code:"peer_"+(i.code||"error"),i.message)}}peersOverview(){return this.peers?this.peers.overview():{machine:this.machine,off:!0,peers:[]}}needsItem(e,n,s=this.now()){if(e.archived)return null;if(!this.isMine(e)&&this.peers){let i=this.peers.needsFor(e.id,e.machine);if(i){let{session:a,...l}=i;return{...l,peer:e.machine}}}let o=this.asks.waiting({sessionId:e.id});if(o.length){let i=o[0];return{kind:i.kind,blocking:!0,since:new Date(i.createdAt).toISOString(),waitSec:Math.round((s-i.createdAt)/1e3),ask:this.asks.publicAsk(i),more:o.length-1}}let r=n||Rt(e,s,this.cfgFor(e));if(r==="ended"||r==="stale"||e.endedAt)return null;if(e.needsInput){let i=e.needsInputAt||e.lastEventAt||e.lastActivityAt,a=this.isMine(e)?null:(()=>{let l=this.peerUsable(e.machine);return l.ok?null:"Answering from here: "+l.reason})();return{kind:"terminal",blocking:!0,since:i,waitSec:i?Math.round((s-R(i))/1e3):null,message:e.lastNotificationMessage||(e.lastNotification?e.lastNotification.replace(/_/g," "):"waiting in the terminal"),notification:e.lastNotification||null,...a?{peerNote:a}:{}}}if(e.question&&!e.inTurn&&this.isMine(e)){if(this.liveInfo(e,r,s).live==="closed")return null;let a=e.capture==="mod"&&this.replies.listening(e.id);return{kind:"reply",blocking:!1,since:e.questionAt,waitSec:e.questionAt?Math.round((s-R(e.questionAt))/1e3):null,question:e.question,canReply:a,replyPending:e.replyPending||null,why:a?null:e.capture==="mod"?"the mod in this session isn’t listening for replies (Claude Code closed, or a mod older than 0.4.4)":"replies need the mod (hooks-only capture can’t type into a session)"}}return null}needsList(){let e=[],n=this.now();for(let s of this.sessions.values()){if(s.archived)continue;let o=this.needsItem(s,null,n);if(!o)continue;let r=this.publicSession(s,n);e.push({...o,session:{id:s.id,title:r.title,repoLabel:r.repoLabel,repoKey:r.repoKey,machine:r.machine,isLocal:r.isLocal,capture:r.capture,live:r.live,jump:r.jump,terminal:r.terminal,status:r.status}})}try{for(let s of this.agentView().agents)for(let o of s.records){let r=Wn(s,o,n);if(!r)continue;let i=qa(s,o,n);e.push({...r,session:{id:i.id,title:i.title,repoLabel:i.repoLabel,repoKey:i.repoKey,machine:i.machine,isLocal:!1,capture:"agent",live:null,jump:null,terminal:null,status:i.status,agent:i.agent}})}}catch(s){this.log("agents: "+s.message)}return e.sort((s,o)=>o.blocking-s.blocking||(o.waitSec||0)-(s.waitSec||0))}recentAnswered(e=20){let n={answered_in_terminal:"Answered in the terminal",cancelled_in_terminal:"Cancelled in the terminal (Esc)",new_prompt:"Moved on: you typed a new prompt",turn_ended:"Moved on: the turn ended",session_ended:"The session ended",no_listener:"Claude Code stopped waiting",unknown:"Gone"},s=[];for(let r of this.asks.items.values()){if(r.state==="waiting")continue;let i=this.sessions.get(r.sessionId),a=r.kind==="permission"?r.describe.verb+": "+r.describe.detail:(r.questions||[]).map(c=>c.question).join(" / "),l=r.state==="answered"?$r(r,r.answer)+" from the board":r.state==="expired"?"Timed out: left to the terminal":n[r.reason]||r.reason||"Gone";s.push({at:new Date(r.settledAt||r.createdAt).toISOString(),askedAt:new Date(r.createdAt).toISOString(),kind:r.kind,tool:r.tool,what:a,state:r.state,reason:r.reason,outcome:l,where:r.by?r.by.where:null,session:{id:r.sessionId,title:i?i.title||fe(i):null,repoLabel:i?this.publicSession(i).repoLabel:null}})}let o=this.now()-24*3600*1e3;for(let r of this.sessions.values())for(let i of r.answers||[])i.kind!=="reply"||R(i.at)<o||s.push({at:i.at,askedAt:null,kind:"reply",tool:null,what:i.what,state:"answered",reason:null,outcome:i.summary+" from the board",where:i.where,session:{id:r.id,title:r.title||fe(r),repoLabel:this.publicSession(r).repoLabel}});return s.sort((r,i)=>R(i.at)-R(r.at)).slice(0,e)}answeringInfo(e){let n=!!(this.cfg.remote&&this.cfg.remote.allowAnswers);return{remoteAllowed:n,allowedHere:e==="remote"?n:!0}}assertMayAnswer(e={}){if(e.via!=="peer"){if(e.via==="remote"&&!(this.cfg.remote&&this.cfg.remote.allowAnswers))throw M(403,"answers_remote_off","Answering from another device is off. On the machine running Leftoff, turn on Settings → Privacy & security → “Allow answering permission prompts and questions from other devices”.");if(e.proxied&&e.via!=="remote")throw M(403,"loopback_only","only from the machine running Leftoff")}}async answerAsk(e,n={},s={}){this.assertMayAnswer(s);let o=this.asks.get(e);if(!o&&this.peers&&this.peers.machineForAsk(e)){let u=this.peers.machineForAsk(e),d=await this.viaPeer(u,"ask.answer",{askId:e,body:n},"Answering");return this.log("answer forwarded to "+u+" via "+d.via+": "+(d.result&&d.result.summary)),this.peers.poll().catch(()=>{}),{...d.result,peer:u,peerVia:d.via}}if(!o)throw M(404,"not_found","That request is gone (Claude Code moved on, or the daemon restarted).");let r=this.sessions.get(o.sessionId);if(r&&!this.isMine(r))throw M(409,"other_machine","That session runs on "+r.machine);let i=Lu(s),a=this.asks.answer(e,n,{where:i,via:s.via||"loopback",ip:s.ip||null,who:s.who||null}),l=await this.asks.delivery(e,3e3),c=$r(a,a.answer);return this.audit(a.sessionId,{at:new Date(a.answeredAt).toISOString(),kind:a.kind,tool:a.tool,what:a.kind==="permission"?a.describe.verb+": "+a.describe.detail:(a.questions||[]).map(u=>u.question).join(" / "),summary:c,action:n.action,where:i,via:s.via||"loopback",who:s.who||null,delivered:l}),{ok:!0,id:e,state:a.state,delivered:l,summary:c,ask:this.asks.publicAsk(a)}}async postReply(e,n,s={}){this.assertNotAgent(e,"replying"),this.assertMayAnswer(s);let o=this.sessions.get(e);if(!o)throw M(404,"not_found","no such session");if(!this.isMine(o)){let l=await this.viaPeer(o.machine,"session.reply",{sessionId:o.id,text:n},"Replying");return{...l.result,peer:o.machine,peerVia:l.via}}if(o.capture!=="mod")throw M(409,"needs_mod","Replies need the Leftoff mod; this session is captured by settings hooks, which can’t type into it. Use Go.");if(this.publicSession(o).live==="closed"||o.endedAt)throw M(409,"closed","That session is closed.");if(!this.replies.listening(o.id))throw M(409,"not_listening","This session’s mod isn’t picking up replies (Claude Code may be busy starting, or the mod is older than 0.4.4). Use Go.");let i=Lu(s),a=this.replies.put(o.id,n,{where:i,via:s.via||"loopback"});return o.replyPending={id:a.id,at:new Date(a.at).toISOString(),text:a.text.slice(0,200)},this.audit(o.id,{at:new Date(a.at).toISOString(),kind:"reply",tool:null,what:o.question||"reply",summary:$r(null,{action:"reply",text:a.text}),action:"reply",where:i,via:s.via||"loopback",who:s.who||null,delivered:a.delivered,busy:!!o.inTurn}),this.emit("session.updated",this.publicSession(o)),this.emit("needs.updated",{sessionId:o.id,what:"replied"}),{ok:!0,id:a.id,delivered:a.delivered,queuedBehindTurn:!!o.inTurn}}async takeReply(e,n,s){let o=this.sessions.get(e),r=this.replies.listening(e),i=this.replies.take(e,n,s);!r&&o&&o.question&&this.emit("session.updated",this.publicSession(o));let a=await i;return a?{reply:{id:a.id,text:a.text}}:{reply:null}}audit(e,n){let s=this.sessions.get(e);if(this.log("answer from the board: "+n.summary+" · "+(n.what||"")+" · "+n.where+" · session "+String(e).slice(0,8)),!!s){s.answers=[...s.answers||[],n].slice(-20),this.persist(s,!0);try{let o=jo.join(this.folder.sessionDir(s),"answers.md"),r=At.existsSync(o)?"":`# Answered from the Leftoff board
|
|
198
|
+
|
|
199
|
+
Every answer given from the board for this session: when, what was asked, the answer, and where it came from.
|
|
200
|
+
|
|
201
|
+
`,i="- "+n.at+" · "+n.summary+(n.what?" · "+(n.tool?n.tool+" — ":"")+String(n.what).replace(/\s+/g," ").slice(0,300):"")+" · from the board "+n.where+(n.delivered===!1?" · not yet picked up by Claude Code":"")+`
|
|
202
|
+
`;At.mkdirSync(jo.dirname(o),{recursive:!0}),At.appendFileSync(o,r+i)}catch(o){this.log("answers.md: "+o.message)}}}scheduleNotify(e){let n=this.cfg.notify||{};if(n.needsYou===!1||!this.isMine(e))return;let s=this.needsItem(e);if(!s||!s.blocking)return;let o=e.id+"|"+(s.ask?s.ask.id:s.kind+s.since);if(this.notified.has(o))return;this.notified.set(o,"pending"),this.notified.size>500&&this.notified.delete(this.notified.keys().next().value);let r=Math.max(0,Number(n.delaySec!=null?n.delaySec:10))*1e3;this.later(r,()=>{let i=this.needsItem(e),a=i&&(i.ask?e.id+"|"+i.ask.id:e.id+"|"+i.kind+i.since);if(!i||a!==o)return;let l=Wm(i,e.title||fe(e)),c=this.notifier({title:"Leftoff",subtitle:l.subtitle,message:l.message,sessionId:e.id,goBin:Fe});this.notified.set(o,c&&c.ok?"sent":"failed"),this.emit("needs.notified",{sessionId:e.id,key:o,result:c})})}remoteHealth(){let e=(this.cfg.remoteHosts||[]).map(Mt).filter(Boolean);return{enabled:e.length>0,hosts:e,urls:ol(e),requireToken:this.cfg.remoteRequireToken!==!1}}remoteState(e){return{...this.remoteHealth(),via:e||null,...ll(this.root)}}setRemoteHosts({add:e=[],remove:n=[],requireToken:s}={}){let o=a=>(Array.isArray(a)?a:[a]).map(Mt).filter(Boolean),r=new Set(o(n)),i=(this.cfg.remoteHosts||[]).map(Mt).filter(a=>a&&!r.has(a));for(let a of o(e))i.includes(a)||i.push(a);return this.cfg.remoteHosts=i,typeof s=="boolean"&&(this.cfg.remoteRequireToken=s),Js(this.cfg,this.root),this.remoteHealth()}reloadRemote(){let e=Z(this.root);return this.cfg.remoteHosts=e.remoteHosts||[],this.cfg.remoteRequireToken=e.remoteRequireToken,this.remoteHealth()}versionHealth(){let e=this.updateState(),n=this.changelog();return{update:e.checked?{available:!!e.available,remoteVersion:e.remote&&e.remote.version,remoteSha:e.remote&&e.remote.sha&&e.remote.sha.slice(0,7),checkedAt:e.checkedAt,error:e.error||null,incoming:(e.incoming||[]).map(s=>s.version)}:{available:!1,checkedAt:null,enabled:e.enabled},whatsNew:{unseen:n.entries.filter(s=>s.isNew).length,upgradedFrom:n.upgradedFrom,upgradeNotice:n.upgradeNotice}}}modHealthFile(){return jo.join(this.root,".mod-health."+this.machine.replace(/[^\w.-]/g,"_")+".json")}recordModHealth(e){let n=Array.isArray(e&&e.commands)?e.commands.slice(0,10):null;if(!n)throw M(400,"bad_request","body must be { sessionId, commands: [{ key, want, name, ok, error }] }");let s=(o,r=200)=>o==null?null:String(o).slice(0,r);this._modHealth={at:s(e.at,40)||new Date(this.now()).toISOString(),sessionId:s(e.sessionId,80),machine:s(e.machine,60)||this.machine,modVersion:s(e.modVersion,20),commands:n.map(o=>({key:s(o.key,20),want:s(o.want,40),name:s(o.name,40),ok:!!o.ok,error:o.ok?null:s(o.error)}))};try{At.writeFileSync(this.modHealthFile(),JSON.stringify(this._modHealth,null,2)+`
|
|
203
|
+
`)}catch{}return this.emit("session.updated",{mod:!0}),{ok:!0}}modHealth(){if(this._modHealth===void 0)try{this._modHealth=JSON.parse(At.readFileSync(this.modHealthFile(),"utf8"))}catch{this._modHealth=null}let e=this._modHealth;return e?{...e,failed:e.commands.filter(n=>!n.ok).map(n=>({want:n.want,error:n.error}))}:null}keyStatus(){let e=this.cfg.summarizer||{},n={};for(let[s,o]of Object.entries(da)){let r=String(e.provider||"").toLowerCase()===s,i=r&&e.apiKeyEnv||o.env,a={apiKeyEnv:r?e.apiKeyEnv:null,apiKeyFile:r?e.apiKeyFile:null,root:this.root},l=!!yn(s,a);n[s]={found:l,env:i,envSet:!!process.env[i],file:r&&e.apiKeyFile||o.file}}return n}get pulls(){if(!this._pulls){let e=this.cfg.summarizer||{},n=String(e.provider||"").toLowerCase()==="ollama"&&e.baseUrl?e.baseUrl:void 0;this._pulls=new nl({...n?{baseUrl:n}:{},...this.pullImpl?{pull:this.pullImpl}:{},...this.pullFetch?{fetchImpl:this.pullFetch}:{},onChange:s=>this.emit("model.pull",s)})}return this._pulls}async modelChoices({provider:e,baseUrl:n}={}){let s=this.cfg.summarizer||{},o=String(e||s.provider||"auto").toLowerCase(),r=o===String(s.provider||"auto").toLowerCase();return im(o,{current:r?s.model:null,baseUrl:n||(r?s.baseUrl:null),pulls:this.pulls.list(),...this.pullFetch?{fetchImpl:this.pullFetch}:{}})}async startOllama(){let e=this.cfg.summarizer||{},n=String(e.provider||"").toLowerCase()==="ollama"&&e.baseUrl||at,s=await Ut({baseUrl:n,...this.pullFetch?{fetchImpl:this.pullFetch}:{}});if(s.running)return{ok:!0,already:!0,models:await this.modelChoices({provider:"ollama"})};let o=Na({binary:s.binary});if(!o)throw M(409,"no_ollama","Ollama isn’t installed here. Install it from ollama.com first.");let r=dp(o);if(!r.started)throw M(500,"start_failed","Couldn’t start Ollama ("+(r.error||o.shell)+")");return this.log("ollama: started from the board ("+o.shell+")"),{ok:!!(await po({baseUrl:n,timeoutMs:Number(process.env.CHECKPOINTS_OLLAMA_START_WAIT_MS)||2e4,...this.pullFetch?{fetchImpl:this.pullFetch}:{}})).running,shell:o.shell,models:await this.modelChoices({provider:"ollama"})}}startPull({tag:e,use:n=!1}={}){return this.pulls.start(String(e||""),{use:n,onDone:s=>{if(!s.use)return;let o=this.cfg.summarizer||{};this.updateConfig({summarizer:{...o,provider:"ollama",model:s.tag,apiKeyEnv:null,apiKeyFile:null}}),this.log("summarizer now ollama "+s.tag+" (downloaded from the board)")}})}publicConfig(){return{...this.cfg,summarizerResolved:this.summarizer.mode,folder:this.root,machineResolved:this.machine,keys:this.keyStatus(),modelHints:{ollama:Mr.map(e=>e.tag),"ollama-cloud":Lr.map(e=>e.tag)}}}saveKey(e,n){let s=da[e];if(!s)throw M(400,"bad_provider","no key needed for "+e);let o=String(n||"").trim();if(!o||o.length>400||/\s/.test(o))throw M(400,"bad_key","that doesn't look like an API key");let r=Vi(s.file.replace(/^\.secret\//,""),o,this.root),i=this.cfg.summarizer||{};return String(i.provider||"").toLowerCase()===e&&i.apiKeyFile&&i.apiKeyFile!==s.file&&(this.cfg.summarizer={...i,apiKeyFile:s.file},Js(this.cfg,this.root)),this.summarizer=new gt(this.cfg,{root:this.root}),{ok:!0,provider:e,file:r,keys:this.keyStatus()}}updateConfig(e,{via:n}={}){let s=["dormantAfterMin","minGapMin","staleAfterSec","longRunCheckpointMin","digest","summarizer","machineNames","updates","notify","remote","summary"];for(let o of Object.keys(e||{}))if(!s.includes(o))throw M(400,"bad_config","cannot change "+o+" here");if(e.remote!==void 0&&n==="remote")throw M(403,"loopback_only","Remote-access settings can only be changed on the machine running Leftoff");if(e.remote!==void 0){if(!e.remote||typeof e.remote!="object")throw M(400,"bad_config","remote is an object");let o={};for(let r of Object.keys(e.remote)){if(r!=="allowAnswers")throw M(400,"bad_config","cannot change remote."+r+" here");o.allowAnswers=e.remote.allowAnswers===!0}e={...e,remote:o},o.allowAnswers!==void 0&&this.log("settings: answering from other devices "+(o.allowAnswers?"ALLOWED":"off"))}if(e.notify!==void 0){if(!e.notify||typeof e.notify!="object")throw M(400,"bad_config","notify is an object");let o={};if(e.notify.needsYou!==void 0&&(o.needsYou=!!e.notify.needsYou),e.notify.delaySec!==void 0){let r=Number(e.notify.delaySec);if(!(r>=0&&r<=600))throw M(400,"bad_config","notify.delaySec is 0–600");o.delaySec=r}e={...e,notify:o}}if(e.summary!==void 0){if(!e.summary||typeof e.summary!="object")throw M(400,"bad_config","summary is an object");let o={};for(let r of Object.keys(e.summary)){if(r!=="length")throw M(400,"bad_config","cannot change summary."+r+" here");if(o.length=cn(e.summary.length,{strict:!0}),!o.length)throw M(400,"bad_config","summary.length is short, informative or long")}e={...e,summary:o}}if(e.summarizer&&e.summarizer.mode&&!e.summarizer.provider&&(e.summarizer.provider=e.summarizer.mode),e.digest&&typeof e.digest=="object"){let o={...e.digest};if(o.graceMin!==void 0){let r=Number(o.graceMin);if(!Number.isFinite(r)||r<0||r>720)throw M(400,"bad_config","digest.graceMin is minutes, 0-720");o.graceMin=r}if(o.time!==void 0&&o.time!==null&&!/^\d{1,2}:\d{2}$/.test(String(o.time)))throw M(400,"bad_config","digest.time is HH:MM, or null for no automatic digest");if(o.days!==void 0){if(!Array.isArray(o.days)||!o.days.length)throw M(400,"bad_config","digest.days needs at least one day");o.days=io(o.days)}if(o.skipEmpty!==void 0&&(o.skipEmpty=!!o.skipEmpty),o.timeZone!==void 0){if(o.timeZone===""||o.timeZone===null)o.timeZone=null;else if(!ro(o.timeZone))throw M(400,"bad_config","unknown time zone: "+o.timeZone)}e={...e,digest:o}}for(let o of s)e[o]!==void 0&&(this.cfg[o]=typeof e[o]=="object"&&e[o]&&!Array.isArray(e[o])?{...this.cfg[o],...e[o]}:e[o]);return Js(this.cfg,this.root),e.summarizer&&(this.summarizer=new gt(this.cfg,{root:this.root})),e.updates&&!this.stopped&&this.timers.length&&this.scheduleUpdateCheck(),this.publicConfig()}}});var Po,Je,ui,Qn,es,ts,di,lg,cg,wb,Sb,kb,bb,ug,vb,xb,Ab,Tb,dg,k_,fg=V(()=>{Po=function(t,e){let o=t,r=ui[e],i=null,a=0,l=null,c=[],u={},d=function(g,_){a=o*4+17,i=(function(x){let C=new Array(x);for(let E=0;E<x;E+=1){C[E]=new Array(x);for(let L=0;L<x;L+=1)C[E][L]=null}return C})(a),f(0,0),f(a-7,0),f(0,a-7),m(),h(),w(g,_),o>=7&&y(g),l==null&&(l=v(o,r,c)),k(l,_)},f=function(g,_){for(let x=-1;x<=7;x+=1)if(!(g+x<=-1||a<=g+x))for(let C=-1;C<=7;C+=1)_+C<=-1||a<=_+C||(0<=x&&x<=6&&(C==0||C==6)||0<=C&&C<=6&&(x==0||x==6)||2<=x&&x<=4&&2<=C&&C<=4?i[g+x][_+C]=!0:i[g+x][_+C]=!1)},p=function(){let g=0,_=0;for(let x=0;x<8;x+=1){d(!0,x);let C=es.getLostPoint(u);(x==0||g>C)&&(g=C,_=x)}return _},h=function(){for(let g=8;g<a-8;g+=1)i[g][6]==null&&(i[g][6]=g%2==0);for(let g=8;g<a-8;g+=1)i[6][g]==null&&(i[6][g]=g%2==0)},m=function(){let g=es.getPatternPosition(o);for(let _=0;_<g.length;_+=1)for(let x=0;x<g.length;x+=1){let C=g[_],E=g[x];if(i[C][E]==null)for(let L=-2;L<=2;L+=1)for(let O=-2;O<=2;O+=1)L==-2||L==2||O==-2||O==2||L==0&&O==0?i[C+L][E+O]=!0:i[C+L][E+O]=!1}},y=function(g){let _=es.getBCHTypeNumber(o);for(let x=0;x<18;x+=1){let C=!g&&(_>>x&1)==1;i[Math.floor(x/3)][x%3+a-8-3]=C}for(let x=0;x<18;x+=1){let C=!g&&(_>>x&1)==1;i[x%3+a-8-3][Math.floor(x/3)]=C}},w=function(g,_){let x=r<<3|_,C=es.getBCHTypeInfo(x);for(let E=0;E<15;E+=1){let L=!g&&(C>>E&1)==1;E<6?i[E][8]=L:E<8?i[E+1][8]=L:i[a-15+E][8]=L}for(let E=0;E<15;E+=1){let L=!g&&(C>>E&1)==1;E<8?i[8][a-E-1]=L:E<9?i[8][15-E-1+1]=L:i[8][15-E-1]=L}i[a-8][8]=!g},k=function(g,_){let x=-1,C=a-1,E=7,L=0,O=es.getMaskFunction(_);for(let H=a-1;H>0;H-=2)for(H==6&&(H-=1);;){for(let W=0;W<2;W+=1)if(i[C][H-W]==null){let I=!1;L<g.length&&(I=(g[L]>>>E&1)==1),O(C,H-W)&&(I=!I),i[C][H-W]=I,E-=1,E==-1&&(L+=1,E=7)}if(C+=x,C<0||a<=C){C-=x,x=-x;break}}},S=function(g,_){let x=0,C=0,E=0,L=new Array(_.length),O=new Array(_.length);for(let P=0;P<_.length;P+=1){let Y=_[P].dataCount,ue=_[P].totalCount-Y;C=Math.max(C,Y),E=Math.max(E,ue),L[P]=new Array(Y);for(let B=0;B<L[P].length;B+=1)L[P][B]=255&g.getBuffer()[B+x];x+=Y;let $=es.getErrorCorrectPolynomial(ue),ne=di(L[P],$.getLength()-1).mod($);O[P]=new Array($.getLength()-1);for(let B=0;B<O[P].length;B+=1){let Ct=B+ne.getLength()-O[P].length;O[P][B]=Ct>=0?ne.getAt(Ct):0}}let H=0;for(let P=0;P<_.length;P+=1)H+=_[P].totalCount;let W=new Array(H),I=0;for(let P=0;P<C;P+=1)for(let Y=0;Y<_.length;Y+=1)P<L[Y].length&&(W[I]=L[Y][P],I+=1);for(let P=0;P<E;P+=1)for(let Y=0;Y<_.length;Y+=1)P<O[Y].length&&(W[I]=O[Y][P],I+=1);return W},v=function(g,_,x){let C=lg.getRSBlocks(g,_),E=cg();for(let O=0;O<x.length;O+=1){let H=x[O];E.put(H.getMode(),4),E.put(H.getLength(),es.getLengthInBits(H.getMode(),g)),H.write(E)}let L=0;for(let O=0;O<C.length;O+=1)L+=C[O].dataCount;if(E.getLengthInBits()>L*8)throw"code length overflow. ("+E.getLengthInBits()+">"+L*8+")";for(E.getLengthInBits()+4<=L*8&&E.put(0,4);E.getLengthInBits()%8!=0;)E.putBit(!1);for(;!(E.getLengthInBits()>=L*8||(E.put(236,8),E.getLengthInBits()>=L*8));)E.put(17,8);return S(E,C)};u.addData=function(g,_){_=_||"Byte";let x=null;switch(_){case"Numeric":x=wb(g);break;case"Alphanumeric":x=Sb(g);break;case"Byte":x=kb(g);break;case"Kanji":x=bb(g);break;default:throw"mode:"+_}c.push(x),l=null},u.isDark=function(g,_){if(g<0||a<=g||_<0||a<=_)throw g+","+_;return i[g][_]},u.getModuleCount=function(){return a},u.make=function(){if(o<1){let g=1;for(;g<40;g++){let _=lg.getRSBlocks(g,r),x=cg();for(let E=0;E<c.length;E++){let L=c[E];x.put(L.getMode(),4),x.put(L.getLength(),es.getLengthInBits(L.getMode(),g)),L.write(x)}let C=0;for(let E=0;E<_.length;E++)C+=_[E].dataCount;if(x.getLengthInBits()<=C*8)break}o=g}d(!1,p())},u.createTableTag=function(g,_){g=g||2,_=typeof _>"u"?g*4:_;let x="";x+='<table style="',x+=" border-width: 0px; border-style: none;",x+=" border-collapse: collapse;",x+=" padding: 0px; margin: "+_+"px;",x+='">',x+="<tbody>";for(let C=0;C<u.getModuleCount();C+=1){x+="<tr>";for(let E=0;E<u.getModuleCount();E+=1)x+='<td style="',x+=" border-width: 0px; border-style: none;",x+=" border-collapse: collapse;",x+=" padding: 0px; margin: 0px;",x+=" width: "+g+"px;",x+=" height: "+g+"px;",x+=" background-color: ",x+=u.isDark(C,E)?"#000000":"#ffffff",x+=";",x+='"/>';x+="</tr>"}return x+="</tbody>",x+="</table>",x},u.createSvgTag=function(g,_,x,C){let E={};typeof arguments[0]=="object"&&(E=arguments[0],g=E.cellSize,_=E.margin,x=E.alt,C=E.title),g=g||2,_=typeof _>"u"?g*4:_,x=typeof x=="string"?{text:x}:x||{},x.text=x.text||null,x.id=x.text?x.id||"qrcode-description":null,C=typeof C=="string"?{text:C}:C||{},C.text=C.text||null,C.id=C.text?C.id||"qrcode-title":null;let L=u.getModuleCount()*g+_*2,O,H,W,I,P="",Y;for(Y="l"+g+",0 0,"+g+" -"+g+",0 0,-"+g+"z ",P+='<svg version="1.1" xmlns="http://www.w3.org/2000/svg"',P+=E.scalable?"":' width="'+L+'px" height="'+L+'px"',P+=' viewBox="0 0 '+L+" "+L+'" ',P+=' preserveAspectRatio="xMinYMin meet"',P+=C.text||x.text?' role="img" aria-labelledby="'+A([C.id,x.id].join(" ").trim())+'"':"",P+=">",P+=C.text?'<title id="'+A(C.id)+'">'+A(C.text)+"</title>":"",P+=x.text?'<description id="'+A(x.id)+'">'+A(x.text)+"</description>":"",P+='<rect width="100%" height="100%" fill="white" cx="0" cy="0"/>',P+='<path d="',W=0;W<u.getModuleCount();W+=1)for(I=W*g+_,O=0;O<u.getModuleCount();O+=1)u.isDark(W,O)&&(H=O*g+_,P+="M"+H+","+I+Y);return P+='" stroke="transparent" fill="black"/>',P+="</svg>",P},u.createDataURL=function(g,_){g=g||2,_=typeof _>"u"?g*4:_;let x=u.getModuleCount()*g+_*2,C=_,E=x-_;return Tb(x,x,function(L,O){if(C<=L&&L<E&&C<=O&&O<E){let H=Math.floor((L-C)/g),W=Math.floor((O-C)/g);return u.isDark(W,H)?0:1}else return 1})},u.createImgTag=function(g,_,x){g=g||2,_=typeof _>"u"?g*4:_;let C=u.getModuleCount()*g+_*2,E="";return E+="<img",E+=' src="',E+=u.createDataURL(g,_),E+='"',E+=' width="',E+=C,E+='"',E+=' height="',E+=C,E+='"',x&&(E+=' alt="',E+=A(x),E+='"'),E+="/>",E};let A=function(g){let _="";for(let x=0;x<g.length;x+=1){let C=g.charAt(x);switch(C){case"<":_+="<";break;case">":_+=">";break;case"&":_+="&";break;case'"':_+=""";break;default:_+=C;break}}return _},b=function(g){g=typeof g>"u"?2:g;let x=u.getModuleCount()*1+g*2,C=g,E=x-g,L,O,H,W,I,P={"██":"█","█ ":"▀"," █":"▄"," ":" "},Y={"██":"▀","█ ":"▀"," █":" "," ":" "},ue="";for(L=0;L<x;L+=2){for(H=Math.floor((L-C)/1),W=Math.floor((L+1-C)/1),O=0;O<x;O+=1)I="█",C<=O&&O<E&&C<=L&&L<E&&u.isDark(H,Math.floor((O-C)/1))&&(I=" "),C<=O&&O<E&&C<=L+1&&L+1<E&&u.isDark(W,Math.floor((O-C)/1))?I+=" ":I+="█",ue+=g<1&&L+1>=E?Y[I]:P[I];ue+=`
|
|
204
|
+
`}return x%2&&g>0?ue.substring(0,ue.length-x-1)+Array(x+1).join("▀"):ue.substring(0,ue.length-1)};return u.createASCII=function(g,_){if(g=g||1,g<2)return b(_);g-=1,_=typeof _>"u"?g*2:_;let x=u.getModuleCount()*g+_*2,C=_,E=x-_,L,O,H,W,I=Array(g+1).join("██"),P=Array(g+1).join(" "),Y="",ue="";for(L=0;L<x;L+=1){for(H=Math.floor((L-C)/g),ue="",O=0;O<x;O+=1)W=1,C<=O&&O<E&&C<=L&&L<E&&u.isDark(H,Math.floor((O-C)/g))&&(W=0),ue+=W?I:P;for(H=0;H<g;H+=1)Y+=ue+`
|
|
205
|
+
`}return Y.substring(0,Y.length-1)},u.renderTo2dContext=function(g,_){_=_||2;let x=u.getModuleCount();for(let C=0;C<x;C++)for(let E=0;E<x;E++)g.fillStyle=u.isDark(C,E)?"black":"white",g.fillRect(E*_,C*_,_,_)},u};Po.stringToBytes=function(t){let e=[];for(let n=0;n<t.length;n+=1){let s=t.charCodeAt(n);e.push(s&255)}return e};Po.createStringToBytes=function(t,e){let n=(function(){let o=xb(t),r=function(){let l=o.read();if(l==-1)throw"eof";return l},i=0,a={};for(;;){let l=o.read();if(l==-1)break;let c=r(),u=r(),d=r(),f=String.fromCharCode(l<<8|c),p=u<<8|d;a[f]=p,i+=1}if(i!=e)throw i+" != "+e;return a})(),s=63;return function(o){let r=[];for(let i=0;i<o.length;i+=1){let a=o.charCodeAt(i);if(a<128)r.push(a);else{let l=n[o.charAt(i)];typeof l=="number"?(l&255)==l?r.push(l):(r.push(l>>>8),r.push(l&255)):r.push(s)}}return r}};Je={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},ui={L:1,M:0,Q:3,H:2},Qn={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},es=(function(){let t=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],e=1335,n=7973,s=21522,o={},r=function(i){let a=0;for(;i!=0;)a+=1,i>>>=1;return a};return o.getBCHTypeInfo=function(i){let a=i<<10;for(;r(a)-r(e)>=0;)a^=e<<r(a)-r(e);return(i<<10|a)^s},o.getBCHTypeNumber=function(i){let a=i<<12;for(;r(a)-r(n)>=0;)a^=n<<r(a)-r(n);return i<<12|a},o.getPatternPosition=function(i){return t[i-1]},o.getMaskFunction=function(i){switch(i){case Qn.PATTERN000:return function(a,l){return(a+l)%2==0};case Qn.PATTERN001:return function(a,l){return a%2==0};case Qn.PATTERN010:return function(a,l){return l%3==0};case Qn.PATTERN011:return function(a,l){return(a+l)%3==0};case Qn.PATTERN100:return function(a,l){return(Math.floor(a/2)+Math.floor(l/3))%2==0};case Qn.PATTERN101:return function(a,l){return a*l%2+a*l%3==0};case Qn.PATTERN110:return function(a,l){return(a*l%2+a*l%3)%2==0};case Qn.PATTERN111:return function(a,l){return(a*l%3+(a+l)%2)%2==0};default:throw"bad maskPattern:"+i}},o.getErrorCorrectPolynomial=function(i){let a=di([1],0);for(let l=0;l<i;l+=1)a=a.multiply(di([1,ts.gexp(l)],0));return a},o.getLengthInBits=function(i,a){if(1<=a&&a<10)switch(i){case Je.MODE_NUMBER:return 10;case Je.MODE_ALPHA_NUM:return 9;case Je.MODE_8BIT_BYTE:return 8;case Je.MODE_KANJI:return 8;default:throw"mode:"+i}else if(a<27)switch(i){case Je.MODE_NUMBER:return 12;case Je.MODE_ALPHA_NUM:return 11;case Je.MODE_8BIT_BYTE:return 16;case Je.MODE_KANJI:return 10;default:throw"mode:"+i}else if(a<41)switch(i){case Je.MODE_NUMBER:return 14;case Je.MODE_ALPHA_NUM:return 13;case Je.MODE_8BIT_BYTE:return 16;case Je.MODE_KANJI:return 12;default:throw"mode:"+i}else throw"type:"+a},o.getLostPoint=function(i){let a=i.getModuleCount(),l=0;for(let d=0;d<a;d+=1)for(let f=0;f<a;f+=1){let p=0,h=i.isDark(d,f);for(let m=-1;m<=1;m+=1)if(!(d+m<0||a<=d+m))for(let y=-1;y<=1;y+=1)f+y<0||a<=f+y||m==0&&y==0||h==i.isDark(d+m,f+y)&&(p+=1);p>5&&(l+=3+p-5)}for(let d=0;d<a-1;d+=1)for(let f=0;f<a-1;f+=1){let p=0;i.isDark(d,f)&&(p+=1),i.isDark(d+1,f)&&(p+=1),i.isDark(d,f+1)&&(p+=1),i.isDark(d+1,f+1)&&(p+=1),(p==0||p==4)&&(l+=3)}for(let d=0;d<a;d+=1)for(let f=0;f<a-6;f+=1)i.isDark(d,f)&&!i.isDark(d,f+1)&&i.isDark(d,f+2)&&i.isDark(d,f+3)&&i.isDark(d,f+4)&&!i.isDark(d,f+5)&&i.isDark(d,f+6)&&(l+=40);for(let d=0;d<a;d+=1)for(let f=0;f<a-6;f+=1)i.isDark(f,d)&&!i.isDark(f+1,d)&&i.isDark(f+2,d)&&i.isDark(f+3,d)&&i.isDark(f+4,d)&&!i.isDark(f+5,d)&&i.isDark(f+6,d)&&(l+=40);let c=0;for(let d=0;d<a;d+=1)for(let f=0;f<a;f+=1)i.isDark(f,d)&&(c+=1);let u=Math.abs(100*c/a/a-50)/5;return l+=u*10,l},o})(),ts=(function(){let t=new Array(256),e=new Array(256);for(let s=0;s<8;s+=1)t[s]=1<<s;for(let s=8;s<256;s+=1)t[s]=t[s-4]^t[s-5]^t[s-6]^t[s-8];for(let s=0;s<255;s+=1)e[t[s]]=s;let n={};return n.glog=function(s){if(s<1)throw"glog("+s+")";return e[s]},n.gexp=function(s){for(;s<0;)s+=255;for(;s>=256;)s-=255;return t[s]},n})(),di=function(t,e){if(typeof t.length>"u")throw t.length+"/"+e;let n=(function(){let o=0;for(;o<t.length&&t[o]==0;)o+=1;let r=new Array(t.length-o+e);for(let i=0;i<t.length-o;i+=1)r[i]=t[i+o];return r})(),s={};return s.getAt=function(o){return n[o]},s.getLength=function(){return n.length},s.multiply=function(o){let r=new Array(s.getLength()+o.getLength()-1);for(let i=0;i<s.getLength();i+=1)for(let a=0;a<o.getLength();a+=1)r[i+a]^=ts.gexp(ts.glog(s.getAt(i))+ts.glog(o.getAt(a)));return di(r,0)},s.mod=function(o){if(s.getLength()-o.getLength()<0)return s;let r=ts.glog(s.getAt(0))-ts.glog(o.getAt(0)),i=new Array(s.getLength());for(let a=0;a<s.getLength();a+=1)i[a]=s.getAt(a);for(let a=0;a<o.getLength();a+=1)i[a]^=ts.gexp(ts.glog(o.getAt(a))+r);return di(i,0).mod(o)},s},lg=(function(){let t=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12,7,37,13],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],e=function(o,r){let i={};return i.totalCount=o,i.dataCount=r,i},n={},s=function(o,r){switch(r){case ui.L:return t[(o-1)*4+0];case ui.M:return t[(o-1)*4+1];case ui.Q:return t[(o-1)*4+2];case ui.H:return t[(o-1)*4+3];default:return}};return n.getRSBlocks=function(o,r){let i=s(o,r);if(typeof i>"u")throw"bad rs block @ typeNumber:"+o+"/errorCorrectionLevel:"+r;let a=i.length/3,l=[];for(let c=0;c<a;c+=1){let u=i[c*3+0],d=i[c*3+1],f=i[c*3+2];for(let p=0;p<u;p+=1)l.push(e(d,f))}return l},n})(),cg=function(){let t=[],e=0,n={};return n.getBuffer=function(){return t},n.getAt=function(s){let o=Math.floor(s/8);return(t[o]>>>7-s%8&1)==1},n.put=function(s,o){for(let r=0;r<o;r+=1)n.putBit((s>>>o-r-1&1)==1)},n.getLengthInBits=function(){return e},n.putBit=function(s){let o=Math.floor(e/8);t.length<=o&&t.push(0),s&&(t[o]|=128>>>e%8),e+=1},n},wb=function(t){let e=Je.MODE_NUMBER,n=t,s={};s.getMode=function(){return e},s.getLength=function(i){return n.length},s.write=function(i){let a=n,l=0;for(;l+2<a.length;)i.put(o(a.substring(l,l+3)),10),l+=3;l<a.length&&(a.length-l==1?i.put(o(a.substring(l,l+1)),4):a.length-l==2&&i.put(o(a.substring(l,l+2)),7))};let o=function(i){let a=0;for(let l=0;l<i.length;l+=1)a=a*10+r(i.charAt(l));return a},r=function(i){if("0"<=i&&i<="9")return i.charCodeAt(0)-48;throw"illegal char :"+i};return s},Sb=function(t){let e=Je.MODE_ALPHA_NUM,n=t,s={};s.getMode=function(){return e},s.getLength=function(r){return n.length},s.write=function(r){let i=n,a=0;for(;a+1<i.length;)r.put(o(i.charAt(a))*45+o(i.charAt(a+1)),11),a+=2;a<i.length&&r.put(o(i.charAt(a)),6)};let o=function(r){if("0"<=r&&r<="9")return r.charCodeAt(0)-48;if("A"<=r&&r<="Z")return r.charCodeAt(0)-65+10;switch(r){case" ":return 36;case"$":return 37;case"%":return 38;case"*":return 39;case"+":return 40;case"-":return 41;case".":return 42;case"/":return 43;case":":return 44;default:throw"illegal char :"+r}};return s},kb=function(t){let e=Je.MODE_8BIT_BYTE,n=t,s=Po.stringToBytes(t),o={};return o.getMode=function(){return e},o.getLength=function(r){return s.length},o.write=function(r){for(let i=0;i<s.length;i+=1)r.put(s[i],8)},o},bb=function(t){let e=Je.MODE_KANJI,n=t,s=Po.stringToBytes;(function(i,a){let l=s(i);if(l.length!=2||(l[0]<<8|l[1])!=a)throw"sjis not supported."})("友",38726);let o=s(t),r={};return r.getMode=function(){return e},r.getLength=function(i){return~~(o.length/2)},r.write=function(i){let a=o,l=0;for(;l+1<a.length;){let c=(255&a[l])<<8|255&a[l+1];if(33088<=c&&c<=40956)c-=33088;else if(57408<=c&&c<=60351)c-=49472;else throw"illegal char at "+(l+1)+"/"+c;c=(c>>>8&255)*192+(c&255),i.put(c,13),l+=2}if(l<a.length)throw"illegal char at "+(l+1)},r},ug=function(){let t=[],e={};return e.writeByte=function(n){t.push(n&255)},e.writeShort=function(n){e.writeByte(n),e.writeByte(n>>>8)},e.writeBytes=function(n,s,o){s=s||0,o=o||n.length;for(let r=0;r<o;r+=1)e.writeByte(n[r+s])},e.writeString=function(n){for(let s=0;s<n.length;s+=1)e.writeByte(n.charCodeAt(s))},e.toByteArray=function(){return t},e.toString=function(){let n="";n+="[";for(let s=0;s<t.length;s+=1)s>0&&(n+=","),n+=t[s];return n+="]",n},e},vb=function(){let t=0,e=0,n=0,s="",o={},r=function(a){s+=String.fromCharCode(i(a&63))},i=function(a){if(a<0)throw"n:"+a;if(a<26)return 65+a;if(a<52)return 97+(a-26);if(a<62)return 48+(a-52);if(a==62)return 43;if(a==63)return 47;throw"n:"+a};return o.writeByte=function(a){for(t=t<<8|a&255,e+=8,n+=1;e>=6;)r(t>>>e-6),e-=6},o.flush=function(){if(e>0&&(r(t<<6-e),t=0,e=0),n%3!=0){let a=3-n%3;for(let l=0;l<a;l+=1)s+="="}},o.toString=function(){return s},o},xb=function(t){let e=t,n=0,s=0,o=0,r={};r.read=function(){for(;o<8;){if(n>=e.length){if(o==0)return-1;throw"unexpected end of file./"+o}let l=e.charAt(n);if(n+=1,l=="=")return o=0,-1;if(l.match(/^\s$/))continue;s=s<<6|i(l.charCodeAt(0)),o+=6}let a=s>>>o-8&255;return o-=8,a};let i=function(a){if(65<=a&&a<=90)return a-65;if(97<=a&&a<=122)return a-97+26;if(48<=a&&a<=57)return a-48+52;if(a==43)return 62;if(a==47)return 63;throw"c:"+a};return r},Ab=function(t,e){let n=t,s=e,o=new Array(t*e),r={};r.setPixel=function(c,u,d){o[u*n+c]=d},r.write=function(c){c.writeString("GIF87a"),c.writeShort(n),c.writeShort(s),c.writeByte(128),c.writeByte(0),c.writeByte(0),c.writeByte(0),c.writeByte(0),c.writeByte(0),c.writeByte(255),c.writeByte(255),c.writeByte(255),c.writeString(","),c.writeShort(0),c.writeShort(0),c.writeShort(n),c.writeShort(s),c.writeByte(0);let u=2,d=a(u);c.writeByte(u);let f=0;for(;d.length-f>255;)c.writeByte(255),c.writeBytes(d,f,255),f+=255;c.writeByte(d.length-f),c.writeBytes(d,f,d.length-f),c.writeByte(0),c.writeString(";")};let i=function(c){let u=c,d=0,f=0,p={};return p.write=function(h,m){if(h>>>m)throw"length over";for(;d+m>=8;)u.writeByte(255&(h<<d|f)),m-=8-d,h>>>=8-d,f=0,d=0;f=h<<d|f,d=d+m},p.flush=function(){d>0&&u.writeByte(f)},p},a=function(c){let u=1<<c,d=(1<<c)+1,f=c+1,p=l();for(let k=0;k<u;k+=1)p.add(String.fromCharCode(k));p.add(String.fromCharCode(u)),p.add(String.fromCharCode(d));let h=ug(),m=i(h);m.write(u,f);let y=0,w=String.fromCharCode(o[y]);for(y+=1;y<o.length;){let k=String.fromCharCode(o[y]);y+=1,p.contains(w+k)?w=w+k:(m.write(p.indexOf(w),f),p.size()<4095&&(p.size()==1<<f&&(f+=1),p.add(w+k)),w=k)}return m.write(p.indexOf(w),f),m.write(d,f),m.flush(),h.toByteArray()},l=function(){let c={},u=0,d={};return d.add=function(f){if(d.contains(f))throw"dup key:"+f;c[f]=u,u+=1},d.size=function(){return u},d.indexOf=function(f){return c[f]},d.contains=function(f){return typeof c[f]<"u"},d};return r},Tb=function(t,e,n){let s=Ab(t,e);for(let a=0;a<e;a+=1)for(let l=0;l<t;l+=1)s.setPixel(l,a,n(l,a));let o=ug();s.write(o);let r=vb(),i=o.toByteArray();for(let a=0;a<i.length;a+=1)r.writeByte(i[a]);return r.flush(),"data:image/gif;base64,"+r},dg=Po,k_=Po.stringToBytes});function hg(t,{ecc:e="M"}={}){let n=dg(0,e);n.addData(String(t),"Byte"),n.make();let s=n.getModuleCount(),o=[];for(let r=0;r<s;r++){let i=[];for(let a=0;a<s;a++)i.push(n.isDark(r,a));o.push(i)}return o}function _b(t,e){let n=t.length+e*2,s=[];for(let o=0;o<n;o++){let r=[];for(let i=0;i<n;i++){let a=o-e,l=i-e;r.push(a>=0&&l>=0&&a<t.length&&l<t.length?t[a][l]:!1)}s.push(r)}return s}function jl(t,{quiet:e=2,color:n=!0,invert:s=!0}={}){let o=_b(hg(t),e);o.length%2&&o.push(o[0].map(()=>!1));let r=[];for(let i=0;i<o.length;i+=2){let a="";for(let l=0;l<o[i].length;l++){let c=o[i][l],u=o[i+1][l];!n&&s&&(c=!c,u=!u),a+=c&&u?"█":c?"▀":u?"▄":" "}r.push(n?"\x1B[30;107m"+a+"\x1B[0m":a)}return r.join(`
|
|
206
|
+
`)}function Pl(t,{quiet:e=4,dark:n="#000",light:s="#fff"}={}){let o=hg(t),r=o.length+e*2,i="";for(let a=0;a<o.length;a++)for(let l=0;l<o.length;l++){if(!o[a][l])continue;let c=1;for(;l+c<o.length&&o[a][l+c];)c++;i+="M"+(l+e)+" "+(a+e)+"h"+c+"v1h-"+c+"z",l+=c-1}return'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 '+r+" "+r+'" shape-rendering="crispEdges"><rect width="'+r+'" height="'+r+'" fill="'+s+'"/><path fill="'+n+'" d="'+i+'"/></svg>'}var fi=V(()=>{fg()});var yg={};zt(yg,{PROVIDERS:()=>mg,backupPath:()=>xd,deleteBackup:()=>pi,detectSyncFolders:()=>Fo,executeMove:()=>Ad,findBackups:()=>Ho,isLeftoffFolder:()=>En,joinLine:()=>Bo,moveStatusFile:()=>hi,ownerOf:()=>Sd,planMove:()=>Uo,readMoveStatus:()=>js,sha256:()=>_n,syncedBy:()=>gg,tilde:()=>ge,walk:()=>Fl,writeMoveStatus:()=>vd});import oe from"node:fs";import Ul from"node:os";import K from"node:path";import pg from"node:crypto";function hi(t=process.env){return t.LEFTOFF_MOVE_STATUS?t.LEFTOFF_MOVE_STATUS:K.join(t.XDG_CONFIG_HOME||K.join(t.HOME||Ul.homedir(),".config"),"checkpoints","move-status.json")}function js(t=process.env){try{return JSON.parse(oe.readFileSync(hi(t),"utf8"))}catch{return null}}function vd(t,e=process.env){let n=hi(e);oe.mkdirSync(K.dirname(n),{recursive:!0});let s=n+"."+process.pid+".tmp";oe.writeFileSync(s,JSON.stringify({...t,at:new Date().toISOString()},null,2)+`
|
|
207
|
+
`),oe.renameSync(s,n)}function Eb(t){let e=[],n=[K.join(t,"Library","Application Support","Syncthing","config.xml"),K.join(t,".config","syncthing","config.xml"),K.join(t,".local","state","syncthing","config.xml")];for(let s of n){let o="";try{o=oe.readFileSync(s,"utf8")}catch{continue}for(let r of o.matchAll(/<folder\b[^>]*>/g)){let i=r[0],a=(i.match(/\bpath="([^"]+)"/)||[])[1],l=(i.match(/\blabel="([^"]*)"/)||[])[1]||(i.match(/\bid="([^"]*)"/)||[])[1]||"";if(!a)continue;let c=a.replace(/^~(?=\/|$)/,t).replace(/&/g,"&");e.push({provider:"syncthing",path:K.resolve(c),label:l||K.basename(c),source:s})}}return!e.length&&ns(K.join(t,"Sync"))&&e.push({provider:"syncthing",path:K.join(t,"Sync"),label:"Sync",source:"default folder"}),e}function Fo({home:t=bd(),current:e=null}={}){let n=[];n.push(...Eb(t));let s=K.join(t,"Library","CloudStorage");try{for(let a of oe.readdirSync(s))/^Dropbox/i.test(a)&&n.push({provider:"dropbox",path:K.join(s,a),label:a.replace(/^Dropbox-?/,"Dropbox ").trim()})}catch{}ns(K.join(t,"Dropbox"))&&!n.some(a=>a.provider==="dropbox")&&n.push({provider:"dropbox",path:K.join(t,"Dropbox"),label:"Dropbox"});let o=K.join(t,"Library","Mobile Documents","com~apple~CloudDocs");ns(o)&&n.push({provider:"icloud",path:o,label:"iCloud Drive"});let r=new Set,i=[];for(let a of n){if(r.has(a.path))continue;r.add(a.path);let l=K.join(a.path,"Leftoff");i.push({...a,suggested:l,display:ge(l),hasLeftoff:En(l),...mg[a.provider]})}return i.sort((a,l)=>(l.recommended?1:0)-(a.recommended?1:0)),{candidates:i,current:e?{path:e,display:ge(e),syncedBy:gg(e,i)}:null}}function gg(t,e){let n=K.resolve(t);for(let s of e)if(n===s.path||n.startsWith(s.path+K.sep))return{provider:s.provider,label:s.label};return/com~apple~CloudDocs/.test(n)?{provider:"icloud",label:"iCloud Drive"}:/Dropbox/.test(n)?{provider:"dropbox",label:"Dropbox"}:null}function En(t){return Qe(K.join(t,"config.json"))&&(ns(K.join(t,"sessions"))||Qe(K.join(t,"README.md")))}function Fl(t){let e=[],n=(s,o)=>{let r=[];try{r=oe.readdirSync(s,{withFileTypes:!0})}catch{return}for(let i of r){let a=o?o+"/"+i.name:i.name;i.isSymbolicLink()||(i.isDirectory()?/^lists\/\.lock/.test(a)||n(K.join(s,i.name),a):i.isFile()&&!Cb(a)&&e.push(a))}};return n(t,""),e.sort()}function _n(t){return pg.createHash("sha256").update(oe.readFileSync(t)).digest("hex")}function Sd(t){let e;return(e=t.match(/^(?:sessions|digests|\.spool)\/([^/]+)\//))&&e[1]!==""||(e=t.match(/^machines\/([^/]+)\.json$/))||(e=t.match(/^\.[\w-]+\.([^/]+)\.json$/))?e[1]:null}function Ob(t){return t==="config.json"?"config":t==="README.md"?"readme":t.startsWith(".secret/")?"secret":t.startsWith("lists/")?"list":t.startsWith("catchups/")?"catchup":"file"}function Uo(t,e,{machine:n,hostname:s=Ul.hostname(),identity:o=null}={}){t=K.resolve(t),e=K.resolve(e);let r=[],i=[],a=[];t===e&&r.push({code:"same_folder",message:"That is already the folder Leftoff uses."}),e.startsWith(t+K.sep)&&r.push({code:"inside_old",message:"The new folder can’t be inside the current one."}),t.startsWith(e+K.sep)&&r.push({code:"contains_old",message:"The new folder can’t contain the current one; pick a subfolder such as "+ge(K.join(e,"Leftoff"))+"."}),ns(t)||r.push({code:"no_source",message:"The current folder "+ge(t)+" doesn’t exist."});let l=!1;if(Qe(e))if(!ns(e))r.push({code:"not_a_folder",message:ge(e)+" is a file, not a folder."});else{let f=oe.readdirSync(e).filter(p=>!/^\.DS_Store$|^\.stfolder$|^\.stignore$|^Icon\r?$/.test(p));En(e)||f.some(p=>["sessions","machines","lists","catchups","digests"].includes(p))?l=!0:f.length&&r.push({code:"not_empty",message:ge(e)+" already has other files in it. Pick an empty folder, or a new one such as "+ge(K.join(e,"Leftoff"))+"."})}else{let f=K.dirname(e);for(;!Qe(f)&&f!==K.dirname(f);)f=K.dirname(f);try{oe.accessSync(f,oe.constants.W_OK)}catch{r.push({code:"not_writable",message:"Can’t write to "+ge(f)+"."})}}if(l&&n){try{let f=JSON.parse(oe.readFileSync(K.join(e,"machines",String(n).replace(/[^\w.-]/g,"_")+".json"),"utf8"));(o?f.fingerprint===o.fingerprint:f.hostname===s)||a.push({machine:n,code:"machine_name_taken",message:"The folder already has a machine called “"+n+"” ("+(f.hostname||"another Mac")+", key "+(f.fingerprint||"?")+'). Give this Mac another name first: leftoff setup → Name for this machine, or "machineNames" in config.json.'})}catch{}if(!a.length&&ns(K.join(e,"sessions",n))){let f=new Set(oe.readdirSync(K.join(e,"sessions",n))),p=ns(K.join(t,"sessions",n))?new Set(oe.readdirSync(K.join(t,"sessions",n))):new Set,h=[...f].filter(m=>!m.startsWith(".")&&!p.has(m));h.length&&a.push({machine:n,code:"machine_name_maybe_taken",message:"The folder already has "+h.length+" session"+(h.length===1?"":"s")+" from a machine called “"+n+"” that this Mac didn’t write. If that’s another Mac, give this one a different name first (leftoff setup). If it really is this Mac, run again with --force."})}}let c=[],u=0,d={total:0,copy:0,same:0,keepTarget:0,conflictCopy:0,renameCopy:0};if(!r.length){for(let f of Fl(t)){let p=K.join(t,f),h=K.join(e,f),m=oe.statSync(p).size;d.total++;let y="copy",w=null,k=f;if(l&&Qe(h))if(_n(p)===_n(h))y="same";else{let S=Sd(f),v=Ob(f);S&&S===n?(y="copy",w="this machine’s own file: ours is newer"):v==="list"?(y="conflict-copy",k=f.replace(/\.md$/,"")+".sync-conflict-"+kd(new Date,!0)+"-LEFTOFF.md",w="the list changed on both sides: merged item by item"):v==="catchup"?(y="rename-copy",k=Ib(e,f),w="same minute, different catch-up"):(y="keep-target",w=v==="config"?"the joined folder’s settings win":S?S+"’s own file: the joined folder has its latest":"the joined folder’s copy wins")}(y==="copy"||y==="conflict-copy"||y==="rename-copy")&&(u+=m),d[{copy:"copy",same:"same","keep-target":"keepTarget","conflict-copy":"conflictCopy","rename-copy":"renameCopy"}[y]]++,c.push({rel:f,dest:k,action:y,why:w,size:m})}if(l){let f=new Set;for(let p of Fl(e)){let h=Sd(p);h&&h!==n&&f.add(h)}f.size&&i.push({code:"merging",message:"Joining a folder that already has "+[...f].sort().join(", ")+"’s data. Nothing of theirs is overwritten."}),Qe(K.join(e,"config.json"))&&Qe(K.join(t,"config.json"))&&_n(K.join(e,"config.json"))!==_n(K.join(t,"config.json"))&&i.push({code:"config",message:"The joined folder has its own settings (config.json); those are kept. This Mac’s name is carried over."})}}return{from:t,to:e,merge:l,ok:!r.length&&!a.length,errors:r,warnings:i,collisions:a,files:c,counts:d,bytes:u,backup:xd(t)}}function kd(t=new Date,e=!1){let n=s=>String(s).padStart(2,"0");return e?t.getFullYear()+n(t.getMonth()+1)+n(t.getDate())+"-"+n(t.getHours())+n(t.getMinutes())+n(t.getSeconds()):t.getFullYear()+"-"+n(t.getMonth()+1)+"-"+n(t.getDate())+"-"+n(t.getHours())+n(t.getMinutes())}function Ib(t,e){let n=K.extname(e),s=e.slice(0,-n.length);for(let o=2;o<100;o++){let r=s+"-"+o+n;if(!Qe(K.join(t,r)))return r}return s+"-"+pg.randomBytes(3).toString("hex")+n}function xd(t,e=new Date){let n=t+".leftoff-backup-"+kd(e);for(let s=2;Qe(n);s++)n=t+".leftoff-backup-"+kd(e)+"-"+s;return n}async function Ad(t,{hooks:e={},machine:n,hostname:s=Ul.hostname(),say:o=()=>{},copyFile:r=oe.copyFileSync,now:i=new Date}={}){if(!t.ok)return{ok:!1,stage:"plan",errors:[...t.errors,...t.collisions]};let a=[],l=[],c={ok:!1,from:t.from,to:t.to,merge:t.merge,copied:0,verified:0,kept:0,conflicts:0,stragglers:0,backup:null},u=m=>{let y=[],w=m;for(;!Qe(w);)y.push(w),w=K.dirname(w);for(let k of y.reverse())oe.mkdirSync(k),l.push(k)},d=(m,y)=>{let w=K.join(t.to,y);u(K.dirname(w));let k=Qe(w),S=K.join(K.dirname(w),"."+K.basename(w)+".move-"+process.pid+".tmp");r(m,S);try{oe.chmodSync(S,oe.statSync(m).mode&511)}catch{}return oe.renameSync(S,w),k||a.push(w),w},f=()=>{for(let m of a.reverse())try{oe.unlinkSync(m)}catch{}for(let m of l.reverse())try{oe.rmdirSync(m)}catch{}},p=!1;try{o("Stopping the daemon so nothing writes mid-move"),e.stopWriters&&(await e.stopWriters(),p=!0),u(t.to),o("Copying "+t.counts.total+" files");let m=[];for(let k of t.files){if(k.action==="same"){c.verified++;continue}if(k.action==="keep-target"){c.kept++;continue}let S=d(K.join(t.from,k.rel),k.dest);(k.action==="conflict-copy"||k.action==="rename-copy")&&c.conflicts++,c.copied++,m.push([K.join(t.from,k.rel),S,k.rel])}t.files.find(k=>k.rel==="config.json"&&k.action==="keep-target")&&Nb(t.from,t.to,s),o("Verifying "+m.length+" copies");for(let[k,S,v]of m){if(_n(k)!==_n(S))throw Object.assign(new Error("verify failed: "+v+" doesn’t match after copying"),{code:"verify_failed"});c.verified++}if(c.verified+c.kept!==t.counts.total)throw Object.assign(new Error("verify failed: "+(c.verified+c.kept)+" of "+t.counts.total+" files accounted for"),{code:"verify_failed"});let w=new Set(t.files.map(k=>k.rel));for(let k of Fl(t.from)){if(w.has(k)||Qe(K.join(t.to,k)))continue;let S=d(K.join(t.from,k),k);if(_n(K.join(t.from,k))!==_n(S))throw Object.assign(new Error("verify failed: "+k),{code:"verify_failed"});c.stragglers++}}catch(m){if(f(),p&&e.startWriters)try{await e.startWriters(t.from)}catch{}return{...c,ok:!1,stage:"copy",error:m.message,code:m.code||"copy_failed",rolledBack:!0}}let h=xd(t.from,i);try{oe.renameSync(t.from,h),oe.writeFileSync(K.join(h,"MOVED-HERE.md"),`# This is a Leftoff backup
|
|
208
|
+
|
|
209
|
+
On `+i.toISOString()+` Leftoff moved this folder to:
|
|
210
|
+
|
|
211
|
+
`+t.to+`
|
|
212
|
+
|
|
213
|
+
Every file was copied and checked (sha-256). This copy is kept until you delete it:
|
|
214
|
+
Settings → Sync on the board, or \`leftoff move --delete-backup\`. Leftoff never deletes it on its own.
|
|
215
|
+
`),c.backup=h,e.switchTo&&await e.switchTo(t.to)}catch(m){try{!Qe(t.from)&&Qe(h)&&oe.renameSync(h,t.from)}catch{}try{oe.unlinkSync(K.join(t.from,"MOVED-HERE.md"))}catch{}if(e.switchTo)try{await e.switchTo(t.from)}catch{}if(f(),e.startWriters)try{await e.startWriters(t.from)}catch{}return{...c,ok:!1,stage:"switch",error:m.message,code:"switch_failed",rolledBack:!0,backup:null}}if(e.startWriters){let m=await e.startWriters(t.to).catch(y=>({ok:!1,error:y.message}));if(!m||m.ok===!1){try{e.stopWriters&&await e.stopWriters()}catch{}try{oe.unlinkSync(K.join(h,"MOVED-HERE.md"))}catch{}try{oe.renameSync(h,t.from)}catch{}if(e.switchTo)try{await e.switchTo(t.from)}catch{}f();try{await e.startWriters(t.from)}catch{}return{...c,ok:!1,stage:"start",error:"the daemon didn’t start on the new folder"+(m&&m.error?": "+m.error:""),code:"start_failed",rolledBack:!0,backup:null}}}return{...c,ok:!0}}function Nb(t,e,n){try{let s=JSON.parse(oe.readFileSync(K.join(t,"config.json"),"utf8")),o=JSON.parse(oe.readFileSync(K.join(e,"config.json"),"utf8")),r=(s.machineNames||{})[n]||(s.machine&&!(o.machineNames||{})[n]?s.machine:null);if(!r||(o.machineNames||{})[n]===r)return;o.machineNames={...o.machineNames||{},[n]:r};let i=K.join(e,".config.json.move.tmp");oe.writeFileSync(i,JSON.stringify(o,null,2)+`
|
|
216
|
+
`),oe.renameSync(i,K.join(e,"config.json"))}catch{}}function Ho(t,{extra:e=[]}={}){let n=[],s=new Set,o=new Set([K.dirname(t),bd()]),r=js(),i=[];for(let a of[...e,r&&r.backup])a&&i.push(K.resolve(a));for(let a of o){let l=[];try{l=oe.readdirSync(a)}catch{continue}for(let c of l)i.push(K.join(a,c))}for(let a of i){if(s.has(a)||(s.add(a),!/\.leftoff-backup-\d{4}-\d{2}-\d{2}-\d{4}(-\d+)?$/.test(K.basename(a))))continue;let l="";try{l=oe.readFileSync(K.join(a,"MOVED-HERE.md"),"utf8")}catch{continue}let c=(l.match(/^ {4}(.+)$/m)||[])[1]||null;c&&K.resolve(c)!==K.resolve(t)||n.push({path:a,display:ge(a),movedTo:c,at:(l.match(/^On (\S+)/m)||[])[1]||null})}return n}function pi(t){let e=K.resolve(t);if(!/\.leftoff-backup-/.test(K.basename(e))||!Qe(K.join(e,"MOVED-HERE.md")))throw Object.assign(new Error("not a Leftoff backup folder: "+t),{code:"not_a_backup"});return oe.rmSync(e,{recursive:!0,force:!0}),{deleted:e}}function Bo(t,{cli:e="leftoff"}={}){let n=ge(t),s=/\s/.test(n)?'"'+n.replace(/^~\//,"$HOME/")+'"':n;return{installed:e+" move "+s,fresh:"npx -y github:"+Ze+" setup --dir "+s}}var bd,Qe,ns,ge,mg,Cb,Ps=V(()=>{wt();bd=()=>process.env.HOME||Ul.homedir(),Qe=t=>{try{return oe.statSync(t),!0}catch{return!1}},ns=t=>{try{return oe.statSync(t).isDirectory()}catch{return!1}},ge=t=>{let e=bd();return t===e?"~":t.startsWith(e+K.sep)?"~"+t.slice(e.length):t};mg={syncthing:{label:"Syncthing",recommended:!0,tradeoff:"Peer to peer, fast, and it never offloads files. Both Macs need Syncthing running and the folder shared between them."},dropbox:{label:"Dropbox",tradeoff:"Works well. Turn off “online-only” for this folder (right-click → Make available offline) so files stay on disk."},icloud:{label:"iCloud Drive",tradeoff:"Works, but syncs slower and macOS may offload files to the cloud. Right-click the folder → “Keep Downloaded”."},other:{label:"Another folder",tradeoff:"Any folder works if something else syncs it (Google Drive, a NAS). Leftoff only needs plain files."}};Cb=t=>/(^|\/)\.[^/]*\.tmp$|\.tmp(-\d+)?$|\.lock$|(^|\/)\.lock(\/|$)|^\.daemon\.pid$|(^|\/)\.DS_Store$|^MOVED-HERE\.md$/.test(t)});import Mb from"node:readline";function ss(t=process.stdout){return process.env.NO_COLOR?!1:process.env.FORCE_COLOR&&process.env.FORCE_COLOR!=="0"?!0:!!(t&&t.isTTY)}function os(t){let e={};for(let[n,[s,o]]of Object.entries(Lb))e[n]=r=>t?`\x1B[${s}m${r}\x1B[${o}m`:String(r);return e}function Lt(t){if(t==null||isNaN(t))return"?";let e=["B","KB","MB","GB","TB"],n=0,s=Number(t);for(;s>=1e3&&n<e.length-1;)s/=1e3,n++;return(s>=100||n===0?Math.round(s):s.toFixed(1))+" "+e[n]}function gi(t){if(t<1e3)return Math.round(t)+" ms";let e=t/1e3;return e<60?e.toFixed(1)+" s":Math.floor(e/60)+"m "+String(Math.round(e%60)).padStart(2,"0")+"s"}function Db(t,e=24){let n=Math.max(0,Math.min(1,Number(t)||0)),s=Math.floor(n*e),o=["","▏","▎","▍","▌","▋","▊","▉"][Math.floor((n*e-s)*8)]||"";return"█".repeat(s)+o+" ".repeat(Math.max(0,e-s-(o?1:0)))}function ut(t,{out:e=process.stdout,colors:n=ee}={}){let s=!!e.isTTY,o=0,r=t,i=null,a=()=>e.write("\r\x1B[2K"+n.cyan(wg[o++%wg.length])+" "+r);s&&(a(),i=setInterval(a,80),i.unref&&i.unref());let l=(c,u)=>{i&&clearInterval(i),i=null,e.write((s?"\r\x1B[2K":"")+c+" "+(u??r)+`
|
|
217
|
+
`)};return{update(c){r=c,s&&!i&&a()},succeed(c){l(n.green("✔"),c)},fail(c){l(n.red("✖"),c)},warn(c){l(n.yellow("!"),c)},info(c){l(n.cyan("ℹ"),c)},stop(){i&&clearInterval(i),i=null,s&&e.write("\r\x1B[2K")},get active(){return!!i}}}function Hl(t,{out:e=process.stdout,colors:n=ee,width:s=26}={}){let o=!!e.isTTY,r=-10,i=0;return{update(a,l=""){let c=Math.floor((a||0)*100);if(o){let u=Date.now();if(u-i<60&&c<100)return;i=u;let f=Db(a,s).trimEnd();e.write("\r\x1B[2K "+n.cyan(f)+n.dim("░".repeat(s-f.length))+" "+String(c).padStart(3)+"% "+n.dim(l))}else c>=r+10&&(r=c-c%10,e.write(" "+t+" "+String(c).padStart(3)+"% "+l+`
|
|
218
|
+
`))},done(a){e.write((o?"\r\x1B[2K":"")+n.green("✔")+" "+a+`
|
|
219
|
+
`)},fail(a){e.write((o?"\r\x1B[2K":"")+n.red("✖")+" "+a+`
|
|
220
|
+
`)}}}function Sg(t,e,{input:n=process.stdin,output:s=process.stdout,colors:o=ee}={}){return new Promise(r=>{let i=0,a="",l=0,c=()=>{let p=a.toLowerCase();return p?e.filter(h=>(h.search||h.label+" "+(h.hint||"")).toLowerCase().includes(p)):e},u=()=>{let p=c();i>=p.length&&(i=Math.max(0,p.length-1)),l&&s.write("\x1B["+l+"A\x1B[0J");let h=[o.cyan("?")+" "+o.bold(t)+" "+o.dim(a?"filter: "+a:"↑↓ move · enter go · type to filter · esc quit")];p.slice(0,15).forEach((m,y)=>{let w=y===i;h.push((w?o.cyan(" › "):" ")+o.dim((y<9?String(y+1):" ")+" ")+(w?o.bold(m.label):m.label)+(m.hint?" "+o.dim(m.hint):"")),m.detail&&h.push(" "+o.dim(m.detail))}),p.length||h.push(o.dim(" nothing matches")),s.write(h.join(`
|
|
221
|
+
`)+`
|
|
222
|
+
`),l=h.length},d=p=>{n.removeListener("data",f),n.setRawMode&&n.setRawMode(!1),n.pause(),s.write("\x1B[?25h"),r(p)},f=p=>{let h=String(p),m=c();if(h===""||h==="\x1B"||h==="q"&&!a)return d(null);if(h==="\r"||h===`
|
|
223
|
+
`)return d(m[i]?m[i].value:null);if(h==="\x1B[A"||h==="k"&&!a)i=Math.max(0,i-1);else if(h==="\x1B[B"||h==="j"&&!a)i=Math.min(m.length-1,i+1);else{if(/^[1-9]$/.test(h)&&!a&&m[Number(h)-1])return d(m[Number(h)-1].value);h===""||h==="\b"?a=a.slice(0,-1):/^[\x20-\x7e]$/.test(h)&&(a+=h,i=0)}u()};n.setRawMode&&n.setRawMode(!0),n.resume(),n.on("data",f),s.write("\x1B[?25l"),u()})}function kg(t,e,{input:n=process.stdin,output:s=process.stdout,colors:o=ee}={}){return new Promise(r=>{let i=0,a=0,l=e.map(f=>f.checked!==!1),c=()=>{a&&s.write("\x1B["+a+"A\x1B[0J");let f=l.filter(Boolean).length,p=[o.cyan("?")+" "+o.bold(t)+" "+o.dim("space toggle · a all/none · enter restore "+f+" · esc quit")],h=Math.max(0,Math.min(i-7,e.length-15));e.slice(h,h+15).forEach((m,y)=>{let w=h+y,k=w===i;p.push((k?o.cyan(" › "):" ")+(l[w]?o.green("[x]"):o.dim("[ ]"))+" "+(k?o.bold(m.label):m.label)+(m.hint?" "+o.dim(m.hint):""))}),e.length>15&&p.push(o.dim(" … "+e.length+" in all")),s.write(p.join(`
|
|
224
|
+
`)+`
|
|
225
|
+
`),a=p.length},u=f=>{n.removeListener("data",d),n.setRawMode&&n.setRawMode(!1),n.pause(),s.write("\x1B[?25h"),r(f)},d=f=>{let p=String(f);if(p===""||p==="\x1B"||p==="q")return u(null);if(p==="\r"||p===`
|
|
226
|
+
`)return u(e.filter((h,m)=>l[m]).map(h=>h.value));if(p==="\x1B[A"||p==="k")i=Math.max(0,i-1);else if(p==="\x1B[B"||p==="j")i=Math.min(e.length-1,i+1);else if(p===" ")l[i]=!l[i];else if(p==="a"){let h=l.every(Boolean);l.fill(!h)}c()};n.setRawMode&&n.setRawMode(!0),n.resume(),n.on("data",d),s.write("\x1B[?25l"),c()})}var mi,Lb,ee,wg,ct,Cn=V(()=>{mi=class extends Error{constructor(e="cancelled"){super(e),this.cancelled=!0}};Lb={bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],gray:[90,39]};ee=os(ss());wg=["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"];ct=class{constructor({input:e=process.stdin,output:n=process.stdout,yes:s=!1,colors:o=ee}={}){this.input=e,this.out=n,this.yes=s,this.c=o,this.queue=[],this.waiter=null,this.closed=!1,this.muted=!1,this.rl=null,this.interruptHandlers=new Set}async interruptible(e){let n=new AbortController,s=()=>n.abort();this.interruptHandlers.add(s);let o=()=>s();this.rl||process.once("SIGINT",o);try{return await e(n.signal)}catch(r){throw n.signal.aborted?new mi("cancelled (Ctrl-C)"):r}finally{this.interruptHandlers.delete(s),process.removeListener("SIGINT",o)}}_open(){if(this.rl||this.yes)return;this.rl=Mb.createInterface({input:this.input,output:this.out,terminal:!!(this.input.isTTY&&this.out.isTTY)});let e=this.rl._writeToOutput&&this.rl._writeToOutput.bind(this.rl);e&&(this.rl._writeToOutput=n=>{this.muted?/\r|\n/.test(n)&&e(`
|
|
227
|
+
`):e(n)}),this.rl.on("line",n=>{if(this.waiter){let s=this.waiter;this.waiter=null,this.rl.terminal||this.out.write((this.muted?"":n)+`
|
|
228
|
+
`),s.resolve(n)}else this.queue.push(n)}),this.rl.on("SIGINT",()=>{for(let n of this.interruptHandlers)try{n()}catch{}this._cancel("cancelled (Ctrl-C)")}),this.rl.on("close",()=>{this.closed=!0,this._cancel("input closed")})}_cancel(e){if(this.waiter){let n=this.waiter;this.waiter=null,n.reject(new mi(e))}else this.pendingCancel=e}_line(e){if(this._open(),this.queue.length){let n=this.queue.shift();return this.rl.terminal||this.out.write(e+(this.muted?"":n)+`
|
|
229
|
+
`),Promise.resolve(n)}return this.pendingCancel||this.closed?Promise.reject(new mi(this.pendingCancel||"input closed")):new Promise((n,s)=>{this.waiter={resolve:n,reject:s},this.rl.terminal?(this.rl.setPrompt(e),this.rl.prompt()):this.out.write(e)})}_echoDefault(e,n){this.out.write(e+this.c.dim(n)+`
|
|
230
|
+
`)}async ask(e,n="",{hint:s,validate:o,secret:r=!1}={}){let i=this.c.cyan("?")+" "+this.c.bold(e)+(s?" "+this.c.dim(s):"")+(n!==""&&!r?" "+this.c.dim("("+n+")"):"")+" ";if(this.yes)return this._echoDefault(i,r?"••••":String(n)),n;for(;;){this.muted=!1,r&&(this.out.write(i),this.muted=!0);let a;try{a=(await this._line(r?"":i)).trim()}finally{this.muted=!1}r&&this.rl&&this.rl.terminal&&this.out.write(`
|
|
231
|
+
`);let l=a===""?n:a,c=o?o(l):null;if(!c)return l;this.out.write(" "+this.c.red(c)+`
|
|
232
|
+
`)}}async confirm(e,n=!0){let s=this.c.cyan("?")+" "+this.c.bold(e)+" "+this.c.dim(n?"(Y/n)":"(y/N)")+" ";if(this.yes)return this._echoDefault(s,n?"yes":"no"),n;for(;;){let o=(await this._line(s)).trim().toLowerCase();if(o==="")return n;if(["y","yes"].includes(o))return!0;if(["n","no"].includes(o))return!1;this.out.write(" "+this.c.red("Please answer y or n.")+`
|
|
233
|
+
`)}}async select(e,n,s=0){this.out.write(this.c.cyan("?")+" "+this.c.bold(e)+`
|
|
234
|
+
`),n.forEach((r,i)=>{let a=String(i+1).padStart(2),l=i===s?this.c.cyan("›"):" ",c=i===s?this.c.bold(r.label):r.label;this.out.write(" "+l+" "+this.c.dim(a+")")+" "+c+(r.hint?" "+this.c.dim(r.hint):"")+`
|
|
235
|
+
`),r.detail&&this.out.write(" "+this.c.dim(r.detail)+`
|
|
236
|
+
`)});let o=" "+this.c.dim("choose 1-"+n.length)+" "+this.c.dim("("+(s+1)+")")+" ";if(this.yes)return this._echoDefault(o,String(s+1)),n[s].value;for(;;){let r=(await this._line(o)).trim();if(r==="")return n[s].value;let i=Number(r);if(Number.isInteger(i)&&i>=1&&i<=n.length)return n[i-1].value;let a=n.find(l=>String(l.value)===r);if(a)return a.value;this.out.write(" "+this.c.red("Pick a number from 1 to "+n.length+".")+`
|
|
237
|
+
`)}}pause(){this.rl&&this.rl.pause()}resume(){this.rl&&this.rl.resume()}close(){this.rl&&(this.rl.removeAllListeners("close"),this.rl.close(),this.rl=null)}}});import Tt from"node:fs";import Rb from"node:os";import Ue from"node:path";import{spawnSync as jb}from"node:child_process";import{fileURLToPath as Pb}from"node:url";function Kl(t=process.env){return t.CHECKPOINTS_CLAUDE_BIN?t.CHECKPOINTS_CLAUDE_BIN:Ia("claude",t)}function On(t,e,{env:n=process.env,timeoutMs:s=12e4}={}){let o=jb(t,e,{env:{...n,CHECKPOINTS_INTERNAL:"1",LEFTOFF_INTERNAL:"1"},encoding:"utf8",timeout:s});return{code:o.status==null?-1:o.status,stdout:o.stdout||"",stderr:o.stderr||(o.error?String(o.error.message):""),args:e}}function Ub(t){let e=String(t||"").match(/(\d+)\.(\d+)\.(\d+)/);return e?e[0]:null}function Hb(t,e){let n=String(t).split(".").map(Number),s=String(e).split(".").map(Number);for(let o=0;o<3;o++){let r=(n[o]||0)-(s[o]||0);if(r)return r>0?1:-1}return 0}function vg(t=process.env){let e=Kl(t);if(!e)return{found:!1,bin:null,version:null,modsSupported:!1};let n=On(e,["--version"],{env:t,timeoutMs:15e3}),s=Ub(n.stdout+" "+n.stderr);return{found:!0,bin:e,version:s,modsSupported:!!s&&Hb(s,yi)>=0}}function Td(t){return/[\\/]_npx[\\/]/.test(t)||/[\\/]\.npm[\\/]/.test(t)}function _d(t=Ko){try{return JSON.parse(Tt.readFileSync(Ue.join(t,"package.json"),"utf8")).leftoffBuild==="npm"}catch{return!1}}function Bb(t=process.env){return t.LEFTOFF_PLUGIN_DIR?t.LEFTOFF_PLUGIN_DIR:Ue.join(t.XDG_CONFIG_HOME||Ue.join(t.HOME||Rb.homedir(),".config"),"checkpoints","plugin")}function Ed({from:t=Ko,env:e=process.env}={}){let n=Bb(e);if(!Tt.existsSync(Ue.join(t,".claude-plugin","marketplace.json")))throw new Error("no .claude-plugin/marketplace.json in "+t);let s=n+"."+process.pid+".tmp";Tt.rmSync(s,{recursive:!0,force:!0}),Tt.mkdirSync(Ue.join(s,".claude-plugin"),{recursive:!0}),Tt.copyFileSync(Ue.join(t,".claude-plugin","marketplace.json"),Ue.join(s,".claude-plugin","marketplace.json"));let o=r=>/^(tests|tsconfig\.json)$/.test(r)||/(^|[\\/])types([\\/]|$)/.test(r);return Tt.cpSync(Ue.join(t,"mod"),Ue.join(s,"mod"),{recursive:!0,filter:r=>!o(Ue.relative(Ue.join(t,"mod"),r))}),Tt.writeFileSync(Ue.join(s,"README.md"),"Leftoff's Claude Code plugin (the mod), copied here by `leftoff setup` / `leftoff upgrade` from the installed package.\nClaude Code reads it as a local marketplace. Safe to delete: `leftoff setup` puts it back.\n"),Tt.rmSync(n,{recursive:!0,force:!0}),Tt.mkdirSync(Ue.dirname(n),{recursive:!0}),Tt.renameSync(s,n),n}function xg({repoDir:t=Ko,override:e,env:n=process.env,copy:s=Ed}={}){if(e)return e;if(!Td(t)&&Tt.existsSync(Ue.join(t,".git"))&&Tt.existsSync(Ue.join(t,".claude-plugin","marketplace.json")))return t;if(_d(t)&&Tt.existsSync(Ue.join(t,".claude-plugin","marketplace.json")))try{return s({from:t,env:n})}catch{}return Bl}function bg(t,e=Ce){let n=String(t||""),s=n.search(new RegExp("(^|[^\\w-])"+e.replace(/[.@]/g,"\\$&")+"(?![\\w-])","m"));if(s<0)return{installed:!1,enabled:!1};let o=n.slice(s,s+400).split(/\n\s*\n/)[0];return{installed:!0,enabled:!(/disabled/i.test(o)&&!/✔\s*enabled|status:\s*enabled/i.test(o))}}function Kb(t){return new RegExp("(^|[^\\w-])"+nn+"([^\\w-]|$)","m").test(String(t||""))}function Gl(t,e){let n=On(t,["plugin","list"],{env:e,timeoutMs:3e4}),s=On(t,["plugin","marketplace","list"],{env:e,timeoutMs:3e4}),o=bg(n.stdout,rs);return{...bg(n.stdout),legacyInstalled:o.installed,legacyEnabled:o.enabled,marketplaceAdded:s.code===0&&Kb(s.stdout),listOk:n.code===0}}function Cd({bin:t,env:e=process.env,state:n=null,onStep:s=()=>{}}){let o=n||Gl(t,e);if(!o.legacyInstalled)return{ok:!0,migrated:!1,steps:[]};let r=[],i=c=>{s("claude "+c.join(" "));let u=On(t,c,{env:e});return r.push(u),u},a=c=>(c.stderr||c.stdout).trim().split(`
|
|
238
|
+
`).pop()||"exit "+c.code;if(!o.installed){i(["plugin","marketplace","update",nn]);let c=i(["plugin","install",Ce]);if(c.code!==0&&!/already installed/i.test(c.stdout+c.stderr))return{ok:!1,migrated:!1,steps:r,error:a(c)};o.legacyEnabled===!1&&i(["plugin","disable",Ce])}let l=i(["plugin","uninstall",rs]);return l.code!==0?{ok:!1,migrated:!0,steps:r,error:"installed "+Ce+" but couldn't uninstall "+rs+": "+a(l)}:{ok:!0,migrated:!0,enabled:o.legacyEnabled!==!1,steps:r}}function Od({bin:t,source:e,env:n=process.env,onStep:s=()=>{}}){let o=[],r=Gl(t,n);if(r.legacyInstalled){let l=Cd({bin:t,env:n,state:r,onStep:s});if(!l.ok)return{ok:!1,steps:l.steps,state:r,error:l.error};let c=Od({bin:t,source:e,env:n,onStep:s});return{...c,migrated:l.migrated,steps:[...l.steps,...c.steps]}}if(r.installed&&r.enabled)return{ok:!0,already:!0,steps:o,state:r};if(r.installed&&!r.enabled){s("claude plugin enable "+Ce);let l=On(t,["plugin","enable",Ce],{env:n});return o.push(l),{ok:l.code===0,already:!0,enabled:l.code===0,steps:o,state:r,error:l.code?(l.stderr||l.stdout).trim():null}}if(!r.marketplaceAdded){s("claude plugin marketplace add "+e);let l=On(t,["plugin","marketplace","add",e],{env:n});if(o.push(l),l.code!==0&&!/already/i.test(l.stdout+l.stderr))return{ok:!1,steps:o,state:r,error:(l.stderr||l.stdout).trim()||"exit "+l.code}}s("claude plugin install "+Ce);let i=On(t,["plugin","install",Ce],{env:n});o.push(i);let a=i.code===0||/already installed/i.test(i.stdout+i.stderr);return{ok:a,steps:o,state:r,error:a?null:(i.stderr||i.stdout).trim()||"exit "+i.code}}function Ag(t){return!!(t&&t.disableAllHooks===!0)}var Ko,Bl,nn,Fb,Ce,rs,yi,$l=V(()=>{go();wt();Ko=Ue.resolve(Ue.dirname(Pb(import.meta.url)),".."),Bl=Ze,nn=Sp,Fb=kp,Ce=Fb+"@"+nn,rs=bp+"@"+nn,yi="2.1.287"});import Gb from"node:dns/promises";import $b from"node:http";import Yb from"node:fs";import Tg from"node:path";import{spawn as Wb}from"node:child_process";function zb(t){return{pretty:"http://"+yt+":"+t+"/",plain:"http://127.0.0.1:"+t+"/"}}async function qb(t=yt,e=n=>Gb.lookup(n,{all:!0})){try{let n=await Promise.race([e(t),Eg(800).then(()=>{throw new Error("timeout")})]),s=(Array.isArray(n)?n:[n]).map(o=>typeof o=="string"?o:o&&o.address).filter(Boolean);return s.length>0&&s.every(Jb)}catch{return!1}}function Vb(t,e,n){return new Promise(s=>{let o=$b.request({host:"127.0.0.1",port:t,path:"/v1/health",method:"GET",headers:{host:e,authorization:"Bearer "+(n||"")},timeout:1500},r=>{r.resume(),s(r.statusCode===403?"refused":"ok")});o.on("timeout",()=>{o.destroy(),s("down")}),o.on("error",()=>s("down")),o.end()})}async function wi({port:t,prefer:e="auto",token:n=null,resolve:s=qb,probe:o=Vb}={}){let r=zb(t),i=String(e||"auto").toLowerCase();return["127.0.0.1","ip","plain","localhost-ip"].includes(i)?{url:r.plain,pretty:!1,reason:"asked for 127.0.0.1"}:[yt,"pretty","name"].includes(i)?{url:r.pretty,pretty:!0,reason:"asked for "+yt}:await s(yt)?await o(t,yt+":"+t,n)==="refused"?{url:r.plain,pretty:!1,reason:"the running daemon predates "+yt+" support (restart it)"}:{url:r.pretty,pretty:!0,reason:yt+" resolves to loopback"}:{url:r.plain,pretty:!1,reason:yt+" doesn't resolve to loopback here"}}function Zb(t,{platform:e=process.platform,spawnImpl:n=Wb}={}){let s=e==="darwin"?["open",[t]]:e==="win32"?["cmd",["/c","start","",t]]:["xdg-open",[t]];try{let o=n(s[0],s[1],{detached:!0,stdio:"ignore"});return o&&o.on&&o.on("error",()=>{}),o&&o.unref&&o.unref(),!0}catch{return!1}}async function _g(t,e){try{let n=await fetch("http://127.0.0.1:"+t+"/v1/health",{headers:{authorization:"Bearer "+(e||"")},signal:AbortSignal.timeout(1500)});return n.status===401?{up:!0,auth:!1}:n.ok?{up:!0,auth:!0,...await n.json()}:{up:!1}}catch{return{up:!1}}}async function Cg({flags:t={},out:e=process.stdout,bin:n,env:s=process.env,openImpl:o=Zb,startImpl:r}={}){let i=q(),a=Z(i),l=Number(t.port||a.port),c=Se(i),u=m=>{t.quiet||e.write(m+`
|
|
239
|
+
`)},d=await _g(l,c);if(!d.up&&!t["no-start"]){if(!Yb.existsSync(Tg.join(i,"config.json")))return u("No Leftoff folder at "+i+" yet. Run: leftoff setup"),1;u("Daemon isn't running; starting it…"),(r||(await Promise.resolve().then(()=>(Fs(),Yl))).startDaemonDetached)({root:i,bin:n,env:s});for(let y=0;y<40&&!(d=await _g(l,c)).up;y++)await Eg(250);if(!d.up)return u("The daemon didn't start; see "+Tg.join(i,".daemon.log")),1}d.up&&!d.auth&&u("Note: something on port "+l+" doesn't know this folder's token (another Leftoff folder?).");let f=t.host||s.CHECKPOINTS_OPEN_HOST||a.openHost||"auto",p=await wi({port:l,prefer:f,token:c});if(t.print||t["no-browser"])return e.write(p.url+`
|
|
240
|
+
`),0;let h=o(p.url);return u((h?"Opened ":"Open ")+p.url+(p.pretty?"":" ("+p.reason+")")),0}var Eg,Jb,Id=V(()=>{ke();Nd();Eg=t=>new Promise(e=>setTimeout(e,t));Jb=t=>/^127\./.test(t)||t==="::1"||t==="0:0:0:0:0:0:0:1"});function Wl(t,{indent:e=""}={}){let n=[],s=r=>n.push(e+r);s("Doing: "+(t.doing||"")),s("Stopped at: "+(t.stoppedAt||"")),s("Next: "+(t.next||""));let o=(r,i,a=l=>l)=>{!Array.isArray(i)||!i.length||(s(r+":"),i.forEach((l,c)=>s((r==="Next steps"?" "+(c+1)+". ":" - ")+a(l))))};return o("Key decisions",t.decisions),o("Open questions",t.questions),o("Files touched",t.files,r=>(r.path||"")+(r.path&&r.why?" — ":"")+(r.why||"")),o("What didn’t work",t.didntWork),t.recap&&s("Recap: "+t.recap),o("Next steps",t.nextSteps),n.join(`
|
|
241
|
+
`)}var Md,Og,Xb,Ig,Ng,Ld=V(()=>{Md={doing:"Adding retry with backoff to the sync client (src/sync/client.ts).",stoppedAt:"Unit tests pass; the integration test times out after 30s on the second retry.",next:"YOU: pick between faking the clock in the integration test or raising its timeout."},Og={decisions:["Exponential backoff capped at 30s: the API rate-limits at 1 request/s.","Retries live in the client, not the job queue, so every caller gets them."],questions:["Should a 429 count toward the 5-retry cap?"],files:[{path:"src/sync/client.ts",why:"the retry loop and backoff"},{path:"src/sync/client.test.ts",why:"unit tests for 500, 429 and timeouts"},{path:"test/sync.int.test.ts",why:"raised the timeout to see if it was just slow"}],didntWork:["Jest fake timers with fetch: the mocked response never resolved."]},Xb={recap:"The session set out to make sync survive the API's rate limit after Monday's failed imports. It added a retry loop with exponential backoff to the client and covered 500s, 429s and timeouts with unit tests. Faking timers broke fetch, so the integration test still runs on real time, and the second retry pushes it past 30 seconds. Everything else is green; the open choice is how to test the slow path.",nextSteps:["Fake the clock in the integration test with an injectable sleep().","Decide whether 429s count toward the retry cap and note it in the README.","Open the PR against main and link the import incident."]},Ig={short:{...Md},informative:{...Md,...Og},long:{...Md,...Og,...Xb}},Ng=[{key:"short",label:"Short",approx:"~300 tokens",blurb:"Doing, Stopped at, Next. Three lines."},{key:"informative",label:"Informative",approx:"~700 tokens",blurb:"Adds key decisions, open questions, files touched and why, and what didn’t work.",recommended:!0},{key:"long",label:"Long",approx:"~1500 tokens",blurb:"Adds a narrative recap and the next 3 steps."}]});var Mg={};zt(Mg,{RESERVED:()=>Si,checkUserName:()=>In,domainsOf:()=>tv,isReserved:()=>Rd,passthroughOf:()=>Qb,userFromHost:()=>ev,validUser:()=>Dd});function Dd(t){let e=String(t||"");return/^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$/.test(e)&&e.length>=2&&!e.includes("--")}function Rd(t){return Si.includes(String(t||"").toLowerCase())}function Qb(t){return String(t&&t.PASSTHROUGH||"").toLowerCase().split(/[\s,]+/).filter(Boolean)}function In(t){let e=String(t||"").trim().toLowerCase();return Dd(e)?Rd(e)?{ok:!1,code:"reserved",message:e+" is reserved"}:{ok:!0,user:e}:{ok:!1,code:"bad_name",message:"2–32 lowercase letters, digits or hyphens"}}function ev(t,e,n=[]){let s=String(t||"").toLowerCase().replace(/:\d+$/,"").replace(/\.$/,"");for(let o of e){if(!o||!s.endsWith("."+o))continue;let r=s.slice(0,-(o.length+1));return r.includes(".")||!Dd(r)||Rd(r)||n.includes(r)?null:r}return null}function tv(t){let e=[String(t.DOMAIN||"leftoff.dev").toLowerCase()];return t.DEV==="1"&&e.push("localhost"),e}var Si,ki=V(()=>{Si=["www","app","api","docs","feed","relay","admin","status","mail"]});var Fg={};zt(Fg,{BAD_NAME_MESSAGE:()=>Jl,POLL_MS:()=>Lg,SIGNUP_TTL_MS:()=>Dg,checkName:()=>vi,relayGet:()=>Rg,relayPost:()=>bi,reservedMessage:()=>zl,signupProblem:()=>Pg,signupStatus:()=>Vl,startSignup:()=>xi,validEmail:()=>ql,waitForConfirm:()=>jd});import nv from"node:http";import sv from"node:https";function ql(t){let e=String(t||"").trim();return e.length<=254&&/^[^\s@<>()",;:]+@[^\s@<>()",;:]+\.[a-z]{2,}$/i.test(e)}function bi(t,e,n,s=null){return jg(t,"POST",e,n,s)}function Rg(t,e){return jg(t,"GET",e)}function jg(t,e,n,s,o=null){let r=t.origin?new URL(t.origin):new URL("https://"+Go(t)),i=e==="GET"?null:Buffer.from(JSON.stringify(s)),a=r.protocol==="https:"?sv:nv;return new Promise((l,c)=>{let u=a.request({host:r.hostname,port:r.port||(r.protocol==="https:"?443:80),method:e,path:n,timeout:2e4,headers:{host:Go(t)+(t.origin&&r.port?":"+r.port:""),...i?{"content-type":"application/json","content-length":i.length}:{},...o?{authorization:"Bearer "+o}:{},"user-agent":"leftoff-cli"},...r.protocol==="https:"&&t.origin?{servername:Go(t)}:{}},d=>{let f="";d.on("data",p=>f+=p),d.on("end",()=>{let p={};try{p=JSON.parse(f)}catch{}l({status:d.statusCode,json:p,text:f})})});u.on("timeout",()=>u.destroy(new Error("timed out"))),u.on("error",c),u.end(i||void 0)})}function Pg(t,e){let n=t&&t.json||{};return t.status===404&&!n.code?Go(e)+" doesn’t take signups (is the relay on 0.6.1 or newer?)":n.message||"The relay answered "+t.status+(n.code?" ("+n.code+")":"")+"."}async function vi({s:t,name:e,sign:n}){let s=In(e);if(!s.ok)return{ok:!1,code:s.code,message:s.code==="reserved"?zl(String(e).trim().toLowerCase()):Jl};let o={...t,user:s.user},r=Go(o),i;try{i=await Rg(o,"/_signup/name"+(n?"?sign="+encodeURIComponent(n):""))}catch(c){return{ok:!1,code:"unreachable",message:"Couldn’t reach "+r+": "+c.message+"."}}let a=i.json||{};if(i.status!==200||!a.status)return{ok:!1,code:"legacy",status:i.status};if(a.status==="reserved")return{ok:!1,code:"reserved",message:zl(s.user)};if(a.status==="invalid")return{ok:!1,code:"bad_name",message:Jl};let l={ok:!0,user:s.user,host:r,status:a.status,open:a.open!==!1,claimed:a.claimed??null};if(a.status==="yours")l.message="This Mac already has "+r+".";else if(a.status==="taken"||a.status==="pending")l.message=r+" is taken. Try another name.";else if(!l.open)return{ok:!1,code:"closed",message:a.message||"Signups aren’t open on this relay yet."};return l}async function xi({s:t,name:e,email:n,machine:s,sign:o}){let r=In(e);if(!r.ok)return{ok:!1,status:400,code:r.code,message:r.code==="reserved"?zl(e):Jl};if(!ql(n))return{ok:!1,status:400,code:"bad_email",message:"That doesn’t look like an email address."};let i={...t,user:r.user},a;try{a=await bi(i,"/_signup",{name:r.user,email:String(n).trim(),machine:s,sign:o})}catch(l){return{ok:!1,status:0,code:"unreachable",message:"Couldn’t reach "+Go(i)+": "+l.message+"."}}return a.status===200&&a.json.ok?{ok:!0,user:r.user,status:a.json.status,poll:a.json.poll||null,email:a.json.email||n,expiresAt:a.json.expiresAt||null,already:!!a.json.already,claimed:a.json.claimed||null}:{ok:!1,status:a.status,code:a.json.code||null,message:Pg(a,i),retryAfter:a.json.retryAfter||null}}async function Vl({s:t,user:e,poll:n}){try{let s=await bi({...t,user:e},"/_signup/status",{poll:n});return s.json.status||(s.status===404?"unknown":"error")}catch{return"error"}}async function jd({s:t,user:e,poll:n,intervalMs:s=Lg,timeoutMs:o=Dg+6e4,stop:r=()=>!1,onTick:i=()=>{}}){let a=Date.now();for(;;){if(r())return"stopped";let l=await Vl({s:t,user:e,poll:n});if(i(l),["confirmed","expired","replaced","unknown"].includes(l))return l;if(Date.now()-a>o)return"timeout";await new Promise(c=>setTimeout(c,s))}}var Go,Lg,Dg,zl,Jl,Zl=V(()=>{ki();Go=t=>t.user+"."+t.domain,Lg=2e3,Dg=1800*1e3;zl=t=>"“"+t+"” is reserved (www, app, api, docs, feed, relay, admin, status, mail). Pick another.",Jl="A name is 2–32 lowercase letters, digits or hyphens."});function Ai(t){let e=t instanceof Uint8Array?t:new Uint8Array(t),n="";for(let s=0;s<e.length;s++)n+=String.fromCharCode(e[s]);return btoa(n).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Ti(t){for(t=String(t||"").replace(/-/g,"+").replace(/_/g,"/");t.length%4;)t+="=";let e=atob(t),n=new Uint8Array(e.length);for(let s=0;s<e.length;s++)n[s]=e.charCodeAt(s);return n}function Fd(t){return Hg.getRandomValues(new Uint8Array(t))}function Kg(t=16){return Ai(Fd(t))}function Gg(){let t=Fd(16),e="";for(let n=0;n<16;n++)e+=Bg[t[n]&31];return e.match(/.{4}/g).join("-")}function $g(t){let e=String(t||"").toUpperCase().replace(/[\s-]/g,"").replace(/O/g,"0").replace(/[IL]/g,"1");return e.length!==16||[...e].some(n=>!Bg.includes(n))?null:e.match(/.{4}/g).join("-")}async function Yg(t){return _t.importKey("raw",is.encode($g(t)||""),{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}async function Wg(t,e){let n=await Yg(t);return Ai(await _t.sign("HMAC",n,is.encode("leftoff-pair-v1|"+e.join("|"))))}async function zg(t,e,n){let s=await Yg(t);try{return await _t.verify("HMAC",s,Ti(n),is.encode("leftoff-pair-v1|"+e.join("|")))}catch{return!1}}async function Jg({extractable:t=!1}={}){let e=await _t.generateKey({name:"ECDH",namedCurve:"P-256"},t,["deriveBits"]),n=Ai(await _t.exportKey("raw",e.publicKey));return{privateKey:e.privateKey,pub:n}}async function qg({privateKey:t,peerPub:e,code:n,device:s,extractable:o=!1}){let r=await _t.importKey("raw",Ti(e),{name:"ECDH",namedCurve:"P-256"},!1,[]),i=await _t.deriveBits({name:"ECDH",public:r},t,256),a=await _t.importKey("raw",i,"HKDF",!1,["deriveKey","deriveBits"]),l={name:"HKDF",hash:"SHA-256",salt:is.encode($g(n)),info:is.encode("leftoff-relay-device-v1|"+s)};if(o){let c=new Uint8Array(await _t.deriveBits(l,a,256));return{raw:c,key:await Ql(c)}}return{key:await _t.deriveKey(l,a,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}}function Ql(t){return _t.importKey("raw",t instanceof Uint8Array?t:Ti(t),{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}async function Vg(t,{keyFor:e,machine:n,seen:s,now:o=Date.now()}){if(!t||t.v!==1||typeof t.d!="string"||typeof t.id!="string"||typeof t.iv!="string"||typeof t.ct!="string"||typeof t.ts!="number")throw new sn("bad_envelope");if(t.to!==n)throw new sn("wrong_target","sealed for "+t.to);if(Math.abs(o-t.ts)>Ug)throw new sn("expired","the request is too old (or a clock is off)");let r=await e(t.d);if(!r)throw new sn("unknown_device","this browser isn’t paired with this Mac, or it was revoked");if(s&&s.has(t.d+"|"+t.id))throw new sn("replayed");let i;try{i=JSON.parse(ov.decode(await _t.decrypt({name:"AES-GCM",iv:Ti(t.iv),additionalData:rv(t)},r,Ti(t.ct))))}catch{throw new sn("decrypt_failed")}return s&&s.add(t.d+"|"+t.id,t.ts+Ug),i}async function Zg(t,e,n){let s=Fd(12),o=await _t.encrypt({name:"AES-GCM",iv:s,additionalData:iv(null,e)},t,is.encode(JSON.stringify(n)));return{v:1,re:e.id,iv:Ai(s),ct:Ai(o)}}var Hg,_t,is,ov,Bg,Pd,Ug,rv,iv,sn,Xl,Xg=V(()=>{Hg=globalThis.crypto,_t=Hg.subtle,is=new TextEncoder,ov=new TextDecoder,Bg="0123456789ABCDEFGHJKMNPQRSTVWXYZ",Pd=600*1e3,Ug=120*1e3;rv=t=>is.encode(["leftoff-tunnel-v1","req",t.d,t.to,t.id,t.ts].join("|")),iv=(t,e)=>is.encode(["leftoff-tunnel-v1","res",e.d,e.to,e.id].join("|")),sn=class extends Error{constructor(e,n){super(n||e),this.code=e}};Xl=class{constructor(){this.m=new Map}has(e){return this.sweep(),this.m.has(e)}add(e,n){this.m.set(e,n)}sweep(e=Date.now()){if(!(this.m.size<500))for(let[n,s]of this.m)s<e&&this.m.delete(n)}}});var ny={};zt(ny,{DEFAULT_DOMAIN:()=>Nn,RESERVED:()=>Si,RelayHost:()=>Ci,boardUrlFor:()=>Ei,connectTarget:()=>Hd,devicesDir:()=>Us,hostFor:()=>Dt,localRelayFile:()=>nc,problemText:()=>Gd,readRelaySettings:()=>Mn,sharedAccountFile:()=>sc,unwrapKey:()=>Kd,wrapKey:()=>Bd,writeRelaySettings:()=>ls});import as from"node:fs";import je from"node:path";import av from"node:http";import et from"node:crypto";import{EventEmitter as lv}from"node:events";import{fileURLToPath as cv}from"node:url";function nc(t=process.env){return je.join(je.dirname(Jt(t)),"relay.json")}function sc(t){return je.join(t,"relay","account.json")}function Us(t){return je.join(t,"relay","devices")}function Hs(t){try{return JSON.parse(as.readFileSync(t,"utf8"))}catch{return null}}function _i(t,e,n){as.mkdirSync(je.dirname(t),{recursive:!0});let s=je.join(je.dirname(t),"."+je.basename(t)+"."+process.pid+".tmp");as.writeFileSync(s,JSON.stringify(e,null,2)+`
|
|
242
|
+
`,n?{mode:n}:void 0),as.renameSync(s,t)}function Mn(t,e=process.env){let n=Hs(nc(e))||{},s=Hs(sc(t))||{},o=String(e.LEFTOFF_RELAY_USER||n.user||s.user||"").toLowerCase()||null,r=String(e.LEFTOFF_RELAY_DOMAIN||n.domain||s.domain||Nn).toLowerCase(),i=e.LEFTOFF_ANYWHERE?e.LEFTOFF_ANYWHERE==="1":!!n.enabled,a=e.LEFTOFF_RELAY_ORIGIN||n.origin||null;return{enabled:i,user:o,domain:r,origin:a,registeredAt:n.registeredAt||null}}function ls(t,e,n=process.env){let o={...Hs(nc(n))||{},...e};if(_i(nc(n),o,384),e.user){let r=Hs(sc(t))||{};(r.user!==e.user||e.domain&&r.domain!==e.domain)&&_i(sc(t),{user:e.user,domain:e.domain||r.domain||Nn,updatedAt:new Date().toISOString()})}return o}function Dt(t){return t.user+"."+t.domain}function Ei(t){if(t.origin){let e=new URL(t.origin);return e.protocol+"//"+t.user+"."+(t.domain==="localhost"?"localhost":t.domain)+(e.port?":"+e.port:"")}return"https://"+Dt(t)}function Hd(t){if(t.origin){let e=new URL(t.origin);return{url:(e.protocol==="https:"?"wss://":"ws://")+e.host+"/_connect",hostHeader:Dt(t)+(e.port?":"+e.port:"")}}return{url:"wss://"+Dt(t)+"/_connect",hostHeader:null}}function Bd(t,{device:e,machine:n,box:s}){let o=et.generateKeyPairSync("x25519"),r=et.createPublicKey({key:{kty:"OKP",crv:"X25519",x:s},format:"jwk"}),i=et.diffieHellman({privateKey:o.privateKey,publicKey:r}),a=et.randomBytes(16),l=Buffer.from(et.hkdfSync("sha256",i,a,"leftoff-relay-wrap-v1|"+n+"|"+e,32)),c=et.randomBytes(12),u=et.createCipheriv("aes-256-gcm",l,c);u.setAAD(Buffer.from(e));let d=Buffer.concat([u.update(Buffer.from(t)),u.final(),u.getAuthTag()]);return{epk:o.publicKey.export({format:"jwk"}).x,salt:ec(a),iv:ec(c),ct:ec(d)}}function Kd(t,{device:e,machine:n,boxPriv:s}){let o=et.createPublicKey({key:{kty:"OKP",crv:"X25519",x:t.epk},format:"jwk"}),r=et.diffieHellman({privateKey:s,publicKey:o}),i=Buffer.from(et.hkdfSync("sha256",r,tc(t.salt),"leftoff-relay-wrap-v1|"+n+"|"+e,32)),a=tc(t.ct),l=et.createDecipheriv("aes-256-gcm",i,tc(t.iv));return l.setAAD(Buffer.from(e)),l.setAuthTag(a.subarray(a.length-16)),Buffer.concat([l.update(a.subarray(0,a.length-16)),l.final()])}function Ud(t,e){let{sig:n,...s}=t;return s.by=e.machine,{...s,sig:ec(et.sign(null,Buffer.from(vt(s)),e.signPriv))}}function dv(t,e){try{let{sig:n,...s}=t;return et.verify(null,Buffer.from(vt(s)),et.createPublicKey({key:{kty:"OKP",crv:"Ed25519",x:e},format:"jwk"}),tc(n))}catch{return!1}}function ty(t){return String(t||"Browser").replace(/[\u0000-\u001f<>]/g,"").trim().slice(0,60)||"Browser"}function Gd(t,e={}){return t?t==="not_registered"?"this Mac isn’t on "+(e.user||"?")+"."+(e.domain||Nn)+" yet: a Mac that is on it adds this one while it’s awake with Use from anywhere on (your synced folder tells it about this Mac). New name? `leftoff relay signup "+(e.user||"<name>")+"`":t==="key_mismatch"?"the relay knows this Mac by a different key (was it reinstalled?): run `leftoff relay register "+(e.user||"<name>")+" --replace`":t==="wrong_user"?"that address belongs to a different account":/ENOTFOUND|EAI_AGAIN/.test(t)?"can’t find "+(e.user||"")+"."+(e.domain||Nn)+" (no internet, or the relay isn’t deployed yet)":/HTTP 404/.test(t)?"no relay answers there yet: deploy it (docs/relay.md)":t:null}var Nn,Qg,uv,ey,ec,tc,Ci,oc=V(()=>{id();qn();ke();fi();Zl();ki();Xg();Nn="leftoff.dev",Qg=je.join(je.dirname(cv(import.meta.url)),"..","ui"),uv={".html":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".svg":"image/svg+xml",".png":"image/png",".ico":"image/x-icon",".webmanifest":"application/manifest+json; charset=utf-8",".json":"application/json; charset=utf-8"},ey=4*1024*1024,ec=t=>Buffer.from(t).toString("base64url"),tc=t=>Buffer.from(String(t||""),"base64url");Ci=class extends lv{constructor({engine:e,root:n,port:s,log:o=()=>{},env:r=process.env,identity:i=null}){super(),this.engine=e,this.root=n,this.port=s,this.log=o,this.env=r,this.machine=e?e.machine:i&&i.machine,this.idOverride=i,this.client=null,this.pairs=new Map,this.keys=new Map,this.seen=new Xl,this.innerSecret=et.randomBytes(24).toString("base64url"),this.lastActivity=null,this.relayDevices=null}get id(){return this.idOverride||this.engine&&this.engine.peers&&this.engine.peers.id||this._id||(this._id=xn(this.machine))}settings(){return Mn(this.root,this.env)}active(){let e=this.settings();return!!(e.enabled&&e.user)}url(){return this.active()?Hd(this.settings()).url:null}cardUser(){let e=this.settings();return e.enabled&&e.user?e.user:null}boardUrl(){let e=this.settings();return e.user?Ei(e):null}async start(){this.active()&&this.connect(),this.timer=setInterval(()=>this.tick(),Number(this.env.LEFTOFF_RELAY_TICK_MS)||3e4),this.timer.unref()}stop(){clearInterval(this.timer),this.disconnect()}connect(){if(this.client)return;let e=this.settings(),n=Hd(e),s=new Mo({url:n.url,hostHeader:n.hostHeader,id:this.id,user:e.user,log:this.log,helloExtra:()=>({version:J,activeAt:this.activityAt()})});s.onEnvelope=o=>this.engine&&this.engine.peers?this.engine.peers.receive(o,"relay"):{kind:"err",code:"not_allowed"},s.on("frame",o=>this.onFrame(o).catch(r=>this.log("relay: "+r.message))),s.on("welcome",()=>this.onWelcome().catch(()=>{})),s.on("state",()=>this.changed()),this.client=s,s.connect(),this.engine&&this.engine.peers&&this.engine.peers.useRelay(s),this.log("relay: connecting to "+Dt(e)+" (Use from anywhere is on)")}disconnect(){let e=this.client;e&&(this.client=null,this.engine&&this.engine.peers&&this.engine.peers.dropRelay(e),e.close(),this.changed())}changed(){this.engine&&this.engine.emit("relay.updated",{})}async enable({user:e,domain:n}={}){let s=this.settings(),o=e||s.user,r=In(o);if(!r.ok)throw Object.assign(new Error(r.code==="reserved"?"“"+o+"” is reserved: pick another name":"The name needs 2–32 lowercase letters, digits or hyphens"),{status:400,code:r.code});let i=s.enabled&&s.user===r.user&&(!n||n===s.domain);return ls(this.root,{enabled:!0,user:r.user,...n?{domain:n}:{}},this.env),(!i||!this.client)&&(this.disconnect(),this.connect()),this.engine&&this.engine.peers&&this.engine.peers.publish(),this.status()}async disable(){return ls(this.root,{enabled:!1},this.env),this.disconnect(),this.engine&&this.engine.peers&&this.engine.peers.publish(),this.status()}async signupStart({user:e,email:n}={}){let s=this.settings(),o=String(e||"").trim().toLowerCase(),r=await vi({s:{...s,user:o},name:o,sign:this.id.signX});if(r.ok&&r.status==="yours")return this.pendingSignup={user:r.user,email:null,poll:null,expiresAt:null,status:"confirmed",startedAt:new Date().toISOString(),claimed:null,already:!0},await this.signupDone(),this.signupState();if(r.ok&&(r.status==="taken"||r.status==="pending"))throw Object.assign(new Error(r.message),{status:409,code:"taken"});if(!r.ok&&["bad_name","reserved"].includes(r.code))throw Object.assign(new Error(r.message),{status:400,code:r.code});if(!r.ok&&r.code==="closed")throw Object.assign(new Error(r.message),{status:503,code:"signups_closed"});let i=await xi({s:{...s,user:o},name:o,email:n,machine:this.machine,sign:this.id.signX});if(!i.ok)throw Object.assign(new Error(i.message),{status:i.status===429?429:i.status>=400&&i.status<500?i.status:502,code:i.code||"signup_failed"});return this.pendingSignup={user:i.user,email:i.email,poll:i.poll,expiresAt:i.expiresAt,status:i.already?"confirmed":"pending",startedAt:new Date().toISOString(),claimed:i.claimed||null},i.already&&await this.signupDone(),this.signupState()}async signupCheck(){let e=this.pendingSignup;if(!e||e.status!=="pending")return this.signupState();let n=await Vl({s:{...this.settings(),user:e.user},user:e.user,poll:e.poll});return n==="confirmed"?(e.status="confirmed",await this.signupDone()):["expired","replaced","unknown"].includes(n)?e.status=n:e.expiresAt&&Date.parse(e.expiresAt)<Date.now()&&(e.status="expired"),this.signupState()}async signupDone(){let e=this.pendingSignup,n=this.settings();ls(this.root,{user:e.user,registeredAt:e.already&&n.user===e.user&&n.registeredAt?n.registeredAt:new Date().toISOString(),via:"signup"},this.env),await this.enable({user:e.user}),this.log("relay: "+e.user+"."+this.settings().domain+(e.already?" already belongs to this Mac":" confirmed by email")+"; Use from anywhere is on"),this.changed()}signupCancel(){return this.pendingSignup=null,this.signupState()}signupState(){let e=this.pendingSignup;return e?{status:e.status,user:e.user,email:e.email,expiresAt:e.expiresAt,startedAt:e.startedAt,claimed:e.claimed,already:!!e.already,host:e.user+"."+this.settings().domain}:{status:"none"}}status(){let e=this.settings(),n=this.client;return{enabled:e.enabled,configured:!!e.user,available:!0,user:e.user,domain:e.domain,host:e.user?Dt(e):null,boardUrl:e.user?Ei(e):null,machine:this.machine,connected:!!(n&&n.connected),connecting:!!(n&&!n.connected),latencyMs:n&&n.connected?n.latencyMs:null,connectedAt:n&&n.connectedAt?new Date(n.connectedAt).toISOString():null,lastError:n?n.lastError:null,problem:n&&!n.connected?Gd(n.lastError,e):null,relayMachines:n&&n.relayMachines?n.relayMachines:null,devices:this.devices().length,reserved:Si,registered:!!e.registeredAt,signup:this.signupState()}}async onFrame(e){if(e.t==="static")return this.client.send(this.staticFile(e));if(e.t==="pair")return this.client.send({t:"pair.res",id:e.id,...await this.redeem(e)});if(e.t==="tunnel")return this.client.send({t:"tunnel.res",id:e.id,...await this.tunnel(e.env)})}staticFile(e){let n=String(e.path||"/").replace(/^\/+/,"")||"index.html",s=je.join(Qg,je.normalize(n).replace(/^(\.\.[/\\])+/,""));return!s.startsWith(Qg+je.sep)||/(^|[/\\])\./.test(n)||!as.existsSync(s)||as.statSync(s).isDirectory()?{t:"static.res",id:e.id,status:404}:{t:"static.res",id:e.id,status:200,type:uv[je.extname(s)]||"application/octet-stream",b64:as.readFileSync(s).toString("base64")}}async tunnel(e){let n;try{n=await Vg(e,{keyFor:i=>this.keyFor(i),machine:this.machine,seen:this.seen})}catch(i){return{error:{code:i instanceof sn?i.code:"bad_envelope",message:i.message}}}let s=await this.keyFor(e.d),o=this.deviceRecord(e.d),r;return typeof n.p!="string"||!n.p.startsWith("/v1/")||!/^(GET|POST|PUT|PATCH|DELETE)$/.test(String(n.m))?r={s:400,h:{"content-type":"application/json"},b:JSON.stringify({error:{code:"bad_request",message:"only the board API goes through the tunnel"}})}:r=await this.local(n,{device:e.d,name:o?o.name:null}),{env:await Zg(s,e,r)}}local(e,{device:n,name:s}){return new Promise(o=>{let r=e.b==null?null:Buffer.from(String(e.b)),i={host:"127.0.0.1:"+this.port,"x-leftoff-relay":this.innerSecret,"x-leftoff-device":n,"x-leftoff-device-name":encodeURIComponent(s||""),accept:"application/json"};r&&(i["content-type"]=e.h&&e.h["content-type"]||"application/json",i["content-length"]=r.length);let a=av.request({host:"127.0.0.1",port:this.port,method:e.m,path:e.p,headers:i,timeout:6e4},l=>{let c=[],u=0;l.on("data",d=>{u+=d.length,u<=ey&&c.push(d)}),l.on("end",()=>o({s:l.statusCode,h:{"content-type":l.headers["content-type"]||"application/json"},b:u>ey?JSON.stringify({error:{code:"too_large"}}):Buffer.concat(c).toString("utf8")}))});a.on("timeout",()=>a.destroy(new Error("timed out"))),a.on("error",l=>o({s:502,h:{"content-type":"application/json"},b:JSON.stringify({error:{code:"local_error",message:l.message}})})),r&&a.write(r),a.end()})}async createPair({now:e=Date.now()}={}){if(!this.client||!this.client.connected)throw Object.assign(new Error("Not connected to the relay yet"+(this.client&&this.client.lastError?" ("+Gd(this.client.lastError,this.settings())+")":"")),{status:409,code:"relay_down"});for(let[a,l]of this.pairs)l.expiresAt<=e&&this.pairs.delete(a);let n;do n=Gg();while(this.pairs.has(n.slice(0,4)));let s=n.slice(0,4),o=e+Pd,r=await this.client.ask({t:"pair.create",pair:s,expiresAt:o});if(!r.ok)throw Object.assign(new Error("The relay refused the code: "+r.code),{status:502});this.pairs.set(s,{code:n,expiresAt:o,tries:0});let i=this.boardUrl()+"/pair.html#"+n;return{code:n,link:i,expiresAt:new Date(o).toISOString(),ttlSec:Math.round(Pd/1e3),qrSvg:Pl(i)}}async cancelPairs(){for(let e of this.pairs.keys())try{await this.client.ask({t:"pair.cancel",pair:e})}catch{}this.pairs.clear()}async redeem(e,n=Date.now()){let s=this.pairs.get(e.pair);if(!s||s.expiresAt<=n)return this.pairs.delete(e.pair),{ok:!1,code:"expired"};if(!await zg(s.code,["browser",e.pair,e.pub],e.mac))return s.tries++,s.tries>=5&&this.pairs.delete(e.pair),this.log("relay: a wrong pairing proof for code "+e.pair+"…"),{ok:!1,code:"bad_code"};this.pairs.delete(e.pair);let o=Kg(12),r=await Jg(),i;try{i=(await qg({privateKey:r.privateKey,peerPub:e.pub,code:s.code,device:o,extractable:!0})).raw}catch{return{ok:!1,code:"bad_key"}}let a=await Wg(s.code,["mac",e.pair,e.pub,r.pub,o]);return this.saveDevice({id:o,name:ty(e.name),createdAt:new Date(n).toISOString(),pairedOn:this.machine},i),this.keys.set(o,{key:await Ql(i),at:Date.now()}),this.log("relay: paired a new browser, "+ty(e.name)+" ("+o.slice(0,6)+"…)"),this.changed(),{ok:!0,device:o,pub:r.pub,mac:a}}trustedCards(){let e=Ds(this.root),n={};for(let[s,o]of Object.entries(e))if(o.valid){if(s===this.machine){o.card.sign===this.id.signX&&(n[s]=o.card);continue}vn(this.id,o,{pinNew:!1})==="trusted"&&(n[s]=o.card)}return n[this.machine]={machine:this.machine,box:this.id.boxX,sign:this.id.signX},n}saveDevice(e,n){let s={};for(let[o,r]of Object.entries(this.trustedCards()))r.box&&(s[o]=Bd(n,{device:e.id,machine:o,box:r.box}));_i(je.join(Us(this.root),e.id+".json"),Ud({v:1,...e,wraps:s},this.id))}deviceFiles(){let e=[];try{e=as.readdirSync(Us(this.root)).filter(n=>/^[\w-]+\.json$/.test(n))}catch{return[]}return e.map(n=>Hs(je.join(Us(this.root),n))).filter(n=>n&&n.id)}verified(e){let s=this.trustedCards()[e.by];return!!(s&&dv(e,s.sign))}deviceRecord(e){if(!/^[\w-]{8,64}$/.test(String(e||"")))return null;let n=Hs(je.join(Us(this.root),e+".json"));return n&&this.verified(n)?n:null}async keyFor(e){let n=this.keys.get(e);if(n&&Date.now()-n.at<5e3)return n.key;let s=this.deviceRecord(e);if(!s||s.revokedAt||!s.wraps||!s.wraps[this.machine])return this.keys.delete(e),null;try{let o=await Ql(Kd(s.wraps[this.machine],{device:e,machine:this.machine,boxPriv:this.id.boxPriv}));return this.keys.set(e,{key:o,at:Date.now()}),o}catch{return null}}devices(){let e=new Map((this.relayDevices||[]).map(n=>[n.id,n]));return this.deviceFiles().filter(n=>!n.revokedAt&&this.verified(n)).map(n=>({id:n.id,name:n.name,createdAt:n.createdAt,pairedOn:n.pairedOn,lastSeenAt:(e.get(n.id)||{}).lastSeenAt||null,macs:Object.keys(n.wraps||{}).sort()})).sort((n,s)=>String(s.createdAt).localeCompare(String(n.createdAt)))}async refreshRelayDevices(){if(!this.client||!this.client.connected)return this.relayDevices;try{this.relayDevices=(await this.client.ask({t:"devices.list"})).devices||[]}catch{}return this.relayDevices}async revoke(e){let n=je.join(Us(this.root),e+".json"),s=Hs(n);if(!s||!this.verified(s))throw Object.assign(new Error("No paired device "+e),{status:404,code:"not_found"});if(!s.revokedAt){let{wraps:r,...i}=s;_i(n,Ud({...i,wraps:{},revokedAt:new Date().toISOString(),revokedOn:this.machine},this.id))}this.keys.delete(e);let o="pending";if(this.client&&this.client.connected)try{await this.client.ask({t:"device.revoke",device:e}),o="done"}catch{}return this.log("relay: revoked "+(s.name||e)),this.changed(),{ok:!0,id:e,relay:o}}activityAt(){let e=0;if(this.engine&&this.engine.sessions)for(let n of this.engine.sessions.values()){if(n.archived||n.machine&&n.machine!==this.machine)continue;let s=Date.parse(n.lastActivityAt||n.startedAt||"")||0;s>e&&(e=s)}return e?new Date(e).toISOString():null}async onWelcome(){let e=this.deviceFiles().filter(n=>n.revokedAt&&this.verified(n)).map(n=>n.id);e.length&&await this.client.ask({t:"devices.sync",revoked:e}).catch(()=>{}),await this.enrollPeers(),this.rewrap(),this.lastActivity=null,this.tick()}async enrollPeers(){if(!this.client||!this.client.connected)return;let e=this.settings(),n=new Set(this.client.relayMachines||[]),s=Ds(this.root);for(let[o,r]of Object.entries(s))if(!(o===this.machine||n.has(o)||!r.valid||r.card.anywhere!==e.user)&&vn(this.id,r,{pinNew:!1})==="trusted")try{let i=await this.client.ask({t:"machine.add",machine:o,sign:r.card.sign});i.ok?(n.add(o),this.log("relay: added "+o+" to "+e.user+"."+e.domain)):this.log("relay: couldn’t add "+o+": "+i.code)}catch{}this.client.relayMachines=[...n]}rewrap(){let e=this.trustedCards();for(let n of this.deviceFiles()){if(n.revokedAt||!n.wraps||!n.wraps[this.machine]||!this.verified(n))continue;let s=Object.keys(e).filter(o=>!n.wraps[o]&&e[o].box);if(s.length)try{let o=Kd(n.wraps[this.machine],{device:n.id,machine:this.machine,boxPriv:this.id.boxPriv}),r={...n.wraps};for(let i of s)r[i]=Bd(o,{device:n.id,machine:i,box:e[i].box});_i(je.join(Us(this.root),n.id+".json"),Ud({...n,wraps:r},this.id)),this.log("relay: "+n.name+" can now reach "+s.join(", "))}catch{}}}tick(){if(this.active()&&!this.client&&this.connect(),!this.active()&&this.client&&this.disconnect(),!this.client||!this.client.connected)return;let e=this.activityAt();e&&e!==this.lastActivity&&(this.lastActivity=e,this.client.send({t:"activity",at:e})),this.enrollPeers().catch(()=>{}),this.rewrap(),this.refreshRelayDevices().catch(()=>{})}}});var Yl={};zt(Yl,{SUMMARIZER_FLAG:()=>Yd,daemonHealth:()=>Yo,flagBool:()=>dt,globalBinPath:()=>Wd,lengthFlow:()=>ly,linkFolder:()=>cc,localModelFlow:()=>uc,mergeSetupConfig:()=>ac,ollamaModelFlow:()=>ay,patchDaemonConfig:()=>rc,relaySignupStep:()=>cy,runSetup:()=>Jd,showTestResult:()=>Ks,startDaemonDetached:()=>ic,testFlow:()=>Wo,writeConfigFile:()=>lc});import qe from"node:fs";import Bs from"node:os";import Ne from"node:path";import{spawn as Oi,spawnSync as $o}from"node:child_process";function dt(t,e){if(t["no-"+e])return!1;if(t[e]!==void 0)return hv(t[e])?!1:fv(t[e])||t[e]===""?!0:!!t[e]}function pv(t,e){let n=e;return{c:n,line:(s="")=>t.write(s+`
|
|
243
|
+
`),ok:s=>t.write(n.green("✔")+" "+s+`
|
|
244
|
+
`),warn:s=>t.write(n.yellow("!")+" "+s+`
|
|
245
|
+
`),fail:s=>t.write(n.red("✖")+" "+s+`
|
|
246
|
+
`),info:s=>t.write(n.dim("·")+" "+s+`
|
|
247
|
+
`),note:s=>t.write(" "+n.dim(s)+`
|
|
248
|
+
`),step:(s,o,r)=>t.write(`
|
|
249
|
+
`+n.bold(n.magenta(`[${s}/${o}]`))+" "+n.bold(r)+`
|
|
250
|
+
`),cmd:s=>t.write(" "+n.cyan("$ "+s)+`
|
|
251
|
+
`)}}function zd(t,e,n,s){let o=i=>String(i).replace(/\x1b\[[0-9;]*m/g,""),r=Math.min(76,Math.max(o(n).length+4,...s.map(i=>o(i).length+4)));t.write(e.dim("╭─ ")+e.bold(n)+" "+e.dim("─".repeat(Math.max(0,r-o(n).length-4))+"╮")+`
|
|
252
|
+
`);for(let i of s)t.write(e.dim("│")+" "+i+" ".repeat(Math.max(0,r-o(i).length-2))+e.dim("│")+`
|
|
253
|
+
`);t.write(e.dim("╰"+"─".repeat(r-1)+"╯")+`
|
|
254
|
+
`)}function iy(t,e=66){let n=[],s="";for(let o of String(t).split(/\s+/))(s+" "+o).trim().length>e?(n.push(s),s=o):s=(s+" "+o).trim();return s&&n.push(s),n}function Ks(t,e,n,s){if(!n.ok){t.write(e.red("✖")+" Test checkpoint failed with "+s+": "+(n.error||"no answer")+`
|
|
255
|
+
`);return}let o=[];for(let[a,l]of[["Doing",n.doing],["Stopped at",n.stoppedAt],["Next",n.next]]){let c=iy(l||"",60);o.push(e.bold((a+":").padEnd(12))+(c[0]||""));for(let u of c.slice(1))o.push(" ".repeat(12)+u)}let r=n.generator||{},i=r.inputTokens!=null?" · "+r.inputTokens+" → "+r.outputTokens+" tokens":"";zd(t,e,"Sample checkpoint · "+s+" · "+gi(n.ms)+i,o),n.parseOk||t.write(e.yellow("!")+` The model didn't follow the 3-line format; a bigger model usually does.
|
|
256
|
+
`)}async function Yo(t,e,n=fetch){try{let s=await n("http://127.0.0.1:"+t+"/v1/health",{headers:{authorization:"Bearer "+(e||"")},signal:AbortSignal.timeout(1500)});return s.status===401?{up:!0,auth:!1}:s.ok?{up:!0,auth:!0,...await s.json()}:{up:!1}}catch{return{up:!1}}}async function rc(t,e,n,s=fetch){let o=await s("http://127.0.0.1:"+t+"/v1/config",{method:"PATCH",headers:{authorization:"Bearer "+e,"content-type":"application/json"},body:JSON.stringify(n),signal:AbortSignal.timeout(4e3)});if(!o.ok)throw new Error("HTTP "+o.status);return o.json()}function ic({root:t,bin:e=Fe,env:n=process.env}){let s=we(t);qe.mkdirSync(s.root,{recursive:!0});let o=qe.openSync(Ne.join(t,".daemon.log"),"a"),r=Oi(process.execPath,[e,"serve"],{detached:!0,stdio:["ignore",o,o],env:{...n,CHECKPOINTS_DIR:t}});return r.unref(),qe.closeSync(o),qe.writeFileSync(Ne.join(t,".daemon.pid"),String(r.pid)+`
|
|
257
|
+
`),r.pid}function mv(t){let e=process.platform==="darwin"?["open",[t]]:process.platform==="win32"?["cmd",["/c","start","",t]]:["xdg-open",[t]];try{let n=Oi(e[0],e[1],{detached:!0,stdio:"ignore"});return n.on("error",()=>{}),n.unref(),!0}catch{return!1}}function ac(t,{summarizer:e,machine:n,hostname:s=Bs.hostname(),port:o,summaryLength:r}={}){let i=Ki({hostname:s,cfg:{}}),a=t&&typeof t=="object"?JSON.parse(JSON.stringify(t)):{};if(e){let l=a.summarizer&&a.summarizer.maxInputChars?{maxInputChars:a.summarizer.maxInputChars}:{};a.summarizer={provider:e.provider,model:e.model??null,baseUrl:e.baseUrl??null,apiKeyEnv:e.apiKeyEnv??null,...e.apiKeyFile?{apiKeyFile:e.apiKeyFile}:{},maxInputChars:24e3,...l},delete a.summarizer.mode}return n!==void 0&&(a.machineNames={...a.machineNames||{}},n&&n!==s&&n!==i?a.machineNames[s]=n:delete a.machineNames[s]),o&&(a.port=Number(o)),r&&(a.summary={...a.summary||{},length:r}),a}function lc(t,e){let n=we(t),s=n.config+".tmp-"+process.pid;return qe.writeFileSync(s,JSON.stringify(e,null,2)+`
|
|
258
|
+
`),qe.renameSync(s,n.config),n.config}function cc(t){let e=qi(),n=nh(t);if(Ne.resolve(t)===e)return ci("CHECKPOINTS_DIR",null),{how:"default",pointer:n};let s=null;try{s=qe.lstatSync(e)}catch{}if(s&&s.isSymbolicLink()){if(Ne.resolve(Ne.dirname(e),qe.readlinkSync(e))===Ne.resolve(t))return ci("CHECKPOINTS_DIR",null),{how:"symlink",pointer:n,already:!0};qe.unlinkSync(e),s=null}return s?(ci("CHECKPOINTS_DIR",t),{how:"settings-env",pointer:n}):(qe.symlinkSync(t,e,"dir"),ci("CHECKPOINTS_DIR",null),{how:"symlink",pointer:n})}function Wd(t=process.env){let e=$o(process.platform==="win32"?"npm.cmd":"npm",["prefix","-g"],{encoding:"utf8",env:t});if(e.status!==0)return null;let n=e.stdout.trim(),s=process.platform==="win32"?Ne.join(n,"node_modules"):Ne.join(n,"lib","node_modules");for(let o of Ur)for(let r of["leftoff.js","checkpoints.js"]){let i=Ne.join(s,o,"bin",r);if(qe.existsSync(i))return i}return null}function gv(t,e,n,s){n.close(),s.write(`
|
|
259
|
+
`);let o=$o(t,e,{stdio:"inherit"});return s.write(`
|
|
260
|
+
`),o.status===0}async function uc(t){let{p:e,u:n,out:s,flags:o,fetchImpl:r,env:i}=t,a=n.c,l=(o["ollama-url"]||(o["base-url"]&&t.wantProvider!=="openai-compatible"?o["base-url"]:null)||at).replace(/\/$/,""),c=(o["lmstudio-url"]||(o["base-url"]&&t.wantProvider==="openai-compatible"?o["base-url"]:null)||Qt).replace(/\/$/,""),u=ut("Looking for local model servers…",{out:s,colors:a}),[d,f]=await Promise.all([Ut({baseUrl:l,fetchImpl:r,env:i}),t.wantProvider==="ollama"?{running:!1,models:[]}:Sn({baseUrl:c,fetchImpl:r})]);u.stop(),d.running?n.ok("Ollama "+(d.version?"v"+d.version+" ":"")+"running at "+d.baseUrl+a.dim(" · "+d.models.length+" model"+(d.models.length===1?"":"s")+" pulled")):d.binary?n.warn("Ollama is installed ("+tt(d.binary)+") but not running"):n.info("Ollama: not found"),f.running?n.ok("LM Studio / OpenAI-compatible server at "+f.baseUrl+a.dim(" · "+f.models.length+" loaded")):t.wantProvider!=="ollama"&&n.info("LM Studio / OpenAI-compatible server: not found at "+c);let p;if(t.wantProvider==="openai-compatible"?p="openai-compatible":t.wantProvider==="ollama"?p="ollama":d.running&&f.running?p=await e.select("Which one should write checkpoints?",[{label:"Ollama",value:"ollama",hint:"pull and switch models from here"},{label:"LM Studio / OpenAI-compatible",value:"openai-compatible",hint:"uses a model you load there"}],0):f.running&&!d.running?p="openai-compatible":p="ollama",p==="openai-compatible")return oy(t,f,c);if(!d.running){if(d.binary){let h=jr(process.platform,d.binary);if(h&&await e.confirm("Start Ollama now? ("+h.shell+")",!0)){if(h.detached){let y=Oi(h.cmd[0],h.cmd.slice(1),{detached:!0,stdio:"ignore"});y.on("error",()=>{}),y.unref()}else $o(h.cmd[0],h.cmd.slice(1),{stdio:"ignore"});let m=ut("Waiting for Ollama to come up…",{out:s,colors:a});d=await po({baseUrl:l,fetchImpl:r,timeoutMs:2e4}),d.running?m.succeed("Ollama is up at "+d.baseUrl):m.fail("Ollama didn't answer at "+l+" within 20 s")}}else{if(t.yes&&!dt(o,"install-ollama"))throw new Error("Ollama isn't installed. Install it (https://ollama.com/download) or pass --install-ollama, then re-run");n.line(),n.line(" No local model server yet. "+a.bold("Ollama")+" is the easiest: one install, then models download from here.");let h=up(process.platform,i),m=await e.select("Install Ollama?",[...h.map(k=>({label:k.label,value:k.id,hint:k.shell+(k.note?" · "+k.note:"")})),{label:"I use LM Studio (or another OpenAI-compatible server)",value:"lmstudio",hint:c},{label:"Back: choose a different summarizer",value:"back"}],0);if(m==="back")return null;if(m==="lmstudio")return oy(t,f,c);let y=h.find(k=>k.id===m);if(y.cmd){if(n.line(" This runs:"),n.cmd(y.shell),!await e.confirm("Run it now?",!0))return null;if(!gv(y.cmd[0],y.cmd.slice(1),e,s))return n.fail("The Ollama installer exited with an error (see above)."),null;if(n.ok("Ollama installed"),y.id==="brew"){let k=jr(process.platform,Rr(i));if(k){let S=Oi(k.cmd[0],k.cmd.slice(1),{detached:k.detached,stdio:"ignore"});S.on("error",()=>{}),S.unref()}}}else{if(n.line(" Install it with:"),n.cmd(y.shell),y.url&&n.note("or download it from "+y.url),t.yes)throw new Error("Ollama isn't installed; install it and re-run setup");await e.ask("Press Enter once Ollama is installed and running","")}let w=ut("Waiting for Ollama…",{out:s,colors:a});if(d=await po({baseUrl:l,fetchImpl:r,timeoutMs:3e4}),!d.running&&d.binary){let k=jr(process.platform,d.binary);if(k){let S=Oi(k.cmd[0],k.cmd.slice(1),{detached:!0,stdio:"ignore"});S.on("error",()=>{}),S.unref(),d=await po({baseUrl:l,fetchImpl:r,timeoutMs:2e4})}}d.running?w.succeed("Ollama is up at "+d.baseUrl):w.fail("Ollama isn't answering at "+l)}if(!d.running){if(t.yes)throw new Error("Ollama isn't running at "+l+" (start it with `ollama serve`, or pass --install-ollama)");return n.note("Start Ollama (`ollama serve` or the app) and run `leftoff models` to finish this later."),null}}return ay(t,d)}async function ay(t,e){let{p:n,u:s,out:o,flags:r,fetchImpl:i}=t,a=s.c,l=ho(e.models,{current:t.currentModel}),c=r.model?_a(r.model):cp(l);s.line(),s.line(" "+a.bold("Models for 3-line checkpoints")+a.dim(" (sizes from ollama.com/library, "+fo+")"));let u=l.map(h=>({label:h.tag.padEnd(13)+" "+a.dim((h.curated?Oa(h,Lt):(h.sizeBytes?Lt(h.sizeBytes)+" · ":"")+"on this machine").padEnd(30))+" "+(h.recommended?a.yellow("★ recommended")+" ":"")+(h.installed?a.green("✔ pulled"):""),detail:h.curated?h.note:null,value:h.tag}));u.push({label:"Other tag…",value:"__other__",hint:"any ollama.com/library tag, e.g. phi4-mini"});let d=u.findIndex(h=>Ge(h.value,c)),f;d<0&&r.model?f=String(r.model).trim():(f=await n.select("Which model?",u,Math.max(0,d)),f==="__other__"&&(f=await n.ask("Model tag","",{validate:h=>/^[\w.\-/]+(:[\w.\-]+)?$/.test(h)?null:"Use a tag like llama3.2:3b"})));let p=l.find(h=>Ge(h.tag,f));if(!p||!p.installed){let h=p&&p.sizeBytes?" ("+Lt(p.sizeBytes)+")":"";if(dt(r,"pull")===!1)throw new Error(f+" isn't pulled and --no-pull was given");if(!await n.confirm("Download "+f+h+" now?",!0))return null;let m=Hl("pull "+f,{out:o,colors:a}),y=Date.now();o.write(a.dim(" pulling "+f+" from ollama.com · Ctrl-C to stop (a re-run resumes)")+`
|
|
261
|
+
`);try{await n.interruptible(w=>mo({tag:f,baseUrl:e.baseUrl,fetchImpl:i,signal:w,onProgress:k=>{let S=(Date.now()-y)/1e3,v=S>.5&&k.completed?Lt(k.completed/S)+"/s":"",A=k.total?Lt(k.completed)+" / "+Lt(k.total)+(v?" · "+v:""):k.status;m.update(k.fraction,A)}}))}catch(w){if(w.cancelled||(m.fail(w.message),t.yes))throw w;return null}m.done("Pulled "+f+a.dim(" in "+gi(Date.now()-y)))}else s.ok(f+" is already pulled");return{provider:"ollama",model:f,baseUrl:e.baseUrl===at?null:e.baseUrl}}async function oy(t,e,n){let{p:s,u:o,flags:r,fetchImpl:i}=t,a=o.c;if(!e.running){let c=(await s.ask("Server URL (OpenAI-compatible, ends in /v1)",n)).replace(/\/$/,"");if(e=await Sn({baseUrl:c,fetchImpl:i}),!e.running){if(o.fail("Nothing answered at "+c+"/models"),o.note("In LM Studio: Developer tab → Start Server, and load a model."),t.yes)throw new Error("no OpenAI-compatible server at "+c);return null}}for(;!e.models.length;){if(o.warn("The server is up but has no model loaded."),t.yes)throw new Error("no model loaded at "+e.baseUrl);if(!await s.confirm("Load one in LM Studio, then check again?",!0))return null;e=await Sn({baseUrl:e.baseUrl,fetchImpl:i})}let l=r.model;if(!l||!e.models.includes(l)){let c=Math.max(0,e.models.findIndex(u=>u===t.currentModel));l=await s.select("Which loaded model?",e.models.map(u=>({label:u,value:u})),c)}return o.ok("Using "+a.bold(l)+" at "+e.baseUrl),{provider:"openai-compatible",model:l,baseUrl:e.baseUrl===Qt?null:e.baseUrl}}async function Wo(t,e,n){let{p:s,u:o,out:r,fetchImpl:i}=t,a=ut("Writing a sample checkpoint with "+n+"…"+(e.provider==="ollama"?o.c.dim(" (the first run loads the model)"):""),{out:r,colors:o.c}),l=await Es(e,{fetchImpl:i});return l.ok?a.succeed("It works."):a.fail("The test didn't work."),Ks(r,o.c,l,n),l}async function ry(t,e,{defaultEnv:n,secretName:s,testPrompt:o,testLabel:r}){let{p:i,u:a,flags:l,env:c}=t,u=t.currentSummarizer||{},d=u.provider===e.provider,f=await i.ask("Env var that holds the key",l["api-key-env"]||d&&u.apiKeyEnv||n,{validate:k=>/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)?null:"That isn't an env var name"});e.apiKeyEnv=f;let p=".secret/"+s,h=!!c[f],m=yn(e.provider,{apiKeyEnv:"__none__",apiKeyFile:d&&u.apiKeyFile?u.apiKeyFile:p,root:t.root},{});h?a.ok("$"+f+" is set in this shell"):a.warn("$"+f+" isn't set in this shell");let y=h?c[f]:null,w=k=>{e.apiKeyFile=p,(t.pendingSecrets=t.pendingSecrets||[]).push({name:s,value:k})};if(!y&&m?(a.ok("A saved key is already in "+p),e.apiKeyFile=d&&u.apiKeyFile||p,y=m):!y&&!t.yes&&(y=await i.ask("Paste the key (hidden; Enter to skip)","",{secret:!0}),y&&w(y)),y&&h&&!t.yes&&await i.confirm("Also save it to "+p+" (mode 0600) so the background service can use it?",!0)&&w(y),y||a.note("Set $"+f+" before starting the daemon, or re-run setup to save a key."),a.note("The key itself never goes into config.json."),y&&dt(l,"test")!==!1&&(t.yes||await i.confirm(o,!0))){process.env.__CHECKPOINTS_TEST_KEY=y;try{await Wo(t,{...e,apiKeyFile:void 0,apiKeyEnv:"__CHECKPOINTS_TEST_KEY"},r)}finally{delete process.env.__CHECKPOINTS_TEST_KEY}}return y}async function yv(t,e){let{p:n,u:s,flags:o,fetchImpl:r,env:i}=t,a=s.c;if(o.model)return String(o.model);let l=String(o["base-url"]||i.CHECKPOINTS_CLOUD_URL||_s),c=t.yes?null:ut("Fetching the cloud model list from "+l.replace(/^https?:\/\//,"")+"…",{out:t.out,colors:a}),u=await Dr({baseUrl:l,fetchImpl:r});c&&(u.ok?c.succeed(u.models.length+" cloud models available"):c.fail("Couldn't fetch the list ("+u.error+"); showing the usual picks"));let d=Ca(u.models,{current:e}),f=d.find(w=>w.current&&w.available!==!1)||d.find(w=>w.recommended&&w.available!==!1)||d.find(w=>w.available!==!1)||d[0];if(t.yes)return f.tag;let p=d.filter(w=>w.curated||w.current),h=p.map(w=>({label:w.tag+(w.recommended?" ★":""),value:w.tag,hint:(w.note||"")+(w.available===!1?" · not in today's list":"")})),m=d.filter(w=>!p.includes(w)).map(w=>w.tag);h.push({label:"Other model",value:"__other",hint:m.length?m.slice(0,6).join(", ")+(m.length>6?", …":""):"any tag from ollama.com/search?c=cloud"});let y=await n.select("Which cloud model?",h,Math.max(0,h.findIndex(w=>w.value===f.tag)));return y!=="__other"?y:n.ask("Model name (as ollama.com/api/tags lists it)",e||f.tag,{validate:w=>/^[\w.:/-]+$/.test(w)?null:"That doesn't look like a model tag"})}async function wv(t){let{p:e,u:n,flags:s,env:o}=t,r=n.c,i=t.currentSummarizer||{},a=Yd[String(i.provider||"auto").toLowerCase()]||"claude",l=s.summarizer?Yd[String(s.summarizer).toLowerCase()]:null;if(s.summarizer&&!l)throw new Error("unknown --summarizer="+s.summarizer+" (claude, ollama, lmstudio, ollama-cloud, anthropic, heuristic)");let c=[{label:"Claude, in the session",value:"claude",hint:"no key; uses your Claude Code login"},{label:"Local model",value:"local",hint:"free and private · Ollama or LM Studio"},{label:"Ollama cloud",value:"ollama-cloud",hint:"hosted gpt-oss, Gemma… · needs an ollama.com API key"},{label:"Anthropic API key",value:"anthropic",hint:"Haiku, about $0.007 per checkpoint"},{label:"No model",value:"heuristic",hint:"last prompt + last reply, offline"}],u=d=>d==="ollama"||d==="openai-compatible"?"local":d;for(;;){let d=u(l||a),f=await e.select("Who writes the 3-line checkpoints?",c,Math.max(0,c.findIndex(h=>h.value===d)));if(f==="claude"){let h={provider:"claude",model:"haiku"};return t.claude.found&&(dt(s,"test")===!0||!t.yes&&await e.confirm("Run a test checkpoint with `claude -p` now? (~10 s, uses your Claude login)",!1))?await Wo(t,h,"claude -p (haiku)"):t.claude.found||n.note('Without the claude CLI the daemon falls back to "no model" for checkpoints it writes on its own.'),h}if(f==="heuristic")return{provider:"heuristic",model:null};if(f==="anthropic"){let h={provider:"anthropic",model:s.model||"haiku"};return await ry(t,h,{defaultEnv:"ANTHROPIC_API_KEY",secretName:"anthropic-api-key",testPrompt:"Test it now? (one Haiku call, well under a cent)",testLabel:"Claude Haiku (API)"}),h}if(f==="ollama-cloud"){let h=await yv(t,i.provider==="ollama-cloud"?i.model:null),m={provider:"ollama-cloud",model:h,...s["base-url"]?{baseUrl:String(s["base-url"])}:{}};return n.note("Your transcripts' last few turns go to ollama.com for each checkpoint. Keys: https://ollama.com/settings/keys"),await ry(t,m,{defaultEnv:"OLLAMA_API_KEY",secretName:"ollama-api-key",testPrompt:"Test it now? (one call to "+h+" on ollama.com)",testLabel:h+" (Ollama cloud)"}),m}let p=await uc({...t,wantProvider:l==="ollama"||l==="openai-compatible"?l:null});if(!p){if(t.yes)throw new Error("local model setup didn't finish");continue}if(dt(s,"test")!==!1){let h=await Wo(t,p,p.model);if(!h.ok&&!t.yes&&await e.select("What now?",[{label:"Keep this model anyway",value:"keep"},{label:"Pick something else",value:"again"}],1)==="again")continue;if(!h.ok&&t.yes)throw new Error("test checkpoint failed: "+h.error)}return p}}async function ly(t,e={}){let{p:n,u:s,flags:o}=t,r=s.c,i=o["summary-length"]||o.length;if(i&&i!==!0){let u=cn(i,{strict:!0});if(!u)throw new Error("unknown --summary-length="+i+" (short, informative, long)");return s.ok("Checkpoint length: "+u),u}let a=cn(e.summary&&e.summary.length),l=Ng.map(u=>({label:u.label+(u.recommended?" (recommended)":""),value:u.key,hint:u.approx,detail:u.blurb})),c=Math.max(0,l.findIndex(u=>u.value===a));for(;;){let u=await n.select("How much should each checkpoint say?",l,c),d=Wl(Ig[u]).split(`
|
|
262
|
+
`).flatMap(f=>iy(f,70));if(zd(t.out,r,"Example · "+u,d),t.yes||await n.confirm("Use "+u+" checkpoints? (change any time in Settings → Models & summaries; /cp --short or --long overrides once)",!0))return u;c=l.findIndex(f=>f.value===u)}}async function Jd({flags:t={},input:e=process.stdin,out:n=process.stdout,env:s=process.env,fetchImpl:o=(...r)=>fetch(...r)}={}){let r=!!(t.yes||t.y),i=os(ss(n)),a=pv(n,i),l=i,c=new ct({input:e,output:n,yes:r,colors:i}),u={p:c,u:a,out:n,flags:t,env:s,fetchImpl:o,yes:r},d=()=>{n.write(`
|
|
263
|
+
`),a.fail("Setup cancelled. Re-run `leftoff setup` any time; finished steps are skipped."),process.exit(130)};r&&process.on("SIGINT",d);let f=5;try{if(a.line(),a.line(l.bold(" ◆ Leftoff setup")+l.dim(" v"+J)),a.line(l.dim(" Track every Claude Code session and get a 3-line checkpoint when one goes quiet.")),a.line(l.dim(" About two minutes. Ctrl-C any time; re-running is safe.")),a.step(1,f,"Preflight"),Number(process.versions.node.split(".")[0])<20)throw new Error("Node "+process.versions.node+" is too old; Leftoff needs Node 20 or newer");a.ok("Node "+process.versions.node);let h=vg(s);u.claude=h,h.found?h.modsSupported?a.ok("Claude Code "+(h.version||"?")+l.dim(" · "+tt(h.bin))):a.warn("Claude Code "+(h.version||"(version unknown)")+" · mods need "+yi+"+ (update with `claude update`); settings hooks work on any version"):a.warn("Claude Code's `claude` command isn't on your PATH");let m=Fe;if(Td(Ko)){let T=Wd(s);if(T)m=T,a.ok("leftoff is installed globally"+l.dim(" · "+tt(T)));else{a.warn("Running from a temporary npx copy.");let N=dt(t,"global"),j=_d(Ko)?Xe:"github:"+Bl;if(N!==!1&&(N===!0||!r&&await c.confirm("Install the `leftoff` command globally (npm install -g "+j+")?",!0))){let U=ut("npm install -g "+j,{out:n,colors:l}),Q=$o(process.platform==="win32"?"npm.cmd":"npm",["install","-g",j],{encoding:"utf8",env:s}),he=Q.status===0?Wd(s):null;he?(m=he,U.succeed("Installed `leftoff` globally")):U.fail("Global install failed: "+((Q.stderr||"").trim().split(`
|
|
264
|
+
`).pop()||"exit "+Q.status)+" (continuing from the npx copy)")}m===Fe&&a.note("Settings hooks and the login service will point at this npx copy. If npm clears its cache, re-run setup (or pass --global).")}}if(u.bin=m,!s.CHECKPOINTS_NO_UPDATE_CHECK)try{let{installInfo:T,lsRemote:N,writeStamp:j,DEFAULT_SOURCE:U}=await Promise.resolve().then(()=>(Kr(),Lp)),Q=await T({env:s});if(Q.kind!=="clone"&&Q.kind!=="npm"&&!Q.sha){let he=await N(U,{env:s,timeoutMs:8e3});he.ok&&j({sha:he.sha,source:U})}}catch{}let y=q(),w=qe.existsSync(we(y).config);w&&a.info("Found an existing setup in "+tt(y)+l.dim(" · your answers from last time are the defaults")),w||a.note("Tip: a synced folder (iCloud Drive, Dropbox, Syncthing) puts every machine on one board.");let k=await c.ask("Where should checkpoints live?",tt($d(t.dir||y)),{validate:T=>T&&Ne.isAbsolute($d(T))?null:"Use an absolute path or ~/…"}),S=Ne.resolve($d(k));u.root=S;let v=un(S)||(w&&S===y?un(y):null)||{},A=Z(S),b=Bs.hostname(),g=await c.ask("Name for this machine",t.machine||le(A),{hint:"(shown on each card)",validate:T=>/^[\w .-]{1,40}$/.test(T)?null:"Letters, numbers, spaces, dots and dashes; up to 40"});if(Ne.resolve(S)!==Ne.resolve(y)&&En(y)&&!t["no-move"]){let{planMove:T,executeMove:N,tilde:j}=await Promise.resolve().then(()=>(Ps(),yg)),{moveHooks:U}=await Promise.resolve().then(()=>(Ii(),dc)),{loadIdentity:Q}=await Promise.resolve().then(()=>(qn(),dd)),he=null;try{he=Q(g)}catch{}let pe=T(y,S,{machine:g,identity:he});if(!pe.ok){for(let Vo of[...pe.errors,...pe.collisions])a.warn(Vo.message);throw new Error("Can’t "+(pe.merge?"join ":"move to ")+j(S)+" yet (see above). Nothing changed.")}if(a.info((pe.merge?"That folder already has Leftoff data"+(pe.warnings.length?" ("+pe.warnings.map(Vo=>Vo.message).join(" ")+")":"")+". ":"")+pe.counts.total+" files in "+j(y)+"."),await c.confirm((pe.merge?"Merge":"Move")+" your sessions into "+j(S)+"? (copied and checked; the old folder is kept as "+j(pe.backup)+")",!0)){let Vo=Z(y),Zo=await N(pe,{hooks:U({from:y,cfg:Vo,token:Se(y),noRestart:!0,env:s}),machine:g,say:Tw=>a.line(" "+l.dim("· "+Tw))});if(!Zo.ok)throw new Error("The move stopped ("+Zo.error+"); put back, still using "+j(y)+".");a.ok((pe.merge?"Merged":"Moved")+": "+Zo.copied+" copied, "+Zo.verified+" verified. Backup: "+j(Zo.backup))}}a.step(2,f,"Claude Code mod");let _=Ol();Ag(_)&&a.warn('"disableAllHooks": true is set in your Claude settings, so neither mods nor settings hooks run until you remove it.');let x=t.capture?String(t.capture):dt(t,"hooks")===!0?"hooks":dt(t,"mod")===!1?"none":null,C=[];h.found&&C.push({label:"Install the mod",value:"mod",hint:h.modsSupported?"recommended · /cp, /cp-name and the /leftoff pane":"needs Claude Code "+yi+"+"}),C.push({label:"Settings hooks only",value:"hooks",hint:"works on any version; no slash commands or pane"+(h.found?"":" · start working once Claude Code is installed")}),C.push({label:"Skip for now",value:"none"});let E=x||(h.found&&h.modsSupported?"mod":"hooks");h.found||(a.line(" Install Claude Code first ("+l.underline("https://code.claude.com/docs")+"), then re-run setup to add the mod."),E==="mod"&&(E="hooks")),E=await c.select("How should Leftoff see your sessions?",C,Math.max(0,C.findIndex(T=>T.value===E)));let L=!1;if(E==="mod"){let T=xg({override:t.marketplace}),N="",j=ut("Checking installed plugins…",{out:n,colors:l}),U=Od({bin:h.bin,source:T,env:s,onStep:Q=>{N=Q,j.update("Running "+l.cyan(Q))}});if(U.ok&&U.migrated)j.succeed("Mod moved to its new name: "+Ce+" (was "+rs+")");else if(U.ok&&U.already&&U.state&&!U.state.enabled)j.succeed("Mod was installed but disabled; enabled it ("+Ce+")");else if(U.ok&&U.already)j.succeed("Mod already installed and enabled ("+Ce+")");else if(U.ok){j.succeed("Mod installed: "+Ce);for(let Q of U.steps)a.cmd("claude "+Q.args.map(he=>tt(he)).join(" "))}else j.fail("`"+N+"` failed: "+String(U.error).split(`
|
|
265
|
+
`).slice(-2).join(" "));L=U.ok,U.ok?h.modsSupported||a.warn("The mod is installed but loads only on Claude Code "+yi+"+."):(/github|clone|auth|permission|denied|not found/i.test(String(U.error))&&T===Bl&&a.note("The repo is private: Claude Code clones it with your git credentials (try `gh auth login`), or run setup from a local clone."),E=await c.confirm("Use settings hooks instead? (same tracking, no slash commands)",!0)?"hooks":"none"),L&&li()&&await c.confirm("Remove the older settings hooks? The mod covers them.",!0)&&(Il(),a.ok("Removed the settings hooks"))}if(E==="hooks"){let T=li(),N=Do(void 0,{bin:m});a.ok((T?"Settings hooks refreshed in ":"Settings hooks added to ")+tt(N)+l.dim(" (backup saved next to it)"))}E==="none"&&a.info("Skipped. Add it later with `leftoff setup`."),a.step(3,f,"Summarizer"),u.currentSummarizer=A.summarizer,u.currentModel=A.summarizer&&A.summarizer.model;let O=await wv(u),H=await ly(u,A);a.step(4,f,"Save and start"),Te(S);for(let T of u.pendingSecrets||[])Vi(T.name,T.value,S,s),a.ok("Key saved to "+tt(Ne.join(Jt(s),T.name))+l.dim(" (0600, on this Mac only)"));let W=ac(v,{summarizer:O,machine:g,hostname:b,port:t.port,summaryLength:H});lc(S,W),a.ok("Config saved: "+tt(we(S).config)+l.dim(" · summarizer "+O.provider+(O.model?" / "+O.model:"")+" · "+H+" checkpoints · machine "+g));let I=cc(S);I.how==="symlink"&&!I.already?a.ok("Linked ~/checkpoints → "+tt(S)+l.dim(" (so the mod and hooks find it)")):I.how==="settings-env"&&a.ok("Set CHECKPOINTS_DIR in your Claude settings so the mod and hooks find "+tt(S));let P=Z(S),Y=P.port,ue=Se(S),$={...s,CHECKPOINTS_DIR:S},X=await Yo(Y,ue,o),ne=dt(t,"service"),B=ne!==void 0?ne:!r&&process.platform!=="win32"?await c.confirm("Start Leftoff automatically when you log in?",!0):!1;if(B){let T=xt(),N=Nl({root:S,bin:m,dryRun:!!t["service-dry-run"]});N&&a.ok("Moved the login service to its new name: "+tt(N.to)+l.dim(" (stopped and removed "+tt(N.from)+")")),qe.mkdirSync(Ne.dirname(T),{recursive:!0});let j=Tn({root:S,bin:m}),U=!qe.existsSync(T)||qe.readFileSync(T,"utf8")!==j;qe.writeFileSync(T,j),a.ok((U?"Login service written: ":"Login service up to date: ")+tt(T));let Q=!0;if(!t["service-dry-run"])for(let he of Rs()){let pe=$o(he[0],he.slice(1),{encoding:"utf8"});if(pe.status!==0&&he[1]!=="unload"){Q=!1,a.warn("`"+he.join(" ")+"` failed: "+((pe.stderr||pe.error&&pe.error.message||"").trim().split(`
|
|
266
|
+
`)[0]||"exit "+pe.status));break}}if(Q?a.ok("Service started"):a.note("Starting the daemon directly instead; the service file stays for next login."),Q)for(let he=0;he<20&&!(X=await Yo(Y,ue,o)).up;he++)await sy(250)}if(dt(t,"start")===!1)a.info("Not starting the daemon (--no-start). Start it with `leftoff serve`.");else if(X.up&&X.auth){try{await rc(Y,ue,{summarizer:W.summarizer,machineNames:W.machineNames||{},summary:{length:H}},o),a.ok("Daemon already running on 127.0.0.1:"+Y+l.dim(" · config updated live"))}catch(T){a.warn("Daemon is running but didn't take the new config ("+T.message+"); restart it to apply.")}X.machine&&X.machine!==g&&a.note("Restart the daemon to show the new machine name.")}else if(X.up&&!X.auth)a.warn("Something else (another Leftoff folder?) is on port "+Y+'. Stop it, or set "port" in config.json, then run `leftoff serve`.');else{let T=ut("Starting the daemon…",{out:n,colors:l});ic({root:S,bin:m,env:$});for(let N=0;N<40&&!(X=await Yo(Y,ue,o)).up;N++)await sy(250);X.up?(T.succeed("Daemon running on 127.0.0.1:"+Y),B||a.note("Runs in the background until reboot; `leftoff setup --service` starts it at login.")):T.fail("The daemon didn't start; see "+tt(Ne.join(S,".daemon.log")))}if(a.step(5,f,"Check"),dt(t,"doctor")!==!1){let T=$o(process.execPath,[m,"doctor"],{encoding:"utf8",env:{...$,NO_COLOR:"1"}});for(let N of(T.stdout||"").trim().split(`
|
|
267
|
+
`).filter(Boolean).map(j=>j.split(Bs.homedir()+Ne.sep).join("~"+Ne.sep))){let j=N.match(/^([✓✗·])\s(.*)$/);j?j[1]==="✓"?a.ok(j[2]):j[1]==="✗"?a.fail(j[2]):a.info(j[2]):a.line(" "+N)}}let Ct=await cy(u,{root:S,machine:g,port:Y,token:ue,up:!!X.up}),$e=X.up?(await wi({port:Y,prefer:s.CHECKPOINTS_OPEN_HOST||P.openHost||"auto",token:ue})).url:"http://127.0.0.1:"+Y+"/",Wt=dt(t,"open");X.up&&(Wt===!0||Wt===void 0&&!r&&n.isTTY&&await c.confirm("Open the board in your browser?",!0))&&(mv($e)?a.ok("Opened "+$e):a.info("Open "+$e)),a.line();let He=[l.bold("Board")+" "+l.cyan($e)];if(E==="mod"){let T=(N,j,U,Q)=>He.push(N+" ".repeat(15-N.replace(/\x1b\[[0-9;]*m/g,"").length)+l.cyan(j)+(U?" "+U:"")+" ".repeat(Math.max(1,21-j.length-(U?U.length+1:0)))+Q);T(l.bold("In any session"),"/cp","[note]","checkpoint now"),T("","/cp-name","<title>","name the session"),T("","/leftoff","","every open session"),He.push(l.dim("Already-open sessions pick the mod up when they restart."))}else E==="hooks"?(He.push(l.bold("Sessions")+" tracked through settings hooks;"),He.push(" checkpoints happen on their own after 15 min idle")):He.push(l.bold("Sessions")+" not hooked up yet: `leftoff setup`");return He.push(l.bold("Models")+" "+l.cyan("leftoff models")+" list, pull, switch"),He.push(l.bold("Digest")+" "+l.cyan("leftoff digest")+" where you left off"),He.push(l.bold("Reopen")+" "+l.cyan("leftoff open")+" the board, any time"),Ct&&Ct.host?He.push(l.bold("Anywhere")+" "+l.cyan("https://"+Ct.host)+l.dim(" (leftoff relay pair)")):He.push(l.bold("Anywhere")+" "+l.cyan("leftoff relay signup")+" yourname"+l.dim(" (your board on your phone)")),zd(n,l,"You're set",He),c.close(),r&&process.removeListener("SIGINT",d),{ok:!0,root:S,capture:E,summarizer:O,port:Y,daemonUp:!!X.up,anywhere:Ct}}catch(p){if(c.close(),r&&process.removeListener("SIGINT",d),p&&p.cancelled)return n.write(`
|
|
268
|
+
`),a.fail("Setup cancelled ("+p.message.replace(/^cancelled \(?|\)$/g,"")+"). Re-run `leftoff setup` any time; finished steps are skipped."),{ok:!1,cancelled:!0};throw p}}async function cy(t,{root:e,machine:n,port:s,token:o,up:r}){let{p:i,u:a,flags:l,env:c,yes:u,fetchImpl:d}=t,f=i.c,{readRelaySettings:p,writeRelaySettings:h,hostFor:m,DEFAULT_DOMAIN:y}=await Promise.resolve().then(()=>(oc(),ny)),{startSignup:w,waitForConfirm:k,validEmail:S,checkName:v}=await Promise.resolve().then(()=>(Zl(),Fg)),{checkUserName:A}=await Promise.resolve().then(()=>(ki(),Mg)),{loadIdentity:b}=await Promise.resolve().then(()=>(qn(),dd)),g=p(e,c);if(g.user)return a.ok("Use from anywhere: "+m(g)+(g.enabled?"":f.dim(" (off on this Mac: leftoff relay enable "+g.user+")"))),{host:m(g),existing:!0};if(dt(l,"signup")===!1)return{skipped:!0};let _=String(l.domain||g.domain||y),x=typeof l.signup=="string"?l.signup.trim().toLowerCase():"",C=typeof l.email=="string"?l.email.trim():"";if(u&&!(x&&C))return{skipped:!0};let E=!1;a.line();let L=W=>W&&W.cancelled&&/input closed/.test(W.message);try{if(!u&&!x&&!await i.confirm("Use your board from anywhere? Claim yourname."+_+f.dim(" (free in the beta; skip any time)"),!1))return a.info("Skipped. Later: "+f.cyan("leftoff relay signup yourname")),{skipped:!0};let W=!!x;for(let I=0;;I++){x||(x=(await i.ask("Your name","",{hint:"(for yourname."+_+")",validate:ue=>{let $=A(String(ue).toLowerCase());return $.ok?null:$.code==="reserved"?"That one’s reserved; pick another":"2–32 lowercase letters, digits or hyphens"}})).toLowerCase());let P=await v({s:{...g,user:x,domain:_},name:x,sign:b(n).signX});if(P.ok&&P.status==="yours"){E=!0;break}let Y=P.ok?P.status==="available"?null:P.message:P.code==="legacy"?null:P.message;if(!Y){P.ok&&P.claimed&&a.note(P.host+" was claimed on leftoff.dev. Use the email you claimed it with.");break}if(a.warn(Y),W||u||I>=2||P.code==="unreachable"||P.code==="closed")return a.info("Try again any time: "+f.cyan("leftoff relay signup yourname")),{skipped:!0,error:Y};x=""}!E&&!C&&(C=await i.ask("Email","",{hint:"(for the confirmation link)",validate:I=>S(I)?null:"That doesn’t look like an email address"}))}catch(W){if(!L(W))throw W;return a.line(),a.info("Skipped. Later: "+f.cyan("leftoff relay signup yourname")),{skipped:!0}}let O={...g,user:x,domain:_},H=E?{ok:!0,already:!0}:await w({s:O,name:x,email:C,machine:n,sign:b(n).signX});if(!H.ok)return a.warn(H.message),a.info("Try again any time: "+f.cyan("leftoff relay signup "+x)),{skipped:!0,error:H.message};if(H.already)a.ok("This Mac already has "+m(O)+". Pair your phone: "+f.cyan("leftoff relay pair"));else{a.ok("Sent a link to "+H.email+". Open it and press "+f.bold("Confirm "+m(O))+f.dim(" (30 minutes)")),H.claimed&&a.note("Your leftoff.dev claim for "+x+" carries over.");let W=ut("Waiting for you to confirm… "+f.dim("(Ctrl-C skips; finish later with leftoff relay enable "+x+")"),{out:t.out,colors:f}),I=!1,P=()=>{I=!0};i.interruptHandlers.add(P),process.on("SIGINT",P);let Y=await k({s:O,user:x,poll:H.poll,intervalMs:Number(c.LEFTOFF_SIGNUP_POLL_MS)||void 0,timeoutMs:Number(c.LEFTOFF_SIGNUP_WAIT_MS)||void 0,stop:()=>I});if(i.interruptHandlers.delete(P),process.removeListener("SIGINT",P),Y!=="confirmed")return W.warn(Y==="stopped"?"Stopped waiting. The link still works; then run "+f.cyan("leftoff relay enable "+x):Y==="expired"||Y==="timeout"?"The link expired. Run "+f.cyan("leftoff relay signup "+x)+" for a fresh one.":"The signup didn’t finish ("+Y+"). Run "+f.cyan("leftoff relay signup "+x)+" again."),{skipped:!0,status:Y};W.succeed(m(O)+" is yours. Pair your phone: "+f.cyan("leftoff relay pair"))}if(h(e,{enabled:!0,user:x,domain:_,registeredAt:new Date().toISOString(),via:"signup"},c),r)try{(await d("http://127.0.0.1:"+s+"/v1/relay/enable",{method:"POST",headers:{authorization:"Bearer "+o,"content-type":"application/json"},body:JSON.stringify({user:x,domain:_})})).ok&&a.ok("Use from anywhere is on; the daemon is connecting")}catch{}return{host:m(O)}}var sy,tt,$d,fv,hv,Yd,Fs=V(()=>{Cn();ke();Zn();$l();go();xs();Kr();Ps();Id();ve();Ld();sy=t=>new Promise(e=>setTimeout(e,t)),tt=t=>t&&t.startsWith(Bs.homedir()+Ne.sep)?"~"+t.slice(Bs.homedir().length):t,$d=t=>t==="~"?Bs.homedir():t&&t.startsWith("~/")?Ne.join(Bs.homedir(),t.slice(2)):t,fv=t=>t===!0||t==="true"||t==="1"||t==="yes",hv=t=>t===!1||t==="false"||t==="0"||t==="no";Yd={claude:"claude","in-session":"claude",session:"claude",auto:"claude",ollama:"ollama",local:"ollama",lmstudio:"openai-compatible","lm-studio":"openai-compatible",openai:"openai-compatible","openai-compatible":"openai-compatible","ollama-cloud":"ollama-cloud",cloud:"ollama-cloud","ollama.com":"ollama-cloud",anthropic:"anthropic",api:"anthropic",key:"anthropic",heuristic:"heuristic",none:"heuristic",stub:"heuristic",off:"heuristic"}});var dc={};zt(dc,{moveHooks:()=>my,parseMoveArgs:()=>py,runMove:()=>qd});import Ni from"node:fs";import uy from"node:os";import zo from"node:path";import{spawnSync as dy}from"node:child_process";async function hc(t,e){try{let n=await fetch("http://127.0.0.1:"+t+"/v1/health",{headers:{authorization:"Bearer "+(e||"")},signal:AbortSignal.timeout(1500)});return n.ok?{up:!0,...await n.json()}:{up:n.status===401,status:n.status}}catch{return{up:!1}}}function py(t){let e={to:null,dryRun:!1,yes:!1,force:!1,json:!1,backups:!1,deleteBackup:null,daemonPid:null,noRestart:!1};for(let n=0;n<t.length;n++){let s=t[n];s==="--dry-run"||s==="-n"?e.dryRun=!0:s==="--yes"||s==="-y"?e.yes=!0:s==="--force"?e.force=!0:s==="--json"?e.json=!0:s==="--backups"?e.backups=!0:s==="--no-restart"?e.noRestart=!0:s==="--help"||s==="-h"?e.help=!0:s==="--delete-backup"?e.deleteBackup=t[n+1]&&!t[n+1].startsWith("--")?t[++n]:!0:s==="--daemon-pid"?e.daemonPid=Number(t[++n])||null:e.to||(e.to=s)}return e}function my({from:t,cfg:e,token:n,daemonPid:s=null,noRestart:o=!1,say:r=()=>{},env:i=process.env}){let a=xt(),l=Ni.existsSync(a)&&!i.CHECKPOINTS_SERVICE_DRY_RUN,c=e.port,u=!1;return{async stopWriters(){let d=await hc(c,n);if(u=d.up,!!d.up){if(l){let f=An()==="darwin"?["launchctl","unload",a]:["systemctl","--user","stop",Os];dy(f[0],f.slice(1),{encoding:"utf8"})}else{let f=s||d.pid||null;if(!f)try{f=Number(Ni.readFileSync(zo.join(t,".daemon.pid"),"utf8"))||null}catch{}if(!f)throw new Error("the daemon is running but I can’t tell its process; stop it (Ctrl-C on `leftoff serve`) and run this again");try{process.kill(f,"SIGTERM")}catch{}for(let p=0;p<50&&hy(f);p++)await fc(100);if(hy(f)){try{process.kill(f,"SIGKILL")}catch{}await fc(200)}}for(let f=0;f<40&&(await hc(c,n)).up;f++)await fc(100);if((await hc(c,n)).up)throw new Error("the daemon didn’t stop");r("Daemon stopped")}},async switchTo(d){if(cc(d),Ni.existsSync(a)){let f=Tn({root:d,bin:Fe});Ni.readFileSync(a,"utf8")!==f&&Ni.writeFileSync(a,f)}},async startWriters(d){if(o||!u)return{ok:!0,skipped:!0};let f=Se(d),p=Z(d).port;if(l)for(let h of Rs())dy(h[0],h.slice(1),{encoding:"utf8"});else{let{LEFTOFF_DIR:h,CHECKPOINTS_DIR:m,...y}=i;ic({root:d,bin:Fe,env:{...y,CHECKPOINTS_DIR:d}})}for(let h=0;h<80;h++){let m=await hc(p,f);if(m.up&&m.folder&&zo.resolve(m.folder)===zo.resolve(d))return{ok:!0,port:p};if(m.up&&m.status===401)return{ok:!1,error:"something else is answering on port "+p};await fc(150)}return{ok:!1,error:"no answer on port "+p}}}}async function qd({argv:t=[],out:e=process.stdout,input:n=process.stdin,env:s=process.env}={}){let o=ee,r=py(t),i=S=>{r.json||e.write(S+`
|
|
269
|
+
`)},a=q();if(r.help)return e.write(Sv),0;if(r.backups||r.deleteBackup){let S=Ho(a);if(r.backups){if(r.json)return e.write(JSON.stringify({backups:S})+`
|
|
270
|
+
`),0;S.length||i("No backups from a move next to "+ge(a)+".");for(let A of S)i(" "+A.display+o.dim(" (moved "+(A.at||"?")+")"));return 0}let v=r.deleteBackup===!0?S.length===1?S[0].path:null:zo.resolve(fy(r.deleteBackup));if(!v)return i(S.length?"Several backups; name one: "+z+" move --delete-backup <path>":"No backups to delete."),1;if(!r.yes){let A=new ct({input:n,output:e});if(!await A.confirm("Delete "+ge(v)+" for good?",!1).finally(()=>A.close&&A.close()))return i("Kept it."),0}try{return pi(v),i(o.green("✓")+" Deleted "+ge(v)),0}catch(A){return i(o.red("✗")+" "+A.message),1}}if(!r.to){let S=Fo({current:a});if(i("usage: "+z+` move <folder> [--dry-run] [--yes]
|
|
271
|
+
|
|
272
|
+
Now: `+ge(a)+(S.current.syncedBy?" (in "+S.current.syncedBy.label+")":" (not in a synced folder)")),S.candidates.length){i(`
|
|
273
|
+
Synced folders on this Mac:`);for(let v of S.candidates)i(" "+v.label.padEnd(14)+" "+v.display+(v.hasLeftoff?o.dim(" · already has Leftoff data (joins it)"):"")+`
|
|
274
|
+
`+" ".repeat(15)+o.dim(v.tradeoff))}return 1}let l=zo.resolve(fy(r.to)),c=Z(a),u=le(c),d=null;try{d=xn(u,{dir:Tl(s)})}catch{}let f=Uo(a,l,{machine:u,identity:d});r.force&&(f.collisions=f.collisions.filter(S=>S.code!=="machine_name_maybe_taken"),f.ok=!f.errors.length&&!f.collisions.length);let p=S=>{try{vd({from:a,to:l,...S},s)}catch{}};if(r.json&&r.dryRun){let{files:S,...v}=f;return e.write(JSON.stringify({...v,sample:S.filter(A=>A.action!=="same").slice(0,20)})+`
|
|
275
|
+
`),f.ok?0:1}i(o.bold((f.merge?"Join ":"Move to ")+ge(l))+o.dim(" (from "+ge(a)+")"));for(let S of[...f.errors,...f.collisions])i(" "+o.red("✗")+" "+S.message);if(!f.ok)return p({state:"refused",ok:!1,error:[...f.errors,...f.collisions][0].message}),r.json&&e.write(JSON.stringify({ok:!1,errors:f.errors,collisions:f.collisions})+`
|
|
276
|
+
`),1;let h=f.counts;i(" "+h.total+" files: "+h.copy+" to copy"+(h.same?", "+h.same+" already there":"")+(h.keepTarget?", "+h.keepTarget+" kept from the joined folder":"")+(h.conflictCopy?", "+h.conflictCopy+" list"+(h.conflictCopy===1?"":"s")+" merged":"")+(h.renameCopy?", "+h.renameCopy+" renamed":""));for(let S of f.warnings)i(" "+o.yellow("!")+" "+S.message);if(i(" The old folder stays, renamed to "+ge(f.backup)+", until you delete it."),r.dryRun)return i(o.dim("Dry run: nothing changed.")),0;if(!r.yes){let S=new ct({input:n,output:e});if(!await S.confirm(f.merge?"Join it?":"Move it?",!0).finally(()=>S.close&&S.close()))return i("Nothing changed."),0}p({state:"moving",ok:null,message:"Copying "+h.total+" files"});let m=my({from:a,cfg:c,token:Se(a),daemonPid:r.daemonPid,noRestart:r.noRestart,say:S=>i(" "+o.dim("· "+S)),env:s}),y=await Ad(f,{hooks:m,machine:u,say:S=>{i(" "+o.dim("· "+S)),p({state:"moving",ok:null,message:S})}});if(!y.ok)return i(" "+o.red("✗")+" "+y.error+(y.rolledBack?o.dim(" · put back: still using "+ge(a)):"")),p({state:"failed",ok:!1,error:y.error,stage:y.stage,rolledBack:!!y.rolledBack}),r.json&&e.write(JSON.stringify(y)+`
|
|
277
|
+
`),1;let w=Bo(l,{cli:z}),k=Fo({current:l});return p({state:"done",ok:!0,copied:y.copied,verified:y.verified,kept:y.kept,conflicts:y.conflicts,backup:y.backup,merge:f.merge,syncedBy:k.current.syncedBy,join:w}),i(" "+o.green("✓")+" "+(f.merge?"Joined":"Moved")+": "+y.copied+" copied, "+y.verified+" verified"+(y.kept?", "+y.kept+" kept":"")+". Leftoff now uses "+ge(l)),i(" "+o.dim("Backup: "+ge(y.backup)+" (delete it when you’re happy: "+z+" move --delete-backup)")),i(`
|
|
278
|
+
On each other Mac, once the folder has synced there:
|
|
279
|
+
`+o.cyan(w.installed)+o.dim(" (Leftoff installed)")+`
|
|
280
|
+
`+o.cyan(w.fresh)+o.dim(" (not yet)")),r.json&&e.write(JSON.stringify({...y,join:w})+`
|
|
281
|
+
`),0}var Sv,fc,fy,hy,Ii=V(()=>{ke();Ps();Zn();Fs();qn();Cn();wt();Sv=`${z} move: move the Leftoff folder, or join a synced one another Mac uses
|
|
282
|
+
|
|
283
|
+
${z} move ~/Sync/Leftoff move (or join) it: copy, verify, switch, keep a backup
|
|
284
|
+
${z} move ~/Sync/Leftoff --dry-run say what it would do; change nothing
|
|
285
|
+
${z} move ... --yes don't ask first
|
|
286
|
+
${z} move ... --force join even though the folder has sessions under this Mac's name
|
|
287
|
+
${z} move --backups the backups earlier moves left
|
|
288
|
+
${z} move --delete-backup [PATH] delete one (asks first)
|
|
289
|
+
|
|
290
|
+
The old folder is renamed to <name>.leftoff-backup-<date> and kept until you delete it.
|
|
291
|
+
The board does the same from Settings → Sync → Set up syncing.
|
|
292
|
+
`,fc=t=>new Promise(e=>setTimeout(e,t)),fy=t=>t==="~"?uy.homedir():String(t).startsWith("~/")?zo.join(process.env.HOME||uy.homedir(),String(t).slice(2)):t;hy=t=>{try{return process.kill(t,0),!0}catch{return!1}}});import gy from"node:fs";import Jo from"node:path";function yy(t){let e={pos:[],yes:!1,json:!1,daemonPid:null,path:null,timeoutMs:null};for(let n=0;n<t.length;n++){let s=t[n];s==="--yes"||s==="-y"?e.yes=!0:s==="--json"?e.json=!0:s==="--help"||s==="-h"?e.help=!0:s==="--daemon-pid"?e.daemonPid=Number(t[++n])||null:s==="--path"||s==="--dir"?e.path=t[++n]:s.startsWith("--path=")||s.startsWith("--dir=")?e.path=s.split("=").slice(1).join("="):s==="--timeout"?e.timeoutMs=Number(t[++n])*1e3:e.pos.push(s)}return e}async function wy({env:t=process.env,say:e=()=>{},confirm:n=async()=>!1,onLine:s=()=>{}}={}){if(hn(t).installed)return{ok:!0,already:!0};let r=ys(t);if(!r.brew)return{ok:!1,code:"no_brew",directions:r.directions,links:r.links,error:"Homebrew isn’t installed, so Leftoff can’t install Syncthing for you."};if(!await n(r.command))return{ok:!1,code:"declined",error:"Not installed."};let i=await dr({env:t,onLine:s});return i.ok?(e("Waiting for Syncthing to start…"),(await ws({env:t,timeoutMs:Number(t.LEFTOFF_SYNCTHING_START_MS)||9e4})).ok?{ok:!0}:{ok:!1,code:"not_running",error:"Syncthing installed, but it didn’t start. Try `brew services restart syncthing`."}):i}async function Qd({env:t=process.env,say:e=()=>{}}={}){let n=ot({env:t});if(n.ok)try{return await n.client.ping(),{ok:!0,client:n.client}}catch{}if(!hn(t).installed&&!n.cfg)return{ok:!1,code:"not_installed",error:"Syncthing isn’t installed on this Mac."};e("Starting Syncthing…");let o=fr(t);if(!o.ok)return{ok:!1,code:o.code,error:"Couldn’t start Syncthing: "+o.error};let r=await ws({env:t,timeoutMs:Number(t.LEFTOFF_SYNCTHING_START_MS)||6e4});return r.ok?{ok:!0,client:r.client}:{ok:!1,code:"not_running",error:r.error}}async function kv({env:t=process.env,target:e=null,daemonPid:n=null,say:s=()=>{},status:o=()=>{}}={}){let r=await Qd({env:t,say:s});if(!r.ok)return r;let i=r.client,a=await i.systemStatus(),l=await i.folders(),c=q(),u=l.find(h=>h.id===Ae),d=Jo.resolve(ea(e||u&&u.path||hr(l,t),t)),f=Ss(d,l);if(f)return{ok:!1,code:"nested",error:Ee(d,t)+(f.how==="inside"?" is inside":" contains")+" your Syncthing folder “"+f.label+"”. Pick a folder outside it; Leftoff won’t change that folder."};if(u&&Jo.resolve(u.path)!==d)return{ok:!1,code:"folder_elsewhere",error:"Syncthing already has a Leftoff folder at "+Ee(u.path,t)+". Use that path, or Stop syncing first."};if(Jo.resolve(c)!==d){s("Moving your Leftoff folder to "+Ee(d,t)+" (copy, verify, keep a backup)…"),o({state:"moving",message:"Moving your folder to "+Ee(d,t)+"…"});let{runMove:h}=await Promise.resolve().then(()=>(Ii(),dc)),m={write:()=>!0};if(await h({argv:[d,"--yes","--json",...n?["--daemon-pid",String(n)]:[]],out:m,env:t})!==0)return{ok:!1,code:"move_failed",error:"The move stopped: "+((js(t)||{}).error||"unknown error")+". Nothing changed; Leftoff still uses "+Ee(c,t)+"."}}s("Adding the Leftoff folder to Syncthing…"),o({state:"configuring",message:"Adding the folder to Syncthing…"});let p=await na(i,{path:d,myID:a.myID});return Rn({folderPath:d},t),{ok:!0,path:d,created:p.created,myID:a.myID}}async function bv(t,{env:e=process.env,target:n=null,daemonPid:s=null,yes:o=!1,say:r=()=>{},status:i=()=>{},confirmInstall:a=null,onLine:l=()=>{},input:c,out:u,now:d=()=>Date.now(),pollMs:f=Number(e.LEFTOFF_SYNCTHING_POLL_MS)||1e3,syncTimeoutMs:p=30*6e4}={}){let h;try{h=zc(t,{now:d()})}catch(P){return{ok:!1,code:P.code||"bad_code",error:P.message}}let m=Jc(t);if(pn(e).usedCodes.includes(m))return{ok:!1,code:"used",error:"This code was already used on this Mac. Make a new one on "+(h.machine||"the other Mac")+"."};let y=h.machine||"the other Mac";if(i({state:"starting",message:"Checking Syncthing…",with:h.machine}),!hn(e).installed&&!ot({env:e}).cfg){if(!a)return{ok:!1,code:"needs_install",error:"Syncthing isn’t installed on this Mac yet.",plan:ys(e)};i({state:"installing",message:"Installing Syncthing…"});let P=await wy({env:e,say:r,confirm:a,onLine:l});if(!P.ok)return P}let w=await Qd({env:e,say:r});if(!w.ok)return w;let k=w.client,S=await k.systemStatus();if(_e(S.myID,h.deviceId))return{ok:!1,code:"same_mac",error:"This is the Mac that made the code. Paste it on your other Mac."};let v=q(),A=await k.folders(),b=A.find(P=>P.id===h.folderId),g=Jo.resolve(ea(n||b&&b.path||hr(A,e),e)),_=Ss(g,A);if(_)return{ok:!1,code:"nested",error:Ee(g,e)+" is inside your Syncthing folder “"+_.label+"”. Pick another folder (--path); Leftoff won’t change that folder."};if(gy.existsSync(g)&&Jo.resolve(v)!==g&&!En(g)&&gy.readdirSync(g).filter(Y=>!/^\.DS_Store$|^\.stfolder$|^\.stignore$/.test(Y)).length)return{ok:!1,code:"not_empty",error:Ee(g,e)+" already has other files in it. Pick an empty folder (--path)."};let C=(await k.devices()).find(P=>_e(P.deviceID,S.myID))||{},E=pn(e),L=E.originalName||Zs(C.name)||le(Z(v));E.originalName||Rn({originalName:L},e);let O=async()=>{try{pn(e).originalName&&(await k.patchDevice(S.myID,{name:L},{self:S.myID}),Rn({originalName:null},e))}catch{}},H=oa(h.secret,{joinerId:S.myID,firstId:h.deviceId,folderId:h.folderId}),W=Vc(L,H);await k.patchDevice(S.myID,{name:W},{self:S.myID});try{r("Adding "+y+" to Syncthing and sharing the Leftoff folder at "+Ee(g,e)+"…");let P=await sa(k,{id:h.deviceId,name:h.machine?h.machine+" (Leftoff)":"Leftoff Mac",env:e});await na(k,{path:g,peers:[h.deviceId],myID:S.myID}),Rn({folderPath:g},e),P.created||P.device.paused||await k.reconnectDevice(h.deviceId,{allow:h.deviceId}).catch(()=>{});let Y=d(),ue=!1,$="",X=Number(e.LEFTOFF_JOIN_STUCK_MS)||6e4,ne={first:y,name:L,url:"http://127.0.0.1:8384",steps:Xc({first:y,name:L}),board:"Or on "+y+"’s Leftoff board: Settings → Sync → Accept "+L+"."},B=(T,N={})=>i({state:"waiting",message:T,with:h.machine,...ue?{stuck:ne}:{},...N}),Ct=async({again:T=!1}={})=>{let N=0,j=d();for(B(T?"Lost the connection to "+y+". Waiting for it to come back…":"Waiting for "+y+" to accept this Mac…");;){let U=await ra(k,{peerId:h.deviceId,folderId:h.folderId});if(N=U.accepted?N+1:0,N>=2)return!0;let Q=U.connected&&U.state==="notSharing"?y+" added this Mac but isn’t sharing the Leftoff folder with it yet. On "+y+", tick Leftoff on this Mac’s Sharing tab in Syncthing.":"";if(Q&&Q!==$&&(r(Q),B(Q)),$=Q,!T&&!ue&&d()-Y>=X&&(ue=!0,r("If it’s stuck: "+ne.steps.join(" ")+" "+ne.board),B("Still waiting for "+y+" to accept this Mac…")),!T&&d()>h.expiresAt+6e4||T&&d()-j>p)return!1;await Vd(f)}};if(r("Waiting for "+y+" to accept this Mac…"),!await Ct())return{ok:!1,code:"expired",error:y+" didn’t accept this Mac before the code expired. Is Leftoff running there? Make a new code and try again, or add this Mac by hand: "+ne.steps.join(" ")};await O(),Rn({usedCodes:[...pn(e).usedCodes,m].slice(-50)},e),r(y+" accepted this Mac. Syncing…");let $e="",Wt=0,He=d();for(;;){let T=await ra(k,{peerId:h.deviceId,folderId:h.folderId}),N=await k.dbStatus(h.folderId).catch(()=>null),j=N&&N.globalFiles>0&&!N.needFiles&&N.state==="idle"&&En(g);if(!T.accepted&&!j){if(r("Lost the connection to "+y+". Waiting for it to come back…"),$e="",Wt=0,!await Ct({again:!0}))return{ok:!1,code:"sync_timeout",error:"Lost the connection to "+y+" and it didn’t come back. Syncthing keeps trying; run `"+z+" move "+Ee(g,e)+"` once the files are here."};r("Connected to "+y+" again. Syncing…");continue}let U=Qc(N);if(U!==$e&&($e=U,r(U),i({state:"syncing",message:U,with:h.machine,have:N?N.inSyncFiles:null,total:N?N.globalFiles:null})),Wt=j?Wt+1:0,Wt>=2)break;if(d()-He>p)return{ok:!1,code:"sync_timeout",error:"The first sync is taking a long time ("+U+"). It keeps going in Syncthing; run `"+z+" join` again later, or `"+z+" move "+Ee(g,e)+"` once it’s done."};await Vd(f)}}finally{await O()}if(i({state:"merging",message:"Joining the folder…",with:h.machine}),Jo.resolve(v)===g)return{ok:!0,path:g,merged:!1,with:h.machine};if(En(v)){r("Joining: this Mac’s Leftoff data goes into "+Ee(g,e)+" (nothing of "+y+"’s is overwritten)…");let{runMove:P}=await Promise.resolve().then(()=>(Ii(),dc));return await P({argv:[g,"--yes","--json",...s?["--daemon-pid",String(s)]:[]],out:{write:()=>!0},env:e})!==0?{ok:!1,code:"merge_failed",error:"Synced, but joining the folder stopped: "+((js(e)||{}).error||"unknown error")+". Run `"+z+" move "+Ee(g,e)+"` to try again."}:{ok:!0,path:g,merged:!0,with:h.machine}}r("Synced. Setting up Leftoff on this Mac with that folder…");let{runSetup:I}=await Promise.resolve().then(()=>(Fs(),Yl));return await I({flags:{dir:g,...o?{yes:!0}:{}},input:c,out:u,env:e}),{ok:!0,path:g,merged:!1,fresh:!0,with:h.machine}}function Xd(t){if(!t)return"never";let e=Math.max(0,(Date.now()-new Date(t).getTime())/1e3);return e<60?"just now":e<3600?Math.round(e/60)+" min ago":e<86400?Math.round(e/3600)+" h ago":Math.round(e/86400)+" d ago"}function ef(t,e=null){let n=[];if(!t.installed)return n.push(["info","Syncthing: not installed"+(t.brew?" · "+z+" sync install":" · get it from syncthing.net or install Homebrew first")]),n;if(!t.running)return n.push(["warn","Syncthing: installed, not running"+(t.reason?" ("+t.reason+")":"")+" · "+z+" sync start"]),n;if(n.push(["ok","Syncthing: running"+(t.version?" "+t.version:"")]),!t.folder)return n.push(["info","Leftoff folder: not in Syncthing yet · "+z+" sync setup"+(t.nested?" (already inside your Syncthing folder “"+t.nested.label+"”)":"")]),n;let s=t.folder;n.push([s.isCurrent?"ok":"warn","Leftoff folder: "+s.display+(s.isCurrent?"":" (Leftoff uses another folder; "+z+" sync setup)")+" · "+(s.state||"?")+(s.globalFiles!=null?" · "+(s.inSyncFiles!=null?s.inSyncFiles+"/":"")+s.globalFiles+" files":"")+" · last sync "+Xd(s.lastSync)]),t.peers.length||n.push(["info","Peers: none yet · "+z+" sync pair"]);for(let o of t.peers)n.push([o.connected?"ok":"warn","Peer "+o.name+": "+(o.connected?"connected"+(o.completion!=null?" · "+o.completion+"% in sync":""):"not connected · last seen "+Xd(o.lastSeen))]);for(let o of s.errors||[])n.push(["warn","Folder error: "+(o.path?o.path+": ":"")+o.error]);for(let o of t.knocked||[])n.push(["warn","A Mac named “"+o.name+"” asked to connect without the pairing proof ("+o.short+") · not accepted. If that’s your Mac: Accept "+o.name+" on the board (Settings → Sync) or "+z+' sync accept "'+o.name+'"']);return e&&e.armed&&n.push(["info","Pairing code: live until "+new Date(e.expiresAt).toLocaleTimeString()]),n}async function Sy({argv:t=[],api:e=null,env:n=process.env,out:s=process.stdout,input:o=process.stdin}={}){let r=yy(t),i=ee,a=u=>{r.json||s.write(u+`
|
|
293
|
+
`)},l=r.pos[0]||"status";if(r.help||l==="help")return s.write(Zd),0;let c={ok:i.green("✓"),warn:i.yellow("!"),info:i.dim("·")};if(l==="status"){let u=await qt({env:n,root:q()}),d=jn(It(n));if(r.json)return s.write(JSON.stringify({...u,pairing:d})+`
|
|
294
|
+
`),0;for(let[f,p]of ef(u,d))a(c[f]+" "+p);return 0}if(l==="install"){let u=new ct({input:o,output:s,yes:r.yes}),d=await wy({env:n,say:a,onLine:f=>a(i.dim(" "+f)),confirm:async f=>(a(`Leftoff will run:
|
|
295
|
+
`+i.cyan(f)+`
|
|
296
|
+
`+i.dim(" (Syncthing from Homebrew, started as a login service so it runs in the background.)")),u.confirm("Install Syncthing?",!0))});return u.close&&u.close(),d.already?(a(c.ok+" Syncthing is already installed"+i.dim(" · next: "+z+" sync setup")),0):!d.ok&&d.code==="no_brew"?(a(c.warn+" "+d.error+`
|
|
297
|
+
`),d.directions.forEach((f,p)=>a(" "+(p+1)+". "+f)),a(`
|
|
298
|
+
Then run `+i.cyan(z+" sync install")+" again."),1):d.ok?(a(c.ok+" Syncthing is installed and running"+i.dim(" · next: "+z+" sync setup")),0):(a((d.code==="declined"?c.info:i.red("✗"))+" "+d.error),d.code==="declined"?0:1)}if(l==="start"){let u=await Qd({env:n,say:a});return a(u.ok?c.ok+" Syncthing is running":i.red("✗")+" "+u.error+(u.code==="not_installed"?" · "+z+" sync install":"")),u.ok?0:1}if(l==="setup"){let u=r.path||r.pos[1]||null,d=p=>{try{Qs({kind:"setup",...p},n)}catch{}};d({state:"starting",message:"Checking Syncthing…"});let f;try{f=await kv({env:n,target:u,daemonPid:r.daemonPid,say:a,status:d})}catch(p){f={ok:!1,code:p.code||"error",error:p.message}}return f.ok?(d({state:"done",path:f.path,message:"The Leftoff folder is in Syncthing"}),a(c.ok+" The Leftoff folder is in Syncthing at "+Ee(f.path,n)+i.dim(" · next: "+z+" sync pair, to add your other Mac")),r.json&&s.write(JSON.stringify(f)+`
|
|
299
|
+
`),0):(d({state:"failed",error:f.error,code:f.code}),a(i.red("✗")+" "+f.error),r.json&&s.write(JSON.stringify(f)+`
|
|
300
|
+
`),1)}if(l==="pair"){let u=ot({env:n});if(!u.ok)return a(i.red("✗")+" "+u.reason+" · "+z+" sync install"),1;let d;if(e)try{d=await e("POST","/v1/syncthing/pair",{})}catch(w){if(d=null,!/ECONNREFUSED|fetch failed/i.test(w.message))return a(i.red("✗")+" "+w.message),1}let f=!!d;if(!d){let w=await qt({env:n,root:q()});if(!w.running)return a(i.red("✗")+" Syncthing isn’t running · "+z+" sync start"),1;if(!w.folder)return a(i.red("✗")+" The Leftoff folder isn’t in Syncthing yet · "+z+" sync setup"),1;let k=mr({deviceId:w.myID,machine:le(Z()),env:n});d={code:k.code,expiresAt:new Date(k.expiresAt).toISOString()}}if(r.json)return s.write(JSON.stringify({code:d.code,expiresAt:d.expiresAt,command:Gs(d.code),fresh:Gs(d.code,{installed:!1})})+`
|
|
301
|
+
`),0;a(`On your other Mac, run:
|
|
302
|
+
|
|
303
|
+
`+i.cyan(Gs(d.code))+`
|
|
304
|
+
|
|
305
|
+
`+i.dim(" Leftoff not installed there yet: ")+Gs(d.code,{installed:!1})+`
|
|
306
|
+
`+i.dim(" Or on its board: Settings → Sync → Join another Mac, and paste the code.")+`
|
|
307
|
+
|
|
308
|
+
`+i.dim(" Works once, for 15 minutes. Only the Mac that uses this code is accepted."));let p="",h=new Set,m=async()=>{f||await gr({client:u.client,env:n,log:()=>{}}).catch(()=>{});let w=jn(It(n)),k=await qt({env:n,root:q()}),S=w.paired?k.peers.find(A=>A.name===w.paired.name):null,v=w.expired?"The code expired; nothing was accepted. Run "+z+" sync pair for a new one.":w.paired?!S||!S.connected?"Paired with "+w.paired.name+" · connecting…":S.completion===100?"Done: "+w.paired.name+" is in sync.":"Paired with "+w.paired.name+" · syncing ("+(S.completion!=null?S.completion+"%":"…")+")":"Waiting for your other Mac…";v!==p&&(p=v,a(c.info+" "+v));for(let A of k.knocked||[])h.has(A.deviceId)||(h.add(A.deviceId),a(c.warn+" A Mac named “"+A.name+"” asked to connect without the pairing proof, so it wasn’t accepted. If that’s your Mac, run: "+z+' sync accept "'+A.name+'"'));return/^Done|expired/.test(v)},y=Date.now()+(r.timeoutMs||16*6e4);for(;Date.now()<y;){if(await m())return/^Done/.test(p)?0:1;await Vd(Number(n.LEFTOFF_SYNCTHING_POLL_MS)||1500)}return 1}if(l==="accept"){let u=ot({env:n});if(!u.ok)return a(i.red("✗")+" "+u.reason),1;let d=await yr(u.client,{env:n}).catch(()=>[]),f=r.pos.slice(1).join(" "),p=f?d.find(h=>h.name===f||_e(h.deviceId,f)||h.short===f.toUpperCase()):d.length===1?d[0]:null;if(!p){if(!d.length)a(c.info+" No Mac asked to connect without the pairing proof. (Only ones that knocked while a code was live show up here.)");else{a("Which one?");for(let h of d)a(" "+z+' sync accept "'+h.name+'" ('+h.short+", "+Xd(h.at)+")")}return 1}if(!r.yes){let h=new ct({input:o,output:s});if(!await h.confirm("Add “"+p.name+"” ("+p.short+") to Syncthing and share the Leftoff folder with it? Only do this if it’s your Mac.",!1).finally(()=>h.close&&h.close()))return a("Nothing changed."),0}try{await wr(u.client,{deviceId:p.deviceId,env:n})}catch(h){return a(i.red("✗")+" "+h.message),1}return a(c.ok+" Added "+p.name+" and shared the Leftoff folder with it. Its join finishes on its own once the files arrive."),0}if(l==="stop"){let u=ot({env:n});if(!u.ok)return a(i.red("✗")+" "+u.reason),1;let d=await u.client.folder(Ae).catch(()=>null);if(!d)return a(c.info+" The Leftoff folder isn’t in Syncthing; nothing to stop."),0;if(!r.yes){let p=new ct({input:o,output:s});if(!await p.confirm("Stop syncing "+Ee(d.path,n)+"? The files stay; other Macs stop getting updates.",!1).finally(()=>p.close&&p.close()))return a("Nothing changed."),0}let f=await pr(u.client);return a(c.ok+" Stopped syncing. "+Ee(f.path,n)+" and its files are still here; Leftoff keeps using it."),0}return s.write(Zd),1}async function ky({argv:t=[],env:e=process.env,out:n=process.stdout,input:s=process.stdin}={}){let o=yy(t),r=ee,i=f=>{o.json||n.write(f+`
|
|
309
|
+
`)};if(o.help)return n.write(Zd),0;let a=o.pos.join("")||e.LEFTOFF_JOIN_CODE||"";if(!a)return i("usage: "+z+" join <code> (the code from Settings → Sync → Pair another Mac, or `"+z+" sync pair`, on your first Mac)"),1;let l=f=>{try{Qs({kind:"join",...f},e)}catch{}},c=new ct({input:s,output:n,yes:o.yes}),u=async f=>(i(`Syncthing isn’t installed here. Leftoff will run:
|
|
310
|
+
`+r.cyan(f)),c.confirm("Install Syncthing?",!0)),d;try{d=await bv(a,{env:e,target:o.path,daemonPid:o.daemonPid,yes:o.yes,input:s,out:n,say:f=>i(r.dim("· ")+f),status:l,confirmInstall:o.json?null:u,onLine:f=>i(r.dim(" "+f))})}catch(f){d={ok:!1,code:f.code||"error",error:f.message}}return c.close&&c.close(),d.ok?(l({state:"done",path:d.path,with:d.with,message:"Done"}),i(r.green("✓")+" Done. This Mac and "+(d.with||"the other Mac")+" share "+Ee(d.path,e)+" through Syncthing"+(d.merged?"; this Mac’s sessions were merged in.":".")),o.json&&n.write(JSON.stringify(d)+`
|
|
311
|
+
`),0):(l({state:"failed",code:d.code,error:d.error}),i(r.red("✗")+" "+d.error),(d.code==="no_brew"||d.plan&&!d.plan.brew)&&(d.directions||d.plan.directions).forEach((f,p)=>i(" "+(p+1)+". "+f)),o.json&&n.write(JSON.stringify({ok:!1,code:d.code,error:d.error})+`
|
|
312
|
+
`),1)}var Vd,Gs,Zd,tf=V(()=>{ke();Ps();Ps();eo();Cn();wt();Vd=t=>new Promise(e=>setTimeout(e,t)),Gs=(t,{installed:e=!0}={})=>(e?z+" join ":"npx -y github:"+Ze+" join ")+t,Zd=`${z} sync: Syncthing for Leftoff, without opening Syncthing
|
|
313
|
+
|
|
314
|
+
${z} sync state: running, peers, last sync, folder errors
|
|
315
|
+
${z} sync install install Syncthing with Homebrew (shows the command, asks first)
|
|
316
|
+
${z} sync start start Syncthing if it's installed but not running
|
|
317
|
+
${z} sync setup [PATH] put the Leftoff folder in Syncthing (default ~/Sync/Leftoff)
|
|
318
|
+
${z} sync pair show a code for your other Mac (15 minutes, works once)
|
|
319
|
+
${z} sync stop stop syncing: removes the Leftoff folder from Syncthing, keeps the files
|
|
320
|
+
${z} sync accept [NAME] add a Mac that asked to connect during pairing without the proof (asks first)
|
|
321
|
+
${z} join <code> on the other Mac: set up Syncthing, pair, sync, then join the folder
|
|
322
|
+
|
|
323
|
+
The board does the same from Settings → Sync.
|
|
324
|
+
`});import vv from"node:http";import nf from"node:fs";import cs from"node:path";import _y from"node:crypto";import{fileURLToPath as xv}from"node:url";import{spawn as by}from"node:child_process";function Av(t){return nt.state==="running"||(Object.assign(nt,{state:"running",lines:[],error:null,code:null,startedAt:new Date().toISOString(),finishedAt:null,stage:null}),t.log("syncthing: installing with Homebrew ("+cr+"), confirmed on the board"),(async()=>{let e=await dr({onLine:s=>{nt.lines.push(String(s).slice(0,300)),nt.lines.length>200&&nt.lines.shift()},onStage:s=>{nt.stage=s}});if(!e.ok){Object.assign(nt,{state:"failed",error:e.error,code:e.code,finishedAt:new Date().toISOString()}),t.log("syncthing: install failed: "+e.error);return}if(nt.stage="Starting Syncthing…",!(await ws({timeoutMs:Number(process.env.LEFTOFF_SYNCTHING_START_MS)||9e4})).ok){Object.assign(nt,{state:"failed",error:"Syncthing installed, but it didn’t start. Try `brew services restart syncthing` in Terminal.",code:"not_running",finishedAt:new Date().toISOString()});return}Object.assign(nt,{state:"done",finishedAt:new Date().toISOString()}),t.log("syncthing: installed and running")})().catch(e=>Object.assign(nt,{state:"failed",error:e.message,finishedAt:new Date().toISOString()}))),nt}function Tv(t,e){let n=t.headers["x-leftoff-relay"];if(!n)return!1;let s=e.relayHost,o=String(t.socket.remoteAddress||"");if(!s||!(o==="127.0.0.1"||o==="::1"||o==="::ffff:127.0.0.1"))return!1;let r=Buffer.from(String(n)),i=Buffer.from(s.innerSecret);return r.length===i.length&&_y.timingSafeEqual(r,i)}function _v(t){try{return decodeURIComponent(String(t||""))||null}catch{return null}}function xy(t){return t.method==="GET"&&/text\/html/.test(String(t.headers.accept||""))}function Ay(t,e,n,s,o){let r='<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>'+an(Pe)+": "+an(s)+'</title><meta name="color-scheme" content="light dark"></head><body><main><h1>'+an(s)+"</h1>"+o.map(i=>"<p>"+i+"</p>").join("")+"<p><small>"+an(Pe)+" "+an(J)+" · "+an(n)+"</small></p></main></body></html>";t.writeHead(e,{"content-type":"text/html; charset=utf-8","cache-control":"no-store","x-content-type-options":"nosniff","content-security-policy":"default-src 'none'","x-leftoff-error":n}),t.end(r)}function D(t,e,n,s={}){let o=JSON.stringify(n);t.writeHead(e,{"content-type":"application/json; charset=utf-8","cache-control":"no-store","x-content-type-options":"nosniff",...s}),t.end(o)}function on(t,e,n,s){D(t,e,{error:{code:n,message:s}})}function re(t){return new Promise((e,n)=>{let s=0,o=[];t.on("data",r=>{if(s+=r.length,s>Ev){n(M(400,"too_large","request body is over 1 MB")),t.destroy();return}o.push(r)}),t.on("end",()=>{let r=Buffer.concat(o).toString("utf8");if(!r.trim())return e({});try{e(JSON.parse(r))}catch{n(M(400,"bad_json","body is not valid JSON"))}}),t.on("error",n)})}function Iv(t,e){let n=Buffer.from(String(t||"")),s=Buffer.from(String(e||""));return n.length===s.length&&_y.timingSafeEqual(n,s)}function pc(t,e){let n=String(t.headers.cookie||"").match(new RegExp("(?:^|;\\s*)"+e+"=([^;]+)"));return n?decodeURIComponent(n[1]):null}function rn(t){return Nv.some(e=>t.headers[e]!==void 0)}function Mi({burst:t=3,everyMs:e=2e3,now:n=()=>Date.now()}={}){let s=t,o=n();return{take(){let r=n();return s=Math.min(t,s+(r-o)/e),o=r,s<1?!1:(s-=1,!0)}}}function Ey(t,{root:e,port:n,host:s="127.0.0.1",goLimit:o,answerLimit:r}={}){e=e||t.root;let i=Se(e),a=new Set,l=Mi(o||{}),c=Mi({burst:2,everyMs:15e3}),u=Mi({burst:2,everyMs:1e4}),d=Mi({burst:6,everyMs:5e3}),f=new Map,p={take:b=>(f.has(b)||(f.size>500&&f.clear(),f.set(b,Mi({burst:5,everyMs:12e3}))),f.get(b).take())},h=b=>String(b.headers["x-forwarded-for"]||b.socket.remoteAddress||"").split(",")[0].trim(),m=Hp(r||(process.env.CHECKPOINTS_ANSWER_LIMIT?{max:Number(process.env.CHECKPOINTS_ANSWER_LIMIT)}:{})),y=[],w=0,k=new Set,S=(b,g)=>{let _="event: "+b+`
|
|
325
|
+
data: `+JSON.stringify(g)+`
|
|
326
|
+
|
|
327
|
+
`;for(let x of a)x.write(_);y.push({seq:++w,event:b,data:g}),y.length>200&&y.shift();for(let x of k)x()};t.on("session.updated",b=>S("session.updated",b)),t.on("checkpoint.created",b=>S("checkpoint.created",b)),t.on("digest.created",b=>S("digest.created",b)),t.on("spaces.updated",b=>S("spaces.updated",b)),t.on("catchup.created",b=>S("catchup.created",b)),t.on("lists.updated",b=>S("lists.updated",b)),t.on("model.pull",b=>S("model.pull",b)),t.on("needs.updated",b=>S("needs.updated",b)),t.on("restore.updated",b=>S("restore.updated",b)),t.on("peers.updated",b=>S("peers.updated",b)),t.on("agents.updated",b=>S("agents.updated",b)),t.on("relay.updated",b=>S("relay.updated",b));let v=setInterval(()=>{for(let b of a)b.write(`: ping
|
|
328
|
+
|
|
329
|
+
`)},25e3);v.unref();let A=vv.createServer(async(b,g)=>{try{let _=t.cfg&&t.cfg.remoteHosts||[],x=Vr(b.headers.host,n,_),C=Tv(b,t);C&&(x="remote");let E=b.headers["x-forwarded-host"]?String(b.headers["x-forwarded-host"]).split(",")[0].trim():"";if(x==="loopback"&&E&&Vr(E,n,_)==="remote"&&(x="remote"),!x)return xy(b)?Ay(g,403,"bad_host","This address isn’t on "+Pe+"’s list",[an(Pe)+" only answers to its own names, so other sites can’t reach it through your browser. <code>"+an(String(b.headers.host||"(none)"))+"</code> isn’t one of them.","On the machine running "+an(Pe)+", run <code>"+z+" tailscale on</code>: it adds this machine’s tailnet name to the list and prints a QR code that signs your phone in."]):on(g,403,"bad_host","Host must be 127.0.0.1, localhost or leftoff.localhost, port "+n+", or a name in config remoteHosts (leftoff tailscale on)");if(b.headers["tailscale-funnel-request"])return xy(b)?Ay(g,403,"funnel_refused","Not on the public internet",[an(Pe)+" is not served over Tailscale Funnel. Use your tailnet: <code>"+z+" tailscale on</code> (tailscale serve)."]):on(g,403,"funnel_refused","Leftoff is not served over Tailscale Funnel; use tailscale serve (tailnet only)");let L=b.headers.origin;if(!fm(L,n,_))return on(g,403,"bad_origin","cross-origin requests are refused");if(x==="loopback"&&rn(b))return on(g,403,"bad_host","a proxied request must use a name in config remoteHosts, not a loopback Host");let O=x==="remote",H=O&&!C&&t.cfg.remoteRequireToken!==!1,W=new URL(b.url,"http://"+b.headers.host),I=W.pathname;if(b.method==="GET"&&!I.startsWith("/v1/")&&!C){let T=I==="/"||I==="/index.html"?"index.html":I.slice(1),N=cs.join(vy,cs.normalize(T).replace(/^(\.\.[/\\])+/,""));if(!N.startsWith(vy)||!nf.existsSync(N)||nf.statSync(N).isDirectory())return on(g,404,"not_found","no such page");let j={"content-type":Cv[cs.extname(N)]||"application/octet-stream","cache-control":"no-cache","x-content-type-options":"nosniff","x-frame-options":"DENY","content-security-policy":Ov};if(j["referrer-policy"]="no-referrer",T==="index.html")if(!H)j["set-cookie"]="cp_token="+encodeURIComponent(i)+"; HttpOnly; SameSite=Strict; Path=/"+(O?"; Secure":"");else if(W.searchParams.has("t")){let U=b.headers["tailscale-user-login"]||null,Q=mm(e,W.searchParams.get("t"),{userAgent:b.headers["user-agent"],who:U});return Q?(t.log("remote login: new browser session "+Q.id+" (link)"+(U?" ("+U+")":"")+" via "+b.headers.host),g.writeHead(302,{location:"/","cache-control":"no-store","referrer-policy":"no-referrer","set-cookie":al(Q.secret)}),g.end()):(g.writeHead(302,{location:"/?login=expired","cache-control":"no-store","referrer-policy":"no-referrer"}),g.end())}else{let U=pc(b,Xr);U&&il(e,U,{touch:!0})&&(j["set-cookie"]=al(U))}return T==="sw.js"&&(j["service-worker-allowed"]="/"),g.writeHead(200,j),nf.createReadStream(N).pipe(g)}if(b.method==="GET"&&I==="/v1/remote/ping"){let T=!H||O&&!!il(e,pc(b,Xr));return D(g,200,{ok:!0,app:Pe,version:J,via:x,proxied:rn(b),loginRequired:H,signedIn:T})}if(b.method==="POST"&&I==="/v1/remote/signin"){if(!O)throw M(400,"not_remote","sign-in codes are for other devices; this machine needs none");if(!H)return D(g,200,{ok:!0,needed:!1});if(!/application\/json/.test(String(b.headers["content-type"]||"")))throw M(415,"json_only","send JSON");if(!p.take(h(b)))throw M(429,"slow_down","Too many tries. Wait a minute, then try again.");let T=await re(b),N=b.headers["tailscale-user-login"]||null,j=gm(e,T.code,{userAgent:b.headers["user-agent"],who:N});if(!j.ok)throw t.log("remote login: wrong sign-in code via "+b.headers.host+(j.reason==="locked"?" (too many: pending codes voided)":"")),j.reason==="locked"?M(429,"locked","Too many wrong codes, so every pending code was cancelled. Get a new one: "+z+" link, on the machine running "+Pe+"."):M(401,"bad_code","That code isn’t right, or it was already used or has expired. Codes work once, for "+Ls/6e4+" minutes.");return t.log("remote login: new browser session "+j.id+" (code)"+(N?" ("+N+")":"")+" via "+b.headers.host),D(g,200,{ok:!0},{"set-cookie":al(j.secret)})}let P=String(b.headers.authorization||""),Y=P.startsWith("Bearer ")?P.slice(7).trim():pc(b,"cp_token");if(!(C||i&&Iv(Y,i)||O&&!!il(e,pc(b,Xr))))return H?on(g,401,"login_required","this browser isn't signed in: on the machine running Leftoff, run `leftoff link` and scan the QR code, or type its sign-in code here"):on(g,401,"unauthorized","missing or wrong token (see ~/checkpoints/.secret/token)");let $=()=>{if(O||rn(b))throw M(403,"loopback_only","only from the machine running Leftoff")},X=Object.fromEntries(W.searchParams),ne=T=>I.match(T),B,Ct=C?_v(b.headers["x-leftoff-device-name"]):null,$e=()=>({via:O?"remote":"loopback",proxied:rn(b),ip:C?null:b.socket.remoteAddress||null,host:C?"relay":b.headers.host||null,who:C?(Ct||"a paired phone")+" (through the relay)":b.headers["tailscale-user-login"]||null,userAgent:b.headers["user-agent"]||null,relay:C||void 0}),Wt=()=>{if(!m.take(C?"relay:"+b.headers["x-leftoff-device"]:O?"remote:"+(b.headers["x-forwarded-for"]||b.socket.remoteAddress):"local"))throw M(429,"rate_limited","Too many answers in a minute; wait a moment.")},He=()=>{let T=new AbortController;return g.on("close",()=>{g.writableEnded||T.abort()}),T.signal};if(b.method==="GET"&&I==="/v1/needs")return D(g,200,{items:t.needsList(),recent:t.recentAnswered(Math.min(50,Number(X.recent)||20)),answering:t.answeringInfo(O?"remote":"loopback")});if(b.method==="POST"&&I==="/v1/asks")return $(),D(g,200,t.openAsk(await re(b)));if(b.method==="POST"&&I==="/v1/asks/settle"){$();let T=await re(b);if(!T.sessionId||!T.tool)throw M(400,"bad_settle","sessionId and tool are required");let N=t.asks.settleSession(T.sessionId,String(T.reason||"answered_in_terminal"),T.all?{}:{key:Gr(T.tool,T.input)});return D(g,200,{ok:!0,settled:N.length})}if(b.method==="GET"&&(B=ne(/^\/v1\/asks\/([\w-]+)\/wait$/))){$();let T=await t.asks.wait(B[1],Number(X.ms)||2e4,He());return T===null?void 0:D(g,200,T)}if(b.method==="POST"&&(B=ne(/^\/v1\/asks\/([\w-]+)\/cancel$/))){$();let T=t.asks.settle(B[1],String((await re(b)).reason||"cancelled_in_terminal"));return D(g,200,T?{ok:!0,state:T.state}:{ok:!1,state:"gone"})}if(b.method==="GET"&&(B=ne(/^\/v1\/asks\/([\w-]+)$/))){let T=t.asks.get(B[1]);if(!T)throw M(404,"not_found","no such request");return D(g,200,t.asks.publicAsk(T))}if(b.method==="POST"&&(B=ne(/^\/v1\/asks\/([\w-]+)\/answer$/)))return t.assertMayAnswer($e()),Wt(),D(g,200,await t.answerAsk(B[1],await re(b),$e()));if(b.method==="POST"&&(B=ne(/^\/v1\/sessions\/([^/]+)\/reply$/)))return t.assertMayAnswer($e()),Wt(),D(g,200,await t.postReply(decodeURIComponent(B[1]),(await re(b)).text,$e()));if(b.method==="GET"&&(B=ne(/^\/v1\/sessions\/([^/]+)\/replies\/next$/))){$();let T=He(),N=await t.takeReply(decodeURIComponent(B[1]),Number(X.ms)||2e4,T);return T.aborted?void 0:D(g,200,N)}if(b.method==="GET"&&I==="/v1/sync"){let T=Fo({current:e});return D(g,200,{...T,machine:t.machine,backups:Ho(e),lastMove:js(),join:Bo(e,{cli:z}),viewer:{local:!O&&!rn(b)}})}if(b.method==="POST"&&I==="/v1/sync/plan"){$();let T=await re(b);if(!T.to)throw M(400,"bad_request","to: the folder to move to");let N=Uo(e,cs.resolve(Ty(String(T.to))),{machine:t.machine,identity:t.peers?t.peers.id:null});T.force&&(N.collisions=N.collisions.filter(Q=>Q.code!=="machine_name_maybe_taken"),N.ok=!N.errors.length&&!N.collisions.length);let{files:j,...U}=N;return D(g,200,{...U,toDisplay:ge(N.to),backupDisplay:ge(N.backup),sample:j.filter(Q=>Q.action!=="same").slice(0,12),join:Bo(N.to,{cli:z})})}if(b.method==="POST"&&I==="/v1/sync/move"){$();let T=await re(b);if(!T.to)throw M(400,"bad_request","to: the folder to move to");let N=cs.resolve(Ty(String(T.to))),j=Uo(e,N,{machine:t.machine,identity:t.peers?t.peers.id:null}),U=j.collisions.filter(pe=>!(T.force&&pe.code==="machine_name_maybe_taken"));if(j.errors.length||U.length)throw M(409,(j.errors[0]||U[0]).code,(j.errors[0]||U[0]).message);let Q=[Fe,"move",N,"--yes","--json","--daemon-pid",String(process.pid)];T.force&&Q.push("--force");let he=by(process.execPath,Q,{detached:!0,stdio:"ignore",env:{...process.env}});return he.unref(),t.log("sync: moving the folder to "+N+" (leftoff move, pid "+he.pid+")"),D(g,202,{started:!0,to:N,statusFile:hi()})}if(b.method==="POST"&&I==="/v1/sync/backup/delete"){$();let T=await re(b);if(T.confirm!==!0)throw M(400,"confirm","send { path, confirm: true } to delete a backup");let N=Ho(e).find(j=>j.path===T.path);if(!N)throw M(404,"not_found","no such Leftoff backup next to this folder");return D(g,200,pi(N.path))}if(b.method==="GET"&&I==="/v1/syncthing"){let T=!O&&!rn(b),N=await qt({root:e}),j=Wc(),U={...N,myID:void 0,pairing:jn(It()),run:j&&Date.now()-new Date(j.at).getTime()<6*36e5?j:null,viewer:{local:T}};return T?U.install={...ys(),job:{...nt,lines:nt.lines.slice(-60)}}:(U.folder&&(U.folder={...U.folder,path:void 0}),U.knocked=(U.knocked||[]).map(Q=>({...Q,deviceId:void 0}))),D(g,200,U)}if(b.method==="POST"&&I==="/v1/syncthing/install"){if($(),(await re(b)).confirm!==!0)throw M(400,"confirm","send { confirm: true } after showing the command: "+cr);if(hn().installed)return D(g,200,{already:!0});if(!ys().brew)throw M(409,"no_brew","Homebrew isn’t installed on this Mac");return D(g,202,{started:!0,job:Av(t)})}if(b.method==="POST"&&I==="/v1/syncthing/start"){$();let T=fr();if(!T.ok)throw M(409,T.code||"start_failed",T.error);let N=await ws({timeoutMs:2e4});if(!N.ok)throw M(504,"not_running",N.error);return t.log("syncthing: started ("+T.how+")"),D(g,200,{ok:!0})}if(b.method==="POST"&&(I==="/v1/syncthing/setup"||I==="/v1/syncthing/join")){$();let T=await re(b),N=I.endsWith("/join");if(N&&!T.code)throw M(400,"bad_request","code: the pairing code from your other Mac");if(N)try{(await Promise.resolve().then(()=>(eo(),xh))).decodePairCode(String(T.code))}catch(Q){throw M(400,Q.code||"bad_code",Q.message)}let j=[Fe,N?"join":"sync",...N?[]:["setup"],"--yes","--json","--daemon-pid",String(process.pid),...T.path?["--path",String(T.path)]:[]];Qs({kind:N?"join":"setup",state:"starting",message:"Starting…"});let U=by(process.execPath,j,{detached:!0,stdio:"ignore",env:{...process.env,...N?{LEFTOFF_JOIN_CODE:String(T.code)}:{}}});return U.unref(),t.log("syncthing: "+(N?"joining another Mac by code":"putting the folder in Syncthing")+" (pid "+U.pid+")"),D(g,202,{started:!0})}if(b.method==="POST"&&I==="/v1/syncthing/pair"){$();let T=await qt({root:e});if(!T.running)throw M(409,"not_running","Syncthing isn’t running");if(!T.folder)throw M(409,"no_folder","The Leftoff folder isn’t in Syncthing yet");let N=await re(b),j=mr({deviceId:T.myID,machine:t.machine,reuse:N.fresh!==!0});return t.log("syncthing: pairing code made; accepting the matching Mac until "+new Date(j.expiresAt).toISOString()),D(g,200,{code:j.code,command:Gs(j.code),fresh:Gs(j.code,{installed:!1}),expiresAt:new Date(j.expiresAt).toISOString()},{"cache-control":"no-store"})}if(b.method==="POST"&&I==="/v1/syncthing/pair/cancel")return $(),Zc(),t.log("syncthing: pairing code cancelled"),D(g,200,{ok:!0});if(b.method==="POST"&&I==="/v1/syncthing/accept"){$();let T=await re(b);if(T.confirm!==!0||!T.deviceId)throw M(400,"confirm","send { deviceId, confirm: true } after the person clicked Accept");let N=ot();if(!N.ok)throw M(409,N.code||"no_syncthing",N.reason);let j;try{j=await wr(N.client,{deviceId:String(T.deviceId),log:U=>t.log(U)})}catch(U){throw M(U.code==="not_knocked"?404:409,U.code||"accept_failed",U.message)}return D(g,200,j)}if(b.method==="POST"&&I==="/v1/syncthing/stop"){if($(),(await re(b)).confirm!==!0)throw M(400,"confirm","send { confirm: true } to stop syncing");let N=ot();if(!N.ok)throw M(409,N.code||"no_syncthing",N.reason);let j=await pr(N.client);return t.log("syncthing: stopped syncing the Leftoff folder (files kept)"),D(g,200,j)}if(b.method==="GET"&&I==="/v1/agents"){let T=!O&&!rn(b);return D(g,200,{...t.agentsOverview({local:T}),viewer:{local:T}})}if(b.method==="POST"&&I==="/v1/agents")return $(),D(g,200,await t.addAgent(await re(b)));if(b.method==="POST"&&(B=ne(/^\/v1\/agents\/([a-z0-9-]+)\/refresh$/))){if(!d.take())throw M(429,"slow_down","too many refreshes; wait a moment");return D(g,200,await t.pullAgent(B[1],{force:!0}))}if(b.method==="DELETE"&&(B=ne(/^\/v1\/agents\/([a-z0-9-]+)$/)))return $(),D(g,200,t.removeAgent(B[1],{purge:X.purge==="1"}));if(b.method==="GET"&&I==="/v1/peers")return D(g,200,{...t.peersOverview(),viewer:{local:!O&&!rn(b)}});if(b.method==="POST"&&I==="/v1/peers/allow"){if($(),!t.peers)throw M(409,"peers_off","cross-machine actions are off on this machine (LEFTOFF_PEERS=0 or peers.enabled false)");return D(g,200,await t.peers.setAllowIncoming((await re(b)).allow===!0))}if(b.method==="POST"&&(B=ne(/^\/v1\/peers\/([\w.-]+)\/trust$/))){if($(),!t.peers)throw M(409,"peers_off","cross-machine actions are off on this machine");return D(g,200,t.peers.trust(decodeURIComponent(B[1])))}if(b.method==="POST"&&I==="/v1/peers/check")return t.peers&&await t.peers.poll(),D(g,200,t.peersOverview());if(b.method==="GET"&&I==="/v1/health")return D(g,200,{...t.health(),answering:t.answeringInfo(O?"remote":"loopback")});if(b.method==="POST"&&I==="/v1/mod/health")return D(g,200,t.recordModHealth(await re(b)));if(b.method==="GET"&&I==="/v1/changelog")return D(g,200,t.changelog());if(b.method==="POST"&&I==="/v1/changelog/seen")return D(g,200,t.markChangelogSeen((await re(b)).version));if(b.method==="POST"&&I==="/v1/changelog/dismiss")return D(g,200,t.dismissUpgradeNotice());if(b.method==="GET"&&I==="/v1/updates")return D(g,200,t.updateState());if(b.method==="POST"&&I==="/v1/updates/check")return D(g,200,await t.runUpdateCheck({force:!0}));if(b.method==="POST"&&I==="/v1/events"){let T=await re(b);if(!Array.isArray(T.events))throw M(400,"bad_request","body must be { events: Event[] }");return D(g,200,t.ingest(T.events))}if(b.method==="POST"&&(B=ne(/^\/v1\/hooks\/([A-Za-z]+)$/))){let T=await re(b);return D(g,200,t.ingestHook(B[1],T))}if(b.method==="POST"&&(B=ne(/^\/v1\/sessions\/([^/]+)\/checkpoints$/))){let T=decodeURIComponent(B[1]),N=await re(b);if(N.generate){let j=await t.generateCheckpoint(T,N.trigger||"manual",{userNote:N.userNote,length:N.length||void 0});return D(g,201,j)}return D(g,201,t.addCheckpoint(T,N))}if(b.method==="POST"&&I==="/v1/checkpoints/preview"){let T=await re(b);if(!T.sessionId)throw M(400,"bad_request","sessionId is required");return D(g,200,await t.generateCheckpoint(String(T.sessionId),"manual",{length:T.length||void 0,save:!1}))}if(b.method==="GET"&&I==="/v1/sessions")return D(g,200,{sessions:t.listSessions({...X,includeArchived:X.archived==="1"}),viewer:{local:!O,mayAnswer:t.answeringInfo(O?"remote":"loopback").allowedHere}});if(b.method==="GET"&&I==="/v1/restore")return D(g,200,{...t.restoreCandidates({since:X.since,repo:X.repo}),viewer:{local:!O&&!rn(b)}});if(b.method==="POST"&&I==="/v1/restore"){$();let T=await re(b);if(!Array.isArray(T.ids))throw M(400,"bad_request","ids: the session ids to restore");if(T.print!==!0&&!u.take())throw M(429,"slow_down","too many restore requests; wait a moment");return D(g,200,await t.restore(T.ids,{print:T.print===!0}))}if(b.method==="POST"&&I==="/v1/restore/launched"){$();let T=await re(b),N=t.restoreTargets(Array.isArray(T.ids)?T.ids:[]).map(j=>j.id);return t.markRestored(N),D(g,200,{ok:!0,marked:N.length})}if(b.method==="POST"&&(B=ne(/^\/v1\/sessions\/([^/]+)\/go$/))){if($(),!l.take())throw M(429,"slow_down","too many Go requests; wait a moment");return D(g,200,await t.jump(decodeURIComponent(B[1])))}if(b.method==="GET"&&(B=ne(/^\/v1\/sessions\/([^/]+)\/where$/)))return $(),D(g,200,t.where(decodeURIComponent(B[1])));if(b.method==="GET"&&I==="/v1/jump/speed")return $(),D(g,200,await t.jumpSpeed());if(b.method==="GET"&&I==="/v1/jump/check")return $(),D(g,200,await t.jumpCheck(X.kind||"ghostty"));if(b.method==="GET"&&I==="/v1/lists")return D(g,200,{...t.listsOverview({all:X.all==="1",repo:X.repo||null}),mayEdit:t.answeringInfo(O?"remote":"loopback").allowedHere});if(b.method==="GET"&&I==="/v1/lists/resolve")return D(g,200,{list:t.getList(X.ref)});if(b.method==="POST"&&I==="/v1/lists")return D(g,200,t.saveList(await re(b),$e()));if(b.method==="GET"&&(B=ne(/^\/v1\/lists\/(~?[\w.-]+\/[\w.-]+)$/)))return D(g,200,{list:t.getList(B[1]),mayEdit:t.answeringInfo(O?"remote":"loopback").allowedHere});if(b.method==="POST"&&(B=ne(/^\/v1\/lists\/(~?[\w.-]+\/[\w.-]+)\/change$/)))return D(g,200,t.changeList(B[1],await re(b),$e()));if(b.method==="GET"&&(B=ne(/^\/v1\/sessions\/([^/]+)\/lists$/)))return D(g,200,t.sessionLists(decodeURIComponent(B[1])));if(b.method==="GET"&&(B=ne(/^\/v1\/sessions\/([^/]+)\/lists\/find$/)))return D(g,200,t.findListsInSession(decodeURIComponent(B[1])));if(b.method==="POST"&&(B=ne(/^\/v1\/sessions\/([^/]+)\/list-hint\/dismiss$/)))return t.assertMayEditLists($e()),D(g,200,t.dismissListHint(decodeURIComponent(B[1])));if(b.method==="GET"&&(B=ne(/^\/v1\/sessions\/([^/]+)$/)))return D(g,200,t.getSession(decodeURIComponent(B[1])));if(b.method==="PATCH"&&(B=ne(/^\/v1\/sessions\/([^/]+)$/)))return D(g,200,t.patchSession(decodeURIComponent(B[1]),await re(b)));if(b.method==="GET"&&(B=ne(/^\/v1\/sessions\/([^/]+)\/checkpoints$/)))return D(g,200,{checkpoints:t.sessionCheckpoints(decodeURIComponent(B[1]),X)});if(b.method==="GET"&&I==="/v1/checkpoints")return D(g,200,{checkpoints:t.timeline(X)});if(b.method==="GET"&&I==="/v1/search")return D(g,200,t.search(X.q));if(b.method==="GET"&&I==="/v1/catchups")return D(g,200,{catchups:t.folder.listCatchups(),lastAt:(()=>{let T=t.folder.lastCatchupAt();return T==null?null:new Date(T).toISOString()})()});if(b.method==="GET"&&I==="/v1/catchups/options")return D(g,200,t.catchupOptions({since:X.since,to:X.to}));if(b.method==="POST"&&I==="/v1/catchups/prepare"){let T=await re(b),N=await t.makeCatchup({...T,save:!1,useModel:!1}),{catchupLength:j,CATCHUP_SYSTEM:U,summaryPrompt:Q}=await Promise.resolve().then(()=>(va(),ap)),he=j(t.cfg,N.recap.length);return D(g,200,{window:N.recap.window,sessions:N.recap.sessions.length,needsYou:N.recap.needsYou.length,prompt:N.recap.sessions.length?{system:U.replace("{n}",String(he.bullets)),prompt:Q(N.recap,{maxChars:t.cfg.summarizer&&t.cfg.summarizer.maxInputChars||24e3}),maxTokens:he.maxTokens}:null})}if(b.method==="POST"&&I==="/v1/catchups"){let T=await re(b);return D(g,200,await t.makeCatchup(T))}if(b.method==="GET"&&(B=ne(/^\/v1\/catchups\/(\d{4}-\d{2}-\d{2}T\d{4}(?:-\d+)?)$/))){let T=t.readCatchup(B[1]);if(!T)throw M(404,"not_found","no catch-up named "+B[1]);return D(g,200,T)}if(b.method==="GET"&&I==="/v1/digests")return D(g,200,{digests:t.folder.listDigests()});if(b.method==="GET"&&(B=ne(/^\/v1\/digests\/(\d{4}-\d{2}-\d{2})$/))){let T=t.folder.readDigest(B[1],X.machine||null);if(!T)throw M(404,"not_found","no digest for "+B[1]);let N=t.folder.listDigests().filter(j=>j.date===B[1]&&!(j.shared&&T.shared)&&j.machine!==T.machine);return D(g,200,{meta:Be(T.markdown).data,markdown:T.markdown,machine:T.machine,shared:!!T.shared,by:T.by||null,duplicates:T.duplicates||0,others:N})}if(b.method==="GET"&&I==="/v1/spaces")return D(g,200,t.spacesView());if(b.method==="POST"&&I==="/v1/spaces")return D(g,200,t.setSpace(await re(b)));if(b.method==="GET"&&I==="/v1/standup")return D(g,200,t.standup({space:X.space||null,date:X.date||null}));if(b.method==="POST"&&I==="/v1/digests"){let T=await re(b),N=await t.makeDigest({date:T.date});return D(g,201,N)}if(b.method==="GET"&&I==="/v1/models")return D(g,200,await t.modelChoices({provider:X.provider,baseUrl:X.baseUrl}));if(b.method==="POST"&&I==="/v1/models/ollama/start"){if($(),!c.take())throw M(429,"slow_down","Starting Ollama already; wait a moment");return D(g,200,await t.startOllama())}if(b.method==="GET"&&I==="/v1/models/pulls")return D(g,200,{pulls:t.pulls.list()});if(b.method==="POST"&&I==="/v1/models/pull"){$();let T=await re(b);return D(g,202,t.startPull({tag:T.tag,use:!!T.use}))}if(b.method==="DELETE"&&(B=ne(/^\/v1\/models\/pull\/(.+)$/))){$();let T=t.pulls.cancel(decodeURIComponent(B[1]));if(!T)throw M(404,"not_found","no download of that model");return D(g,200,T)}if(b.method==="GET"&&I==="/v1/remote")return D(g,200,t.remoteState(x));if(b.method==="POST"&&I==="/v1/remote/hosts")return $(),D(g,200,t.setRemoteHosts(await re(b)));if(b.method==="POST"&&I==="/v1/remote/reload")return $(),D(g,200,t.reloadRemote());if(b.method==="POST"&&I==="/v1/remote/revoke")return $(),D(g,200,{revoked:ei(e),...t.remoteState(x)});if(b.method==="POST"&&I==="/v1/remote/link"){let T=ol(t.cfg.remoteHosts);if(!T.length)throw M(409,"remote_off","remote access is off: run leftoff tailscale on");if(t.cfg.remoteRequireToken===!1)return D(g,200,{needed:!1,urls:T});if(O&&!p.take("mint:"+h(b)))throw M(429,"slow_down","Too many codes in a row; wait a minute.");let N=Qr(e,{codeOnly:O,note:O?"from a signed-in remote browser":null}),j={needed:!0,base:T[0],code:rl(N.signInCode),expiresAt:N.expiresAt,ttlMin:Ls/6e4};return N.code&&Object.assign(j,{urls:T.map(U=>U+"?t="+N.code),url:T[0]+"?t="+N.code,qrSvg:Pl(T[0]+"?t="+N.code)}),D(g,200,j)}if(I==="/v1/relay"||I.startsWith("/v1/relay/")){let T=t.relayHost;if(!T){if(b.method==="GET"&&I==="/v1/relay")return D(g,200,{enabled:!1,configured:!1,available:!1,user:null,host:null,boardUrl:null,connected:!1,connecting:!1,latencyMs:null,devices:0,list:[]});if(b.method==="GET"&&I==="/v1/relay/devices")return D(g,200,{devices:[]});throw M(409,"relay_unavailable","the relay isn’t available in this process")}if(b.method==="GET"&&I==="/v1/relay")return await T.refreshRelayDevices().catch(()=>{}),D(g,200,{...T.status(),list:T.devices(),viewer:{relay:C,device:C?b.headers["x-leftoff-device"]:null,local:!O&&!rn(b)}});if(b.method==="POST"&&I==="/v1/relay/enable")return $(),D(g,200,await T.enable(await re(b)));if(b.method==="POST"&&I==="/v1/relay/signup")return $(),D(g,200,await T.signupStart(await re(b)));if(b.method==="GET"&&I==="/v1/relay/signup")return $(),D(g,200,await T.signupCheck());if(b.method==="POST"&&I==="/v1/relay/signup/cancel")return $(),D(g,200,T.signupCancel());if(b.method==="POST"&&I==="/v1/relay/disable")return $(),D(g,200,await T.disable());if(b.method==="POST"&&I==="/v1/relay/pair")return $(),D(g,200,await T.createPair());if(b.method==="POST"&&I==="/v1/relay/pair/cancel")return $(),await T.cancelPairs(),D(g,200,{ok:!0});if(b.method==="GET"&&I==="/v1/relay/devices")return await T.refreshRelayDevices().catch(()=>{}),D(g,200,{devices:T.devices()});if(b.method==="POST"&&(B=ne(/^\/v1\/relay\/devices\/([\w-]+)\/revoke$/)))return $(),D(g,200,await T.revoke(B[1]));throw M(404,"not_found","no route for "+b.method+" "+I)}if(b.method==="GET"&&I==="/v1/events"){let T=Number(X.after),N=()=>y.filter(U=>U.seq>T);if(!Number.isFinite(T)||T<0||T>w)return D(g,200,{seq:w,events:[],reset:Number.isFinite(T)&&T>w});let j=N();if(!j.length){let U=He();if(await new Promise(Q=>{let he=setTimeout(pe,Math.min(25e3,Number(X.ms)||2e4));function pe(){clearTimeout(he),k.delete(pe),U.removeEventListener("abort",pe),Q()}k.add(pe),U.addEventListener("abort",pe)}),U.aborted)return;j=N()}return t.boardOpened&&j.length,D(g,200,{seq:w,events:j})}if(b.method==="GET"&&I==="/v1/config")return D(g,200,t.publicConfig());if(b.method==="PATCH"&&I==="/v1/config")return D(g,200,t.updateConfig(await re(b),{via:O?"remote":"loopback"}));if(b.method==="PUT"&&(B=ne(/^\/v1\/config\/keys\/([a-z-]+)$/)))return D(g,200,t.saveKey(B[1],(await re(b)).key));if(b.method==="GET"&&I==="/v1/stream"){g.writeHead(200,{"content-type":"text/event-stream","cache-control":"no-store",connection:"keep-alive"}),g.write(`retry: 3000
|
|
330
|
+
|
|
331
|
+
`),a.add(g),t.boardOpened&&t.boardOpened(),b.on("close",()=>{a.delete(g),t.boardClosed&&t.boardClosed()});return}return on(g,404,"not_found","no route for "+b.method+" "+I)}catch(_){return _.status?on(g,_.status,_.code||"error",_.message):(t.log("500: "+(_.stack||_.message)),on(g,500,"internal",_.message))}});return A.on("close",()=>{clearInterval(v);for(let b of a)b.end()}),{server:A,listen:()=>new Promise((b,g)=>{A.once("error",g),A.listen(n,s,()=>b(A))}),close:()=>new Promise(b=>{for(let g of a)g.end();A.close(()=>b()),A.closeAllConnections&&A.closeAllConnections()})}}var nt,vy,Ev,Cv,Ov,an,Ty,Nv,Nd=V(()=>{ke();Yr();wd();ve();cl();fi();ke();wt();Ps();Zn();eo();tf();nt={state:"idle",lines:[],error:null,startedAt:null,finishedAt:null,stage:null};vy=cs.join(cs.dirname(xv(import.meta.url)),"..","ui"),Ev=1024*1024,Cv={".html":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".svg":"image/svg+xml",".png":"image/png",".ico":"image/x-icon",".webmanifest":"application/manifest+json; charset=utf-8",".json":"application/json; charset=utf-8"},Ov="default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' data:; connect-src 'self'; manifest-src 'self'; worker-src 'self'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'";an=t=>String(t).replace(/[&<>"']/g,e=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[e]);Ty=t=>t==="~"?process.env.HOME||"":t.startsWith("~/")?cs.join(process.env.HOME||"",t.slice(2)):t;Nv=["x-forwarded-for","x-forwarded-host","x-forwarded-proto","forwarded","x-real-ip","tailscale-user-login","tailscale-user-name","tailscale-headers-info"]});for(let[t,e]of Object.entries(process.env))t.startsWith("LEFTOFF_")&&t.length>8&&e!==void 0&&(process.env["CHECKPOINTS_"+t.slice(8)]=e);ke();wd();Nd();import $t from"node:fs";import vf from"node:os";import fs from"node:path";import{fileURLToPath as y1}from"node:url";ke();gn();ve();Yr();import{execFileSync as Mv}from"node:child_process";function Lv({start:t=process.ppid,ps:e=Dv,maxDepth:n=6,now:s=Date.now()}={}){let o=t;for(let r=0;r<n&&o>1;r++){let i=e(o);if(!i)return null;if(/claude/i.test(i.comm))return Hf(o+" "+i.tty+" "+i.etime,s);o=i.ppid}return null}function Dv(t){try{let n=Mv("ps",["-o","ppid=,tty=,etime=,comm=","-p",String(t)],{encoding:"utf8",env:{...process.env,LC_ALL:"C"},timeout:1e3}).trim().match(/^(\d+)\s+(\S+)\s+(\S+)\s+(.+)$/);return n?{ppid:Number(n[1]),tty:n[2],etime:n[3],comm:n[4]}:null}catch{return null}}function Rv(t=process.env){let e={};for(let n of Uf)t[n]&&(e[n]=t[n]);return e}async function Cy(t=2e3){return process.stdin.isTTY?"":new Promise(e=>{let n="",s=setTimeout(()=>e(n),t);process.stdin.setEncoding("utf8"),process.stdin.on("data",o=>n+=o),process.stdin.on("end",()=>{clearTimeout(s),e(n)}),process.stdin.on("error",()=>{clearTimeout(s),e(n)})})}async function Oy(t,{stdinText:e,timeoutMs:n=800}={}){if(process.env.CHECKPOINTS_INTERNAL)return{skipped:"internal"};let s=q(),o=e??await Cy(),r;try{r=JSON.parse(o||"{}")}catch{return{skipped:"bad_json"}}let i=t||r.hook_event_name;if(!i||!r.session_id)return{skipped:"no_event"};if(i==="SessionStart"&&!r._checkpoints){let u=null;try{u=Lv()}catch{}r._checkpoints={proc:u,termEnv:Rv()}}let a=Z(s),l=Se(s),c=new Date().toISOString();try{let u=await fetch("http://127.0.0.1:"+a.port+"/v1/hooks/"+encodeURIComponent(i),{method:"POST",headers:{"content-type":"application/json",authorization:"Bearer "+(l||"")},body:JSON.stringify(r),signal:AbortSignal.timeout(n)});if(u.ok)return{posted:!0};if(u.status===400||u.status===404)return{rejected:u.status};throw new Error("status "+u.status)}catch{try{return Rh(s,le(a),r.session_id,{hook:i,payload:r,at:c}),{spooled:!0}}catch(u){return{lost:u.message}}}}async function Iy({stdinText:t,out:e=process.stdout,maxMs:n=9.5*60*1e3,fetchImpl:s=fetch,onSignal:o=r=>{process.once("SIGTERM",r),process.once("SIGINT",r),process.once("SIGHUP",r)}}={}){if(process.env.CHECKPOINTS_INTERNAL)return{skipped:"internal"};let r=q(),i=t??await Cy(),a;try{a=JSON.parse(i||"{}")}catch{return{skipped:"bad_json"}}if(!a.session_id||!a.tool_name)return{skipped:"no_event"};let l=Z(r);if(l.answers&&l.answers.enabled===!1)return{skipped:"off"};let c=Se(r),u="http://127.0.0.1:"+l.port,d={"content-type":"application/json",authorization:"Bearer "+(c||"")},f=async(w,k,S,v)=>{let A=await s(u+k,{method:w,headers:d,body:S===void 0?void 0:JSON.stringify(S),signal:AbortSignal.timeout(v)});if(!A.ok)throw new Error("status "+A.status);return A.json()},p;try{p=await f("POST","/v1/asks",{sessionId:a.session_id,tool:a.tool_name,input:a.tool_input,suggestions:a.permission_suggestions,capture:"hooks",cwd:a.cwd,permissionMode:a.permission_mode},1500)}catch{return{skipped:"daemon_down"}}if(!p||!p.id||p.state!=="waiting")return{skipped:p&&p.reason?p.reason:"not_waiting"};let h=!1;o(async()=>{h=!0;try{await f("POST","/v1/asks/"+p.id+"/cancel",{reason:"cancelled_in_terminal"},800)}catch{}process.exit(0)});let m=Date.now(),y=0;for(;!h&&Date.now()-m<n;){let w;try{w=await f("GET","/v1/asks/"+p.id+"/wait?ms=20000",void 0,25e3),y=0}catch{if(++y>=3)return{gaveUp:"daemon_unreachable"};await new Promise(k=>setTimeout(k,1e3));continue}if(w.state==="answered"&&w.decision)return e.write(JSON.stringify(Up(w.decision))+`
|
|
332
|
+
`),{answered:w.decision.behavior};if(w.state!=="waiting")return{settled:w.state,reason:w.reason}}return{gaveUp:"timeout"}}Qa();gn();xs();Zn();$l();wt();Fs();Cn();ke();go();xs();Fs();async function Ny({sub:t,args:e=[],flags:n={},out:s=process.stdout,input:o=process.stdin,env:r=process.env,fetchImpl:i=(...a)=>fetch(...a)}={}){let a=os(ss(s)),l=q(),c=Z(l),u=c.summarizer||{},d=(n["ollama-url"]||u.provider==="ollama"&&u.baseUrl||at).replace(/\/$/,""),f=(m="")=>s.write(m+`
|
|
333
|
+
`),p=async m=>{Te(l);let y=ac(un(l)||{},{summarizer:m});lc(l,y),f(a.green("✔")+" Summarizer is now "+a.bold(m.provider+(m.model?" / "+m.model:"")));let w=await Yo(c.port,Se(l),i);if(w.up&&w.auth)try{await rc(c.port,Se(l),{summarizer:y.summarizer},i),f(a.green("✔")+" Running daemon switched over")}catch(k){f(a.yellow("!")+" Daemon didn't take it ("+k.message+"); restart it")}},h=async()=>{let m=String(n["cloud-url"]||u.provider==="ollama-cloud"&&u.baseUrl||r.CHECKPOINTS_CLOUD_URL||_s).replace(/\/$/,""),y=await Dr({baseUrl:m,fetchImpl:i}),w=yn("ollama-cloud",{apiKeyEnv:u.provider==="ollama-cloud"?u.apiKeyEnv:null,apiKeyFile:u.provider==="ollama-cloud"?u.apiKeyFile:null,root:l},r);if(f(a.bold("Ollama cloud")+a.dim(" · "+m+" · "+(w?"API key found":"no key: set OLLAMA_API_KEY (ollama.com/settings/keys)"))),!y.ok){f(a.dim(" couldn't fetch the model list ("+y.error+")"));return}let k=u.provider==="ollama-cloud"&&u.model||null;for(let S of Ca(y.models,{current:k})){let v=S.current?a.cyan("●"):S.available===!1?a.dim("×"):a.dim("○");f(" "+v+" "+S.tag.padEnd(22)+a.dim(S.note||"")+(S.recommended?a.yellow(" ★"):"")+(S.current?a.cyan(" (in use)"):"")+(S.available===!1?a.dim(" (not offered today)"):""))}f(a.dim(" `leftoff models use <tag> --cloud` switches · nothing to download"))};if(t==="cloud")return await h(),0;if(!t||t==="list"||t==="ls"){f(a.bold("Summarizer: ")+(u.provider||"auto")+(u.model?" / "+u.model:"")+(u.baseUrl?a.dim(" @ "+u.baseUrl):""));let m=await Ut({baseUrl:d,fetchImpl:i,env:r});if(f(),m.running){f(a.bold("Ollama")+a.dim(" · "+m.baseUrl+(m.version?" · v"+m.version:"")+" · sizes from ollama.com/library, "+fo));let w=u.provider==="ollama"?u.model:null;for(let k of ho(m.models,{current:w})){let S=k.current?a.cyan("●"):k.installed?a.green("✔"):a.dim("○"),v=(k.recommended?a.yellow(" ★"):"")+(k.current?a.cyan(" (in use)"):""),A=k.curated?Oa(k,Lt)+" · "+k.note:k.sizeBytes?Lt(k.sizeBytes):"";f(" "+S+" "+k.tag.padEnd(16)+a.dim(A)+v)}f(a.dim(" ✔ pulled ○ not pulled · `leftoff models use <tag>` pulls and switches"))}else f(a.bold("Ollama")+": "+(m.binary?"installed, not running (`ollama serve`)":"not installed · https://ollama.com/download"));let y=await Sn({baseUrl:n["lmstudio-url"]||u.provider==="openai-compatible"&&u.baseUrl||Qt,fetchImpl:i});if(f(),y.running){f(a.bold("LM Studio / OpenAI-compatible")+a.dim(" · "+y.baseUrl));for(let w of y.models)f(" "+(u.provider==="openai-compatible"&&u.model===w?a.cyan("●"):a.green("✔"))+" "+w);y.models.length||f(a.dim(" no model loaded"))}else f(a.bold("LM Studio")+": not running at "+y.baseUrl);return n.local||(f(),await h()),0}if(t==="pull"||t==="use"){let m=e[0];if(!m)throw new Error("usage: leftoff models "+t+" <tag>");if(t==="pull"&&(n.cloud||n.provider==="ollama-cloud"))throw new Error("cloud models run on ollama.com; nothing to pull. Use: leftoff models use "+m+" --cloud");if(t==="use"&&(n.cloud||n.provider==="ollama-cloud")){let k=String(n["base-url"]||_s).replace(/\/$/,""),S=await Dr({baseUrl:k,fetchImpl:i});S.ok&&!S.models.some(g=>Ge(g,m))&&f(a.yellow("!")+" "+m+" isn't in ollama.com's cloud list right now ("+S.models.slice(0,5).join(", ")+", …); using it anyway");let v=u.provider==="ollama-cloud"?u:{},A={provider:"ollama-cloud",model:m,baseUrl:k===_s?null:k,apiKeyEnv:v.apiKeyEnv||(n["api-key-env"]?String(n["api-key-env"]):null),...v.apiKeyFile?{apiKeyFile:v.apiKeyFile}:{}},b=yn("ollama-cloud",{apiKeyEnv:A.apiKeyEnv,apiKeyFile:A.apiKeyFile,root:l},r);if(b||f(a.yellow("!")+" No key found: export "+(A.apiKeyEnv||"OLLAMA_API_KEY")+" (from ollama.com/settings/keys) or run `leftoff setup` to save one"),await p(A),!n["no-test"]&&b){let g=ut("Writing a sample checkpoint with "+m+" on ollama.com…",{out:s,colors:a}),_=await Es(A,{fetchImpl:i,root:l});return _.ok?g.succeed("It works."):g.fail("The test didn't work."),Ks(s,a,_,m+" (cloud)"),_.ok?0:1}return 0}if(t==="use"&&(n.lmstudio||n.provider==="openai-compatible")){let k=(n["base-url"]||Qt).replace(/\/$/,""),S=await Sn({baseUrl:k,fetchImpl:i});if(!S.running)throw new Error("nothing answers at "+k+"/models");if(!S.models.includes(m))throw new Error(m+" isn't loaded there (loaded: "+(S.models.join(", ")||"none")+")");let v={provider:"openai-compatible",model:m,baseUrl:k===Qt?null:k};return await p(v),n["no-test"]||Ks(s,a,await Es(v,{fetchImpl:i}),m),0}let y=await Ut({baseUrl:d,fetchImpl:i,env:r});if(!y.running)throw new Error("Ollama isn't running at "+d+" (start it with `ollama serve`)");if(y.models.some(k=>Ge(k.name,m)))f(a.green("✔")+" "+m+" is already pulled");else{let k=Hl("pull "+m,{out:s,colors:a}),S=Date.now();try{await mo({tag:m,baseUrl:y.baseUrl,fetchImpl:i,onProgress:v=>k.update(v.fraction,v.total?Lt(v.completed)+" / "+Lt(v.total):v.status)})}catch(v){return k.fail(v.message),1}k.done("Pulled "+m+a.dim(" in "+gi(Date.now()-S)))}if(t==="use"){let k={provider:"ollama",model:m,baseUrl:y.baseUrl===at?null:y.baseUrl};if(await p(k),!n["no-test"]){let S=ut("Writing a sample checkpoint with "+m+"…",{out:s,colors:a}),v=await Es(k,{fetchImpl:i});return v.ok?S.succeed("It works."):S.fail("The test didn't work."),Ks(s,a,v,m),v.ok?0:1}}return 0}if(t==="test"){let m=ut("Writing a sample checkpoint with "+(u.provider||"auto")+(u.model?" / "+u.model:"")+"…",{out:s,colors:a}),y=await Es(u,{fetchImpl:i,root:l});return y.ok?m.succeed("It works."):m.fail("The test didn't work."),Ks(s,a,y,u.model||u.provider),y.ok?0:1}if(t==="pick"){let m=new ct({input:o,output:s,yes:!!n.yes,colors:a}),w={p:m,u:{c:a,line:f,ok:k=>f(a.green("✔")+" "+k),warn:k=>f(a.yellow("!")+" "+k),fail:k=>f(a.red("✖")+" "+k),info:k=>f(a.dim("·")+" "+k),note:k=>f(" "+a.dim(k)),cmd:k=>f(" "+a.cyan("$ "+k))},out:s,flags:n,env:r,fetchImpl:i,yes:!!n.yes,currentModel:u.model};try{let k=await uc(w);return k?(n["no-test"]||await Wo(w,k,k.model),await p(k),0):(f("Nothing changed."),1)}catch(k){if(k.cancelled)return f(a.red("✖")+" Cancelled; nothing changed."),130;throw k}finally{m.close()}}throw new Error("unknown: leftoff models "+t+" (list, cloud, pull <tag>, use <tag> [--cloud|--lmstudio], test, pick)")}Id();ke();Kr();La();$l();Zn();Cn();Zn();wt();import ft from"node:fs";import Et from"node:path";import{spawn as Fv,spawnSync as gc}from"node:child_process";import sf from"node:fs";import My from"node:os";import Li from"node:path";var of=["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"],jv=t=>String(t).replace(/\x1b\[[0-9;]*[A-Za-z]/g,"");function Pv(t=process.env){return Li.join(t.XDG_CONFIG_HOME||Li.join(t.HOME||My.homedir(),".config"),"checkpoints","logs")}function Ly(t=process.env,e=new Date){let n=e.getFullYear()+"-"+String(e.getMonth()+1).padStart(2,"0")+"-"+String(e.getDate()).padStart(2,"0");return Li.join(Pv(t),"upgrade-"+n+".log")}var rf=(t,e=process.env)=>{let n=e.HOME||My.homedir();return n&&t.startsWith(n+Li.sep)?"~"+t.slice(n.length):t};function Dy({out:t=process.stdout,quiet:e=!0,c:n,logFile:s,tty:o=!!t.isTTY,env:r=process.env}){try{sf.mkdirSync(Li.dirname(s),{recursive:!0})}catch{}let i=(p="")=>{try{sf.appendFileSync(s,jv(p)+`
|
|
334
|
+
`)}catch{}},a=p=>t.write(p),l=null,c="",u=0,d=()=>{l&&(clearInterval(l),l=null,a("\r\x1B[2K"))};return{quiet:e,logFile:s,log:i,say:(p="")=>{i(p),e||a(p+`
|
|
335
|
+
`)},print:(p="")=>{d(),i(p),a(p+`
|
|
336
|
+
`)},start(p){c=p,i("== "+p),!(!e||!o)&&(d(),u=0,a(n.cyan(of[0])+" "+p),l=setInterval(()=>{u=(u+1)%of.length,a("\r\x1B[2K"+n.cyan(of[u])+" "+c)},80),l.unref&&l.unref())},done(p){d(),i("✓ "+p),e&&a(n.green("✓")+" "+p+`
|
|
337
|
+
`)},warn(p){d(),i("! "+p),a(n.yellow("!")+" "+p+`
|
|
338
|
+
`)},fail(p,{lines:h=30}={}){if(d(),i("✗ "+p),a(n.red("✗")+" "+p+`
|
|
339
|
+
`),e){let m=[];try{m=sf.readFileSync(s,"utf8").trimEnd().split(`
|
|
340
|
+
`).slice(-h)}catch{}m.length&&a(n.dim(m.map(y=>" │ "+y).join(`
|
|
341
|
+
`))+`
|
|
342
|
+
`)}a(n.dim("Full log: "+rf(s,r))+`
|
|
343
|
+
`)},stop:d}}qn();var mc=t=>new Promise(e=>setTimeout(e,t)),us=t=>t?String(t).slice(0,7):"unknown",Uv=[{to:"0.3.0",note:'adds "updates": { "check": true }',run:t=>t.updates?t:{...t,updates:{check:!0,source:null}}}];function Hv(t,e,n=J){let s=t&&typeof t=="object"?JSON.parse(JSON.stringify(t)):{},o=[];for(let r of Uv)if((!e||Ye(r.to,e)>0)&&Ye(r.to,n)<=0){let i=r.run(s);JSON.stringify(i)!==JSON.stringify(s)&&o.push(r),s=i}return{cfg:s,applied:o}}function Bv(t){let e=Ur.map(s=>Et.join(t,s)).filter(s=>ft.existsSync(Et.join(s,"package.json")));if(!e.length)return Et.join(t,"checkpoints");let n=s=>{try{return JSON.parse(ft.readFileSync(Et.join(s,"package.json"),"utf8")).version||"0.0.0"}catch{return"0.0.0"}};return e.sort((s,o)=>Ye(n(o),n(s)))[0]}function jy(){return process.platform==="win32"?"npm.cmd":"npm"}function Ry(t=process.env,e="npm"){let n=e==="pnpm"?process.platform==="win32"?"pnpm.cmd":"pnpm":jy(),s=gc(n,["root","-g"],{encoding:"utf8",env:t});return s.status===0?s.stdout.trim():null}async function Kv(t){try{return Number(ft.readFileSync(Et.join(t,".daemon.pid"),"utf8"))||null}catch{return null}}async function af(t,e){try{let n=await fetch("http://127.0.0.1:"+t+"/v1/health",{headers:{authorization:"Bearer "+(e||"")},signal:AbortSignal.timeout(1500)});return n.ok?{up:!0,...await n.json()}:{up:n.status===401}}catch{return{up:!1}}}async function Gv({root:t,bin:e,env:n=process.env,say:s=()=>{},dryRun:o=!1}){let r=Z(t),i=Se(t),a=xt(),l=!!(o||n.CHECKPOINTS_SERVICE_DRY_RUN),c=Nl({root:t,bin:e,dryRun:l});if(c&&s("Login service renamed: stopped "+c.from+", wrote "+c.to+(l?" (dry run: nothing stopped)":"")),ft.existsSync(a)){let d=Tn({root:fd(ft.readFileSync(a,"utf8"))||t,bin:e});if(ft.readFileSync(a,"utf8")!==d&&(ft.writeFileSync(a,d),s("Login service now points at "+e)),l)return s("Would restart the login service: "+Rs().map(f=>f.join(" ")).join(" && ")),{how:"service",dryRun:!0,moved:c};for(let f of Rs()){let p=gc(f[0],f.slice(1),{encoding:"utf8"});if(p.status!==0&&f[1]!=="unload")return{how:"service",ok:!1,error:(p.stderr||"").trim().split(`
|
|
344
|
+
`)[0]||"exit "+p.status}}}else{let d=await af(r.port,i),f=await Kv(t),p=m=>{try{return process.kill(m,0),!0}catch{return!1}};if(d.up&&f&&p(f)){try{process.kill(f,"SIGTERM")}catch{}for(let m=0;m<50&&p(f);m++)await mc(200);if(p(f)){try{process.kill(f,"SIGKILL")}catch{}await mc(200)}}else if(d.up)return{how:"unknown",ok:!1,error:"a daemon is running that setup didn't start (stop it and run `leftoff serve`)"};for(let m=0;m<25&&(await af(r.port,i)).up;m++)await mc(200);let{startDaemonDetached:h}=await Promise.resolve().then(()=>(Fs(),Yl));h({root:t,bin:e,env:n})}let u={up:!1};for(let d=0;d<60&&(u=await af(r.port,i),!(u.up&&(!u.version||u.version===J)));d++)await mc(250);return{how:ft.existsSync(a)?"service":"detached",ok:u.up&&(!u.version||u.version===J),version:u.version,moved:c}}async function Py({flags:t={},out:e=process.stdout}={}){let n=os(ss(e)),s=yo();return t.since&&(s=wo(s,String(t.since).replace(/^v/,""),null)),t.latest&&(s=s.slice(0,1)),e.write(n.bold(Pe+" v"+J)+n.dim(" · what changed")+`
|
|
345
|
+
|
|
346
|
+
`+Ma(s,{colors:n})),0}function lf(t,e,{env:n,log:s,timeoutMs:o=3e5}){return new Promise(r=>{s("$ "+t+" "+e.join(" "));let i="",a="",l=Fv(t,e,{env:n,stdio:["ignore","pipe","pipe"]}),c=setTimeout(()=>l.kill("SIGKILL"),o);l.stdout.on("data",u=>{a+=u,s(String(u).trimEnd())}),l.stderr.on("data",u=>{i+=u,s(String(u).trimEnd())}),l.on("error",u=>{clearTimeout(c),r({status:-1,stderr:u.message,stdout:a})}),l.on("close",u=>{clearTimeout(c),r({status:u,stderr:i,stdout:a})})})}async function Fy({flags:t={},out:e=process.stdout,env:n=process.env}={}){let s=os(ss(e)),o=!t.verbose&&!t.check&&!(t.finish&&!t.quiet),r=Dy({out:e,quiet:o,c:s,logFile:t.log?String(t.log):Ly(n),tty:t.finish?n.LEFTOFF_PROGRESS_TTY==="1":!!e.isTTY&&!n.CI,env:n}),i=r.say,a=A=>i(s.green("✔")+" "+A),l=A=>{o?r.warn($v(A)):i(s.yellow("!")+" "+A)},c=A=>o?r.fail(A):i(s.red("✖")+" "+A);if(t.finish||r.log(`
|
|
347
|
+
# leftoff upgrade, `+new Date().toISOString()+(o?"":" (--verbose)")),t.finish)return zv({flags:t,out:e,env:n,c:s,say:i,ok:a,warn:l,fail:c,P:r});let u=await Hr({env:n,globalRoot:n.CHECKPOINTS_NPM_GLOBAL_ROOT||void 0}),d=ko(t.source||n.CHECKPOINTS_UPDATE_SOURCE||(Z().updates||{}).source||u.source||Fr);u.kind==="npm"&&(d="npm:"+Xe),i(s.bold(Pe+" v"+u.version)+s.dim((u.kind==="npm"?" · installed from npm with "+u.pm+" at ":" ("+us(u.sha)+") · "+{clone:"a git clone at ",global:"installed globally at ",npx:"an npx copy at ",local:"installed at "}[u.kind])+u.repoDir)),r.start("Checking for updates…");let f=await Br({info:u,source:d,env:n});if(f.error&&u.kind==="npm")return c("Couldn't reach the npm registry ("+Ra(n)+"): "+f.error),1;if(f.error)return i(s.dim(" (git uses your own credentials; for a private repo, check `git ls-remote "+d+'` works, or set "updates": { "source": "git@github.com:'+Ze+'.git" } in config.json)')),c("Couldn't reach "+d+": "+f.error),1;if(f.source&&f.source!==d&&(i(s.dim("· reached GitHub over SSH ("+f.source+")")),d=f.source),!(f.available||!u.sha&&f.remote.version&&Ye(f.remote.version,u.version)>0)&&!t.force)return o?r.done("Up to date: "+Pe+" "+u.version):a("Up to date"+s.dim(" (remote "+us(f.remote.sha)+(f.remote.version?", v"+f.remote.version:"")+")")),0;let h=f.remote.version||us(f.remote.sha);if(r.done("Found "+h+" (you have "+u.version+")"),i(s.bold(s.cyan("Update available: "))+(f.remote.version?"v"+u.version+" → v"+f.remote.version:"")+(u.kind==="npm"?"":s.dim(" "+us(u.sha)+" → "+us(f.remote.sha)))),f.incoming.length?i(`
|
|
348
|
+
`+Ma(f.incoming,{colors:s})):f.fetchError&&i(s.dim(" (couldn't read the new changelog: "+f.fetchError+")")),t.check)return i(s.dim("Run `leftoff upgrade` to install it.")),0;r.start("Downloading "+h+"…");let m=u.repoDir;if(u.kind==="npm"){let A=Nu(u.pm);i(s.dim("$ "+A.join(" ")));let b=await lf(A[0],A.slice(1),{env:n,log:r.log});if(b.status!==0)return o||i((b.stderr||"").trim()),c(A.slice(0,3).join(" ")+" failed: "+((b.stderr||"").trim().split(`
|
|
349
|
+
`).pop()||"exit "+b.status)),1;let g=n.CHECKPOINTS_NPM_GLOBAL_ROOT||Ry(n,u.pm);m=g&&ft.existsSync(Et.join(g,Xe,"package.json"))?Et.join(g,Xe):u.repoDir,a("Installed "+Xe+"@"+(f.remote.version||"latest")+s.dim(" → "+m))}else if(u.kind==="clone"){u.source&&Is(u.source)!==u.source&&(await St(["remote","set-url","origin",Is(u.source)],{cwd:u.repoDir,env:n})).code===0&&i(s.dim("· the repo is now "+Ze+"; pointed origin at "+Is(u.source))),i(s.dim("$ git -C "+u.repoDir+" pull --ff-only"));let A=await St(["pull","--ff-only","-q"],{cwd:u.repoDir,env:n,timeoutMs:12e4});if(A.code!==0)return i(A.stderr.trim()),i(s.dim(" Local changes? Commit or stash them and run `leftoff upgrade` again.")),c("git pull failed: "+(A.stderr.trim().split(`
|
|
350
|
+
`).pop()||"exit "+A.code)),1;a("Pulled "+us(f.remote.sha))}else{let A=d.match(/^(?:https:\/\/github\.com\/|git@github\.com:)([\w.-]+\/[\w.-]+?)(?:\.git)?$/),b=A?"github:"+A[1]:d;i(s.dim("$ npm install -g "+b));let g=await lf(jy(),["install","-g",b,"--no-fund","--no-audit","--loglevel=error"],{env:n,log:r.log});if(g.status!==0)return o||i((g.stderr||"").trim()),c("npm install -g failed: "+((g.stderr||"").trim().split(`
|
|
351
|
+
`).pop()||"exit "+g.status)),1;let _=n.CHECKPOINTS_NPM_GLOBAL_ROOT||Ry(n);m=_?Bv(_):u.repoDir,a("Installed "+b+s.dim(" → "+m));try{ft.existsSync(Et.join(m,".install.json"))||Iu({sha:f.remote.sha,source:d,version:f.remote.version||void 0},m)}catch{}}r.done("Downloaded "+h),r.start("Updating the Claude Code plugin…");let y=null,w=Kl(n);if(u.kind==="npm")try{Ed({from:m,env:n})}catch(A){l("Couldn't refresh the mod's files: "+A.message)}if(!w)l("`claude` isn't on PATH; update the mod later with: claude plugin marketplace update "+nn+" && claude plugin update "+Ce);else{let A=Gl(w,n);if(!A.installed&&A.legacyInstalled){i(s.dim("$ claude plugin marketplace update "+nn));let b=On(w,["plugin","marketplace","update",nn],{env:n});b.code!==0&&l("`claude plugin marketplace update "+nn+"` failed: "+((b.stderr||b.stdout).trim().split(`
|
|
352
|
+
`).pop()||"exit "+b.code))}else if(!A.installed)i(s.dim("· The mod isn't installed through `claude plugin`; nothing to update there.")),y="skip";else{let b=!1;for(let g of[["plugin","marketplace","update",nn],["plugin","update",Ce]]){i(s.dim("$ claude "+g.join(" ")));let _=await lf(w,g,{env:{...n,CHECKPOINTS_INTERNAL:"1",LEFTOFF_INTERNAL:"1"},log:r.log,timeoutMs:12e4});if(_.status!==0){b=!0,l("`claude "+g.join(" ")+"` failed: "+((_.stderr||_.stdout).trim().split(`
|
|
353
|
+
`).pop()||"exit "+_.status));break}}a("Mod updated"+s.dim(" · restart open Claude Code sessions to load it")),b||(y="ok")}}o&&(y==="ok"?r.done("Updated the Claude Code plugin"):y==="skip"?r.done("Claude Code plugin: not installed with `claude plugin`, nothing to update"):r.stop());let S=[Et.join(m,"bin","checkpoints.js"),"upgrade","--finish","--from="+u.version,"--from-sha="+(u.sha||""),"--repo="+m];if(t["no-restart"]&&S.push("--no-restart"),!o){let A=gc(process.execPath,S,{env:n,stdio:"inherit"});return A.status==null?1:A.status}S.push("--quiet","--log="+r.logFile);let v=gc(process.execPath,S,{env:{...n,LEFTOFF_PROGRESS_TTY:e.isTTY&&!n.CI?"1":"0"},stdio:["ignore","inherit","pipe"],encoding:"utf8"});return v.stderr&&r.log(v.stderr.trimEnd()),v.status!==0?(r.fail("Finishing the upgrade failed (exit "+v.status+")"),v.status==null?1:v.status):0}var $v=t=>String(t).replace(/\x1b\[2m.*?\x1b\[22m/g,"").trim();function Yv(t,e=3){let n=t.map(o=>{let r=o.sections.flatMap(a=>a.items),i=r.filter(a=>/^\*\*/.test(a));return i.length?i:r}),s=[];for(let o=0;s.length<e&&n.some(r=>r.length>o);o++)for(let r of n){if(s.length>=e||!r[o])continue;let i=r[o].match(/^\*\*(.+?)\*\*/),a=(i?i[1]:r[o]).replace(/[*`]/g,"").replace(/\s+/g," ").trim().replace(/[.:]$/,"");a.length>72&&(a=a.slice(0,71).replace(/\s+\S*$/,"")+"…"),s.push(a)}return s}function Wv(t,e,n=J){let s=[];for(let[o,r]of Object.entries(Ds(t)))o!==e&&r.valid&&r.card.version&&Ye(r.card.version,n)<0&&s.push(o+" ("+r.card.version+")");return s.sort()}async function zv({flags:t,out:e,env:n,c:s,say:o,ok:r,warn:i,fail:a,P:l}){let c=l.quiet,u=String(t.from||""),d=String(t.repo||$n),f=await Hr({repoDir:d,env:n}),p=q(),h=un(p);if(h){let{cfg:k,applied:S}=Hv(h,u,J);if(S.length){ft.writeFileSync(we(p).config,JSON.stringify(k,null,2)+`
|
|
354
|
+
`);for(let v of S)r("Config: "+v.note)}}let m=ft.existsSync(Et.join(d,"bin","leftoff.js"))?Et.join(d,"bin","leftoff.js"):Et.join(d,"bin","checkpoints.js");try{if(li()){let k=ft.readFileSync(tn(),"utf8");Do(void 0,{bin:m}),k.includes(Cs)&&r("Settings hooks now run `leftoff`"+s.dim(" (replaced the checkpoints-hook entries)"))}}catch(k){i("Couldn't refresh the settings hooks: "+k.message)}let y=Kl(n);if(y){let k;try{k=Cd({bin:y,env:n})}catch(S){k={ok:!1,error:S.message}}k.migrated&&k.ok?r("Mod renamed: "+rs+" → "+Ce+(k.enabled?"":s.dim(" (kept disabled, as it was)"))+s.dim(" · restart open Claude Code sessions to load it")):k.ok||i("Couldn't move the mod to its new name: "+k.error+s.dim(" · run: claude plugin install "+Ce+" && claude plugin uninstall "+rs))}if(!t["no-restart"]){l.start("Restarting the daemon…");let k=await Gv({root:p,bin:m,env:n,say:S=>o(s.dim("· "+S))});if(k.dryRun)l.stop();else if(k.ok){r("Daemon restarted"+s.dim(" ("+k.how+", v"+(k.version||J)+")")),l.done("Restarted the daemon");try{let S=JSON.parse(ft.readFileSync(Et.join(p,".cleanup."+le(Z(p)).replace(/[^\w.-]/g,"_")+".json"),"utf8"));S.lastArchived&&Date.now()-Date.parse(S.lastArchivedAt)<5*6e4&&r("Archived "+S.lastArchived+" empty "+(S.lastArchived===1?"session":"sessions")+" with no folder or process"+s.dim(" (kept on disk, marked archived; see leftoff doctor)"))}catch{}}else i("Restart the daemon yourself: "+(k.error||"it didn't come back")+s.dim(" · leftoff serve"))}if(c){let k=wo(yo(),u||null,J);l.print(""),l.print(s.bold(Pe+" "+(u?u+" → ":"")+J)+" "+s.green("✓"));for(let v of Yv(k))l.print(" • "+v);l.print(s.dim("Full notes: leftoff changelog (also What’s new on the board) · log: "+rf(l.logFile,n)));let S=Wv(p,le(Z(p)));return S.length&&l.print(s.yellow("!")+" Upgrade your other Mac"+(S.length>1?"s":"")+" too: "+S.join(", ")),0}o(),o(s.bold(Pe+" "+(u?"v"+u+" → ":"")+"v"+J)+(t["from-sha"]||f.sha?s.dim(" "+us(t["from-sha"])+" → "+us(f.sha)):""));let w=wo(yo(),u||null,J);if(w.length&&o(`
|
|
355
|
+
`+Ma(w,{colors:s})),f.kind==="clone"&&t["from-sha"]){let k=await St(["log","--format=%h %s",String(t["from-sha"])+"..HEAD"],{cwd:d,env:n});if(k.code===0&&k.stdout.trim()){o(s.bold("Commits"));for(let S of k.stdout.trim().split(`
|
|
356
|
+
`).slice(0,30))o(" "+s.dim(S.slice(0,7))+S.slice(7))}}return o(s.dim("The board shows these under What's new. `leftoff changelog` prints them again.")),0}ke();cl();fi();wt();import yc from"node:fs";import Uy from"node:path";import Jv from"node:https";import{spawnSync as qv}from"node:child_process";var cf="/Applications/Tailscale.app/Contents/MacOS/Tailscale";function Yy({env:t=process.env,platform:e=process.platform,exists:n=s=>{try{return yc.accessSync(s,yc.constants.X_OK),yc.statSync(s).isFile()}catch{return!1}}}={}){if(t.CHECKPOINTS_TAILSCALE)return n(t.CHECKPOINTS_TAILSCALE)?t.CHECKPOINTS_TAILSCALE:null;let s=e==="win32"?["tailscale.exe"]:["tailscale"];for(let o of String(t.PATH||"").split(Uy.delimiter).filter(Boolean))for(let r of s){let i=Uy.join(o,r);if(n(i))return i}return e==="darwin"&&n(cf)?cf:null}function Wy(t,e,{inherit:n=!1,timeoutMs:s=15e3}={}){let o=qv(t,e,{encoding:"utf8",stdio:n?"inherit":["ignore","pipe","pipe"],timeout:n?void 0:s});return{code:o.status==null?o.error?127:1:o.status,stdout:o.stdout||"",stderr:(o.stderr||"")+(o.error?String(o.error.message):"")}}function uf(t){let e;try{e=typeof t=="string"?JSON.parse(t):t}catch{return null}if(!e||typeof e!="object")return null;let n=e.Self||{},s=String(n.DNSName||"").replace(/\.$/,"").toLowerCase()||null;return{state:e.BackendState||null,dnsName:s,hostName:n.HostName||null,online:n.Online!==!1,magicDNSSuffix:e.MagicDNSSuffix||null}}function df(t,e,n=443){let s;try{s=typeof t=="string"?t.trim()?JSON.parse(t):{}:t}catch{return{parsed:!1}}s=s||{};let o=e+":"+n,r=(s.Web||{})[o],i=r&&r.Handlers&&r.Handlers["/"];return{parsed:!0,proxy:i&&i.Proxy||null,other:i?!i.Proxy:!1,funnel:!!(s.AllowFunnel&&s.AllowFunnel[o]),any:!!r}}var Vv=t=>"http://127.0.0.1:"+t,ff=(t,e)=>!!t&&new RegExp("^(https?://)?(127\\.0\\.0\\.1|localhost):"+e+"/?$").test(t)||String(t)===String(e);async function hf(t,e,n,s,o){try{let r=await fetch("http://127.0.0.1:"+t+s,{method:n,headers:{authorization:"Bearer "+(e||""),"content-type":"application/json"},body:o?JSON.stringify(o):void 0,signal:AbortSignal.timeout(3e3)}),i=await r.json().catch(()=>({}));return r.ok?{up:!0,ok:!0,body:i}:{up:!0,ok:!1,status:r.status,body:i}}catch{return{up:!1}}}async function Hy({root:t,port:e,token:n,add:s=[],remove:o=[],requireToken:r}){let i=await hf(e,n,"POST","/v1/remote/hosts",{add:s,remove:o,requireToken:r});if(i.ok)return{live:!0,hosts:i.body.hosts};let a=un(t)||{},l=new Set(o.map(Mt)),c=(a.remoteHosts||[]).map(Mt).filter(d=>d&&!l.has(d));for(let d of s.map(Mt))d&&!c.includes(d)&&c.push(d);a.remoteHosts=c,typeof r=="boolean"&&(a.remoteRequireToken=r),Js(a,t);let u=!1;return i.up&&(u=!!(await hf(e,n,"POST","/v1/remote/reload")).ok),{live:u,daemonUp:!!i.up,status:i.status||null,hosts:c}}function By(t,{timeoutMs:e=45e3,env:n=process.env}={}){return new Promise(s=>{let o;try{o=new URL("v1/remote/ping",t)}catch{return s({ok:!1,kind:"bad_url",message:"not a URL: "+t})}let r=n.CHECKPOINTS_REMOTE_CHECK_ADDR?n.CHECKPOINTS_REMOTE_CHECK_ADDR.split(":"):null,i={method:"GET",hostname:o.hostname,port:o.port||443,path:o.pathname,servername:o.hostname,headers:{host:o.host,accept:"application/json"},timeout:e};if(r&&(i.hostname=r[0],i.port=Number(r[1])),n.CHECKPOINTS_REMOTE_CHECK_CA)try{i.ca=yc.readFileSync(n.CHECKPOINTS_REMOTE_CHECK_CA)}catch{}let a=Date.now(),l=Jv.request(i,c=>{let u="";c.setEncoding("utf8"),c.on("data",d=>{u.length<2e4&&(u+=d)}),c.on("end",()=>{let d=null;try{d=JSON.parse(u)}catch{}let f=Date.now()-a;if(c.statusCode===200&&d&&d.app===Pe)return s({ok:!0,ms:f,version:d.version,via:d.via});if(c.statusCode===200&&d&&d.ok&&d.app)return s({ok:!1,kind:"other_app",message:"something else answers there ("+d.app+")"});let p=d&&d.error&&d.error.code||c.headers["x-leftoff-error"]||null;if(p==="bad_host")return s({ok:!1,kind:"bad_host",message:"the daemon refused the tailnet name (403 bad_host): it isn't in remoteHosts of the running daemon"});if(p==="funnel_refused")return s({ok:!1,kind:"funnel",message:"the request came through Tailscale Funnel"});if(c.statusCode===404&&p==="not_found")return s({ok:!1,kind:"old_daemon",message:"the running daemon is older than v0.4.6 (restart it: leftoff open)"});if(c.statusCode===502||c.statusCode===503)return s({ok:!1,kind:"daemon_down",message:"Tailscale answered but couldn't reach the daemon on this machine (HTTP "+c.statusCode+"): is it running? leftoff open"});s({ok:!1,kind:"http",message:"HTTP "+c.statusCode+(p?" "+p:"")})})});l.on("timeout",()=>{l.destroy(Object.assign(new Error("timed out after "+Math.round(e/1e3)+" s"),{code:"ETIMEDOUT"}))}),l.on("error",c=>{let u=String(c.code||"");if(u==="ENOTFOUND"||u==="EAI_AGAIN")return s({ok:!1,kind:"dns",message:"this Mac can't resolve "+o.hostname+' (MagicDNS off, or "Use Tailscale DNS" unticked in the Tailscale menu)'});if(/CERT|SELF_SIGNED|UNABLE_TO_VERIFY|ERR_TLS|EPROTO|ECONNRESET/.test(u)||/certificate|handshake|socket hang up/i.test(c.message))return s({ok:!1,kind:"tls",message:"no valid HTTPS certificate yet ("+(u||c.message)+"). Turn on HTTPS certificates for the tailnet (https://login.tailscale.com/admin/dns); the first one can take a minute to issue"});if(u==="ETIMEDOUT")return s({ok:!1,kind:"timeout",message:c.message+" (Tailscale may still be fetching the certificate; try leftoff tailscale status in a minute)"});if(u==="ECONNREFUSED")return s({ok:!1,kind:"refused",message:"nothing listens on "+o.host+" (tailscale serve isn't running)"});s({ok:!1,kind:"error",message:u?u+": "+c.message:c.message})}),l.end()})}function Ky(t,e){return t.ok?"✓ checked "+e+" end to end from this machine: "+Pe+" "+(t.version?"v"+t.version+" ":"")+"answered in "+(t.ms<1e3?t.ms+" ms":(t.ms/1e3).toFixed(1)+" s")+" (MagicDNS, HTTPS certificate, Serve and the daemon are all fine)":"! couldn't open "+e+" from this machine: "+t.message}function Gy(t={},e=process.stdout){return t["no-qr"]||t.qr==="false"||t.qr==="0"?!1:t.qr?!0:!!e.isTTY}function $y(t,{base:e,link:n,code:s,expiresAt:o,qr:r,color:i=!0}){let a=Math.max(1,Math.round((Date.parse(o)-Date.now())/6e4)),l=new Date(o).toLocaleTimeString([],{hour:"numeric",minute:"2-digit"});t("Board on your tailnet: "+e),r?(t(""),t("Scan this with your iPhone camera (Tailscale has to be on there) to open the board signed in:"),t(""),t(jl(n,{color:i})),t("")):t("Open this once on your phone to sign that browser in:"),t(" "+n),s&&t("Home-screen app? It has its own cookies, so type this code in it instead: "+rl(s)),t("The link and the code each work once, until "+l+" ("+a+" min). Another device later: leftoff link")}var wc="Tailscale Funnel (public internet) is intentionally unsupported: the board stays inside your tailnet.";function Di(){return`leftoff tailscale — view the board from your phone or other machines on your tailnet
|
|
357
|
+
|
|
358
|
+
leftoff tailscale on [--https-port=443] [--force] [--no-login|--login]
|
|
359
|
+
runs \`tailscale serve --bg --https=443 http://127.0.0.1:<port>\`, adds this
|
|
360
|
+
machine's tailnet name to config remoteHosts, opens the https URL from this
|
|
361
|
+
machine to check it end to end, and prints a QR code that signs your phone in
|
|
362
|
+
(remoteRequireToken, on by default)
|
|
363
|
+
leftoff tailscale off removes the serve entry and the name, signs out remote browsers
|
|
364
|
+
leftoff tailscale status tailscale, serve and Leftoff's side of it
|
|
365
|
+
leftoff tailscale link a fresh sign-in: a QR code (one browser) and a typed code (the
|
|
366
|
+
(or: leftoff link) home-screen app), each single-use, ${Ls/6e4} minutes
|
|
367
|
+
--qr / --no-qr print the QR code (default: only when the output is a terminal)
|
|
368
|
+
--no-check on/status: skip opening the URL from this machine
|
|
369
|
+
leftoff tailscale revoke sign out every remote browser
|
|
370
|
+
|
|
371
|
+
${wc}
|
|
372
|
+
No Tailscale? An SSH tunnel works too: ssh -N -L 7429:127.0.0.1:7429 you@this-machine
|
|
373
|
+
`}function zy(t,e){return(t||[]).map(sl).filter(n=>n&&n.name===e)}function pf({env:t=process.env,platform:e=process.platform,run:n=Wy,port:s,cfgHosts:o=[]}={}){let r=Yy({env:t,platform:e});if(!r)return{installed:!1};let i=n(r,["status","--json"],{timeoutMs:5e3}),a=i.code===0?uf(i.stdout):null,l={installed:!0,bin:r,state:a?a.state:null,dnsName:a?a.dnsName:null,error:i.code===0?null:(i.stderr||i.stdout).trim().split(`
|
|
374
|
+
`)[0]||"tailscale status failed"};if(!a||!a.dnsName)return l;let c=zy(o,a.dnsName),u=c.length&&c[0].port||443,d=n(r,["serve","status","--json"],{timeoutMs:5e3}),f=d.code===0?df(d.stdout,a.dnsName,u):{parsed:!1};return{...l,httpsPort:u,serve:f,serving:!!(f.proxy&&ff(f.proxy,s)),listed:c.length>0,url:"https://"+a.dnsName+(u===443?"":":"+u)+"/"}}function mf(t,{cfgHosts:e=[]}={}){return!t||!t.installed?e.length?"! remote access: remoteHosts is set but no tailscale CLI found":"· remote access: off (Tailscale not found; see leftoff tailscale)":t.dnsName?t.serving&&t.listed?"✓ remote access: "+t.url+" (tailscale serve → 127.0.0.1, tailnet only)":t.listed&&!t.serving?"! remote access: "+t.dnsName+" is in remoteHosts but tailscale serve isn't proxying to us (run: leftoff tailscale on)":t.serving&&!t.listed?"! remote access: tailscale serve points here but "+t.dnsName+" isn't in remoteHosts, so it gets 403 (run: leftoff tailscale on)":"· remote access: off (run: leftoff tailscale on, to open the board from your tailnet)":"· remote access: off (tailscale "+(t.state||"not connected")+")"}async function gf({sub:t="status",flags:e={},out:n=process.stdout,env:s=process.env,platform:o=process.platform,run:r=Wy}={}){let i=(h="")=>n.write(h+`
|
|
375
|
+
`);if(["help","--help","-h"].includes(t))return i(Di()),0;if(t==="funnel")return i(wc),i("Use: leftoff tailscale on (tailscale serve, tailnet only)"),1;let a=q(),l=Z(a),c=Number(e.port||l.port),u=Se(a),d=l.remoteHosts||[];if(t==="revoke"){let h=ei(a);return i("Signed out "+h+" remote browser"+(h===1?"":"s")+". Unused login links are void too."),0}let f=Yy({env:s,platform:o}),p=()=>(i("Tailscale CLI not found (looked on PATH"+(o==="darwin"?" and in "+cf:"")+")."),i("Install Tailscale from https://tailscale.com/download and sign in, then run this again."),i("Or, without Tailscale: ssh -N -L "+c+":127.0.0.1:"+c+" you@this-machine, then open http://localhost:"+c+"/"),1);if(t==="link"){if(!d.length)return i("Remote access is off. Run: leftoff tailscale on"),1;if(l.remoteRequireToken===!1)return i("remoteRequireToken is off, so no login is needed: "+d.map(y=>"https://"+Mt(y)+"/").join(" ")),0;Te(a);let h=Qr(a),m="https://"+Mt(d[0])+"/";$y(i,{base:m,link:m+"?t="+h.code,code:h.signInCode,expiresAt:h.expiresAt,qr:Gy(e,n),color:!e["no-color"]&&!s.NO_COLOR});for(let y of d.slice(1))i(" also: https://"+Mt(y)+"/?t="+h.code);return 0}if(t==="status"){if(!f)return p(),i(""),i("remoteHosts: "+(d.length?d.join(", "):"(none)")),1;let h=pf({env:s,platform:o,run:r,port:c,cfgHosts:d}),m=r(f,["version"],{timeoutMs:5e3});if(i("tailscale "+f+(m.code===0?" ("+m.stdout.trim().split(`
|
|
376
|
+
`)[0]+")":"")),i("state "+(h.state||h.error||"unknown")+(h.dnsName?" · "+h.dnsName:"")),h.dnsName){let k=h.serve||{};i("serve "+(k.parsed?k.proxy?"https://"+h.dnsName+(h.httpsPort===443?"":":"+h.httpsPort)+"/ → "+k.proxy+(h.serving?"":" (not this daemon, port "+c+")"):"nothing on https:"+h.httpsPort:"couldn't read tailscale serve status")),k.funnel&&i("! Funnel is ON for this port. Leftoff refuses Funnel traffic; turn it off: tailscale funnel --https="+h.httpsPort+" off")}i("remoteHosts "+(d.length?d.join(", "):"(none)")+" · sign-in "+(l.remoteRequireToken===!1?"not required":"required (QR code or typed code, leftoff link)"));let y=ll(a);i("browsers "+y.sessions.length+" signed in (30 days from each one's last visit)"+(y.pendingLinks?", "+y.pendingLinks+" unused link"+(y.pendingLinks===1?"":"s"):"")+(y.pendingCodes?", "+y.pendingCodes+" unused code"+(y.pendingCodes===1?"":"s"):""));let w=await hf(c,u,"GET","/v1/remote");return i("daemon "+(w.up?w.ok?"answers "+(w.body.hosts.length?w.body.hosts.join(", "):"loopback only"):"running but too old for remote access (restart it)":"not running")),i(mf(h,{cfgHosts:d})),h.serving&&h.listed&&!e["no-check"]&&s.CHECKPOINTS_REMOTE_CHECK!=="off"&&i(Ky(await By(h.url,{timeoutMs:15e3,env:s}),h.url)),0}if(t==="on"){if(!f)return p();let h=r(f,["status","--json"],{timeoutMs:8e3}),m=h.code===0?uf(h.stdout):null;if(!m)return i("`tailscale status --json` failed: "+((h.stderr||h.stdout).trim()||"no output")+`
|
|
377
|
+
Is Tailscale running and signed in?`),1;if(m.state&&m.state!=="Running")return i("Tailscale is "+m.state+", not connected. Open Tailscale (or run: tailscale up), then try again."),1;if(!m.dnsName)return i("This machine has no tailnet DNS name. Turn on MagicDNS and HTTPS certificates: https://login.tailscale.com/admin/dns"),1;let y=Number(e["https-port"]||443),w=r(f,["serve","status","--json"],{timeoutMs:8e3}),k=w.code===0?df(w.stdout,m.dnsName,y):{parsed:!1},S=!!(k.proxy&&ff(k.proxy,c));if(k.funnel)return i("Funnel is ON for "+m.dnsName+":"+y+", which would put this port on the public internet. "+wc+`
|
|
378
|
+
Turn it off first: tailscale funnel --https=`+y+" off"),1;if((k.proxy||k.other)&&!S&&!e.force)return i("https://"+m.dnsName+(y===443?"":":"+y)+"/ is already served by tailscale ("+(k.proxy||"a file or text handler")+")."),i("Use another port (leftoff tailscale on --https-port=8443) or replace it (--force)."),1;let v=["serve","--bg","--https="+y,Vv(c)];if(S)i("✓ tailscale serve already proxies https://"+m.dnsName+"/ to "+k.proxy);else{i("Running: tailscale "+v.join(" "));let x=r(f,v,{inherit:!0});if(x.code!==0)return i("tailscale serve failed (exit "+x.code+"). If it says HTTPS or Serve isn't enabled, turn on HTTPS certificates for your tailnet: https://login.tailscale.com/admin/dns"),1}let A=m.dnsName+(y===443?"":":"+y);Te(a);let b=await Hy({root:a,port:c,token:u,add:[A],requireToken:e["no-login"]?!1:e.login?!0:void 0});i((b.live||!b.daemonUp?"✓ ":"! ")+A+" added to remoteHosts"+(b.live?" (live in the running daemon)":b.daemonUp?" in config.json, but the running daemon didn't take it (HTTP "+(b.status||"?")+(b.status===401?": it uses a different folder or token than this shell; check CHECKPOINTS_DIR / LEFTOFF_DIR":"")+"). Restart it: leftoff open":" (the daemon isn't running; start it with: leftoff open)"));let g="https://"+A+"/",_=e["no-login"]?!1:Z(a).remoteRequireToken!==!1;if(!e["no-check"]&&s.CHECKPOINTS_REMOTE_CHECK!=="off"){i("Checking "+g+" from this machine (the first HTTPS request can take up to a minute while Tailscale gets the certificate)…");let x=await By(g,{env:s});i(Ky(x,g)),!x.ok&&x.kind==="bad_host"&&!b.live&&i(" The running daemon didn't take the new name. Restart it: leftoff open (or log out and in), then: leftoff tailscale status")}if(i(""),_){let x=Qr(a);$y(i,{base:g,link:g+"?t="+x.code,code:x.signInCode,expiresAt:x.expiresAt,qr:Gy(e,n),color:!e["no-color"]&&!s.NO_COLOR})}else i("Board on your tailnet: "+g),i("remoteRequireToken is off: anyone on your tailnet who can reach this URL gets the board.");return i("Turn it off: leftoff tailscale off · "+wc),0}if(t==="off"){let h=null;if(f){let S=r(f,["status","--json"],{timeoutMs:8e3}),v=S.code===0?uf(S.stdout):null;h=v&&v.dnsName}let m=h?zy(d,h):[],y=m.length?m.map(S=>S.port||443):[Number(e["https-port"]||443)];if(f&&h)for(let S of y){let v=r(f,["serve","status","--json"],{timeoutMs:8e3}),A=v.code===0?df(v.stdout,h,S):{parsed:!1};if(A.proxy&&ff(A.proxy,c)){let b=["serve","--https="+S,"off"];i("Running: tailscale "+b.join(" "));let g=r(f,b,{inherit:!0});g.code!==0&&i("! tailscale serve off failed (exit "+g.code+"); check: tailscale serve status")}else A.proxy&&i("· leaving https:"+S+" alone: it proxies to "+A.proxy+", not this daemon")}else f||i("· tailscale CLI not found; removing Leftoff's side only");let w=e.all?d:m.length?m.map(S=>S.name+(S.port&&S.port!==443?":"+S.port:"")):d;await Hy({root:a,port:c,token:u,remove:w});let k=ei(a);return i("✓ remote access off: "+(w.length?w.join(", ")+" removed from remoteHosts":"no remoteHosts to remove")+(k?", "+k+" remote browser"+(k===1?"":"s")+" signed out":"")),0}return i(Di()),1}As();ni();Io();Cn();ve();function Ri(t,e=Date.now()){let n=Date.parse(t||"");if(isNaN(n))return"";let s=Math.max(0,Math.round((e-n)/1e3));return s<60?"just now":s<3600?Math.round(s/60)+"m ago":s<86400?Math.round(s/3600)+"h ago":Math.round(s/86400)+"d ago"}function ds(t){return t.title||"(untitled)"}function yf(t,e){return[t.repoLabel||t.repoName,t.repo&&t.repo.branch,t.status,t.idleSec!=null?"idle "+Ri(t.lastActivityAt,e).replace(" ago",""):null,"cp:"+(t.shortId||Ke(t.id)),t.terminal?t.terminal.label:null].filter(Boolean).join(" · ")}function Zv(t){return t.filter(e=>e.isLocal!==!1&&e.live!=="closed"&&!e.endedAt)}async function Jy({pos:t=[],flags:e={},api:n,out:s=process.stdout,err:o=process.stderr,input:r=process.stdin,jumpRunner:i=yl,now:a=Date.now(),env:l=process.env}={}){let c=S=>s.write(S+`
|
|
379
|
+
`);e.id===!0&&(e={...e,id:t.shift()});let u;try{e.id?u=[await n("GET","/v1/sessions/"+encodeURIComponent(e.id))]:u=(await n("GET","/v1/sessions?limit=300")).sessions||[]}catch(S){let v=/fetch failed|ECONNREFUSED|abort|timed? ?out/i.test(String(S.message))?"The Leftoff daemon isn't running. Start it with: leftoff serve (or leftoff status)":S.message;return o.write((e.plain?"":ee.red("✖ "))+v+`
|
|
380
|
+
`),e.plain&&c(v),1}let d=e.id?u:Zv(u);if(e.repo===!0&&(e={...e,repo:t.shift()}),typeof e.repo=="string"&&!e.id){let S=d;if(d=e.repo.split(",").flatMap(v=>fa(S,v.trim())).filter((v,A,b)=>b.indexOf(v)===A),!d.length)return c('No open session in a repo matching "'+e.repo+'".'+(S.length?" Open repos: "+[...new Set(S.map(v=>v.repoLabel||v.repoName))].join(", "):"")),2}if(e.list){d.length||c("No open sessions on this machine.");for(let S of d)c((S.live==="open"?ee.green("●"):ee.dim("○"))+" "+ds(S)+" "+ee.dim(yf(S,a)));return 0}let f=t.join(" ").trim(),p=null;if(e.id)p=d[0];else{let S=f?Mm(f,d):d;if(!S.length)return c(f?'No open session matches "'+f+'".'+(d.length?" Open: "+d.slice(0,5).map(ds).join(", "):""):"No open sessions on this machine."),2;if(S.length===1)p=S[0];else if(r.isTTY&&s.isTTY){let v=await Sg(f?'Sessions matching "'+f+'"':"Open Claude Code sessions",S.map(A=>({label:ds(A),hint:yf(A,a),value:A.id,search:[A.title,A.repoLabel,A.repoName,A.repoKey,A.repo&&A.repo.branch,A.cwd,A.id].filter(Boolean).join(" ")})),{input:r,output:s});if(!v)return 2;p=S.find(A=>A.id===v)}else{c((f?S.length+' sessions match "'+f+'"':S.length+" open sessions")+"; be more specific, or use the id:");for(let v of S.slice(0,10))c(" "+ds(v)+" "+ee.dim(yf(v,a)));return 2}}if(!p)return c("No such session."),2;if(p.live==="closed")return c('"'+F(ds(p),60)+'" is closed. Resume it with: '+p.resumeCommand),3;let h=p.terminal&&p.terminal.label||p.term&&p.term.label||"its terminal";e.plain||c(ee.dim("Switching to “"+F(ds(p),60)+"” in "+h+"…"));let m=Date.now(),y;if(e.daemon)try{y=await n("POST","/v1/sessions/"+encodeURIComponent(p.id)+"/go",{})}catch(S){y={ok:!1,message:S.message}}else{let S;try{let A=await n("GET","/v1/sessions/"+encodeURIComponent(p.id)+"/where");A.isLocal?A.term?S=No(A):S={supported:!1,reason:"No terminal was recorded for this session (it started before v0.4, or without the mod). Restart it to get Go."}:S={supported:!1,reason:"This session runs on "+A.machine+"; Go only works there"}}catch(A){S={supported:!1,reason:A.message}}let v=Ln(l);y=await i(S,{ghostty:{activate:v.kind==="ghostty"?"never":"auto"}})}let w=Date.now()-m,k=" in "+(w<1e4?(w/1e3).toFixed(2):Math.round(w/1e3))+" s";if(e.debug){c(ee.dim(" total "+w+" ms"+(y.elapsedMs!=null?" (jump "+y.elapsedMs+" ms"+(e.daemon?", inside the daemon":"")+")":"")));for(let S of y.steps||[])c(ee.dim(" · "+S.step+": "+S.ms+" ms"+(S.result?" → "+S.result:"")))}return e.plain?(c(y.ok?"Switched to “"+F(ds(p),50)+"”"+k:y.message),y.ok?0:3):y.ok?(c(ee.green("✔")+" "+ds(p)+ee.dim(" ("+String(y.message||"").replace(/^Switched to /,"")+k+")")),0):(c(ee.red("✖")+" "+y.message+ee.dim(k+(y.steps&&y.steps.length&&!e.debug?" · "+pl(y.steps):""))),3)}function qy(t=process.env){let e=Ln(t),n=No({id:"x",term:e,tty:"/dev/ttys000",cwd:"/"});return{term:e,supported:n.supported,reason:n.reason||null}}Cn();ve();ni();yd();Io();var Vy=`\`leftoff restore\` (v0.4.7): reopen the Claude Code sessions that were open
|
|
381
|
+
before this Mac restarted, each in a new Ghostty tab in its folder, resumed
|
|
382
|
+
by id and with its name back.
|
|
383
|
+
leftoff restore pick from the list (all checked; space toggles, enter goes)
|
|
384
|
+
leftoff restore <name|id> just that one (any closed session on this machine)
|
|
385
|
+
--all / --yes no picker: restore everything listed
|
|
386
|
+
--dry-run print what would open, open nothing
|
|
387
|
+
--since=12h how far back before the restart (default 24h)
|
|
388
|
+
--repo=NAME only sessions in that repo (folder name, or part of it)
|
|
389
|
+
--print print the commands instead of opening tabs
|
|
390
|
+
The tabs open from this terminal (macOS asks once whether it may control
|
|
391
|
+
Ghostty), like \`leftoff go\`. Without Ghostty's AppleScript (Ghostty 1.3.0+,
|
|
392
|
+
macOS) it prints one \`cd … && claude --resume …\` line per session.
|
|
393
|
+
Exit codes: 0 done (or nothing to do), 1 error, 2 cancelled, 3 some didn't open.`;function qo(t){return t.title||"(untitled)"}function wf(t,e){return[t.repoLabel,t.branch,t.lastAliveAt?"last seen "+Ri(t.lastAliveAt,e):null,"cp:"+(t.shortId||Ke(t.id))].filter(Boolean).join(" · ")}var Sf=t=>{let e=new Date(t);return isNaN(e)?"":e.toLocaleString(void 0,{weekday:"short",hour:"numeric",minute:"2-digit"})};async function Zy({pos:t=[],flags:e={},api:n,out:s=process.stdout,err:o=process.stderr,input:r=process.stdin,exec:i=Kt,platform:a=process.platform,now:l=Date.now()}={}){let c=v=>s.write(v+`
|
|
394
|
+
`),u=v=>(o.write(ee.red("✖ ")+v+`
|
|
395
|
+
`),1),d=new URLSearchParams;e.since&&e.since!==!0&&d.set("since",String(e.since)),e.repo&&e.repo!==!0&&d.set("repo",String(e.repo));let f;try{f=await n("GET","/v1/restore"+(d.toString()?"?"+d:""))}catch(v){return u(/fetch failed|ECONNREFUSED|abort|timed? ?out/i.test(String(v.message))?"The Leftoff daemon isn't running. Start it with: leftoff serve (or leftoff status)":v.message)}let p=f.sessions||[],h=t.join(" ").trim();if(h){let v;try{v=(await n("GET","/v1/sessions?limit=500&archived=1")).sessions||[]}catch(E){return u(E.message)}let A=v.filter(E=>E.isLocal!==!1),b=A.find(E=>E.id===h||(E.shortId||Ke(E.id))===h.replace(/^cp:/,"")),g=b?[{s:b,score:1/0}]:A.map(E=>({s:E,score:sd(h,E)})).filter(E=>E.score>0).sort((E,L)=>L.score-E.score||Date.parse(L.s.lastActivityAt||0)-Date.parse(E.s.lastActivityAt||0));if(!g.length)return u("No session on this machine matches “"+h+"”.");let _=g[0].s,x=g.filter(E=>E.score===g[0].score);if(x.length>1){c("More than one session matches “"+h+"”:");for(let E of x.slice(0,8))c(" "+qo(E.s)+ee.dim(" "+[E.s.repoLabel,E.s.live==="open"?"running":null,"cp:"+(E.s.shortId||Ke(E.s.id))].filter(Boolean).join(" · ")));return c("Use the id: leftoff restore <id>"),1}if(_.live==="open")return c("“"+F(qo(_),60)+"” is already running. Jump to it: leftoff go "+(_.shortId||Ke(_.id))),0;p=[p.find(E=>E.id===_.id)||{id:_.id,shortId:_.shortId,title:_.title,repoLabel:_.repoLabel,cwd:_.cwd,lastAliveAt:_.lastActivityAt}]}if(!p.length){let v=f.restartAt?f.restartKind==="boot"?"this Mac restarted ("+Sf(f.restartAt)+")":"the daemon started ("+Sf(f.restartAt)+")":"the last restart";return c("Nothing to restore: no session on "+(f.machine||"this machine")+" was open when "+v+" that isn't running again."+(e.since?"":" Look further back with --since=48h.")),0}let m=p,y=!h&&!e.all&&!e.yes&&!e["dry-run"]&&!e.print&&r.isTTY&&s.isTTY;if(!h&&!y&&!e.all&&!e.yes&&!e["dry-run"]&&!e.print){c(p.length+" session"+(p.length===1?" was":"s were")+" open before the restart:");for(let v of p)c(" "+qo(v)+ee.dim(" "+wf(v,l)));return c("Restore them all: leftoff restore --all (or one: leftoff restore <name|id>)"),0}if(y){let v=p.length+" session"+(p.length===1?" was":"s were")+" open before "+(f.restartKind==="boot"?"your Mac restarted":"Leftoff restarted")+(f.restartAt?" ("+Sf(f.restartAt)+")":"");if(m=await kg(v,p.map(A=>({label:F(qo(A),60),hint:wf(A,l),value:A})),{input:r,output:s}),!m)return c(ee.dim("Cancelled.")),2;if(!m.length)return c(ee.dim("Nothing picked.")),2}if(e["dry-run"]){c("Would restore "+m.length+" session"+(m.length===1?"":"s")+" (nothing opened):");for(let v of m)c(" "+qo(v)+ee.dim(" "+wf(v,l))+`
|
|
396
|
+
`+v.command);return 0}let w=v=>{v&&c(ee.yellow("!")+" Couldn't open Ghostty tabs: "+v+". Run these yourself, one per tab:");for(let A of m)c(A.command)};if(e.print)return w(null),0;if(!m.every(v=>v.command))try{m=(await n("POST","/v1/restore",{ids:m.map(A=>A.id),print:!0})).sessions}catch(v){return u(v.message)}c(ee.dim("Opening "+m.length+" Ghostty tab"+(m.length===1?"":"s")+"…"));let k=await Dl(m.map(v=>({cwd:v.cwd,claude:v.claude})),{exec:i,platform:a});if(k.how==="print")return w(k.message),3;let S=m.filter((v,A)=>k.results[A]&&k.results[A].ok).map(v=>v.id);if(S.length)try{await n("POST","/v1/restore/launched",{ids:S})}catch{}return m.forEach((v,A)=>{let b=k.results[A]||{};c((b.ok?ee.green("✔"):ee.red("✖"))+" "+qo(v)+ee.dim(" "+[v.repoLabel,"cp:"+(v.shortId||Ke(v.id))].filter(Boolean).join(" · "))+(b.ok?"":" "+b.error+`
|
|
397
|
+
`+v.command))}),c(S.length===m.length?ee.green("Restored "+S.length+".")+ee.dim(" Each tab runs claude --resume with the session id, in its folder."):ee.yellow(S.length+" of "+m.length+" opened.")),S.length===m.length?0:3}ke();gn();xs();va();Aa();zn();ve();var Xv=new Set(["since","to","repo","machine","length"]),Qv={r:"repo",m:"machine",s:"since"};function e1(t=[]){let e={repos:[],machines:[],rest:[]};for(let n=0;n<t.length;n++){let s=t[n],o=s.match(/^--?([A-Za-z-]+)(?:=(.*))?$/);if(!o){e.rest.push(s);continue}let r=Qv[o[1]]||o[1],i=o[2];if(Xv.has(r)&&i===void 0)if(n+1<t.length&&!/^--/.test(t[n+1]))i=t[++n];else throw new Error("--"+r+" needs a value");r==="repo"?e.repos.push(...String(i).split(",").map(a=>a.trim()).filter(Boolean)):r==="machine"?e.machines.push(...String(i).split(",").map(a=>a.trim()).filter(Boolean)):r==="short"||r==="long"||r==="informative"?e.length=r:e[r]=i===void 0?!0:i}return e.all&&(e.repos=[]),e}function Xy(){return`leftoff catchup: one recap of what happened across your sessions
|
|
398
|
+
|
|
399
|
+
leftoff catchup since your last catch-up (else the last 24 hours), every repo
|
|
400
|
+
leftoff catchup --since 3d a duration: 90m, 12h, 3d, 1w
|
|
401
|
+
leftoff catchup --since workday since the start of the last workday (Friday, on a weekend or Monday)
|
|
402
|
+
leftoff catchup --since friday also: today, yesterday, a date (2026-10-09) or a time (2026-10-09 14:00)
|
|
403
|
+
leftoff catchup --repo leftoff --repo site only these repos (repeat, or a,b); --all: every repo
|
|
404
|
+
leftoff catchup --machine studio only sessions from this machine (repeatable)
|
|
405
|
+
leftoff catchup --to 2026-10-09 end the window here instead of now
|
|
406
|
+
leftoff catchup --print to stdout only, no file (doesn't move "since I last caught up")
|
|
407
|
+
leftoff catchup --json the recap as JSON
|
|
408
|
+
leftoff catchup --short | --long how much each session says (default: config summary.length)
|
|
409
|
+
leftoff catchup --list the saved catch-ups, newest first
|
|
410
|
+
|
|
411
|
+
Saved as catchups/YYYY-MM-DDTHHMM.md in your Leftoff folder; the board lists them under Catch me up.`}function t1(t){return Be(t).body.replace(/^\n+/,"")}var Qy=t=>/fetch failed|ECONNREFUSED|aborted|timeout|ECONNRESET/i.test(String(t&&t.message)+(t&&t.cause?t.cause.code:""));async function ew({argv:t=[],api:e,out:n=o=>process.stdout.write(o),err:s=o=>process.stderr.write(o)}={}){let o;try{o=e1(t)}catch(c){return s("leftoff catchup: "+c.message+`
|
|
412
|
+
|
|
413
|
+
`+Xy()+`
|
|
414
|
+
`),2}if(o.help||o.h)return n(Xy()+`
|
|
415
|
+
`),0;o.rest.length&&!o.since&&(o.since=o.rest.join(" "));let r={since:o.since===!0?void 0:o.since,to:o.to||null,repos:o.repos,machines:o.machines,save:!o.print,length:o.length};if(o.list){let c=null;if(!o.offline)try{c=(await e("GET","/v1/catchups")).catchups}catch(u){if(!Qy(u))throw u}if(!c){let u=q();c=new mt(u,le(Z(u))).listCatchups()}if(o.json)return n(JSON.stringify(c,null,2)+`
|
|
416
|
+
`),0;if(!c.length)return n(`No catch-ups yet. Make one: leftoff catchup
|
|
417
|
+
`),0;for(let u of c)n(u.name.padEnd(18)+" "+String(u.label||"").padEnd(26)+" "+String(u.sessions==null?"?":u.sessions).padStart(3)+" sessions"+(u.needsYou?" · "+u.needsYou+" need you":"")+(u.machine?" · "+u.machine:"")+`
|
|
418
|
+
`);return 0}let i=null,a="daemon";if(!o.offline)try{i=await e("POST","/v1/catchups",r)}catch(c){if(!Qy(c))return s("leftoff catchup: "+c.message+`
|
|
419
|
+
`),/window|can't read/.test(c.message)?2:1}if(!i){a="folder";let c=q();Te(c);let u=Z(c),d=le(u),f=new mt(c,d),p;try{let m=(()=>{try{return new bt(c,d).view({fresh:!0})}catch{return{agents:[]}}})();p=await Nr(f.loadAll().concat(Za(m)),{...r,cfg:u,machine:d,lastAt:f.lastCatchupAt(),summarizer:new gt(u,{root:c}),lists:new Gn(c,d).all({includeArchived:!1}).map(y=>it(y)).concat(Va(m))})}catch(m){return s("leftoff catchup: "+m.message+`
|
|
420
|
+
`),m.badWindow?2:1}let h=r.save?f.writeCatchup(p.fileName,p.markdown):null;i={name:h?h.name:null,path:h?"catchups/"+h.name+".md":null,saved:!!h,recap:p.recap,summary:p.summary,markdown:p.markdown}}if(o.json)return n(JSON.stringify({...i,via:a},null,2)+`
|
|
421
|
+
`),0;n(t1(i.markdown));let l=i.recap.selection&&i.recap.selection.unmatched;return l&&l.length&&s("! no repo with activity in this window matches: "+l.join(", ")+`
|
|
422
|
+
`),i.recap.window.fallback==="no earlier catch-up"&&s(`· no earlier catch-up, so this covers the last 24 hours
|
|
423
|
+
`),i.saved&&s(`
|
|
424
|
+
Saved `+i.path+(a==="folder"?" (daemon not running: built from the folder)":"")+` · on the board: Catch me up
|
|
425
|
+
`),0}ke();Aa();gn();nu();br();ve();Cn();var n1=t=>/fetch failed|ECONNREFUSED|abort|timed? ?out/i.test(String(t&&t.message+(t.cause?" "+t.cause.code:"")));function s1(t){let e={sub:null,args:[],all:!1,json:!1,repo:null,pick:null,name:null,offline:!1};for(let s=0;s<t.length;s++){let o=t[s],r;if(o==="--all")e.all=!0;else if(o==="--json")e.json=!0;else if(o==="--offline")e.offline=!0;else if(r=o.match(/^--repo(?:=(.*))?$/))e.repo=r[1]!=null?r[1]:t[++s];else if(r=o.match(/^--name(?:=(.*))?$/))e.name=r[1]!=null?r[1]:t[++s];else if(r=o.match(/^--pick(?:=(\d+))?$/))r[1]?e.pick=Number(r[1]):/^\d+$/.test(t[s+1]||"")?e.pick=Number(t[++s]):e.pick=!0;else if(o==="--"){e.args.push(...t.slice(s+1));break}else!e.sub&&!e.args.length?e.sub=o:e.args.push(o)}let n=["ls","list","show","done","tick","check","undo","uncheck","add","rename","archive","unarchive","new","capture","help"];return e.sub&&!n.includes(e.sub)&&(e.args.unshift(e.sub),e.sub="show"),e.sub||(e.sub="ls"),e}function Sc(t,{color:e=ee,number:n=null}={}){let s=[],o=(n?n+". ":"")+e.bold(t.name)+" "+t.progress+(t.archived?" · archived":"")+" "+e.dim(t.ref+(t.repo,""));s.push(o);for(let r of t.items||[])s.push(" "+String(r.n).padStart(2)+" "+(r.done?e.green("[x]"):"[ ]")+" "+(r.done?e.dim(r.text):r.text));return s.join(`
|
|
426
|
+
`)}async function tw({argv:t=[],api:e,out:n=r=>process.stdout.write(r),err:s=r=>process.stderr.write(r),root:o}={}){let r=s1(t),i=(h,m=1)=>(s("leftoff todo: "+h+`
|
|
427
|
+
`),m),a=null,l=()=>{if(a)return a;let h=o||q();return Te(h),a=new Gn(h,le(Z(h))),a},c=async(h,m,y)=>{if(r.offline||!e)return null;try{return await e(h,m,y)}catch(w){if(n1(w))return null;throw w}},u=h=>it(l().resolve(h)),d=async h=>{let m=await c("GET","/v1/lists/resolve?ref="+encodeURIComponent(h));return m?m.list:u(h)},f=async(h,m)=>{let y=await d(h),w=await c("POST","/v1/lists/"+y.ref+"/change",m);if(w)return w;let k=l(),S={};if(m.text){let v=k.findItem(k.resolve(y.ref),m.text);if(!v)throw new Error('no item on "'+y.name+'" matches "'+m.text+'"; give its number');m={[m.undo?"undone":"done"]:[v.id]}}if(m.done||m.undone){let v=k.setDone(y.ref,[].concat(m.done||m.undone),!!m.done);S.missing=v.missing}return m.add&&(S.added=k.addItem(y.ref,m.add).item.id),m.name&&k.rename(y.ref,m.name),m.archived!=null&&k.archive(y.ref,m.archived),{ok:!0,list:u(y.ref),...S}},p=h=>h.flatMap(m=>String(m).split(",")).map(m=>m.replace(/^#/,"")).filter(Boolean);try{switch(r.sub){case"help":return n(r1),0;case"ls":case"list":{let h=null,m=await c("GET","/v1/lists"+(r.all?"?all=1":""));if(h=m?m.lists:l().all({includeArchived:r.all}).map(w=>it(w)),r.repo&&(h=h.filter(w=>w.repoDir===r.repo||w.repo===r.repo||String(w.repo||"").toLowerCase().includes(String(r.repo).toLowerCase()))),r.all||(h=h.filter(w=>!w.archived)),r.json)return n(JSON.stringify(h,null,2)+`
|
|
428
|
+
`),0;if(!h.length)return n("No "+(r.all?"":"open ")+`lists yet. When Claude writes you a checklist, save it with /cp-todo in the session, or Save on the board.
|
|
429
|
+
`),0;let y=null;return h.forEach((w,k)=>{let S=w.repo||"no repo";S!==y&&(n((k?`
|
|
430
|
+
`:"")+ee.dim("── "+S)+`
|
|
431
|
+
`),y=S),n(Sc(w,{number:k+1})+`
|
|
432
|
+
`)}),n(ee.dim(`
|
|
433
|
+
Tick one: leftoff todo done <list> <n> · on the board: Lists`)+`
|
|
434
|
+
`),0}case"show":{if(!r.args.length)return i("which list? leftoff todo show <list>",2);let h=await d(r.args.join(" "));return r.json?(n(JSON.stringify(h,null,2)+`
|
|
435
|
+
`),0):(n(Sc(h)+`
|
|
436
|
+
`),0)}case"done":case"tick":case"check":case"undo":case"uncheck":{let h=r.sub==="undo"||r.sub==="uncheck",[m,...y]=r.args;if(!m)return i("usage: leftoff todo "+r.sub+" <list> <n…>",2);if(!y.length&&/^#?\d+(,\d+)*$/.test(m)){let b=await c("GET","/v1/lists"),g=(b?b.lists:l().all().map(_=>it(_))).filter(_=>!_.archived);if(g.length!==1)return i("which list? leftoff todo "+r.sub+" <list> "+m+(g.length?" (open: "+g.map(_=>_.ref).join(", ")+")":""),2);y=[m],m=g[0].ref}if(!y.length)return i("which item? leftoff todo "+r.sub+" "+m+" <n>",2);let w=p(y),k=w.every(b=>/^\d+$/.test(b))?{[h?"undone":"done"]:w.map(Number)}:{text:y.join(" "),undo:h},S=await f(m,k),v=S.list;if(r.json)return n(JSON.stringify(S,null,2)+`
|
|
437
|
+
`),0;let A=k.text?v.items.filter(b=>b.done!==h&&b.text.toLowerCase().includes(String(k.text).toLowerCase().split(" ")[0])).slice(0,1):v.items.filter(b=>(k.done||k.undone).includes(b.n));return n((h?"↺ ":"✓ ")+v.name+": "+A.map(b=>"#"+b.n+" "+F(b.text,70)).join("; ")+(h?" unticked":"")+" · "+v.progress+` done
|
|
438
|
+
`),S.missing&&S.missing.length&&s("! no item "+S.missing.join(", ")+` on that list
|
|
439
|
+
`),0}case"add":{let[h,...m]=r.args;if(!h||!m.length)return i("usage: leftoff todo add <list> <text>",2);let y=await f(h,{add:m.join(" ")});return r.json?(n(JSON.stringify(y,null,2)+`
|
|
440
|
+
`),0):(n("+ "+y.list.name+": #"+y.added+" "+F(m.join(" "),80)+" · "+y.list.progress+`
|
|
441
|
+
`),0)}case"rename":{let[h,...m]=r.args;if(!h||!m.length)return i("usage: leftoff todo rename <list> <new name>",2);let y=await f(h,{name:m.join(" ")});return n("Renamed: "+y.list.name+" ("+y.list.ref+`)
|
|
442
|
+
`),0}case"archive":case"unarchive":{if(!r.args.length)return i("usage: leftoff todo "+r.sub+" <list>",2);let h=await f(r.args.join(" "),{archived:r.sub==="archive"});return n((r.sub==="archive"?"Archived: ":"Back on the board: ")+h.list.name+`
|
|
443
|
+
`),0}case"new":{let[h,...m]=r.args;if(!h||!m.length)return i('usage: leftoff todo new "<name>" [--repo NAME] "<item>" ["<item>"…]',2);let y={name:h,items:m.map(k=>({text:k})),repo:r.repo||null},w=await c("POST","/v1/lists",y);return w||(w={list:it(l().create({name:h,items:y.items,repo:r.repo||null}))}),r.json?(n(JSON.stringify(w.list,null,2)+`
|
|
444
|
+
`),0):(n("Saved: "+Sc(w.list)+`
|
|
445
|
+
`),0)}case"capture":return await o1(r,{call:c,local:l,out:n,err:s,fail:i})}}catch(h){return i(h.message,/no list|no item|matches \d+ lists|which/.test(h.message)?3:1)}return i("unknown command "+r.sub,2)}async function o1(t,{call:e,local:n,out:s,err:o,fail:r}){let i=t.args.join(" ").trim();if(!i)return r("usage: leftoff todo capture <session name|id> [--pick [N]] [--name NAME]",2);let a=null,l=await e("GET","/v1/sessions?limit=500&archived=1");if(l)a=l.sessions;else{let w=n();a=new mt(w.root,w.machine).loadAll().map(({session:k})=>({...k,repoName:fe(k),shortId:Ke(k.id)}))}let c=i.toLowerCase(),u=[w=>w.id===i,w=>w.id.startsWith(c)||w.shortId&&w.shortId===c.replace(/^cp:/,""),w=>String(w.title||"").toLowerCase()===c,w=>String(w.title||"").toLowerCase().includes(c)],d=null;for(let w of u){let k=a.filter(w);if(k.length===1){d=k[0];break}if(k.length>1)return r('"'+i+'" matches '+k.length+" sessions: "+k.slice(0,6).map(S=>(S.title||"untitled")+" ("+S.id.slice(0,8)+")").join(", ")+". Use the id.",3)}if(!d)return r('no session matches "'+i+'" (give its name or id; `leftoff status` lists them)',3);let f,p=await e("GET","/v1/sessions/"+encodeURIComponent(d.id)+"/lists/find");if(p)f=p.candidates;else{let w=d.transcriptPath||vr(d.id);if(!w)return r(`Claude Code's transcript for "`+(d.title||d.id)+`" isn't on this machine (looked in ~/.claude/projects)`,1);f=Yi(kr(w,{maxBytes:32*1024*1024}).messages,{fallback:d.title}).reverse().map(k=>({...k,count:k.items.length}))}let h=d.title||"untitled session";if(!f.length)return s('No checklists in "'+h+`". Leftoff looks for "- [ ]" checklists and numbered or bulleted lists under a heading like "Next steps".
|
|
446
|
+
`),4;if(t.pick===!0){s('Lists Claude wrote in "'+h+`" (newest first):
|
|
447
|
+
`);for(let w of f)s(" "+String(w.n).padStart(2)+". "+w.name+" · "+w.count+" items"+(w.savedAs?ee.dim(" · saved as "+w.savedAs.ref):"")+`
|
|
448
|
+
`+w.items.slice(0,3).map(k=>ee.dim(" "+F(k.text,70))).join(`
|
|
449
|
+
`)+`
|
|
450
|
+
`);return s(ee.dim("Save one: leftoff todo capture "+JSON.stringify(i)+' --pick <n> [--name "…"]')+`
|
|
451
|
+
`),0}let m=t.pick?f.find(w=>w.n===t.pick):f[0];if(!m)return r("no list "+t.pick+" in that session (--pick lists them)",3);let y=await e("POST","/v1/lists",{sessionId:d.id,fromTranscript:m.n,name:t.name||void 0});if(!y){let w=n();y={list:it(w.create({name:t.name||m.name,items:m.items,repo:d.repoName&&d.repoName!=="no-repo"?d.repoName:null,sourceSession:d.id,sourceMachine:d.machine||w.machine,sourceTitle:d.title||null}))}}return t.json?(s(JSON.stringify(y.list,null,2)+`
|
|
452
|
+
`),0):(s('Saved from "'+h+`":
|
|
453
|
+
`+Sc(y.list)+`
|
|
454
|
+
`),s(ee.dim("Tick: leftoff todo done "+y.list.ref+" <n> · in the session: /cp-done <n>")+`
|
|
455
|
+
`),0)}var r1=`leftoff todo: the checklists Claude writes for you, saved as Markdown in lists/<repo>/ in your folder.
|
|
456
|
+
|
|
457
|
+
leftoff todo [--all] [--repo NAME] open lists with their items
|
|
458
|
+
leftoff todo show <list>
|
|
459
|
+
leftoff todo done <list> <n…> tick items (or words: done app-store "upload the build")
|
|
460
|
+
leftoff todo undo <list> <n…>
|
|
461
|
+
leftoff todo add <list> "<text>"
|
|
462
|
+
leftoff todo rename <list> "<name>"
|
|
463
|
+
leftoff todo archive <list> (unarchive brings it back)
|
|
464
|
+
leftoff todo new "<name>" "<item>" … a list of your own
|
|
465
|
+
leftoff todo capture <session> [--pick [N]] [--name NAME]
|
|
466
|
+
save a list Claude wrote in a session (open or ended);
|
|
467
|
+
--pick shows them all
|
|
468
|
+
|
|
469
|
+
<list>: its id (repo/slug), its name or the start of it, or its number in \`leftoff todo\`.
|
|
470
|
+
Item numbers never change. In Claude Code: /cp-todo [name] saves, /cp-done <n> ticks.
|
|
471
|
+
`;ke();zn();wt();import kc from"node:fs";import rw from"node:crypto";var i1=t=>/fetch failed|ECONNREFUSED|abort|timed? ?out/i.test(String(t&&t.message+(t.cause?" "+t.cause.code:"")));function a1(t){let e={sub:null,args:[],key:null,json:!1,purge:!1,offline:!1,url:null,writeKey:null,dryRun:!1};for(let n=0;n<t.length;n++){let s=t[n],o;s==="--json"?e.json=!0:s==="--purge"?e.purge=!0:s==="--offline"?e.offline=!0:(o=s.match(/^--key=(.*)$/))?e.key=o[1]:s==="--key"?e.key=t[++n]==null?"":t[n]:(o=s.match(/^--url=(.*)$/))?e.url=o[1]:s==="--url"?e.url=t[++n]==null?"":t[n]:(o=s.match(/^--write-key=(.*)$/))?e.writeKey=o[1]:s==="--write-key"?e.writeKey=t[++n]==null?"":t[n]:s==="--dry-run"||s==="-n"?e.dryRun=!0:e.sub?e.args.push(s):e.sub=s}return e.sub||(e.sub="list"),e.sub==="ls"&&(e.sub="list"),(e.sub==="rm"||e.sub==="delete")&&(e.sub="remove"),e.sub==="pull"&&(e.sub="refresh"),e.sub==="keys"&&(e.sub="keygen"),e.sub==="push"&&(e.sub="publish"),e.sub==="check"&&(e.sub="validate"),e}var nw=t=>rw.createHash("sha256").update(String(t),"utf8").digest("hex");function l1(t=rw.randomBytes){let e="lfr_"+t(24).toString("base64url"),n="lfw_"+t(24).toString("base64url");return{read:e,write:n,readSha256:nw(e),writeSha256:nw(n)}}function c1(t){return["Two new keys for a private Leftoff agent feed. They are shown once; save them now (a password manager is a good place).","","Read key: give this to Leftoff on the Mac that pulls the feed"," "+t.read," leftoff agents add <name> <feed url> --key - (paste it on stdin)","","Write key: for the agent or script that publishes the feed. Keep it there"," "+t.write," leftoff agents publish feed.json --url <feed url> --write-key -","","For wrangler.toml (templates/agent-feed-worker), under [vars]. Hashes only, safe to commit:",' READ_KEY_SHA256 = "'+t.readSha256+'"',' WRITE_KEY_SHA256 = "'+t.writeSha256+'"',"","Guide: https://github.com/"+Ze+"/blob/main/docs/agent-feed-hosting.md",""].join(`
|
|
472
|
+
`)}function u1(t,{stdinUsed:e}){if(t==="-"){if(e)return{error:"the feed and the write key can't both come from stdin; pass the feed as a file"};try{return{body:kc.readFileSync(0)}}catch(n){return{error:"couldn't read stdin: "+n.message}}}try{return kc.statSync(t).isFile()?{body:kc.readFileSync(t)}:{error:t+" is not a file"}}catch(n){return{error:"couldn't read "+t+": "+(n.code==="ENOENT"?"no such file":n.message)}}}async function d1({body:t,url:e,writeKey:n,fetchImpl:s=globalThis.fetch,timeoutMs:o=2e4,userAgent:r="Leftoff"}){let i=za(t);if(!i.ok)return{ok:!1,stage:"validate",errors:i.errors};let a=Fu(e);if(!a.ok)return{ok:!1,stage:"url",errors:[a.message]};if(!n)return{ok:!1,stage:"key",errors:["no write key (--write-key <key>, --write-key - for stdin, or LEFTOFF_AGENT_WRITE_KEY)"]};let l=new AbortController,c=setTimeout(()=>l.abort(),o),u;try{u=await s(a.url,{method:"PUT",body:t,redirect:"manual",signal:l.signal,headers:{authorization:"Bearer "+n,"content-type":"application/json","user-agent":r}})}catch(p){clearTimeout(c);let h=p.name==="AbortError"?"timed out after "+Math.round(o/1e3)+"s":String(p.cause&&(p.cause.code||p.cause.message)||p.message);return{ok:!1,stage:"put",errors:["couldn't reach "+new URL(a.url).host+": "+h.split(n).join("…")]}}clearTimeout(c);let d="";try{d=(await u.text()).slice(0,300)}catch{}if(d=d.split(n).join("…").replace(/\s+/g," ").trim(),u.status>=200&&u.status<300){let p=u.headers.get("etag")||null;try{let h=JSON.parse(d);h&&typeof h.etag=="string"&&(p=h.etag)}catch{}return{ok:!0,status:u.status,etag:p,sessions:i.feed.sessions.length,bytes:i.bytes,agent:i.feed.agent.name,url:a.url}}let f=u.status===401||u.status===403?"the server refused the write key ("+u.status+")":u.status>=300&&u.status<400?"the server redirected ("+u.status+(u.headers.get("location")?" to "+u.headers.get("location"):"")+"); use the final URL, the key isn't sent on":u.status===413?"the server says the feed is too big (413)":"the server answered "+u.status+(d?": "+d:"");return{ok:!1,stage:"put",status:u.status,errors:[f]}}var sw=(t,e=Date.now())=>{if(!t)return"never";let n=Math.max(0,Math.round((e-Date.parse(t))/1e3));return n<60?n+"s ago":n<3600?Math.round(n/60)+"m ago":n<86400?Math.round(n/3600)+"h ago":Math.round(n/86400)+"d ago"};function kf(t,{now:e=Date.now()}={}){let n=t.status||{},s=t.pullsHere?"pulled by this Mac":t.puller?"pulled by "+t.puller:"not pulled",o=(t.icon?t.icon+" ":"")+t.display+(t.display!==t.name?" ("+t.name+")":"");return n.error?{ok:!1,line:o+": "+n.error.message+" · last success "+sw(n.lastSuccessAt,e)+" · "+s,details:n.error.details||[]}:n.ok?{ok:!0,line:o+": "+t.sessions+" session"+(t.sessions===1?"":"s")+(t.needsYou?", "+t.needsYou+" waiting on you":"")+" · pulled "+sw(n.lastSuccessAt,e)+" · "+s,details:[]}:{ok:null,line:o+": not pulled yet · "+s+(t.pullsHere||t.hasKey||!t.puller?"":" (its key is on that Mac)"),details:[]}}function bc(t){let e=t.view({fresh:!0});return{machine:t.machine,agents:e.agents.map(n=>({name:n.name,display:n.display,icon:n.icon,url:n.url,feedUrl:n.source.url,puller:n.puller,pullsHere:n.pullsHere,hasKey:n.hasKey,status:n.status,sessions:n.records.length,needsYou:n.records.reduce((s,o)=>s+(o.inFeed&&o.session.status!=="done"?o.session.needsYou.length:0),0)}))}}function ow(){try{return kc.readFileSync(0,"utf8").trim()}catch{return""}}async function iw({argv:t=[],api:e,out:n=i=>process.stdout.write(i),err:s=i=>process.stderr.write(i),env:o=process.env,fetchImpl:r=null}={}){let i=a1(t),a=q(),l=()=>(Te(a),new bt(a,le(Z(a)),{env:o,fetchImpl:r,userAgent:"Leftoff/"+J})),c=async u=>{if(i.offline||!e)return{offline:!0};try{return{r:await u()}}catch(d){if(i1(d))return{offline:!0};throw d}};try{if(i.sub==="list"){let u=(await c(()=>e("GET","/v1/agents"))).r;if(u||(u=bc(l())),i.json)return n(JSON.stringify(u,null,2)+`
|
|
473
|
+
`),0;if(!u.agents.length)return n(`No agent feeds yet. Add one: leftoff agents add <name> <url> --key <key>
|
|
474
|
+
`),0;for(let d of u.agents){let f=kf(d);n((f.ok===!1?"✗ ":f.ok?"✓ ":"· ")+f.line+`
|
|
475
|
+
`),n(" "+(d.feedUrl||"")+`
|
|
476
|
+
`);for(let p of f.details.slice(0,5))n(" - "+p+`
|
|
477
|
+
`)}return u.agents.some(d=>d.status&&d.status.error)?1:0}if(i.sub==="add"){let[u,d]=i.args;if(!u||!d)return s(`usage: leftoff agents add <name> <url> --key <key> (--key - reads it from stdin)
|
|
478
|
+
`),2;let f=i.key;f==="-"&&(f=ow()),f==null&&o.LEFTOFF_AGENT_KEY&&(f=o.LEFTOFF_AGENT_KEY);let p={name:u.toLowerCase(),url:d,key:f||null},h=(await c(()=>e("POST","/v1/agents",p))).r;if(!h){let y=l();h=y.add(p);try{h.pull=await y.pull(h.source.name,{force:!0})}catch(w){h.pull={result:"error",error:{message:w.message}}}}n((h.replaced?"Updated ":"Added ")+h.source.name+" ("+h.source.url+")."+(h.hasKey?" Key saved on this Mac only.":" No key: the feed is pulled without one.")+`
|
|
479
|
+
`),h.previousPuller&&n("This Mac pulls it now (was "+h.previousPuller+`).
|
|
480
|
+
`);let m=h.pull||{};if(m.result==="ok")n("Pulled: "+(m.applied?m.applied.sessions+" session"+(m.applied.sessions===1?"":"s"):"ok")+". It shows on the board as "+(m.status&&m.status.agent&&m.status.agent.name||h.source.name)+`.
|
|
481
|
+
`);else if(m.result==="unchanged")n(`Pulled: nothing new.
|
|
482
|
+
`);else if(m.result==="error"){s("First pull failed: "+(m.error&&m.error.message||"error")+`
|
|
483
|
+
`);for(let y of(m.error&&m.error.details||[]).slice(0,5))s(" - "+y+`
|
|
484
|
+
`);return 1}return 0}if(i.sub==="remove"){let[u]=i.args;if(!u)return s(`usage: leftoff agents remove <name> [--purge]
|
|
485
|
+
`),2;let d=(await c(()=>e("DELETE","/v1/agents/"+encodeURIComponent(u)+(i.purge?"?purge=1":"")))).r;return d||(d=l().remove(u,{purge:i.purge})),n("Removed "+u+". "+(d.movedTo?"Its history is in "+d.movedTo+" (nothing deleted).":"Its files are deleted.")+(d.keyDeleted?" This Mac forgot its key.":"")+`
|
|
486
|
+
`),0}if(i.sub==="refresh"){let u=i.args.length?i.args:null,d=(await c(()=>e("GET","/v1/agents"))).r,f=!!d;d||(d=bc(l()));let p=d.agents.filter(y=>u?u.includes(y.name):y.pullsHere);if(u){for(let y of u)if(!d.agents.some(w=>w.name===y))return s("No agent feed named "+y+`
|
|
487
|
+
`),1}if(!p.length)return n("Nothing to pull on this Mac"+(d.agents.length?" (each feed is pulled by the Mac that holds its key)":"")+`.
|
|
488
|
+
`),0;let h=0,m=f?null:l();for(let y of p){let w;try{w=f?await e("POST","/v1/agents/"+encodeURIComponent(y.name)+"/refresh"):await m.pull(y.name,{force:!0})}catch(k){w={result:"error",error:{message:k.message}}}if(w.result==="error"){h++,s("✗ "+y.name+": "+(w.error&&w.error.message||"error")+`
|
|
489
|
+
`);for(let k of(w.error&&w.error.details||[]).slice(0,5))s(" - "+k+`
|
|
490
|
+
`)}else n("✓ "+y.name+": "+(w.result==="unchanged"?"nothing new":w.applied?w.applied.sessions+" session"+(w.applied.sessions===1?"":"s")+", "+w.applied.written+" file"+(w.applied.written===1?"":"s")+" updated":"pulled")+`
|
|
491
|
+
`)}return h?1:0}if(i.sub==="keygen"){let u=l1();return n(i.json?JSON.stringify(u,null,2)+`
|
|
492
|
+
`:c1(u)),0}if(i.sub==="validate"||i.sub==="publish"){let[u]=i.args,d=i.sub==="validate"?"usage: leftoff agents validate <file>":"usage: leftoff agents publish <file> --url <url> --write-key <key|-> (--dry-run validates only)";if(!u)return s(d+`
|
|
493
|
+
`),2;let f=i.writeKey,p=f==="-",h=u1(u,{stdinUsed:p});if(h.error)return s(h.error+`
|
|
494
|
+
`),1;let m=za(h.body);if(!m.ok){s("✗ "+(u==="-"?"the feed":u)+" is not a valid leftoff-agent-feed/1 feed"+(i.sub==="publish"?"; nothing was published":"")+`:
|
|
495
|
+
`);for(let k of m.errors.slice(0,20))s(" - "+k+`
|
|
496
|
+
`);return m.errors.length>20&&s(" … and "+(m.errors.length-20)+` more
|
|
497
|
+
`),1}let y=m.feed.agent.name+", "+m.feed.sessions.length+" session"+(m.feed.sessions.length===1?"":"s")+", "+m.bytes+" bytes";if(i.sub==="validate"||i.dryRun)return n("✓ Valid ("+y+")."+(i.sub==="publish"?" Not published (--dry-run).":"")+`
|
|
498
|
+
`),0;if(!i.url)return s(d+`
|
|
499
|
+
`),2;p&&(f=ow()),!f&&o.LEFTOFF_AGENT_WRITE_KEY&&(f=o.LEFTOFF_AGENT_WRITE_KEY);let w=await d1({body:h.body,url:i.url,writeKey:f,fetchImpl:r||globalThis.fetch,userAgent:"Leftoff/"+J});return w.ok?(n("✓ Published to "+w.url+" ("+y+(w.etag?", ETag "+w.etag:"")+`).
|
|
500
|
+
`),0):(s("✗ Not published: "+w.errors.join("; ")+`
|
|
501
|
+
`),1)}return s("unknown: leftoff agents "+i.sub+`
|
|
502
|
+
usage: leftoff agents [list|add|remove|refresh|keygen|publish|validate]
|
|
503
|
+
`),2}catch(u){return s(u&&u.message||String(u)),s(`
|
|
504
|
+
`),1}}zn();Ii();ke();gn();zn();vs();Yu();Cr();import{spawnSync as f1}from"node:child_process";function lw(t){let e={},n=[];for(let s=0;s<t.length;s++){let o=t[s],r=o.match(/^--([^=]+)(?:=(.*))?$/);if(!r){n.push(o);continue}r[2]!==void 0?e[r[1]]=r[2]:["space","date"].includes(r[1])&&t[s+1]&&!t[s+1].startsWith("--")?e[r[1]]=t[++s]:e[r[1]]=!0}return{flags:e,pos:n}}function cw(){let t=q();Te(t);let e=Z(t),n=le(e);return{root:t,cfg:e,machine:n,folder:new mt(t,n)}}function bf(t,e){let n=new Set(t.folder.machines());for(let s of t.folder.aliveMachines())n.add(s.machine);for(let s of Object.keys(Nt(e,"machines")))n.add(s);return[...n].filter(Boolean).sort()}var aw=`leftoff spaces Macs, their default space, and overrides
|
|
505
|
+
leftoff spaces set-machine [mac] <space> a Mac's default space (default: this Mac)
|
|
506
|
+
leftoff spaces set-repo <repo> <space> a repo counts as <space> on every Mac
|
|
507
|
+
leftoff spaces set-session <id> <space> one session
|
|
508
|
+
leftoff spaces set-agent <name> <space> an agent feed (default: the space of the Mac that pulls it)
|
|
509
|
+
leftoff spaces unset machine|repo|session|agent <key>
|
|
510
|
+
A session's space: its own override, else its repo's, else its Mac's default.
|
|
511
|
+
Quick picks: Work, Personal (or any name).`;function uw({argv:t=[],out:e=console.log}={}){let{flags:n,pos:s}=lw(t),o=cw(),r=s[0]||"list",i=a=>e(a);try{if(r==="help"||n.help)return i(aw),0;if(r==="list"||r==="ls"){let{map:c,conflicts:u}=bs(o.root),d=bf(o,c);if(n.json)return i(JSON.stringify({machine:o.machine,machines:d.map(f=>({machine:f,space:Hn(c,f),set:!!(c.machines[f]&&c.machines[f].space!=null)})),repos:Nt(c,"repos"),sessions:Nt(c,"sessions"),agents:Nt(c,"agents"),spaces:Vt(c,d)},null,2)),0;i("Spaces: "+(Vt(c,d).join(", ")||"(none)")),i("Macs (default space):");for(let f of d){let p=c.machines[f]&&c.machines[f].space!=null;i(" "+f.padEnd(18)+Hn(c,f)+(p?"":" (not chosen yet: leftoff spaces set-machine "+f+" Work|Personal)")+(f===o.machine?" ← this Mac":""))}for(let[f,p]of[["repos","Repo overrides"],["sessions","Session overrides"],["agents","Agent feeds"]]){let h=Nt(c,f);if(Object.keys(h).length){i(p+":");for(let[m,y]of Object.entries(h).sort(([w],[k])=>w.localeCompare(k)))i(" "+m.padEnd(28)+y)}}return u.length&&i("("+u.length+" Syncthing conflict cop"+(u.length===1?"y":"ies")+" of spaces.json merged; the next change folds "+(u.length===1?"it":"them")+" in)"),0}let a=(c,u,d)=>su(o.root,(f,p)=>{let h=Tr(c),m=Nh(h,u),y=d==null?null:Er(d);return y&&(y=[...Vt(f,bf(o,f)),...no].find(w=>w.toLowerCase()===y.toLowerCase())||y),f[h][m]={space:y,at:p.at,by:p.by},y},{machine:o.machine});if(r==="set-machine"){let[c,u]=s.length>=3?[s[1],s.slice(2).join(" ")]:[o.machine,s[1]];if(!u)throw new Error("usage: leftoff spaces set-machine [mac] <space>");let d=a("machine",c,u);return i("✓ "+c+" is in "+d+" (its sessions count as "+d+" unless a repo or session says otherwise)"),0}let l={"set-repo":"repo","set-session":"session","set-agent":"agent"}[r];if(l){if(!s[1]||!s[2])throw new Error("usage: leftoff spaces "+r+" <"+l+"> <space>");let c=a(l,s[1],s.slice(2).join(" "));return i("✓ "+l+" "+s[1]+" → "+c),0}if(r==="unset"||r==="rm"||r==="remove"){if(!s[1]||!s[2])throw new Error("usage: leftoff spaces unset machine|repo|session|agent <key>");a(s[1],s.slice(2).join(" "),null);let c=Tr(s[1]);return i("✓ "+s[1]+" "+s.slice(2).join(" ")+(c==="machines"?" is back to its own name as its space":" override removed")),0}return i(aw),1}catch(a){return console.error("✗ "+a.message),1}}function h1(t,{env:e=process.env,platform:n=process.platform}={}){let s=e.LEFTOFF_CLIPBOARD_CMD,o=s?[["sh",["-c",s]]]:n==="darwin"?[["pbcopy",[]]]:n==="win32"?[["clip",[]]]:[["wl-copy",[]],["xclip",["-selection","clipboard"]],["xsel",["--clipboard","--input"]]];for(let[r,i]of o){let a=f1(r,i,{input:t,stdio:["pipe","ignore","ignore"],timeout:5e3});if(!a.error&&a.status===0)return r}return null}async function dw({argv:t=[],api:e=null,out:n=s=>process.stdout.write(s)}={}){let{flags:s}=lw(t),o=cw(),{map:r}=bs(o.root),i=typeof s.space=="string"?s.space:Hn(r,o.machine);(s.all||/^all$/i.test(i))&&(i=null);let a=typeof s.date=="string"?s.date:null;if(a&&!/^\d{4}-\d{2}-\d{2}$/.test(a))return console.error("✗ --date is YYYY-MM-DD"),1;let l=null;if(e&&!s.offline)try{l=await e("GET","/v1/standup?"+new URLSearchParams({...i?{space:i}:{space:"all"},...a?{date:a}:{}}))}catch(c){if(!/fetch failed|ECONNREFUSED|aborted|timeout/i.test(c.message+(c.cause?c.cause.code:"")))return console.error("✗ "+c.message),1}if(!l){let c=pa(o.cfg.digest,jt()),u=Date.now(),d=null;try{d=new bt(o.root,o.machine).view({fresh:!0})}catch{}let f=i?Vt(r,bf(o,r)).find(p=>p.toLowerCase()===i.toLowerCase())||i:null;l={...el(o.folder.loadAll(),{now:u,date:a||ao(u,c).date,space:f,spaces:r,cfg:o.cfg,tz:c.tz,digestTime:c.time||"07:00",agents:d}),date:a||ao(u,c).date}}if(s.json)return n(JSON.stringify(l,null,2)+`
|
|
512
|
+
`),0;if(n(l.text+`
|
|
513
|
+
`),s.copy){let c=h1(l.text);console.error(c?"✓ Copied "+(l.space||"all spaces")+" standup to the clipboard":"· No clipboard tool found (pbcopy, wl-copy, xclip, xsel); copy the text above")}return 0}oc();ke();qn();oc();ki();fi();Zl();function p1(t){let e={},n=[];for(let s of t){let o=s.match(/^--([^=]+)(?:=(.*))?$/);o?e[o[1]]=o[2]===void 0?!0:o[2]:n.push(s)}return{flags:e,pos:n}}var vc=t=>{let e=Date.parse(t||"");if(!e)return"never";let n=Math.round((Date.now()-e)/6e4);return n<2?"just now":n<60?n+" min ago":n<2160?Math.round(n/60)+" h ago":Math.round(n/1440)+" days ago"};function fw(t){let e=[];if(!t.user)return e.push("Use from anywhere: off (no account yet)"),e.push(" Claim your name: leftoff relay signup <name> (yourname.leftoff.dev, an email link, free in the beta)"),e;e.push("Use from anywhere: "+(t.enabled?"on":"off")+" · "+t.host+(t.enabled?"":" (leftoff relay enable)")),t.enabled&&e.push(" Relay: "+(t.connected?"connected"+(t.latencyMs!=null?", "+t.latencyMs+" ms":""):"not connected"+(t.problem?": "+t.problem:""))),t.relayMachines&&t.relayMachines.length&&e.push(" Macs on it: "+t.relayMachines.join(", "));let n=t.list||[];e.push(" Paired devices: "+(n.length?n.length:"none yet (leftoff relay pair)"));for(let s of n)e.push(" "+s.name+" · "+s.id+" · paired "+vc(s.createdAt)+" on "+s.pairedOn+(s.lastSeenAt?" · last used "+vc(s.lastSeenAt):""));return t.enabled&&e.push(" Board: "+t.boardUrl),e}async function m1(t){let e=t["admin-token"]===!0?"-":t["admin-token"]||process.env.LEFTOFF_RELAY_ADMIN_TOKEN||"";if(e==="-"||!e&&!process.stdin.isTTY){let n=[];for await(let s of process.stdin)n.push(s);e=Buffer.concat(n).toString("utf8").trim()}else e||(process.stdout.write("Admin token (the ADMIN_TOKEN secret you set with wrangler): "),e=await new Promise(n=>{let s=process.stdin,o="";s.setRawMode&&s.setRawMode(!0),s.resume(),s.setEncoding("utf8");let r=i=>{i==="\r"||i===`
|
|
514
|
+
`||i===""?(s.setRawMode&&s.setRawMode(!1),s.pause(),s.off("data",r),process.stdout.write(`
|
|
515
|
+
`),n(o)):i===""?(process.stdout.write(`
|
|
516
|
+
`),process.exit(130)):i===""?o=o.slice(0,-1):o+=i};s.on("data",r)}));return String(e).trim()}function g1(){let t=null,e=async n=>(t||(t=(await import("node:readline")).createInterface({input:process.stdin,output:process.stdout})),new Promise(s=>t.question(n,s)));return e.close=()=>{t&&t.close(),t=null},e}async function hw({argv:t=[],api:e,out:n=o=>console.log(o),err:s=o=>console.error(o)}={}){let{flags:o,pos:r}=p1(t),i=r[0]||"status",a=q(),l=async(c,u,d)=>{try{return await e(c,u,d)}catch(f){throw/fetch failed|ECONNREFUSED/.test(String(f.message)+String(f.cause&&f.cause.code))?Object.assign(new Error("The Leftoff daemon isn’t running. Start it (leftoff setup), then try again."),{offline:!0}):f}};try{if(i==="signup"){Te(a);let c=Z(a),u=le(c),d=Mn(a),f=g1(),p=String(r[1]||"").trim().toLowerCase();if(!p){if(!process.stdin.isTTY)return s("Usage: leftoff relay signup <name> --email=you@example.com"),2;p=(await f("Your name (for yourname."+(d.domain||Nn)+"): ")).trim().toLowerCase()}let h=In(p);if(!h.ok)return f.close(),s(h.code==="reserved"?"“"+p+"” is reserved (www, app, api, docs, feed, relay, admin, status, mail). Pick another.":"A name is 2–32 lowercase letters, digits or hyphens, e.g. yourname for yourname.leftoff.dev."),2;let m={...d,user:h.user,domain:String(o.domain||d.domain||Nn)},y=xn(u),w=await vi({s:m,name:h.user,sign:y.signX});if(!w.ok&&w.code!=="legacy")return f.close(),s(w.message),w.code==="unreachable"||w.code==="closed"?1:2;if(w.ok&&w.status==="yours"){if(f.close(),n("✓ "+w.message),!(d.enabled&&d.user===h.user)){ls(a,{enabled:!0,user:h.user,domain:m.domain,registeredAt:d.registeredAt||new Date().toISOString(),via:d.via||"signup"});try{await e("POST","/v1/relay/enable",{user:h.user,domain:m.domain})}catch{}}return n(" Pair your phone: leftoff relay pair"),0}if(w.ok&&(w.status==="taken"||w.status==="pending"))return f.close(),s(w.message),s(" Yours on another Mac? leftoff relay enable "+h.user),1;w.ok&&w.claimed&&n(" "+w.host+" was claimed on leftoff.dev. Use the email you claimed it with.");let k=typeof o.email=="string"?o.email.trim():process.env.LEFTOFF_SIGNUP_EMAIL||"";if(!k){if(!process.stdin.isTTY)return f.close(),s("Add your email: leftoff relay signup "+h.user+" --email=you@example.com"),2;k=(await f("Email (for the confirmation link): ")).trim()}if(f.close(),!ql(k))return s("That doesn’t look like an email address."),2;let S=await xi({s:m,name:h.user,email:k,machine:u,sign:y.signX});if(!S.ok)return s(S.message),1;if(S.already)n("✓ This Mac already has "+Dt(m)+".");else{if(n("✉ Sent a link to "+S.email+". Open it and press Confirm (it works for 30 minutes)."),S.claimed&&n(" Your leftoff.dev claim for "+h.user+" carries over."),o["no-wait"])return n(" Not waiting (--no-wait). After you confirm: leftoff relay enable "+h.user),0;n(" Waiting for you to confirm… (Ctrl-C stops waiting; the link still works, then run: leftoff relay enable "+h.user+")");let v=!1,A=()=>{v=!0};process.once("SIGINT",A);let b=await jd({s:m,user:h.user,poll:S.poll,intervalMs:Number(process.env.LEFTOFF_SIGNUP_POLL_MS)||void 0,stop:()=>v});if(process.removeListener("SIGINT",A),b==="stopped")return n(`
|
|
517
|
+
Stopped waiting. Once you press Confirm: leftoff relay enable `+h.user),130;if(b==="expired"||b==="timeout")return s("The link expired before it was confirmed. Run `leftoff relay signup "+h.user+"` again for a fresh one."),1;if(b==="replaced")return s("A newer link was sent for "+h.user+"; this one no longer counts. Use the newest email (or run signup again)."),1;if(b!=="confirmed")return s("Lost track of the signup ("+b+"). Run `leftoff relay signup "+h.user+"` again."),1}ls(a,{enabled:!0,user:h.user,domain:m.domain,registeredAt:new Date().toISOString(),via:"signup"}),n("✓ "+Dt(m)+" is yours. Pair your phone: leftoff relay pair");try{await e("POST","/v1/relay/enable",{user:h.user,domain:m.domain}),n(" Use from anywhere is on; the daemon is connecting.")}catch{n(" Start the daemon (leftoff setup) and it connects on its own.")}return n(" Your other Macs: leftoff relay enable "+h.user+" (while this Mac is awake, it vouches for them)"),0}if(i==="register"){Te(a);let c=Z(a),u=le(c),d=In(r[1]||Mn(a).user||"");if(!d.ok)return s(r[1]?d.code==="reserved"?"“"+r[1]+"” is reserved (www, app, api, docs, feed, relay, admin, status, mail).":"A name is 2–32 lowercase letters, digits or hyphens.":"Usage: leftoff relay register <name> (your subdomain, e.g. yourname for yourname.leftoff.dev)"),2;let f={...Mn(a),user:d.user,domain:String(o.domain||Mn(a).domain||Nn)},p=await m1(o);if(!p)return s("The admin token is needed: the ADMIN_TOKEN secret you set with `pnpm exec wrangler secret put ADMIN_TOKEN` (docs/relay.md). No token? Use `leftoff relay signup "+d.user+"` instead."),2;let h=xn(u),m;try{m=await bi(f,"/_admin/register",{machine:u,sign:h.signX,replace:!!o.replace},p)}catch(y){return s("Couldn’t reach "+Dt(f)+": "+y.message+". Is the relay deployed and the DNS record in place? (docs/relay.md)"),1}if(m.status===401)return s("The relay refused the admin token."),1;if(m.status===409)return s(m.json.message||"This Mac is registered with another key. Add --replace to swap it."),1;if(m.status!==200)return s("The relay answered "+m.status+": "+(m.json.message||m.json.code||m.text.slice(0,200))),1;ls(a,{enabled:!0,user:d.user,domain:f.domain,registeredAt:new Date().toISOString()}),n("✓ "+u+" is on "+Dt(f)+" (Macs there: "+m.json.machines.join(", ")+")");try{await e("POST","/v1/relay/enable",{user:d.user,domain:f.domain}),n("✓ Use from anywhere is on; the daemon is connecting")}catch{n(" Start the daemon (leftoff setup) and it connects on its own.")}return n(" Next: leftoff relay pair (pair your phone), and turn on Use from anywhere on your other Mac: it joins on its own."),0}if(i==="status"){let c;try{c=await l("GET","/v1/relay")}catch(u){if(!u.offline)throw u;let d=Mn(a);c={...d,host:d.user?Dt(d):null,boardUrl:d.user?Ei(d):null,connected:!1,problem:"the daemon isn’t running",list:[]}}if(o.json)return n(JSON.stringify(c,null,2)),0;for(let u of fw(c))n(u);return 0}if(i==="enable"){let c=r[1]||Mn(a).user;if(!c)return s("Usage: leftoff relay enable <name> (the first time, on one Mac: leftoff relay signup <name>)"),2;let u=await l("POST","/v1/relay/enable",{user:c,...o.domain?{domain:o.domain}:{}});n("✓ Use from anywhere is on for this Mac: "+u.host),await new Promise(d=>setTimeout(d,1500));for(let d of fw(await l("GET","/v1/relay")))n(d);return 0}if(i==="disable")return await l("POST","/v1/relay/disable"),n("✓ Use from anywhere is off for this Mac (paired phones stay paired; they reach your other Macs)"),0;if(i==="pair"){let c=await l("POST","/v1/relay/pair");if(n("Scan this with your phone’s camera, or open "+c.link.split("#")[0]+" and type the code."),!o["no-qr"])try{n(jl(c.link))}catch{}return n(" Link: "+c.link),n(" Code: "+c.code+" (works once, for 10 minutes)"),0}if(i==="devices"){let c=await l("GET","/v1/relay");if(o.json)return n(JSON.stringify(c.list,null,2)),0;if(!c.list.length)return n("No paired devices. leftoff relay pair"),0;for(let u of c.list)n(u.id+" "+u.name+" · paired "+vc(u.createdAt)+" on "+u.pairedOn+(u.lastSeenAt?" · last used "+vc(u.lastSeenAt):"")+" · reaches "+u.macs.join(", "));return 0}if(i==="revoke"){if(!r[1])return s("Usage: leftoff relay revoke <device id> (ids: leftoff relay devices)"),2;let u=(await l("GET","/v1/relay")).list.find(f=>f.id===r[1]||f.id.startsWith(r[1])||f.name.toLowerCase()===r[1].toLowerCase());if(!u)return s("No paired device matches "+r[1]),1;let d=await l("POST","/v1/relay/devices/"+u.id+"/revoke");return n("✓ Revoked "+u.name+(d.relay==="done"?" (the relay refuses it now)":" (the relay hears it when this Mac reconnects; your Macs refuse it already)")),0}return s("Usage: leftoff relay [status|signup [name] [--email=you@x]|enable [name]|disable|pair|devices|revoke <id>|register <name>]"),2}catch(c){return s(c.message),1}}vs();tf();eo();ni();Io();As();Ld();var xf=fs.resolve(fs.dirname(y1(import.meta.url)),".."),Yt=process.argv.slice(2),Aw=Yt[0],ce={},st=[];for(let t of Yt.slice(1)){let e=t.match(/^--([^=]+)(?:=(.*))?$/);e?ce[e[1]]=e[2]===void 0?!0:e[2]:st.push(t)}async function Oe(t,e,n){let s=Z(),o=await fetch("http://127.0.0.1:"+s.port+e,{method:t,headers:{authorization:"Bearer "+(Se()||""),"content-type":"application/json"},body:n?JSON.stringify(n):void 0,signal:AbortSignal.timeout(t==="GET"?2e3:9e4)}),r=await o.json().catch(()=>({}));if(!o.ok)throw new Error(r.error&&r.error.message||"HTTP "+o.status);return r}async function w1(){let t=q();Te(t);let e=Z(t);ce.port&&(e.port=Number(ce.port));let n=i=>console.log(new Date().toISOString()+" "+i);for(let i of oh(t))n("moved "+i+" out of the folder to "+fs.join(Jt(),i)+" (keys stay on this Mac)");let s=new Rl({root:t,cfg:e,log:n}).load().start();s.relayHost=new Ci({engine:s,root:t,port:e.port,log:n}),await s.startPeers();let o=Ey(s,{root:t,port:e.port});try{await o.listen()}catch(i){console.error(i.code==="EADDRINUSE"?"Port "+e.port+" is in use. Is the daemon already running? Try: leftoff status":i.message),process.exit(1)}n("leftoff "+J+" listening on http://127.0.0.1:"+e.port+" · folder "+t+" · summarizer "+s.summarizer.mode),await s.relayHost.start().catch(i=>n("relay: "+i.message));let r=async()=>{n("shutting down"),s.stop(),s.relayHost.stop(),await s.stopPeers().catch(()=>{}),await o.close(),process.exit(0)};process.on("SIGINT",r),process.on("SIGTERM",r)}async function S1(){let t=Te();if(console.log("✓ folder ready: "+t.root+" (config.json, token)"),ce.hooks){let e=Do();console.log("✓ settings hooks added to "+e+" (a backup sits next to it)")}if(ce.service){let e=xt();$t.mkdirSync(fs.dirname(e),{recursive:!0}),$t.writeFileSync(e,Tn({root:q()})),console.log("✓ login service written: "+e),console.log(" enable it with: "+Zm())}console.log(""),console.log("Next:"),ce.service||console.log(" 1. start the daemon: leftoff serve (or re-run init with --service)"),console.log(" 2. load the mod: claude --plugin-dir "+fs.join(xf,"mod")+" (one session)"),console.log(" or install it: claude plugin marketplace add "+xf+" && claude plugin install leftoff@checkpoints"),ce.hooks||console.log(" no mods allowed? leftoff init --hooks (settings-hook fallback)"),console.log(" 3. open the UI: leftoff open (http://127.0.0.1:"+Z().port+"/)")}async function k1(){try{let t=await Oe("GET","/v1/health"),e=typeof ce.repo=="string"?ce.repo:ce.repo===!0?st[0]:null,{sessions:n}=await Oe("GET","/v1/sessions?limit="+(e?300:30));e&&(n=e.split(",").flatMap(r=>fa(n,r.trim())).filter((r,i,a)=>a.indexOf(r)===i).slice(0,30)),console.log("daemon up · v"+t.version+" · "+t.sessionsOpen+" open · spool "+t.spoolPending+" · summarizer "+t.summarizer);let s=Z(),o=await wi({port:s.port,prefer:process.env.CHECKPOINTS_OPEN_HOST||s.openHost||"auto",token:Se()});console.log("board "+o.url+(o.pretty?" (or http://127.0.0.1:"+s.port+"/)":"")+" · reopen it with: leftoff open"),t.remote&&t.remote.enabled?console.log("remote "+t.remote.urls.join(" ")+" (tailnet"+(t.remote.requireToken?", one-time login links":", no login")+") · leftoff tailscale status"):console.log("remote off · to open the board from your phone or other machines: leftoff tailscale on"),t.update&&t.update.available&&console.log("! update available: "+(t.update.remoteVersion?"v"+t.update.remoteVersion:t.update.remoteSha)+" · run: leftoff upgrade"),t.mod&&t.mod.failed&&t.mod.failed.length&&console.log("! mod commands failed to register: "+t.mod.failed.map(r=>"/"+r.want).join(", ")+" (see: leftoff doctor)"),e&&console.log(n.length?"repo "+[...new Set(n.map(r=>r.repoLabel||r.repoName))].join(", ")+" · "+n.length+" session"+(n.length===1?"":"s"):'no sessions in a repo matching "'+e+'"');for(let r of n){let i=r.live==="open"?"●":r.live==="closed"?"○":"·",a=r.live==="closed"?"closed "+Ri(r.closedAt):r.terminal?r.terminal.label:"";console.log(" "+i+" "+r.status.padEnd(11)+" "+(r.title||"(untitled)").slice(0,44).padEnd(44)+" "+String(r.repoLabel||r.repoName).padEnd(16)+" cp:"+(r.shortId||r.id.slice(0,6))+(a?" "+a:""))}n.some(r=>r.live==="open"&&r.jump&&r.jump.supported)&&console.log("jump to one: leftoff go [name]");try{let r=await Oe("GET","/v1/restore");r.sessions&&r.sessions.length&&console.log(r.sessions.length+" session"+(r.sessions.length===1?" was":"s were")+" open before the restart · put them back: leftoff restore")}catch{}}catch(t){console.log("daemon not reachable ("+t.message+"). Start it with: leftoff serve"),process.exitCode=1}}function b1(t){let e=s=>s?new Date(s).toLocaleString([],{weekday:"short",hour:"numeric",minute:"2-digit"}):"?";if(t.lastError)return"✗ digest: the last one failed "+e(t.lastError.at)+" ("+t.lastError.message+"); retried in 10 min, or run: leftoff digest";let n=t.today?"today's made "+e(t.today.generatedAt)+(t.today.by?" by "+t.today.by:"")+" ("+(t.today.sessions!=null?t.today.sessions+" session"+(t.today.sessions===1?"":"s"):"?")+")":t.todayNote&&/^waiting for /.test(t.todayNote)?"none yet today; "+t.first+" makes it, this Mac at "+e(t.nextAt)+" if not":"none yet today";return(t.today?"✓ ":"· ")+"digest: "+n+(t.time?" · next automatic "+e(t.nextAt):" · automatic digests off (digest.time is null)")}async function v1(){let t=typeof ce.date=="string"?ce.date:void 0;if(!ce.offline)try{let a=await Oe("POST","/v1/digests",{date:t});process.stdout.write(a.markdown);return}catch(a){if(!/fetch failed|ECONNREFUSED|aborted|timeout/i.test(a.message+(a.cause?a.cause.code:"")))throw a}let e=q();Te(e);let n=Z(e),s=new mt(e,le(n)),o=(()=>{try{return new bt(e,le(n)).view({fresh:!0})}catch{return null}})(),{meta:r,markdown:i}=await Xa(s.loadAll(),{cfg:n,date:t,machine:le(n),summarizer:new gt(n),agents:o,spaces:bs(e).map});s.writeDigest(r.date,i),process.stdout.write(i)}async function pw(){process.exitCode=uw({argv:Yt.slice(1)})}async function x1(){process.exitCode=await dw({argv:Yt.slice(1),api:Oe})}async function mw(){process.exitCode=await hw({argv:Yt.slice(1),api:Oe})}async function gw(){process.exitCode=await ew({argv:Yt.slice(1),api:Oe})}async function yw(){process.exitCode=await Sy({argv:Yt.slice(1),api:Oe})}async function A1(){process.exitCode=await ky({argv:Yt.slice(1)})}async function T1(){process.exitCode=await qd({argv:Yt.slice(1)})}async function ww(){let t=st[0];if(t==="allow"||t==="deny"){let n=await Oe("POST","/v1/peers/allow",{allow:t==="allow"});console.log((n.allowIncoming?"✓ Other machines may now answer and Go on ":"✓ Other machines may no longer answer or Go on ")+n.machine+(n.listening?" (listening on port "+n.listening+")":""));return}if(t==="trust"){if(!st[1])throw new Error("usage: leftoff machines trust <machine>");await Oe("POST","/v1/peers/"+encodeURIComponent(st[1])+"/trust"),console.log("✓ Trusting "+st[1]+"’s new key");return}let e=await Oe("POST","/v1/peers/check");if(ce.json){console.log(JSON.stringify(e,null,2));return}console.log("This machine: "+e.machine+" key "+e.fingerprint+" · other machines may answer and Go here: "+(e.allowIncoming?"yes":"no (leftoff machines allow)")),e.relay&&console.log("Relay: "+e.relay.url+(e.relay.connected?" (connected)":" (not connected)")),e.peers.length||console.log("No other machines in this folder yet. Run `leftoff move <synced folder>` here and on the other Mac.");for(let n of e.peers)console.log(" "+n.machine.padEnd(16)+(n.usable.ok?"✓ can answer and Go there ("+n.usable.via+")":"· "+n.usable.reason)+" key "+n.fingerprint+(n.trust==="key_changed"?" KEY CHANGED: leftoff machines trust "+n.machine:""))}async function Sw(){process.exitCode=await iw({argv:Yt.slice(1),api:Oe})}async function kw(){process.exitCode=await tw({argv:Yt.slice(1),api:Oe})}async function _1(){let t=st[0];if(!t)throw new Error("usage: leftoff checkpoint <session-id> [--short|--informative|--long] [note]");let e=ce.short?"short":ce.long?"long":ce.informative?"informative":ce.length||void 0,n=await Oe("POST","/v1/sessions/"+encodeURIComponent(t)+"/checkpoints",{trigger:"manual",generate:!0,length:e,userNote:st.slice(1).join(" ")||void 0});console.log(Wl(n))}async function E1(){let t=q(),e=we(t),n=Z(t),s=(c,u)=>console.log((c?"✓ ":"✗ ")+u);s($t.existsSync(e.root),"folder "+e.root),s(!!Se(t),"token "+e.token);let o=null;try{o=await Oe("GET","/v1/health"),s(!0,"daemon on 127.0.0.1:"+n.port+" (v"+o.version+", summarizer "+o.summarizer+", spool "+o.spoolPending+")")}catch(c){s(!1,"daemon on 127.0.0.1:"+n.port+": "+c.message)}let r=o&&o.mod;if(!o)try{r=JSON.parse($t.readFileSync(fs.join(t,".mod-health."+le(n).replace(/[^\w.-]/g,"_")+".json"),"utf8"))}catch{}for(let c of L1(r))console.log(c);if(o&&o.update){let c=o.update;c.available?console.log("! update available: v"+J+" → "+(c.remoteVersion?"v"+c.remoteVersion:c.remoteSha)+" · run: leftoff upgrade"):c.error?console.log("· update check: couldn't reach the repo ("+c.error+")"):c.checkedAt?console.log("✓ up to date (v"+J+", checked "+c.checkedAt.replace("T"," ").slice(0,16)+" UTC)"):console.log("· update check: "+(c.enabled===!1?"off (Settings)":"not run yet"))}let i="";try{i=$t.readFileSync(tn(),"utf8")}catch{}let a=i.includes(Pr)||i.includes(Cs);if(console.log((a?"✓ ":"· ")+"settings hooks "+(a?"installed":"not installed (fine if the mod is loaded)")),i.includes(Cs)&&console.log("! settings hooks are from before v0.4.5 (they still work); refresh them with: leftoff upgrade --force"),$t.existsSync(Ro())&&console.log("! the login service still has its old name ("+Ro().replace(vf.homedir(),"~")+"); move it with: leftoff upgrade --force"),console.log('· mod: run /plugin in Claude Code and look for "'+Ce+'" (before v0.4.5: checkpoints@checkpoints; leftoff upgrade moves it); in a session type /cp, /cp-name or /leftoff'),console.log("· summarizer mode: "+new gt(n).mode),o&&o.digest&&console.log(b1(o.digest)),o&&o.sessions)for(let c of await C1(o.sessions))console.log(c);console.log(mf(pf({port:n.port,cfgHosts:n.remoteHosts||[]}),{cfgHosts:n.remoteHosts||[]}));try{let c={ok:"✓ ",warn:"! ",info:"· "};for(let[u,d]of ef(await qt({root:t}),jn(It())))console.log(c[u]+d)}catch(c){console.log("· Syncthing: couldn't check ("+c.message+")")}let l=(()=>{try{return $t.readdirSync(e.secret).filter(c=>/-api-key$/.test(c))}catch{return[]}})();try{let c=null;if(o)try{c=await Oe("GET","/v1/agents")}catch{}c||(c=bc(new bt(t,le(n)))),c.agents.length||console.log("· agents: none (add a feed: leftoff agents add <name> <url> --key <key>)");for(let u of c.agents){let d=kf(u);console.log((d.ok===!1?"✗ ":d.ok?"✓ ":"· ")+"agent "+d.line);for(let f of d.details.slice(0,3))console.log(" - "+f)}}catch(c){console.log("· agents: couldn't check ("+c.message+")")}l.length&&console.log("! API key files inside the folder ("+l.join(", ")+"): the daemon moves them to "+Jt().replace(vf.homedir(),"~")+" at its next start");for(let c of await O1(o))console.log(c)}async function C1(t){let e=[];if(t.ghosts){let n="";try{let{sessions:s}=await Oe("GET","/v1/sessions?limit=500");n=s.filter(o=>o.ghost).slice(0,5).map(o=>o.shortId+" ("+(o.live==="open"?"open, heartbeating":o.live)+")").join(", ")}catch{}e.push("! "+t.ghosts+(t.ghosts===1?" session is":" sessions are")+" known only by id (no folder, no process, no turns)"+(n?": "+n:"")+`. Usually one that was open with no prompt yet when an older daemon restarted. The daemon keeps looking it up; it's folded under "empty sessions" on the board and shows as itself after its next prompt.`)}return t.empty-(t.ghosts||0)>0&&e.push("· empty sessions (open, no prompt yet): "+(t.empty-(t.ghosts||0))+", folded into one row on the board"),t.healed&&e.push("✓ filled in "+t.healed+(t.healed===1?" session":" sessions")+" the daemon knew only by id (folder, process or terminal)"),t.archivedGhosts&&e.push("✓ archived "+t.archivedGhosts+" empty "+(t.archivedGhosts===1?"session":"sessions")+" with no folder or process (v0.4.8 cleanup"+(t.lastArchivedAt?", "+t.lastArchivedAt.replace("T"," ").slice(0,16)+" UTC":"")+"); their files are kept, marked archived, not deleted"),e.length||e.push("✓ no empty or id-only sessions"),e}async function O1(t){let e=[],n=qy(process.env),s=n.term;if(e.push((n.supported?"✓ ":"· ")+"terminal: "+(s.label||s.kind)+(n.supported?" (Go can focus its tabs)":" (Go: "+n.reason+")")),N1()===!1&&e.push("· tab titles: off (config tabTitle: false), so Go matches Ghostty tabs by folder only"),(process.env.CHECKPOINTS_JUMP_PLATFORM||process.platform)!=="darwin"||!["ghostty","iterm2","apple-terminal"].includes(s.kind==="tmux"?s.host:s.kind))return e;let r=s.kind==="tmux"?s.host:s.kind;if(ce["no-prompt"])return e;let i=await wl(r,{timeoutMs:8e3});if(e.push((i.ok?"✓ ":"✗ ")+"automation from this terminal: "+i.message),t)try{let a=await Oe("GET","/v1/jump/check?kind="+r);e.push((a.ok?"✓ ":"✗ ")+"automation from the daemon (the board's Go button): "+a.message)}catch(a){e.push("· automation from the daemon: "+a.message)}if(r==="ghostty"&&e.push(...await I1(t,s)),process.platform==="darwin"){let a=xt(),l=null;try{l=$t.readFileSync(a,"utf8")}catch{}l!=null&&!/<key>ProcessType<\/key>\s*<string>Interactive<\/string>/.test(l)&&e.push("! login service is from before v0.4.1, so macOS throttles the daemon and the board's Go is slow. Fix: leftoff upgrade --force (rewrites "+a.replace(vf.homedir(),"~")+" and restarts it)")}return e}async function I1(t,e){let n=[],s=i=>i.steps.map(a=>a.step+" "+a.ms+" ms"+(a.result&&a.result!=="ok"?" ("+a.result+")":"")).join(" · "),o=i=>i?(i.tty?" · tty lookup on":" · tty lookup off")+(i.version?" (Ghostty "+i.version+")":""):"",r=await hl({hint:{resourcesDir:e.resourcesDir,version:e.version}});if(n.push((r.ok?"✓ ":"✗ ")+"Go speed from this terminal: "+s(r)+o(r.caps)+(r.ok?"":" · "+(r.stderr||r.code))),t)try{let i=await Oe("GET","/v1/jump/speed"),a=i.steps.some(l=>l.ms>1500);n.push((i.ok&&!a?"✓ ":i.ok?"! ":"✗ ")+"Go speed from the daemon: "+s(i)+(i.cache?" · remembers "+i.cache.remembered+" of "+i.cache.sessions+" Ghostty sessions":"")+(i.ok?"":" · "+(i.stderr||i.code))),a&&n.push(" the daemon is much slower than this terminal: see the login service line below, or run the daemon in a terminal (`leftoff serve`) to compare")}catch(i){n.push("· Go speed from the daemon: "+i.message)}return n}function N1(){try{return Z().tabTitle}catch{return!0}}async function bw(){process.exitCode=await Jy({pos:st,flags:ce,api:Oe})}async function M1(){if(ce.help){console.log(Vy);return}process.exitCode=await Zy({pos:st,flags:ce,api:Oe,platform:process.env.CHECKPOINTS_JUMP_PLATFORM||process.platform})}function L1(t){if(!t||!Array.isArray(t.commands))return["· mod commands: no report yet (start a Claude Code session with the mod loaded)"];let e=t.commands.filter(r=>r.ok).map(r=>"/"+r.name),n=t.commands.filter(r=>!r.ok),s=t.at?" (session "+String(t.sessionId||"").slice(0,8)+", "+t.at.replace("T"," ").slice(0,16)+" UTC)":"",o=[];e.length&&o.push("✓ mod commands registered: "+e.join(", ")+s);for(let r of n)o.push("✗ mod command /"+r.want+" failed to register: "+(r.error||"unknown error"));return o}async function D1(){let t=Il();console.log("removed "+t+" settings hook(s) from "+tn());for(let e of[xt(),Ro()])$t.existsSync(e)&&($t.unlinkSync(e),console.log("removed "+e+" (stop it: "+(process.platform==="darwin"?"launchctl unload "+e:"systemctl --user disable --now "+fs.basename(e))+")"));console.log("mod: claude plugin uninstall "+Ce),ce.purge?($t.rmSync(q(),{recursive:!0,force:!0}),console.log("deleted "+q())):console.log("kept your folder: "+q()+" (pass --purge to delete it)")}function xc(){let t=globalThis.LEFTOFF_INVOKED_AS==="checkpoints"?"\n(`checkpoints` is the old name of `leftoff`; both commands do the same thing.)\n":"";console.log(`leftoff ${J} · WTF was I working on? Track every Claude Code session, checkpoint the quiet ones
|
|
518
|
+
${t}
|
|
519
|
+
leftoff setup guided install: the mod, a summarizer (local models too), the daemon
|
|
520
|
+
[--yes] [--dir=~/checkpoints] [--machine=NAME] [--capture=mod|hooks|none]
|
|
521
|
+
[--summarizer=claude|ollama|lmstudio|ollama-cloud|anthropic|heuristic] [--model=TAG]
|
|
522
|
+
[--base-url=URL] [--api-key-env=NAME] [--install-ollama] [--no-pull] [--no-test]
|
|
523
|
+
[--service|--no-service] [--no-start] [--open|--no-open] [--marketplace=SRC]
|
|
524
|
+
[--summary-length=short|informative|long]
|
|
525
|
+
leftoff models [list|cloud|pull <tag>|use <tag> [--cloud]|test|pick] models for the summarizer
|
|
526
|
+
leftoff init [--hooks] [--service] create ~/checkpoints; optionally add settings hooks / login service
|
|
527
|
+
leftoff serve [--port=7429] run the daemon (API + web UI on 127.0.0.1)
|
|
528
|
+
leftoff status [--repo=NAME] sessions, their status, and the board URL; --repo shows one repo
|
|
529
|
+
(folder name or part of it; a,b for several)
|
|
530
|
+
leftoff open [--print] [--host=auto|leftoff.localhost|127.0.0.1]
|
|
531
|
+
open the board (starts the daemon if needed)
|
|
532
|
+
leftoff go [name|id] [--list] [--repo=NAME]
|
|
533
|
+
jump to an open session's terminal tab (Ghostty, iTerm2, WezTerm,
|
|
534
|
+
kitty, tmux, Terminal.app); no name: pick from a list (typing
|
|
535
|
+
filters by title or repo); --repo limits it to one repo;
|
|
536
|
+
--debug prints how long each step took
|
|
537
|
+
leftoff restore [name|id] [--all|--yes] [--dry-run] [--since=24h] [--repo=NAME] [--print]
|
|
538
|
+
after a restart, reopen the sessions that were open before it:
|
|
539
|
+
each in a new Ghostty tab, in its folder, claude --resume <id>
|
|
540
|
+
with its name; no name: pick from the list (all checked)
|
|
541
|
+
leftoff relay [status|enable|disable|pair|devices|revoke <id>]
|
|
542
|
+
Use from anywhere: your board at <you>.leftoff.dev, no Tailscale
|
|
543
|
+
relay signup [name] [--email=you@x] claim <name>.leftoff.dev with an email link (no admin token)
|
|
544
|
+
relay register <name> [--admin-token=…] first setup on your own relay (docs/relay.md)
|
|
545
|
+
leftoff tailscale [on|off|status|link|revoke]
|
|
546
|
+
view the board from your phone over your tailnet (tailscale
|
|
547
|
+
serve, https; Funnel/public internet is intentionally unsupported)
|
|
548
|
+
leftoff link [--qr|--no-qr] sign a phone in: a QR code (Safari) and a typed code (the
|
|
549
|
+
home-screen app), each single-use, 15 minutes
|
|
550
|
+
leftoff upgrade [--check] [--force] [--verbose]
|
|
551
|
+
update the CLI and the mod, restart the daemon, show what changed
|
|
552
|
+
(one line per step; --verbose prints everything, as before 0.6.0)
|
|
553
|
+
leftoff changelog [--since=0.2.0] what's new, in the terminal
|
|
554
|
+
leftoff digest [--date=YYYY-MM-DD] [--offline]
|
|
555
|
+
leftoff catchup [--since 24h|3d|workday|friday|DATE] [--repo NAME]... [--all] [--machine NAME]
|
|
556
|
+
[--to DATE] [--print] [--json] [--list]
|
|
557
|
+
"catch me up": one recap across repos and a window (default: since
|
|
558
|
+
your last catch-up, else 24h), saved to catchups/ (--print: no file)
|
|
559
|
+
leftoff spaces [list|set-machine|set-repo|set-session|set-agent|unset]
|
|
560
|
+
Work / Personal: each Mac's default space, per-repo overrides
|
|
561
|
+
spaces set-machine [mac] Work · spaces set-repo <repo> Work · spaces unset repo <repo>
|
|
562
|
+
leftoff standup [--space Work] [--date YYYY-MM-DD] [--copy]
|
|
563
|
+
Yesterday / Today / Blocked for one space, ready for Slack
|
|
564
|
+
(default: this Mac's space; --copy puts it on the clipboard)
|
|
565
|
+
leftoff todo [--all] your saved checklists (lists/ in the folder); also:
|
|
566
|
+
todo done <list> <n…> · todo add <list> "<text>" · todo show|rename|archive <list>
|
|
567
|
+
todo capture <session> [--pick [N]] [--name NAME] save a list Claude wrote in a session
|
|
568
|
+
leftoff agents [add|remove|refresh] external agents (Tab, later others) on your board: their feeds,
|
|
569
|
+
agents add <name> <url> --key <key|-> who pulls each, last success or error
|
|
570
|
+
agents remove <name> [--purge] · agents refresh [name]
|
|
571
|
+
agents keygen a read + write key and their sha256, to host your own feed
|
|
572
|
+
agents publish <file> --url <url> --write-key <key|-> validate a feed and upload it
|
|
573
|
+
agents validate <file> (guide: docs/agent-feed-hosting.md)
|
|
574
|
+
leftoff move <folder> [--dry-run] move the folder (e.g. into Syncthing/Dropbox/iCloud) or join one
|
|
575
|
+
another Mac uses: copies, verifies, keeps the old one as a backup
|
|
576
|
+
move --backups · move --delete-backup [PATH]
|
|
577
|
+
leftoff sync [install|start|setup|pair|stop] Syncthing without opening Syncthing: install it with
|
|
578
|
+
Homebrew (asks first), put the folder in it, pair another Mac
|
|
579
|
+
leftoff join <code> on your other Mac: set up Syncthing, pair by code, sync, join
|
|
580
|
+
leftoff machines [allow|deny|trust <machine>] other machines; let them answer and Go here (off by default)
|
|
581
|
+
leftoff checkpoint <session-id> [--short|--long] [note]
|
|
582
|
+
leftoff hook <Event> settings-hook entry point (reads stdin)
|
|
583
|
+
leftoff doctor
|
|
584
|
+
leftoff uninstall [--purge]
|
|
585
|
+
`)}async function R1(){let t=await Jd({flags:ce});t.ok||(process.exitCode=t.cancelled?130:1)}async function vw(){process.exitCode=await Cg({flags:ce,bin:fs.join(xf,"bin","leftoff.js")})}async function j1(){process.exitCode=await Fy({flags:ce})}async function xw(){process.exitCode=await Py({flags:ce})}async function P1(){if(ce.help){console.log(Di());return}process.exitCode=await gf({sub:"link",flags:ce})}async function F1(){if(ce.help){console.log(Di());return}process.exitCode=await gf({sub:st[0]||"status",flags:ce})}async function U1(){process.exitCode=await Ny({sub:st[0],args:st.slice(1),flags:ce})}var H1={setup:R1,models:U1,go:bw,jump:bw,restore:M1,tailscale:F1,link:P1,upgrade:j1,changelog:xw,"whats-new":xw,open:vw,board:vw,serve:w1,init:S1,status:k1,digest:v1,catchup:gw,"catch-up":gw,todo:kw,todos:kw,spaces:pw,space:pw,standup:x1,relay:mw,anywhere:mw,agents:Sw,agent:Sw,move:T1,sync:yw,syncthing:yw,join:A1,machines:ww,peers:ww,checkpoint:_1,doctor:E1,uninstall:D1,hook:async()=>{st[0]==="PermissionRequest"?(await Iy(),process.exit(0)):await Oy(st[0])},help:xc,"--help":xc,"-h":xc,version:()=>console.log(J),"--version":()=>console.log(J)},B1=H1[Aw]||xc;Promise.resolve(B1()).catch(t=>{console.error((globalThis.LEFTOFF_INVOKED_AS||"leftoff")+": "+t.message),process.exit(Aw==="hook"?0:1)});
|