@leftoff/cli 0.8.9 → 0.9.1
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 +26 -1
- package/assets/sample-transcript.jsonl +12 -12
- package/bin/leftoff.js +377 -372
- package/mod/.claude-plugin/plugin.json +1 -1
- package/mod/hooks/core.js +2 -1
- package/mod/hooks/register.js +14 -4
- package/package.json +1 -1
- package/ui/app.css +36 -0
- package/ui/app.js +244 -43
- package/ui/icons.js +2 -0
- package/ui/picker.js +38 -1
- package/ui/prs.js +61 -14
- package/ui/repos.js +23 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leftoff",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Leftoff (formerly Checkpoints): WTF was I working on? Tracks every Claude Code session on this machine, writes a 3-line checkpoint when one goes quiet, and adds /cp (checkpoint now), /cp-name and a /leftoff pane. Talks to the local Leftoff daemon on 127.0.0.1:7429 and falls back to writing markdown in ~/checkpoints.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Joel"
|
package/mod/hooks/core.js
CHANGED
|
@@ -537,7 +537,7 @@ const SESSION_ORDER = ['id', 'title', 'title_source', 'machine', 'cwd', 'repo',
|
|
|
537
537
|
export const SESSION_EXTRA = ['inTurn', 'needsInput', 'backgroundTasks', 'turnsSinceCheckpoint', 'dormantStretch',
|
|
538
538
|
'finalCheckpointDone', 'firstPrompt', 'lastPreview', 'lastNotification', 'lastEventAt', 'statusChanges', 'folder',
|
|
539
539
|
'pid', 'tty', 'pidStartedAt', 'pidSource', 'term', 'closedAt', 'answers', 'lastNotificationMessage', 'needsInputAt', 'question', 'questionAt',
|
|
540
|
-
'startSource', 'historyTurns', 'healedAt', 'healedVia', 'archivedReason', 'backend', 'prLinks', 'prUnlinked', 'todos', 'files', 'startRepo', 'editedRepos', 'parked', 'nonInteractive']
|
|
540
|
+
'startSource', 'historyTurns', 'healedAt', 'healedVia', 'archivedReason', 'backend', 'prLinks', 'prUnlinked', 'todos', 'files', 'startRepo', 'editedRepos', 'parked', 'nonInteractive', 'transcriptCwd', 'doubtedCheckpoints']
|
|
541
541
|
|
|
542
542
|
export const SESSION_FIELDS = ['id', 'title', 'titleSource', 'machine', 'cwd', 'repo', 'transcriptPath', 'model', 'surface', 'capture',
|
|
543
543
|
'status', 'startedAt', 'lastActivityAt', 'lastHeartbeatAt', 'endedAt', 'endReason', 'turns', 'lastCheckpointId',
|
|
@@ -1222,6 +1222,7 @@ const CHECKPOINT_BASE = [
|
|
|
1222
1222
|
'Stopped at: <the exact state when it went quiet: what passed, what failed, what is half-done>',
|
|
1223
1223
|
'Next: <the single next action a human or the agent should take>',
|
|
1224
1224
|
'If the session is blocked on the human (a question, a permission, a decision), start Next with "YOU:".',
|
|
1225
|
+
'Describe only what the transcript shows; the lines above it (repo, branch, title, previous checkpoint) are background and may be out of date.',
|
|
1225
1226
|
]
|
|
1226
1227
|
const INFORMATIVE_SECTIONS = [
|
|
1227
1228
|
'Then these sections, each a heading line followed by "- " bullets (write "- none" when there is nothing; never invent):',
|
package/mod/hooks/register.js
CHANGED
|
@@ -28,7 +28,7 @@ const END_TIMEOUT_MS = 1000
|
|
|
28
28
|
const MODEL_TIMEOUT_MS = 20000
|
|
29
29
|
|
|
30
30
|
// Module state. $.state is not used: the pane redraws with $.ui.invalidate.
|
|
31
|
-
const MOD_VERSION = '0.
|
|
31
|
+
const MOD_VERSION = '0.9.1' // keep in step with .claude-plugin/plugin.json
|
|
32
32
|
|
|
33
33
|
const st = {
|
|
34
34
|
internal: false,
|
|
@@ -197,7 +197,7 @@ async function heartbeat($) {
|
|
|
197
197
|
// release takes a name. -> st.commands: [{ key, want, name, ok, error }]
|
|
198
198
|
const COMMANDS = [
|
|
199
199
|
{ key: 'checkpoint', names: ['cp', 'cp-save'], description: 'Leftoff: save a checkpoint of this session now (--short / --long for this one)', argumentHint: '[--short|--long] [note]', immediate: true },
|
|
200
|
-
{ key: 'name', names: ['cp-name'], description: 'Leftoff: name this session (runs /rename, tells the board)', argumentHint: '<title>', immediate: false },
|
|
200
|
+
{ key: 'name', names: ['cp-name'], description: 'Leftoff: name this session (runs /rename, tells the board); --auto names it from the latest messages', argumentHint: '<title> | --auto', immediate: false },
|
|
201
201
|
{ key: 'pane', names: ['leftoff'], description: 'Leftoff: every open session and its status (/leftoff open: the board)', argumentHint: '[open]', immediate: true },
|
|
202
202
|
// The pane's pre-0.4.5 name, kept so the habit keeps working.
|
|
203
203
|
{ key: 'pane-old', names: ['checkpoints'], description: 'Leftoff (old name): same as /leftoff', argumentHint: '[open]', immediate: true },
|
|
@@ -684,8 +684,18 @@ function afterCheckpoint($, cp, trigger) {
|
|
|
684
684
|
}
|
|
685
685
|
|
|
686
686
|
async function nameSession($, args) {
|
|
687
|
-
|
|
688
|
-
if (!title) { $.ui.toast('Usage: /' + st.nameCommand + ' <title>'); return {} }
|
|
687
|
+
let title = String(args || '').trim()
|
|
688
|
+
if (!title) { $.ui.toast('Usage: /' + st.nameCommand + ' <title> (or --auto: a new title from the latest messages)'); return {} }
|
|
689
|
+
// 0.9.0: --auto (also auto, -a): the daemon writes a title from the most recent messages
|
|
690
|
+
if (/^(--auto|auto|-a)$/i.test(title)) {
|
|
691
|
+
const r = st.sessionId ? await api($, 'POST', '/v1/sessions/' + encodeURIComponent(st.sessionId) + '/title/auto', {}, 30000) : null
|
|
692
|
+
if (!(r && r.ok && r.json && r.json.title)) { $.ui.toast('Couldn’t make a title: ' + ((r && r.json && r.json.error && r.json.error.message) || 'the Leftoff daemon isn’t answering')); return {} }
|
|
693
|
+
title = r.json.title
|
|
694
|
+
if (tabTitleOn()) { if (!st.record) st.record = {}; st.record.title = title; await applyTabTitle($, title, { force: true }) }
|
|
695
|
+
else { try { await $.command.run({ command: 'rename', args: title }) } catch {} }
|
|
696
|
+
$.ui.toast('Named this session “' + clip(title, 60) + '” (from the latest messages)')
|
|
697
|
+
return {}
|
|
698
|
+
}
|
|
689
699
|
if (tabTitleOn()) {
|
|
690
700
|
if (!st.record) st.record = {}
|
|
691
701
|
st.record.title = title
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leftoff/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Leftoff: WTF was I working on? Tracks every Claude Code session, writes a 3-line checkpoint when one goes quiet, and gives you a daily digest and a board, on your Mac and (optionally) your phone.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
package/ui/app.css
CHANGED
|
@@ -1739,3 +1739,39 @@ html.dv-open .lo-menu { z-index: 62; }
|
|
|
1739
1739
|
.lo-menu.sheet.lo-actmenu { max-width: none; }
|
|
1740
1740
|
.lo-menu.sheet .lo-act { padding: 14px 10px; font-size: 16px; }
|
|
1741
1741
|
@media (pointer: coarse) { .btn.icon-btn { width: 36px; height: 34px; } }
|
|
1742
|
+
|
|
1743
|
+
/* 0.9.0: projects. The chip next to the repo, the filter chips, the name form. */
|
|
1744
|
+
.chip.proj { display: inline-flex; align-items: center; gap: 4px; color: var(--text2); font-weight: 600; border-color: color-mix(in srgb, var(--accent) 45%, transparent); background: color-mix(in srgb, var(--accent) 9%, transparent); max-width: 12em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; font-family: inherit; }
|
|
1745
|
+
.chip.proj:hover { color: var(--text); border-color: var(--accent); }
|
|
1746
|
+
.chip.proj .pe, .rchip .pe { font-size: 1.05em; line-height: 1; }
|
|
1747
|
+
.chip.proj-add { cursor: pointer; font-family: inherit; color: var(--muted); border-style: dashed; background: none; }
|
|
1748
|
+
.chip.proj-add:hover { color: var(--text); border-color: var(--line-strong); }
|
|
1749
|
+
.repos.projects .rchip, .rchip.pchip { font-family: inherit; font-weight: 600; }
|
|
1750
|
+
.repos.projects .rchip .rl { display: inline-flex; align-items: center; gap: 5px; }
|
|
1751
|
+
.repos.projects .proj-more { flex: none; width: 30px; height: 30px; padding: 0; border-radius: 999px; }
|
|
1752
|
+
.lo-namesheet { padding: 10px; min-width: 260px; }
|
|
1753
|
+
.lo-namesheet .lo-sheet-head { padding: 2px 4px 8px; }
|
|
1754
|
+
.lo-nameform { display: flex; gap: 8px; align-items: center; }
|
|
1755
|
+
.lo-nameform .lo-name { flex: 1; min-width: 0; font: inherit; font-size: 14px; padding: 8px 10px; border: 1px solid var(--line-strong); border-radius: 8px; background: var(--card); color: var(--text); }
|
|
1756
|
+
.lo-nameform .lo-name:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--tint-accent); }
|
|
1757
|
+
.lo-name-err { color: var(--red, #d64545); font-size: 12.5px; padding: 6px 2px 0; }
|
|
1758
|
+
.lo-act.on { color: var(--text); font-weight: 600; }
|
|
1759
|
+
.lo-act.danger { color: var(--red, #d64545); }
|
|
1760
|
+
@media (pointer: coarse), (max-width: 600px) { .lo-nameform .lo-name { font-size: 16px; } }
|
|
1761
|
+
|
|
1762
|
+
/* 0.9.0: checkpoints written with another session's branch, folded away */
|
|
1763
|
+
.tl-doubt { margin: 10px 0 0; color: var(--muted); font-size: 13px; }
|
|
1764
|
+
.tl-doubt > summary { cursor: pointer; padding: 6px 0; }
|
|
1765
|
+
.tl-doubt .timeline { opacity: .6; margin-top: 6px; }
|
|
1766
|
+
|
|
1767
|
+
/* 0.9.0: Merged and Closed fold to one row each */
|
|
1768
|
+
.prgroup.done > summary { list-style: none; cursor: pointer; border: 1px solid var(--line); border-radius: 10px; padding: 0 12px; min-height: 44px; display: flex; align-items: center; }
|
|
1769
|
+
.prgroup.done > summary::-webkit-details-marker { display: none; }
|
|
1770
|
+
.prgroup.done > summary h2 { margin: 0; flex: 1; display: flex; align-items: center; gap: 6px; color: var(--b); }
|
|
1771
|
+
.prgroup.done > summary h2::before { content: ''; display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 1px; background: var(--b); }
|
|
1772
|
+
.prgroup.done > summary .chev { margin-left: auto; color: var(--muted); font-size: 18px; transition: transform .15s; }
|
|
1773
|
+
.prgroup.done[open] > summary { margin-bottom: 8px; }
|
|
1774
|
+
.prgroup.done[open] > summary .chev { transform: rotate(90deg); }
|
|
1775
|
+
.pr-more-sess > summary { list-style: none; cursor: pointer; display: inline; }
|
|
1776
|
+
.pr-more-sess > summary::-webkit-details-marker { display: none; }
|
|
1777
|
+
.pr-more-sess[open] > summary { display: none; }
|