@leftoff/cli 0.9.18-beta.10 → 0.9.18-beta.12

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/CHANGELOG.md CHANGED
@@ -4,7 +4,15 @@ What changed in Leftoff (called Checkpoints until 0.4.5), newest first. The boar
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ - **Walk me through any one PR.** When a session opened several PRs, each one now has its own range in Walk me through it. Up to two show as chips; with more, one PRs chip opens a list with each PR's state. A PR's walkthrough reads only that PR's own commits, whatever branch the folder has checked out. If its branch is checked out somewhere, uncommitted work counts too. When the branch isn't on this Mac, or the PR is merged, it reads GitHub's diff with `gh pr diff`. Each PR row in the session panel and on the PRs page has one Open button that asks where: Walk me through it in Leftoff, or GitHub. Test plans use each PR's own walkthrough as well. An opened PR's details in the session panel now use the panel's full width. Needs `leftoff restart`, then reload the board.
8
+ - **Slash commands from the board.** Type / in a session's message box and pick a command: /model, /effort, /compact, /clear, /context or /usage. /model and /effort then list their choices. The command runs in the session, and its bubble shows what Claude Code printed. In a session Leftoff runs, /model and /effort apply to that session only. In a session in your own terminal they're greyed out, because there Claude Code also saves them as your default for new sessions. Run them in that terminal instead. Needs `leftoff restart`, then `leftoff upgrade --beta` and a restart of Claude Code for terminal sessions.
9
+ - **Decisions you already made don't wait for a second yes.** A question you answered in the session, or a plan you approved, now goes into the decision log as Accepted, marked "automatically", with a Reject next to it. If you answer the same question differently later in the session, the earlier answer is rejected on its own. A one-off choice about the task in front of you (who does a step, whether to push now) isn't recorded at all. The agent's own trade-offs and corrections still wait for you, and so does an answer that would change a decision you accepted before. Your own Accept or Reject is never overridden. Needs `leftoff restart` and `leftoff upgrade --beta`.
10
+ - **https://leftoff.localhost works next to another app's Caddy.** When port 443 already belongs to another Caddy (one serving another app's https://<name>.localhost, say), `leftoff setup` no longer stops at "Something else is using port 443". It writes Leftoff's site to a file and gives you the one `import` line to add to that Caddy's Caddyfile, with the reload command. The daemon then runs no Caddy of its own, and you don't need Caddy installed yourself. When something else holds the port, setup and `leftoff doctor` now name it ("Tailscale (pid 1689)"), and macOS no longer gets a Linux-only fix. Needs `leftoff restart`, then `leftoff setup`.
11
+ - **Tab titles get the room.** A tab's status shape (spinner, !, dot) now sits where its × is, and the × comes back when you point at the tab. The words "Working", "Needs you" and the rest moved to the tab's tip. With many tabs open, the one you're on keeps its full width, the others stay readable and the strip scrolls with a fade where more tabs continue, and a pinned tab shrinks to its pin and status. Reload the board.
12
+ - **Regenerated titles no longer show Leftoff's own message tag.** With some models (Haiku, for one), a new title for a session you talk to from the board could come out as `<channel source="leftoff" …>`. The model now sees only your words, and a reply that still carries the tag is cleaned up. Needs `leftoff restart`, which also renames a session that already got such a title.
13
+ - **The phone's session sheet stays out of your way.** While you type in the reply box, the sheet with checkpoints, What it did and Details steps aside and comes back a beat after the keyboard closes, as it was. Before, it only stepped aside when the board measured a keyboard's worth of lost height, which some keyboards (a third-party one coming up late or in steps) never report, so a raised sheet could sit over the reply box. The sheet also scrolls back up now: in full screen a finger moving down inside it was taken as a pull to refresh, which cancelled the scroll and reloaded the board (the jump), and its head no longer changes height as it scrolls. Drag the raised sheet down from its peek, or from its content scrolled to the top, to lower it; drag the peek up to raise it.
7
14
  - **Decisions show where you look.** A session's panel lists every decision it made, with its status and Accept / Reject right there, even after a newer checkpoint; a repo outside a project gets its own Decisions section and a ◌ count on its chip, and an empty log says where decisions come from. The checkpoint fold now names what's inside (approach notes, files…), and another tool's 401 or 403, such as an MCP server's, no longer reads as Claude signed out. Needs `leftoff restart`.
