@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leftoff",
3
- "version": "0.8.9",
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):',
@@ -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.8.9' // keep in step with .claude-plugin/plugin.json
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
- const title = String(args || '').trim()
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.8.9",
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; }