@leftoff/cli 0.8.0 → 0.8.5
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 +41 -6
- package/README.md +43 -16
- package/assets/sample-transcript.jsonl +11 -11
- package/bin/leftoff.js +315 -304
- package/docs/SECURITY.md +152 -0
- package/docs/agent-feed-hosting.md +104 -0
- package/docs/agent-feed.example.json +103 -0
- package/docs/agent-feed.md +139 -0
- package/docs/peer-protocol.md +136 -0
- package/hooks/README.md +1 -1
- package/mod/.claude-plugin/plugin.json +1 -1
- package/mod/hooks/core.js +33 -3
- package/mod/hooks/register.js +33 -5
- package/package.json +2 -1
- package/ui/app.css +80 -0
- package/ui/app.js +245 -28
- package/ui/changelog-md.js +5 -2
- package/ui/prs.js +188 -24
- package/ui/repos.js +24 -2
- package/ui/viewer.js +51 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
What changed in Leftoff (called Checkpoints until 0.4.5), newest first. The board's **What's new** page and `leftoff changelog` both read this file.
|
|
4
4
|
|
|
5
|
+
## [0.8.5] - 2026-10-12
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **PR filters**: repo, state (Needs attention, Open, Draft, Merged, Closed) and checks chips, plus a search box. They're in the link, so you can share or bookmark a view. PRs with no activity for 14 days fold into a Quiet group.
|
|
9
|
+
- **Tap a PR** to see its failing checks (each linking to its log), reviews, description, files changed and latest comments, right on the page.
|
|
10
|
+
- Each linked PR says why: created here, same branch, you mentioned it, or linked by you.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- A PR no longer links to a session just because Claude mentioned it. A mention links only if you pasted it yourself or it's on the session's branch.
|
|
14
|
+
|
|
15
|
+
## [0.8.4] - 2026-10-12
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- The "set up this computer too" banner no longer shows on a Mac that already runs Leftoff.
|
|
19
|
+
|
|
20
|
+
## [0.8.3] - 2026-10-12
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- `leftoff doctor` gives the fix: every problem line gets a `fix:` line with the exact command. `leftoff doctor --fix` runs the safe ones (restarts, never installs or deletes), asking first.
|
|
24
|
+
- **Low disk warning** before Syncthing stops syncing, on the board and in doctor. When sync has stopped, or another Mac has gone quiet, a sync chip says so, and that Mac's sessions show "as of" when they were last synced.
|
|
25
|
+
- Opening the board from another computer offers the one line to set up Leftoff there too.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- Closed: a repo filter always shows as a chip you can remove, All counts every closed session, sessions started outside a repo are "No repo" (not "/"), and a repo is named after its git remote, not its folder.
|
|
29
|
+
|
|
30
|
+
## [0.8.2] - 2026-10-12
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- `/cp-park [note]`: checkpoint, park the session to come back to, and exit. Parked sessions get their own section on the board and a line in the digest, and resuming one unparks it. Also Park on a session's page (Codex too) and `leftoff park`.
|
|
34
|
+
|
|
35
|
+
## [0.8.1] - 2026-10-12
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
- Settings → About links to npm, the security and feed-hosting docs open right in the board, and the "not installed yet" hints use `npx -y -p @leftoff/cli leftoff`. Codex cards count their turns instead of showing "0 turns".
|
|
39
|
+
|
|
5
40
|
## [0.8.0] - 2026-10-12
|
|
6
41
|
|
|
7
42
|
### Added
|
|
@@ -234,7 +269,7 @@ What changed in Leftoff (called Checkpoints until 0.4.5), newest first. The boar
|
|
|
234
269
|
- The footer under each item is short and accurate: "From Tab · reply by texting Tab", using `agent.replyHint` (new, optional, up to 120 characters), or "From Tab · Read-only here" without one.
|
|
235
270
|
- The digest and Catch me up mention a reply only when the feed gives one. Catch-ups saved by 0.5.4/0.5.5 still read back.
|
|
236
271
|
- Leftoff 0.5.5 and older reject a feed with `agent.reply` (strict schema). Upgrade before your agent adds it.
|
|
237
|
-
- **The
|
|
272
|
+
- **The source repo moved to its new name.** Install and upgrade lines, the join line for a new Mac, and links in the board and the docs all use it. The old names keep working everywhere they're read: Leftoff maps them (as `owner/repo`, `github:`, https, `git@` or `ssh://`) to the new one, so an install that recorded an old source upgrades from the new one, and `leftoff upgrade` on a git clone also points its `origin` at the new URL.
|
|
238
273
|
|
|
239
274
|
### Tests
|
|
240
275
|
- New `test/agents-hosting-e2e.mjs` (9): keygen → the real Worker template on loopback → publish (invalid refused, wrong key 403, dry run) → read keys, 401s, ETag/weak ETag/304 → `leftoff agents add` → an update that drops `agent.reply`.
|
|
@@ -298,7 +333,7 @@ Before a device is accepted, Syncthing passes on only what's in its Hello: ID, n
|
|
|
298
333
|
- **Easy Syncthing.** Settings → Sync now gets Syncthing working in a few clicks, without opening Syncthing:
|
|
299
334
|
1. **Install Syncthing (recommended).** It shows the exact command (`brew install syncthing && brew services start syncthing`) and runs it only after you click Install. Homebrew's output streams into the page, and a failure gets a plain sentence ("Homebrew couldn't download Syncthing…") and **Try again**. Without Homebrew you get two short directions and **Check again**. Leftoff uses the formula with `brew services` (a background login service) rather than the menu-bar app.
|
|
300
335
|
2. **Sync this folder.** It does the verified move into `~/Sync/Leftoff` (or `~/Leftoff` when `~/Sync` is another Syncthing folder, or your pick), then adds a Syncthing folder with the ID `leftoff` through Syncthing's local API, plus a `.stignore`.
|
|
301
|
-
3. **Pair another Mac.** You get a one-time code (15 minutes, works once). On the other Mac: `leftoff join <code
|
|
336
|
+
3. **Pair another Mac.** You get a one-time code (15 minutes, works once). On the other Mac: `leftoff join <code>` (or the `npx` line shown next to it, if Leftoff isn't installed there yet), or **Join another Mac** on its board. It installs or starts Syncthing (asking first), adds your first Mac, and is accepted automatically, but only because it proves it holds the code. You see *Waiting for studio…*, *Syncing 214/530 files…* and *Done*, and then its sessions are merged in.
|
|
302
337
|
- **Status:** Settings → Sync, `leftoff sync` and `leftoff doctor` show whether Syncthing is running, the peers that are connected (and how far along they are), the last sync and folder errors. **Stop syncing** (`leftoff sync stop`) removes the Leftoff folder from Syncthing and keeps the files.
|
|
303
338
|
- **CLI:** `leftoff sync [install|start|setup|pair|stop]` and `leftoff join <code>`.
|
|
304
339
|
- **Security notes:** [docs/SECURITY.md](docs/SECURITY.md) covers what's in the folder, where it lives with Syncthing, iCloud, Dropbox or another folder, what never syncs, the pairing trust model, cross-machine commands, phone access, and what Leftoff doesn't protect against. A short "How your data is protected" panel appears in the sync walkthrough and in Settings → Other machines.
|
|
@@ -474,7 +509,7 @@ Run `checkpoints upgrade` (or `leftoff upgrade` once it's installed), then resta
|
|
|
474
509
|
- **Login service:** macOS's `dev.checkpoints.daemon` LaunchAgent is unloaded and removed, and replaced with `dev.leftoff.daemon` (still `ProcessType Interactive`, on the same folder). On Linux, `checkpoints.service` becomes `leftoff.service`. Exactly one daemon runs afterwards.
|
|
475
510
|
- **The mod:** `checkpoints@checkpoints` is uninstalled and `leftoff@checkpoints` is installed from the same marketplace. If you had disabled it, it stays disabled.
|
|
476
511
|
- **Settings hooks (hooks-only mode):** the `--tag=checkpoints-hook` entries are replaced with `--tag=leftoff-hook` ones, one per event with no duplicates. Hooks of your own in the same file are left alone. Until you upgrade, the old entries keep working, because `checkpoints` is still installed.
|
|
477
|
-
- **The
|
|
512
|
+
- **The install line is unchanged** for now. The npm package keeps its internal name `checkpoints`, because npm won't install a package called `leftoff` over one that owns the `checkpoints` command. It installs both commands.
|
|
478
513
|
- `leftoff doctor` points out anything still using an old name (the login service or settings hooks).
|
|
479
514
|
|
|
480
515
|
## [0.4.4] - 2026-10-10
|
|
@@ -588,13 +623,13 @@ Run `checkpoints upgrade` (or `leftoff upgrade` once it's installed), then resta
|
|
|
588
623
|
## [0.3.3] - 2026-10-09
|
|
589
624
|
|
|
590
625
|
### Fixed
|
|
591
|
-
- **`checkpoints upgrade` said "Authentication failed" on a private repo when your GitHub login is SSH.** The update check and `upgrade` try `https://github.com/…` first and now fall back to `git@github.com:…` over SSH. You can also pin it with `"updates": { "source": "git@github.com
|
|
626
|
+
- **`checkpoints upgrade` said "Authentication failed" on a private repo when your GitHub login is SSH.** The update check and `upgrade` try `https://github.com/…` first and now fall back to `git@github.com:…` over SSH. You can also pin it with `"updates": { "source": "git@github.com:<owner>/<repo>.git" }` in config.json.
|
|
592
627
|
- GitHub now marks `bin/checkpoints.js` executable, which fixes `permission denied: checkpoints` after `npm link` in a fresh clone.
|
|
593
628
|
|
|
594
629
|
## [0.3.2] - 2026-10-09
|
|
595
630
|
|
|
596
631
|
### Fixed
|
|
597
|
-
-
|
|
632
|
+
- **Installing from git with `npm i -g` failed with `ENOTDIR` (git dep preparation failed).** A `prepare` script made npm run a nested install for the git dependency, and under `-g` that nested install tried to install globally too. The script is gone. `setup` and `upgrade` record the installed commit themselves.
|
|
598
633
|
|
|
599
634
|
## [0.3.1] - 2026-10-09
|
|
600
635
|
|
|
@@ -626,7 +661,7 @@ Run `checkpoints upgrade` (or `leftoff upgrade` once it's installed), then resta
|
|
|
626
661
|
## [0.2.0] - 2026-10-09
|
|
627
662
|
|
|
628
663
|
### Added
|
|
629
|
-
- **One-command setup
|
|
664
|
+
- **One-command setup** with `npx`. It checks Node and Claude Code, installs the mod with `claude plugin`, helps you pick a summarizer, starts the daemon and opens the board. It's safe to re-run.
|
|
630
665
|
- **Local models in setup:** finds Ollama or LM Studio, offers to install Ollama, suggests four small models with their sizes, pulls your pick with a progress bar and writes a sample checkpoint so you can see the result.
|
|
631
666
|
- `checkpoints models` lists, pulls, switches and tests local models later.
|
|
632
667
|
- `--yes` and flags let you script setup.
|
package/README.md
CHANGED
|
@@ -1,30 +1,57 @@
|
|
|
1
1
|
# Leftoff
|
|
2
2
|
|
|
3
|
-
**WTF was I working on?** Leftoff watches your Claude Code
|
|
4
|
-
3-line checkpoint when one goes quiet, and gives you a
|
|
5
|
-
board of everything you had open, on every Mac you use.
|
|
3
|
+
**WTF was I working on?** Leftoff watches your Claude Code (and Codex)
|
|
4
|
+
sessions, writes a 3-line checkpoint when one goes quiet, and gives you a
|
|
5
|
+
morning digest and a board of everything you had open, on every Mac you use.
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
npm i -g @leftoff/cli
|
|
9
|
-
leftoff setup
|
|
8
|
+
npm i -g @leftoff/cli && leftoff setup
|
|
10
9
|
```
|
|
11
10
|
|
|
12
|
-
(`pnpm add -g @leftoff/cli` works too; the command is `leftoff`.) Setup takes
|
|
13
|
-
the Claude Code plugin, picks a summarizer and
|
|
14
|
-
a web board at http://127.0.0.1:7429.
|
|
15
|
-
signed in, a card that needs you also shows the branch's pull request.
|
|
11
|
+
(`pnpm add -g @leftoff/cli` works too; the command is `leftoff`.) Setup takes
|
|
12
|
+
about two minutes: it installs the Claude Code plugin, picks a summarizer and
|
|
13
|
+
starts a small local daemon with a web board at http://127.0.0.1:7429.
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
## What it does
|
|
16
|
+
|
|
17
|
+
- **Board:** every session, open or closed, by repo, with what it's doing now.
|
|
18
|
+
- **Needs you:** sessions waiting on an answer or an OK, answerable from the board.
|
|
19
|
+
- **Checkpoints and digest:** a short note when a session goes quiet, and a daily recap.
|
|
20
|
+
- **To do and Files:** steps a session handed back to you, and the docs, plans and artifacts it made.
|
|
21
|
+
- **PRs:** the pull requests your sessions opened, and your team's open ones (with GitHub's `gh`, optional).
|
|
22
|
+
- **Resume:** closed sessions are one tap from coming back.
|
|
23
|
+
- **Phone:** your board at your own address, end-to-end encrypted.
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
18
26
|
|
|
19
27
|
```sh
|
|
20
|
-
leftoff
|
|
28
|
+
leftoff setup # set up or change settings
|
|
29
|
+
leftoff open # open the board
|
|
30
|
+
leftoff status # sessions and their status
|
|
31
|
+
leftoff go [name] # jump to a session's terminal tab
|
|
32
|
+
leftoff restore # reopen what was open before a restart
|
|
33
|
+
leftoff catchup # one recap across repos
|
|
34
|
+
leftoff park [session] [note] # park a session to come back to
|
|
35
|
+
leftoff relay signup yourname # your board on your phone, at yourname.leftoff.dev
|
|
36
|
+
leftoff upgrade # update
|
|
37
|
+
leftoff doctor # check the install; each problem comes with its fix
|
|
38
|
+
leftoff doctor --fix # run the safe fixes, asking first
|
|
39
|
+
leftoff --help # everything else
|
|
21
40
|
```
|
|
22
41
|
|
|
23
|
-
|
|
24
|
-
- How it works: https://leftoff.dev/how-it-works
|
|
25
|
-
- Update: `leftoff upgrade`
|
|
42
|
+
In a Claude Code session:
|
|
26
43
|
|
|
27
|
-
|
|
28
|
-
|
|
44
|
+
```text
|
|
45
|
+
/cp [note] checkpoint now
|
|
46
|
+
/cp-name <title> name the session
|
|
47
|
+
/cp-park [note] checkpoint, park it on the board, and exit (the note is where to pick up)
|
|
48
|
+
/leftoff every open session
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Everything Leftoff writes is plain Markdown in a folder you choose.
|
|
52
|
+
|
|
53
|
+
- Website: https://leftoff.dev
|
|
54
|
+
- How it works: https://leftoff.dev/how-it-works
|
|
55
|
+
- Questions: hello@leftoff.dev
|
|
29
56
|
|
|
30
57
|
Proprietary for now (see LICENSE.md); a source-available license is planned.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{"type": "summary", "summary": "Larry sync retry", "leafUuid": "x"}
|
|
2
|
-
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/
|
|
3
|
-
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/
|
|
4
|
-
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/
|
|
5
|
-
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/
|
|
6
|
-
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/
|
|
7
|
-
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/
|
|
8
|
-
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/
|
|
9
|
-
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/
|
|
10
|
-
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/
|
|
11
|
-
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/
|
|
12
|
-
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/
|
|
2
|
+
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/yourname/code/larry", "sessionId": "00893aaf-19fa-41d2-8238-13269b9b3ca0", "version": "2.1.290", "gitBranch": "fix/sync-retry", "uuid": "u2", "timestamp": "2026-10-09T13:02:00.000Z", "type": "user", "message": {"role": "user", "content": "The Larry sync worker retries forever when the API is down. Add exponential backoff with jitter and cap it at 5 attempts."}}
|
|
3
|
+
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/yourname/code/larry", "sessionId": "00893aaf-19fa-41d2-8238-13269b9b3ca0", "version": "2.1.290", "gitBranch": "fix/sync-retry", "uuid": "u3", "timestamp": "2026-10-09T13:03:00.000Z", "type": "assistant", "message": {"id": "msg_1", "role": "assistant", "model": "claude-opus-5", "content": [{"type": "text", "text": "Reading the worker and the retry helper first."}]}}
|
|
4
|
+
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/yourname/code/larry", "sessionId": "00893aaf-19fa-41d2-8238-13269b9b3ca0", "version": "2.1.290", "gitBranch": "fix/sync-retry", "uuid": "u4", "timestamp": "2026-10-09T13:04:00.000Z", "type": "assistant", "message": {"id": "msg_1", "role": "assistant", "model": "claude-opus-5", "content": [{"type": "tool_use", "id": "tu1", "name": "Read", "input": {"file_path": "/Users/yourname/code/larry/src/sync/worker.ts"}}]}}
|
|
5
|
+
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/yourname/code/larry", "sessionId": "00893aaf-19fa-41d2-8238-13269b9b3ca0", "version": "2.1.290", "gitBranch": "fix/sync-retry", "uuid": "u5", "timestamp": "2026-10-09T13:05:00.000Z", "type": "assistant", "message": {"id": "msg_1", "role": "assistant", "model": "claude-opus-5", "content": [{"type": "tool_use", "id": "tu2", "name": "Read", "input": {"file_path": "/Users/yourname/code/larry/src/sync/retry.ts"}}]}}
|
|
6
|
+
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/yourname/code/larry", "sessionId": "00893aaf-19fa-41d2-8238-13269b9b3ca0", "version": "2.1.290", "gitBranch": "fix/sync-retry", "uuid": "u6", "timestamp": "2026-10-09T13:06:00.000Z", "type": "user", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "tu1", "content": "export async function syncWorker() {...}"}, {"type": "tool_result", "tool_use_id": "tu2", "content": "export function retry() {...}"}]}}
|
|
7
|
+
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/yourname/code/larry", "sessionId": "00893aaf-19fa-41d2-8238-13269b9b3ca0", "version": "2.1.290", "gitBranch": "fix/sync-retry", "uuid": "u7", "timestamp": "2026-10-09T13:07:00.000Z", "type": "assistant", "message": {"id": "msg_2", "role": "assistant", "model": "claude-opus-5", "content": [{"type": "text", "text": "Rewriting retry() with exponential backoff and full jitter, capped at 5 attempts."}, {"type": "tool_use", "id": "tu3", "name": "Edit", "input": {"file_path": "/Users/yourname/code/larry/src/sync/retry.ts", "old_string": "a", "new_string": "b"}}, {"type": "tool_use", "id": "tu4", "name": "Edit", "input": {"file_path": "/Users/yourname/code/larry/src/sync/worker.ts", "old_string": "a", "new_string": "b"}}]}}
|
|
8
|
+
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/yourname/code/larry", "sessionId": "00893aaf-19fa-41d2-8238-13269b9b3ca0", "version": "2.1.290", "gitBranch": "fix/sync-retry", "uuid": "u8", "timestamp": "2026-10-09T13:08:00.000Z", "type": "user", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "tu3", "content": "ok"}, {"type": "tool_result", "tool_use_id": "tu4", "content": "ok"}]}}
|
|
9
|
+
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/yourname/code/larry", "sessionId": "00893aaf-19fa-41d2-8238-13269b9b3ca0", "version": "2.1.290", "gitBranch": "fix/sync-retry", "uuid": "u9", "timestamp": "2026-10-09T13:09:00.000Z", "type": "assistant", "message": {"id": "msg_3", "role": "assistant", "model": "claude-opus-5", "content": [{"type": "tool_use", "id": "tu5", "name": "Bash", "input": {"command": "npm test -- sync"}}]}}
|
|
10
|
+
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/yourname/code/larry", "sessionId": "00893aaf-19fa-41d2-8238-13269b9b3ca0", "version": "2.1.290", "gitBranch": "fix/sync-retry", "uuid": "u10", "timestamp": "2026-10-09T13:10:00.000Z", "type": "user", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "tu5", "content": "PASS test/sync.unit.test.ts\nFAIL test/sync.int.test.ts\n staging request timed out after 30000 ms", "is_error": true}]}}
|
|
11
|
+
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/yourname/code/larry", "sessionId": "00893aaf-19fa-41d2-8238-13269b9b3ca0", "version": "2.1.290", "gitBranch": "fix/sync-retry", "uuid": "u11", "timestamp": "2026-10-09T13:11:00.000Z", "type": "user", "message": {"role": "user", "content": "<local-command-stdout>Set model to opus</local-command-stdout>"}}
|
|
12
|
+
{"parentUuid": null, "isSidechain": false, "userType": "external", "cwd": "/Users/yourname/code/larry", "sessionId": "00893aaf-19fa-41d2-8238-13269b9b3ca0", "version": "2.1.290", "gitBranch": "fix/sync-retry", "uuid": "u12", "timestamp": "2026-10-09T13:12:00.000Z", "type": "assistant", "message": {"id": "msg_4", "role": "assistant", "model": "claude-opus-5", "content": [{"type": "text", "text": "Unit tests pass with the new backoff. The integration test against staging still times out after 30s because the staging endpoint is slow. Do you want me to bump the integration timeout to 90s, or mock the slow endpoint?"}]}}
|