15
+ - **A file named in a message opens the file the sidebar opens.** A relative path Claude mentions was looked for in one folder only (the session's worktree, else its repo root, else its folder), so a path said from a parent folder ("app/docs/specs/a.md" in a session whose folder is app/), or a file in the checkout while the session had moved into its own worktree, said “Not found” while the same file opened from Files. Now it is looked for in the session's folder, its worktree and its repo root, without a repeated folder name, then matched against the session's own Files list; every candidate still passes the same allowed-folders and no-secrets check. “Not found” now names the path and where it looked.
8
16
  - **Session names say what the session is doing.** The name is the summarizer's title, or else the first prompt tidied deterministically: no leading “Please”/“Can you”/“Hey”, a pasted GitHub link read as “PR 408”, a capital first letter, cut at a word with “…” and never on a dangling “and”/“the”/“to”. A “folder@branch · …” stuck to the front of a name by whatever named it comes off; folder@branch is now a small muted second line under the name (full folder in its tip) in full screen's bar, on the session page and in its panel, and the digest uses the same name. A session whose only name is such a stand-in gets a generated one after its first turn, as a first-prompt name does.
9
17
  - **Fewer false "isn't signed in" warnings.** A login date in the past from Claude Code no longer turns the banner red on its own; only a turn that actually failed does. An MCP server that couldn't sign in, or a differently worded sign-in reminder, no longer reads as Claude being signed out, and Claude Code's config folder can have a space in its path. Needs `leftoff restart`.
10
18
  - **leftoff ↗ shows in Claude Code's status line again.** The npm package was missing the small script the status line runs, so nothing showed at the bottom of Claude Code, and `leftoff doctor` still said it was fine. The script ships now, and doctor says when the status line points at a file or a Node that's gone. Run `leftoff upgrade --beta`; your status line setting stays as it is.
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import i from"node:fs";import c from"node:os";import l from"node:path";import{spawnSync as U}from"node:child_process";function O(e,t=40){return String(e||"").toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g,"").replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,t).replace(/-+$/g,"")||"untitled"}function g(e){return O(String(e||"").replace(/\.local$/i,""),40)||"unknown"}function T({env:e,hostname:t,cfg:n={}}={}){if(e)return g(e);let r=n.machineNames||{};return t&&r[t]?g(r[t]):n.machine?g(n.machine):g(t||"unknown")}var _="[A-Za-z0-9_.-]*(?:key|token|secret|passw(?:or)?d|pwd|auth(?!or)|credential|cookie|signature)[A-Za-z0-9_.-]*",ee=[[/\b(Bearer|Basic|Token)\s+[A-Za-z0-9._~+\/=-]{6,}/gi,"$1 …"],[/\b(authorization|cookie|x-api-key)(\s*[:=]\s*)("[^"]*"|'[^']*'|\S+(?:\s+[A-Za-z0-9._~+\/=-]{12,})?)/gi,"$1$2…"],[/(:\/\/)[^\s\/@:]+:[^\s\/@]+@/g,"$1…@"],[new RegExp("(--?"+_+`)(=|\\s+)("[^"]*"|'[^']*'|\\S+)`,"gi"),"$1$2…"],[new RegExp("\\b("+_+`)(\\s*[=:]\\s*)("[^"]*"|'[^']*'|[^\\s&;,]+)`,"gi"),"$1$2…"],[/\b([A-Z][A-Z0-9_]{1,})=("[^"]*"|'[^']*'|[^\s;&|]+)/g,"$1=…"],[/\b(?:sk|pk|rk|ghp|gho|ghs|ghu|ghr|github_pat|glpat|xox[abprs]|AKIA|ASIA|npm|hf|ya29)[-_][A-Za-z0-9_-]{8,}/g,"…"],[/\bAKIA[A-Z0-9]{12,}\b/g,"…"],[/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(?:\.[A-Za-z0-9_-]+)?/g,"…"],[/(?<![\/\w.-])(?=[A-Za-z0-9_+=-]*\d)(?=[A-Za-z0-9_+=-]*[A-Za-z])[A-Za-z0-9_+=-]{32,}(?![\/\w.-])/g,"…"]];var te=10*6e4;var v={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."}},ne=Object.keys(v);var x=["You write checkpoints for a developer juggling many coding-agent sessions, read when they come back after half an hour, a day or a weekend. Given the recent transcript of ONE session, start with exactly these five lines:","Goal: <the outcome the developer is after, from what they asked for, in a few plain words; not the latest step. Keep the previous goal unless they changed course or it is done and they moved on>","Progress: <one of Just started, Underway, Mostly done, Done, Stuck> - <what is left between here and the goal, a few words>","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; say what was actually run or checked, and what is only assumed>","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:".',"Describe only what the transcript shows; the lines above it (repo, branch, title, what was asked, previous checkpoint) are background and may be out of date."],y=['Only if the transcript shows a decision (a choice between real alternatives that the developer made or approved, a plan they approved, a correction they gave such as "no, do X instead", or a trade-off the agent chose and said so), add after those lines a "Decided:" heading and one "- " line per decision, at most 4:',"- <what was decided, in the words the transcript uses> · why: <the reason given, one line> · over: <alternative>; <alternative> · from: <asked | plan | correction | agent>","A task done, a file edited or a routine step is not a decision. Never invent a choice or a reason the transcript does not show. When unsure, or when there is none, leave the Decided heading out entirely. Skip decisions listed as already recorded."],A=['Then these sections, each a heading line followed by "- " bullets (write "- none" when there is nothing; never invent):',"Approach notes:","- <how the work is being done: the approach taken, a pattern or constraint it follows; one line each, at most 4. Not a choice between alternatives: those go only under Decided>","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>"],R=["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>"],M={short:[...x,...y,"Write only those five lines (and Decided, when there is one).",'Optionally a 6th line "Title: <max 6 words>" if asked for a title.',"No preamble. No markdown. Under 90 words total."].join(`
2
+ import i from"node:fs";import c from"node:os";import l from"node:path";import{spawnSync as U}from"node:child_process";function O(e,t=40){return String(e||"").toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g,"").replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,t).replace(/-+$/g,"")||"untitled"}function g(e){return O(String(e||"").replace(/\.local$/i,""),40)||"unknown"}function T({env:e,hostname:t,cfg:n={}}={}){if(e)return g(e);let r=n.machineNames||{};return t&&r[t]?g(r[t]):n.machine?g(n.machine):g(t||"unknown")}var _="[A-Za-z0-9_.-]*(?:key|token|secret|passw(?:or)?d|pwd|auth(?!or)|credential|cookie|signature)[A-Za-z0-9_.-]*",ee=[[/\b(Bearer|Basic|Token)\s+[A-Za-z0-9._~+\/=-]{6,}/gi,"$1 …"],[/\b(authorization|cookie|x-api-key)(\s*[:=]\s*)("[^"]*"|'[^']*'|\S+(?:\s+[A-Za-z0-9._~+\/=-]{12,})?)/gi,"$1$2…"],[/(:\/\/)[^\s\/@:]+:[^\s\/@]+@/g,"$1…@"],[new RegExp("(--?"+_+`)(=|\\s+)("[^"]*"|'[^']*'|\\S+)`,"gi"),"$1$2…"],[new RegExp("\\b("+_+`)(\\s*[=:]\\s*)("[^"]*"|'[^']*'|[^\\s&;,]+)`,"gi"),"$1$2…"],[/\b([A-Z][A-Z0-9_]{1,})=("[^"]*"|'[^']*'|[^\s;&|]+)/g,"$1=…"],[/\b(?:sk|pk|rk|ghp|gho|ghs|ghu|ghr|github_pat|glpat|xox[abprs]|AKIA|ASIA|npm|hf|ya29)[-_][A-Za-z0-9_-]{8,}/g,"…"],[/\bAKIA[A-Z0-9]{12,}\b/g,"…"],[/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(?:\.[A-Za-z0-9_-]+)?/g,"…"],[/(?<![\/\w.-])(?=[A-Za-z0-9_+=-]*\d)(?=[A-Za-z0-9_+=-]*[A-Za-z])[A-Za-z0-9_+=-]{32,}(?![\/\w.-])/g,"…"]];var te=10*6e4;var L={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."}},ne=Object.keys(L);var x=["You write checkpoints for a developer juggling many coding-agent sessions, read when they come back after half an hour, a day or a weekend. Given the recent transcript of ONE session, start with exactly these five lines:","Goal: <the outcome the developer is after, from what they asked for, in a few plain words; not the latest step. Keep the previous goal unless they changed course or it is done and they moved on>","Progress: <one of Just started, Underway, Mostly done, Done, Stuck> - <what is left between here and the goal, a few words>","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; say what was actually run or checked, and what is only assumed>","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:".',"Describe only what the transcript shows; the lines above it (repo, branch, title, what was asked, previous checkpoint) are background and may be out of date."],y=['Only if the transcript shows a decision (a choice between real alternatives that the developer made or approved, a plan they approved, a correction they gave such as "no, do X instead", or a trade-off the agent chose and said so), add after those lines a "Decided:" heading and one "- " line per decision, at most 4:',"- <what was decided, in the words the transcript uses> · why: <the reason given, one line> · over: <alternative>; <alternative> · from: <asked | plan | correction | agent>","A task done, a file edited or a routine step is not a decision, and neither is a one-off choice about this task right now (who does a step, whether to commit, push or deploy now). Never invent a choice or a reason the transcript does not show. When unsure, or when there is none, leave the Decided heading out entirely. Skip decisions listed as already recorded."],A=['Then these sections, each a heading line followed by "- " bullets (write "- none" when there is nothing; never invent):',"Approach notes:","- <how the work is being done: the approach taken, a pattern or constraint it follows; one line each, at most 4. Not a choice between alternatives: those go only under Decided>","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>"],R=["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>"],M={short:[...x,...y,"Write only those five lines (and Decided, when there is one).",'Optionally a 6th line "Title: <max 6 words>" if asked for a title.',"No preamble. No markdown. Under 90 words total."].join(`
3
3
  `),informative:[...x,...y,...A,'Optionally a final line "Title: <max 6 words>" if asked for a title.',"No preamble. No other markdown. Under 280 words total."].join(`
4
4
  `),long:[...x,...y,...A,...R,'Optionally a final line "Title: <max 6 words>" if asked for a title.',"No preamble. No other markdown. Under 630 words total."].join(`
5
- `)},re=M.short;var D="Write it in the same language as the user's own messages (English if unsure), in one language only: never mix in words or characters from another language.",oe="Write a title of at most 6 words for a coding session, based on its first prompt. "+D+" Reply with the title only, no quotes, no punctuation at the end.";var ie=[["latin",new RegExp("\\p{Script=Latin}","u")],["han",/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Bopomofo}]/u],["hangul",new RegExp("\\p{Script=Hangul}","u")],["cyrillic",new RegExp("\\p{Script=Cyrillic}","u")],["greek",new RegExp("\\p{Script=Greek}","u")],["arabic",new RegExp("\\p{Script=Arabic}","u")],["hebrew",new RegExp("\\p{Script=Hebrew}","u")],["thai",new RegExp("\\p{Script=Thai}","u")],["devanagari",new RegExp("\\p{Script=Devanagari}","u")]];var $=new Set("the a an and or but if then so of to in on at by for from with without into onto about over under after before is are was were be been being am do does did done has have had having can could will would should shall may might must i me my we us our you your he him his she her it its they them their this that these those there here what which who whom whose when where why how not no yes all any some each every both more most less many much few other another such only own same than too very just also again still now today up down out off via per as because while until since though although whether let lets please hey hi thanks ok okay one two three first last next new old way something anything nothing everything".split(" ")),P=new Set([...$,..."add added adding adds allow api app apps ask asks asking answer answers auth auto backend background bad base basic batch better big board bot box branch break broken bug bugs build builds button cache call calls card cards case change changes chat check checks clean cleanup clear cli click client close code column columns command commit config connect copy core count create cron css current dark data date db debug default delete deploy design detail details dev diff doc docs driver drop edit editor element empty end error errors event events explain export extra fail fails failing fast feature field fields file files filter find fix fixes flag flaky flow folder font form format full get git give go good graph group grouping guard guide handle handler header help hide history hook hooks host icon idea image import improve info input install issue issues item items job jobs join json key keys kind label layout lazy line lines link links lint list lists live load local lock log logic login logs look loop main make map mark menu merge message messages method migrate mobile mode model module move multi name names nav note notes number open option options order output overview page pages panel parse parser part pass patch path phone pick plan play polish port post prompt prompts proof pull push query question questions queue quick read readme real record refactor release remote remove rename render repo report request reset response rest result results retry review route router rule run runs runner save scan schema screen script scroll search session sessions set setup ship show sidebar sign size slow small sort source spec split start state status step stop store style sync table tables tag task tasks test tests text theme time timeout tip title titles today token tool tools top track tree trim try tune type typo typos ui undo unit up update upgrade url use user users value values view wait warn watch web wire word words work worker write wrong yaml focus bonus plus bus virus menu cursor support transcript transcripts tidy bump speed toggle suite crash startup prototype investigate audit align avoid batch bind boot cancel cap cart chart cleanup compare compose convert cover cut define detect disable enable draft drag embed ensure expose extract fetch finish fit fold forms gate handoff hover inline keep limit lint map match measure memo mock mute note pin poll port preview profile prune rate reach rebase redo reduce refresh reload repair replace reply resize resolve restore reuse revert rework rewrite scope seed send serve share shrink skip slot snap spike stub swap tidy trace tweak unify unlock untangle verify wrap zoom basics summary context notes intro".split(" ")]),se=new Set("de del la las el los un una unos unas y con para por que como sobre su sus le les des du une et pour avec sur dans au aux ce cette ces est der das und mit für fur von zu auf ein eine einen dem ist nicht über uber gli della delle dei di per con nel uma não nao cum est sunt atque quae qui quod".split(" ").filter(e=>!P.has(e)));var F="leftoff ↗",j=" · ",S=7429,k="\x1B",E=k+"\\";function W(e,t){return k+"]8;;"+e+E+t+k+"]8;;"+E}function z(e){return"/#/s/"+encodeURIComponent(String(e))}function q(e,t){return"http://127.0.0.1:"+(Number(e)||S)+z(t)}function I(e=process.env,t=c.homedir()){if(e.LEFTOFF_DIR||e.CHECKPOINTS_DIR)return e.LEFTOFF_DIR||e.CHECKPOINTS_DIR;try{let n=l.join(e.XDG_CONFIG_HOME||l.join(t,".config"),"checkpoints","folder"),r=i.readFileSync(n,"utf8").trim();if(r)return r}catch{}return l.join(t,"checkpoints")}function H(e=process.env,t=c.homedir()){let n=e.LEFTOFF_PORT||e.CHECKPOINTS_PORT;if(n&&Number(n))return Number(n);try{let r=JSON.parse(i.readFileSync(l.join(I(e,t),"config.json"),"utf8"));if(Number(r.port))return Number(r.port)}catch{}return S}function B(e,{port:t=S}={}){let n=e&&typeof e.session_id=="string"?e.session_id.trim():"";return n?W(q(t,n),F):""}function K(e){let t=String(e||"");if(!t.startsWith("b64:"))return null;try{return Buffer.from(t.slice(4),"base64url").toString("utf8")}catch{return null}}function Z(e,t){let n=String(e||"").replace(/\r?\n+$/,"");if(!t)return n;if(!n.trim())return t;let r=n.split(`
5
+ `)},re=M.short;var D="Write it in the same language as the user's own messages (English if unsure), in one language only: never mix in words or characters from another language.",oe="Write a title of at most 6 words for a coding session, based on its first prompt. "+D+" Reply with the title only, no quotes, no punctuation at the end.";var ie=[["latin",new RegExp("\\p{Script=Latin}","u")],["han",/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Bopomofo}]/u],["hangul",new RegExp("\\p{Script=Hangul}","u")],["cyrillic",new RegExp("\\p{Script=Cyrillic}","u")],["greek",new RegExp("\\p{Script=Greek}","u")],["arabic",new RegExp("\\p{Script=Arabic}","u")],["hebrew",new RegExp("\\p{Script=Hebrew}","u")],["thai",new RegExp("\\p{Script=Thai}","u")],["devanagari",new RegExp("\\p{Script=Devanagari}","u")]];var $=new Set("the a an and or but if then so of to in on at by for from with without into onto about over under after before is are was were be been being am do does did done has have had having can could will would should shall may might must i me my we us our you your he him his she her it its they them their this that these those there here what which who whom whose when where why how not no yes all any some each every both more most less many much few other another such only own same than too very just also again still now today up down out off via per as because while until since though although whether let lets please hey hi thanks ok okay one two three first last next new old way something anything nothing everything".split(" ")),P=new Set([...$,..."add added adding adds allow api app apps ask asks asking answer answers auth auto backend background bad base basic batch better big board bot box branch break broken bug bugs build builds button cache call calls card cards case change changes chat check checks clean cleanup clear cli click client close code column columns command commit config connect copy core count create cron css current dark data date db debug default delete deploy design detail details dev diff doc docs driver drop edit editor element empty end error errors event events explain export extra fail fails failing fast feature field fields file files filter find fix fixes flag flaky flow folder font form format full get git give go good graph group grouping guard guide handle handler header help hide history hook hooks host icon idea image import improve info input install issue issues item items job jobs join json key keys kind label layout lazy line lines link links lint list lists live load local lock log logic login logs look loop main make map mark menu merge message messages method migrate mobile mode model module move multi name names nav note notes number open option options order output overview page pages panel parse parser part pass patch path phone pick plan play polish port post prompt prompts proof pull push query question questions queue quick read readme real record refactor release remote remove rename render repo report request reset response rest result results retry review route router rule run runs runner save scan schema screen script scroll search session sessions set setup ship show sidebar sign size slow small sort source spec split start state status step stop store style sync table tables tag task tasks test tests text theme time timeout tip title titles today token tool tools top track tree trim try tune type typo typos ui undo unit up update upgrade url use user users value values view wait warn watch web wire word words work worker write wrong yaml focus bonus plus bus virus menu cursor support transcript transcripts tidy bump speed toggle suite crash startup prototype investigate audit align avoid batch bind boot cancel cap cart chart cleanup compare compose convert cover cut define detect disable enable draft drag embed ensure expose extract fetch finish fit fold forms gate handoff hover inline keep limit lint map match measure memo mock mute note pin poll port preview profile prune rate reach rebase redo reduce refresh reload repair replace reply resize resolve restore reuse revert rework rewrite scope seed send serve share shrink skip slot snap spike stub swap tidy trace tweak unify unlock untangle verify wrap zoom basics summary context notes intro".split(" ")]),se=new Set("de del la las el los un una unos unas y con para por que como sobre su sus le les des du une et pour avec sur dans au aux ce cette ces est der das und mit für fur von zu auf ein eine einen dem ist nicht über uber gli della delle dei di per con nel uma não nao cum est sunt atque quae qui quod".split(" ").filter(e=>!P.has(e)));var F="leftoff ↗",j=" · ",w=7429,k="\x1B",E=k+"\\";function W(e,t){return k+"]8;;"+e+E+t+k+"]8;;"+E}function z(e){return"/#/s/"+encodeURIComponent(String(e))}function H(e,t){return"http://127.0.0.1:"+(Number(e)||w)+z(t)}function I(e=process.env,t=c.homedir()){if(e.LEFTOFF_DIR||e.CHECKPOINTS_DIR)return e.LEFTOFF_DIR||e.CHECKPOINTS_DIR;try{let n=l.join(e.XDG_CONFIG_HOME||l.join(t,".config"),"checkpoints","folder"),r=i.readFileSync(n,"utf8").trim();if(r)return r}catch{}return l.join(t,"checkpoints")}function q(e=process.env,t=c.homedir()){let n=e.LEFTOFF_PORT||e.CHECKPOINTS_PORT;if(n&&Number(n))return Number(n);try{let r=JSON.parse(i.readFileSync(l.join(I(e,t),"config.json"),"utf8"));if(Number(r.port))return Number(r.port)}catch{}return w}function B(e,{port:t=w}={}){let n=e&&typeof e.session_id=="string"?e.session_id.trim():"";return n?W(H(t,n),F):""}function K(e){let t=String(e||"");if(!t.startsWith("b64:"))return null;try{return Buffer.from(t.slice(4),"base64url").toString("utf8")}catch{return null}}function Z(e,t){let n=String(e||"").replace(/\r?\n+$/,"");if(!t)return n;if(!n.trim())return t;let r=n.split(`
6
6
  `),s=/\x1b\[[0-9;]*m/.test(r[0])&&!/\x1b\[0?m(?!.*\x1b\[)/.test(r[0]);return r[0]=r[0]+(s?k+"[0m":"")+j+t,r.join(`
7
- `)}function G(e){for(let t of[e.LEFTOFF_STATUS_SHELL,"/bin/bash","/bin/sh"])if(t&&i.existsSync(t))return t;return!0}function Y(e,t,{env:n=process.env,cwd:r=process.cwd(),timeoutMs:s=1e4,run:d=U,home:a=c.homedir()}={}){try{let o=d(e,{shell:G(n),input:t,encoding:"utf8",env:n,cwd:X(r,a),timeout:s,maxBuffer:1048576});return{out:String(o&&o.stdout||""),code:o&&typeof o.status=="number"?o.status:1}}catch{return{out:"",code:1}}}function N(e){try{return!!e&&i.statSync(e).isDirectory()}catch{return!1}}function X(e,t=c.homedir()){if(N(e))return e;let n="";try{n=process.cwd()}catch{}for(let r of[n,t])if(N(r))return r;return"/"}function C(e,t,n={}){let{env:r=process.env,home:s=c.homedir(),cwd:d=process.cwd(),run:a}=n,o=null;try{o=JSON.parse(t||"{}")}catch{}let p=(e||[]).find(w=>String(w).startsWith("--wrap=")),f=p?K(p.slice(7)):null,u=S;try{u=H(r,s)}catch{}let h=B(o,{port:u});try{n.noName||Q(o,{env:r,home:s})}catch{}if(!f)return h;let m=Y(f,t,{env:r,cwd:o&&o.cwd||d,run:a,home:s});return Z(m.out,h)}function Q(e,{env:t=process.env,home:n=c.homedir(),tmp:r=c.tmpdir(),now:s=Date.now(),machine:d=null}={}){let a=e&&typeof e.session_id=="string"?e.session_id.trim():"",o=e&&typeof e.session_name=="string"?e.session_name.trim().slice(0,200):"";if(!a||!o||/[\/]/.test(a)||a.length>100)return!1;let p=l.join(r,"leftoff-names"),f=l.join(p,a.replace(/[^A-Za-z0-9_-]/g,"_"));try{if(i.readFileSync(f,"utf8")===o)return!1}catch{}let u=I(t,n);if(!i.existsSync(l.join(u,"config.json")))return!1;let h=d||V(t,u),m=l.join(u,".spool",h,a.replace(/[^A-Za-z0-9_-]/g,"_"));i.mkdirSync(m,{recursive:!0});let w={v:1,sessionId:a,at:new Date(s).toISOString(),type:"activity",data:{kind:"name",generatedTitle:o}};return i.writeFileSync(l.join(m,s+"-"+Math.random().toString(16).slice(2,10)+".json"),JSON.stringify(w)+`
8
- `,{flag:"wx"}),i.mkdirSync(p,{recursive:!0}),i.writeFileSync(f,o),!0}function V(e,t){let n={};try{n=JSON.parse(i.readFileSync(l.join(t,"config.json"),"utf8"))}catch{}return J({env:e.CHECKPOINTS_MACHINE,hostname:c.hostname(),cfg:n})}function J(e){return T(e)}function L(e=0){try{return i.readFileSync(e,"utf8")}catch{return""}}var b="";try{b=C(process.argv.slice(2),L())}catch{}b&&process.stdout.write(b+`
7
+ `)}function G(e){for(let t of[e.LEFTOFF_STATUS_SHELL,"/bin/bash","/bin/sh"])if(t&&i.existsSync(t))return t;return!0}function Y(e,t,{env:n=process.env,cwd:r=process.cwd(),timeoutMs:s=1e4,run:d=U,home:a=c.homedir()}={}){try{let o=d(e,{shell:G(n),input:t,encoding:"utf8",env:n,cwd:X(r,a),timeout:s,maxBuffer:1048576});return{out:String(o&&o.stdout||""),code:o&&typeof o.status=="number"?o.status:1}}catch{return{out:"",code:1}}}function N(e){try{return!!e&&i.statSync(e).isDirectory()}catch{return!1}}function X(e,t=c.homedir()){if(N(e))return e;let n="";try{n=process.cwd()}catch{}for(let r of[n,t])if(N(r))return r;return"/"}function C(e,t,n={}){let{env:r=process.env,home:s=c.homedir(),cwd:d=process.cwd(),run:a}=n,o=null;try{o=JSON.parse(t||"{}")}catch{}let p=(e||[]).find(S=>String(S).startsWith("--wrap=")),f=p?K(p.slice(7)):null,u=w;try{u=q(r,s)}catch{}let h=B(o,{port:u});try{n.noName||Q(o,{env:r,home:s})}catch{}if(!f)return h;let m=Y(f,t,{env:r,cwd:o&&o.cwd||d,run:a,home:s});return Z(m.out,h)}function Q(e,{env:t=process.env,home:n=c.homedir(),tmp:r=c.tmpdir(),now:s=Date.now(),machine:d=null}={}){let a=e&&typeof e.session_id=="string"?e.session_id.trim():"",o=e&&typeof e.session_name=="string"?e.session_name.trim().slice(0,200):"";if(!a||!o||/[\/]/.test(a)||a.length>100)return!1;let p=l.join(r,"leftoff-names"),f=l.join(p,a.replace(/[^A-Za-z0-9_-]/g,"_"));try{if(i.readFileSync(f,"utf8")===o)return!1}catch{}let u=I(t,n);if(!i.existsSync(l.join(u,"config.json")))return!1;let h=d||V(t,u),m=l.join(u,".spool",h,a.replace(/[^A-Za-z0-9_-]/g,"_"));i.mkdirSync(m,{recursive:!0});let S={v:1,sessionId:a,at:new Date(s).toISOString(),type:"activity",data:{kind:"name",generatedTitle:o}};return i.writeFileSync(l.join(m,s+"-"+Math.random().toString(16).slice(2,10)+".json"),JSON.stringify(S)+`
8
+ `,{flag:"wx"}),i.mkdirSync(p,{recursive:!0}),i.writeFileSync(f,o),!0}function V(e,t){let n={};try{n=JSON.parse(i.readFileSync(l.join(t,"config.json"),"utf8"))}catch{}return J({env:e.CHECKPOINTS_MACHINE,hostname:c.hostname(),cfg:n})}function J(e){return T(e)}function v(e=0){try{return i.readFileSync(e,"utf8")}catch{return""}}var b="";try{b=C(process.argv.slice(2),v())}catch{}b&&process.stdout.write(b+`
9
9
  `);process.exitCode=0;