claude-inbox 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/README.md +149 -358
- data/exe/claude-inbox +0 -1
- data/lib/claude_inbox/agents_client.rb +72 -14
- data/lib/claude_inbox/app.rb +76 -40
- data/lib/claude_inbox/dialog.rb +7 -3
- data/lib/claude_inbox/job_state.rb +11 -3
- data/lib/claude_inbox/keymap.rb +1 -1
- data/lib/claude_inbox/logs.rb +5 -2
- data/lib/claude_inbox/new_session_form.rb +41 -19
- data/lib/claude_inbox/palette.rb +0 -2
- data/lib/claude_inbox/peek.rb +2 -2
- data/lib/claude_inbox/poller.rb +5 -11
- data/lib/claude_inbox/rate_limits.rb +19 -7
- data/lib/claude_inbox/reaper.rb +13 -16
- data/lib/claude_inbox/renderer.rb +91 -69
- data/lib/claude_inbox/session.rb +17 -3
- data/lib/claude_inbox/sessions.rb +2 -2
- data/lib/claude_inbox/slash_commands.rb +6 -6
- data/lib/claude_inbox/store/row.rb +3 -3
- data/lib/claude_inbox/store.rb +11 -2
- data/lib/claude_inbox/subprocess.rb +3 -2
- data/lib/claude_inbox/text.rb +6 -10
- data/lib/claude_inbox/text_buffer.rb +3 -2
- data/lib/claude_inbox/theme.rb +5 -4
- data/lib/claude_inbox/vt_screen.rb +0 -2
- data/lib/claude_inbox.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 76328d9652ac26483494547c0b26c4099a5c8996816743c9b88b21dfebff0def
|
|
4
|
+
data.tar.gz: 15d80ce8e007a533cf70fe35a6215d148fe9f48925fb9b51b50a9913f43ebc9c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7cd8965adf8b34a34b8ee2fb412b8742d3457e2616cc3a0de549f4a9ee7b5fd9385635445e56bf6de37f3eef43da85ce5456df52da0319ac4a2312e223b872ed
|
|
7
|
+
data.tar.gz: 39c760a37be19c854d69c98b8e429fa5fcc48ab4bbfe03c00033e78bd217c161a29051074e70dbb98db234d1913f2320187eafcfabb68396a96a95c8eee1f0a3
|
data/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Inbox-style triage on top of Claude Code's background sessions. A companion to
|
|
4
4
|
`claude agents`, not a replacement: it reads the same daemon state through
|
|
5
|
-
`claude agents --json` and adds
|
|
6
|
-
|
|
5
|
+
`claude agents --json` and adds snooze, auto-settle and the session's pull
|
|
6
|
+
request.
|
|
7
7
|
|
|
8
8
|

|
|
9
9
|
|
|
@@ -20,24 +20,20 @@ are published to rubygems and listed under GitHub Releases.
|
|
|
20
20
|
|
|
21
21
|
## Sections
|
|
22
22
|
|
|
23
|
-
1. **Pinned.** Parked at the top by hand, regardless of state. `
|
|
24
|
-
2. **Needs you.** `blocked` or `failed`,
|
|
23
|
+
1. **Pinned.** Parked at the top by hand, regardless of state. `t` toggles it.
|
|
24
|
+
2. **Needs you.** `blocked` or `failed`, until you attach to it (see
|
|
25
25
|
Acknowledge below).
|
|
26
|
-
3. **Active.** `working`,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
3. **Active.** `working`, recently finished sessions that haven't settled yet,
|
|
27
|
+
interactive sessions, and a needs-you session you've attached to that
|
|
28
|
+
hasn't resolved. Interactive sessions report only `status`, so it gets
|
|
29
|
+
mapped: busy is working, idle is done, waiting needs you. The daemon calls
|
|
30
30
|
a terminal you opened yourself, a Remote Control worker, a sub-agent and a
|
|
31
|
-
headless `claude -p` run all "interactive"
|
|
32
|
-
the
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
attach lands on whatever asked for them. Remote sessions settle and snooze
|
|
38
|
-
like any other row. A terminal you are sitting in never settles. Neither
|
|
39
|
-
can be attached, peeked or stopped from outside; you can land on them, and
|
|
40
|
-
Enter tells you why nothing happens.
|
|
31
|
+
headless `claude -p` run all "interactive"; the inbox tells them apart from
|
|
32
|
+
the process tree. Sub-agents and headless runs are dropped, since nobody is
|
|
33
|
+
sitting in them. Remote sessions settle and snooze like any other row,
|
|
34
|
+
and Enter pulls one into the daemon (see Remote Control below). A
|
|
35
|
+
terminal you are sitting in never settles and can't be attached, peeked
|
|
36
|
+
or stopped from outside; Enter tells you so.
|
|
41
37
|
4. **Snoozed.** Sorted by wake time, parked ("until I wake it") entries last.
|
|
42
38
|
Collapsed; Enter expands.
|
|
43
39
|
5. **Settled.** Parked with `x`, or whose pull request is merged or closed.
|
|
@@ -48,247 +44,171 @@ A row with a pull request shows it after the state: `#885 open`, `#885 draft`,
|
|
|
48
44
|
|
|
49
45
|
`working` from the daemon covers two situations, and the row says which. A
|
|
50
46
|
spinner and `working` mean the agent is thinking. A steady `◌` and `idle · 1
|
|
51
|
-
shell` mean the agent has stopped and is
|
|
52
|
-
|
|
53
|
-
can sit there for an hour.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
terminal you opened yourself has none and shows its directory instead.
|
|
64
|
-
|
|
65
|
-
A session you gave a color to with `/color` wears it on the label — see
|
|
66
|
-
[Colors](#colors).
|
|
47
|
+
shell` mean the agent has stopped and is waiting on work it started, such as a
|
|
48
|
+
`--watch` shell or a sub-agent. That is why a session with nothing left to do
|
|
49
|
+
can sit there for an hour. The open work is named next to the state either
|
|
50
|
+
way: `working · 2 agents`, `idle · 1 shell`.
|
|
51
|
+
|
|
52
|
+
Under each row in Pinned, Needs You and Active is the line `claude agents`
|
|
53
|
+
prints too: what it is waiting on while blocked (`↳ confirm: drop the top
|
|
54
|
+
line?`), what it produced once done, otherwise its status line. A terminal you
|
|
55
|
+
opened yourself has none and shows its directory instead.
|
|
56
|
+
|
|
57
|
+
A session you gave a color to with `/color` wears it on the label. Nothing
|
|
58
|
+
else is tinted, so a blocked session still looks blocked.
|
|
67
59
|
|
|
68
60
|
## Keys
|
|
69
61
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
rather than uncovering the scrollback behind us, in terminals that report
|
|
75
|
-
mouse events (`\e[?1000h` + `\e[?1006h`, the same mode clicks use) or that at
|
|
76
|
-
least support alternate scroll mode (`\e[?1007h`, wheel-as-arrows only, no
|
|
77
|
-
clicks). Apple Terminal supports neither escape, so there the wheel scrolls
|
|
78
|
-
its own history and clicking a row does nothing.
|
|
62
|
+
Vim bindings; arrows work as well as `j`/`k`. The mouse works too, in
|
|
63
|
+
terminals that report it: clicking a row selects and attaches, and the wheel
|
|
64
|
+
moves the selection. Apple Terminal reports neither, so there the wheel
|
|
65
|
+
scrolls its own history and clicks do nothing.
|
|
79
66
|
|
|
80
67
|
| Key | Action |
|
|
81
68
|
|---|---|
|
|
82
69
|
| `j` `k` | move down / up |
|
|
83
70
|
| `gg` `G` | first / last row |
|
|
84
71
|
| `Ctrl-d` `Ctrl-u` | half page down / up (`Ctrl-f` `Ctrl-b` full page) |
|
|
85
|
-
| `Enter` `l` | attach (full-screen handoff; `←` or `Ctrl+Z` return here), or expand Snoozed / Settled |
|
|
72
|
+
| `Enter` `l` | attach (full-screen handoff; `←` or `Ctrl+Z` return here), adopt a remote session, or expand Snoozed / Settled |
|
|
86
73
|
| `Tab` `Shift+Tab` | jump to the next / previous section |
|
|
87
74
|
| `h` | close the peek pane, else collapse the current Snoozed / Settled fold |
|
|
88
75
|
| `za` `zo` `zc` | toggle / open / close the Snoozed or Settled fold under the cursor |
|
|
89
76
|
| `p` | toggle the read-only peek pane |
|
|
90
77
|
| `J` `K` (`Ctrl-e` `Ctrl-y`) | scroll the peek pane |
|
|
91
|
-
| `n` | new session (
|
|
92
|
-
| `t` | pin / unpin
|
|
78
|
+
| `n` | new session (see below) |
|
|
79
|
+
| `t` | pin / unpin |
|
|
93
80
|
| `s` | snooze: `1` 15m · `2` 1h · `3` tomorrow 9am · `4` until woken |
|
|
94
81
|
| `u` | wake a snoozed session now, or bring back one you settled |
|
|
95
82
|
| `x` | settle a working or needs-you session by hand (returns when it changes state, unless it just finished) |
|
|
96
83
|
| `a` | set a local alias (never touches the real session name) |
|
|
97
84
|
| `o` | open the session's pull request in the browser |
|
|
85
|
+
| `w` | open the session at claude.ai/code |
|
|
98
86
|
| `P` | link a pull request by hand (empty clears; the scanned links return) |
|
|
99
87
|
| `X` | stop the session (`y` to confirm) |
|
|
100
88
|
| `Ctrl-x` | delete the session for good, conversation and worktree with it (`y` to confirm) |
|
|
101
|
-
| `/` | filter by name or
|
|
89
|
+
| `/` | filter by name, cwd, the prompt it started from, or what it is doing now; `Enter` keeps it, `Esc` clears |
|
|
102
90
|
| `R` | poll now |
|
|
103
91
|
| `q` | quit |
|
|
104
92
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
the same thing. `X` runs `claude stop`: the process ends, the conversation is
|
|
111
|
-
kept, and `Enter` resumes it later. `Ctrl-x` runs `claude rm`. The session,
|
|
112
|
-
its transcript and its worktree all go, and so does our own state entry for
|
|
113
|
-
it, rather than sitting out the seven-day prune. Nothing resumes afterwards,
|
|
114
|
-
so read the box before answering.
|
|
93
|
+
`X` and `Ctrl-x` both ask first, and both take `y`, but they are not the same
|
|
94
|
+
thing. `X` runs `claude stop`: the process ends, the conversation is kept, and
|
|
95
|
+
`Enter` resumes it later. `Ctrl-x` runs `claude rm`: the session, its
|
|
96
|
+
transcript and its worktree all go. Nothing resumes afterwards, so read the
|
|
97
|
+
box before answering.
|
|
115
98
|
|
|
116
99
|
### New session
|
|
117
100
|
|
|
118
|
-
`n` opens a form
|
|
119
|
-
|
|
120
|
-
`h` `l` or the arrows
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
`
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
left for the agent to read and act on. Typing `/` at the start of any word
|
|
157
|
-
opens a menu of what the CLI would offer — project and personal skills and
|
|
158
|
-
commands, plugin skills as `plugin:name`, claude.ai's synced skills as
|
|
159
|
-
`anthropic-skills:name` — narrowed as you type, each with the description
|
|
160
|
-
from its front matter. `↑` `↓` choose, `Tab` or `Enter` drop the command in
|
|
161
|
-
with a space after it, `Esc` closes the menu (a second `Esc` cancels the
|
|
162
|
-
form). A `/` inside a word, as in `a/b`, opens nothing, and the menu only
|
|
163
|
-
appears while something matches, so a path like `/Users/…` is left alone
|
|
164
|
-
after its first letters. Built-ins such as `/init` live inside the CLI and
|
|
165
|
-
are not listed; typing one still works. Project commands follow the
|
|
166
|
-
Directory field.
|
|
167
|
-
|
|
168
|
-
### Why `←` comes back here and not to native agent view
|
|
169
|
-
|
|
170
|
-
Inside an attached session, `←` on an empty prompt detaches. `claude attach`
|
|
171
|
-
then execs itself in place as `claude agents`, so you would land in the native
|
|
172
|
-
view and only get back to the inbox after quitting that. No flag or setting
|
|
173
|
-
suppresses just that relaunch: the one switch that exists disables `attach`
|
|
174
|
-
too. So the inbox watches its child's command line and, the moment it turns
|
|
175
|
-
into the agents view, terminates it. Measured round trip is about 0.4s and the
|
|
176
|
-
agents view never draws a frame. `Ctrl+Z` is handled locally by the attach
|
|
177
|
-
client and returns directly with no trick needed.
|
|
178
|
-
|
|
179
|
-
## Rules
|
|
180
|
-
|
|
181
|
-
All four rules are pure methods on `ClaudeInbox::Store::Row`, a session paired
|
|
182
|
-
with its entry, and are the only place triage logic lives.
|
|
183
|
-
|
|
184
|
-
**Wake.** A snoozed session returns when its timer elapses, when you press `u`,
|
|
185
|
-
or when it *becomes* blocked or failed after being snoozed. A session that was
|
|
186
|
-
already blocked when you snoozed it stays snoozed; that is the point of snoozing.
|
|
187
|
-
`u` also brings back a settled row, whether it settled by hand or because its
|
|
188
|
-
pull request resolved, and puts it wherever its raw state belongs — Active or
|
|
189
|
-
Needs You. It stays there until the state actually changes again.
|
|
190
|
-
|
|
191
|
-
**Acknowledge.** Attaching to a needs-you session (`Enter`) marks its current
|
|
192
|
-
state seen, moving it to Active instead of leaving it in Needs You. It comes
|
|
193
|
-
back to Needs You the moment its state changes again. Still blocked with a
|
|
194
|
-
new prompt doesn't count, only an actual state change does, same as hand-settle.
|
|
101
|
+
`n` opens a form: prompt, name, directory, model, effort, permissions,
|
|
102
|
+
worktree, Remote Control. `Tab` / `Shift+Tab` move between fields, `Esc` cancels. Choice
|
|
103
|
+
fields cycle with `h` `l` or the arrows; "default" shows what your settings
|
|
104
|
+
resolve to. The directory defaults to the selected row's, and `Tab` completes
|
|
105
|
+
it.
|
|
106
|
+
|
|
107
|
+
`Ctrl-S` starts the session and drops you back in the inbox; `Ctrl-O` starts
|
|
108
|
+
it and attaches. Either way the new row is selected once it shows up. It runs
|
|
109
|
+
`claude --bg "<prompt>"` in that directory, with only the flags you changed.
|
|
110
|
+
|
|
111
|
+
The prompt is multi-line; `Enter` breaks a line. Paste an image with `Cmd-V`
|
|
112
|
+
(or `Ctrl-V`), or drop a file onto the window, and it lands at the cursor as
|
|
113
|
+
an `[Image #1]` token the prompt can point at: "make the button look like
|
|
114
|
+
[Image #1]". Pasted images are saved under `~/.config/claude-inbox/images/`
|
|
115
|
+
for 14 days; a dropped file is referenced where it is. Reading the clipboard
|
|
116
|
+
is macOS only.
|
|
117
|
+
|
|
118
|
+
Remote Control set to yes starts the session with `--remote-control`, so it
|
|
119
|
+
runs under the daemon like any other row and is also listed at claude.ai/code
|
|
120
|
+
and in the Claude mobile app. See Remote Control below.
|
|
121
|
+
|
|
122
|
+
Slash commands work as at Claude Code's own prompt. Type `/` at the start of a
|
|
123
|
+
word for a menu of your skills and commands, plugin skills as `plugin:name`,
|
|
124
|
+
claude.ai's synced skills as `anthropic-skills:name`. `↑` `↓` choose, `Tab` or
|
|
125
|
+
`Enter` insert, `Esc` closes the menu. Built-ins such as `/init` aren't listed
|
|
126
|
+
but still work.
|
|
127
|
+
|
|
128
|
+
## How sessions move
|
|
129
|
+
|
|
130
|
+
**Wake.** A snoozed session returns when its timer elapses, when you press
|
|
131
|
+
`u`, or when it becomes blocked or failed after being snoozed. A session that
|
|
132
|
+
was already blocked when you snoozed it stays snoozed; that is the point of
|
|
133
|
+
snoozing. `u` also brings back a settled row and puts it wherever its raw
|
|
134
|
+
state belongs.
|
|
135
|
+
|
|
136
|
+
**Acknowledge.** Attaching to a needs-you session marks its current state
|
|
137
|
+
seen and moves it to Active. It comes back to Needs You when its state
|
|
138
|
+
changes again. Still blocked with a new prompt doesn't count.
|
|
195
139
|
|
|
196
140
|
**Settle.** Settled by hand with `x`, or because every pull request it has is
|
|
197
141
|
merged or closed. A hand-settled session stays put when it finishes, and
|
|
198
|
-
returns as soon as it changes state in any other way
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
A session
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
State
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
with whatever states are already known, and asks gh afterwards; the rows come
|
|
256
|
-
round again only if an answer moved one. That is why the inbox is up in well
|
|
257
|
-
under a second rather than after a dozen serial `gh` calls. Once gh has said a
|
|
258
|
-
PR is merged or closed the answer is kept in `~/.config/claude-inbox/prs.json`,
|
|
259
|
-
in the same shape as Claude Code's cache, because that cache only covers PRs
|
|
260
|
-
its own sessions opened and a link scan picks up plenty of others — without
|
|
261
|
-
the file every launch would ask about every merged PR again.
|
|
262
|
-
|
|
263
|
-
Until the first poll lands the body is blank rather than claiming "nothing
|
|
264
|
-
running"; past a second and a half it gets a spinner and a rotating excuse
|
|
265
|
-
with the elapsed time, and past ten seconds a hint to check the daemon.
|
|
266
|
-
|
|
267
|
-
## Colors
|
|
268
|
-
|
|
269
|
-
`/color` inside a session is the only way to set one; there is no key for it
|
|
270
|
-
here, and nothing is stored on our side. The daemon writes it to the job file
|
|
271
|
-
as `color`, `claude agents --json` leaves it out, so `JobState` reads it on
|
|
272
|
-
every poll and a color you change shows up on the next one.
|
|
273
|
-
|
|
274
|
-
It lands on the label and nowhere else. The glyph, the state badge and the PR
|
|
275
|
-
badge keep the colors their own state gives them, so no color you pick can
|
|
276
|
-
stop a blocked session looking blocked. Settled rows stay dim — that section is
|
|
277
|
-
meant to be quiet, and a color shouting out of a collapsed fold would undo it.
|
|
278
|
-
|
|
279
|
-
The eight colors `/color` offers are mapped the way Claude Code's own tmux
|
|
280
|
-
code maps them when it tints a teammate pane: `red`, `blue`, `green`, `yellow`,
|
|
281
|
-
`purple` and `cyan` go to the terminal's own ansi colors, so they follow your
|
|
282
|
-
theme; `orange` and `pink` have no ansi name and go through 256-color indexes
|
|
283
|
-
208 and 205. Anything else is left unpainted. Colors close with SGR 39
|
|
284
|
-
(default foreground) rather than 0, so a label that is already bold or italic
|
|
285
|
-
stays that way.
|
|
286
|
-
|
|
287
|
-
## Usage
|
|
142
|
+
returns as soon as it changes state in any other way. `failed` never settles.
|
|
143
|
+
A session with no pull request never settles on its own, however long it has
|
|
144
|
+
been quiet. A session with one stays Active while any of its PRs is open or a
|
|
145
|
+
draft.
|
|
146
|
+
|
|
147
|
+
**Reap.** A background session quiet for 14 days is deleted on the next poll
|
|
148
|
+
with `claude rm`, so the transcript and the worktree go with it, same as
|
|
149
|
+
`Ctrl-x`. This is the one thing here that destroys anything without asking
|
|
150
|
+
first. Reaping does not key on Settled: an open draft keeps a row in Active
|
|
151
|
+
for ever, and those are precisely the rows that need reaping. Idle time is the
|
|
152
|
+
only clock.
|
|
153
|
+
|
|
154
|
+
Never reaped, whatever the clock says: a `working` session, one that still
|
|
155
|
+
holds a process, a pin, and a snooze. `failed` is reaped, even though it never
|
|
156
|
+
settles; after a fortnight of not looking at it you never will. A pin or a
|
|
157
|
+
parked snooze is the way to keep a session indefinitely.
|
|
158
|
+
|
|
159
|
+
Unpushed work is safe. `claude rm` refuses a worktree holding commits that
|
|
160
|
+
aren't pushed, and nothing here ever overrides that refusal. Every reap
|
|
161
|
+
appends a line to `~/.config/claude-inbox/reaped.log`, the last record a
|
|
162
|
+
session existed once its transcript is gone. `CLAUDE_INBOX_NO_REAP=1` turns
|
|
163
|
+
reaping off.
|
|
164
|
+
|
|
165
|
+
## Remote Control
|
|
166
|
+
|
|
167
|
+
Every background session has a page at claude.ai/code, where you can follow
|
|
168
|
+
it from a browser or the Claude mobile app; `w` opens it. A session with
|
|
169
|
+
Remote Control on can also be driven from there, the way `claude
|
|
170
|
+
--remote-control` and `/rc` allow in a terminal. Its row wears a `⇅` after
|
|
171
|
+
the state. Two kinds of session have it:
|
|
172
|
+
|
|
173
|
+
- A background session the `n` form started with Remote Control set to yes,
|
|
174
|
+
or one you ran as `claude --bg "…" --remote-control` yourself. It is a
|
|
175
|
+
daemon row like any other: `Enter` attaches, `X` stops, and it keeps its
|
|
176
|
+
Remote Control across a stop and a wake, since the daemon saves the flag.
|
|
177
|
+
- A worker a `claude remote-control` server spawned for a request from your
|
|
178
|
+
phone. That is how a session started away from the desk lands on this
|
|
179
|
+
machine. The daemon calls it interactive and can't attach to it, so
|
|
180
|
+
`Enter` offers to adopt it instead: `y` ends the worker and resumes the
|
|
181
|
+
conversation as a background session under the same id, with Remote
|
|
182
|
+
Control on, and attaches. The phone session you were in ends there and a
|
|
183
|
+
new one takes its place, with the whole conversation; the server keeps
|
|
184
|
+
running for the next request. A worker nobody has messaged yet has
|
|
185
|
+
nothing to adopt, and Enter says so.
|
|
186
|
+
|
|
187
|
+
## Pull request state
|
|
188
|
+
|
|
189
|
+
The daemon scans each background session's transcript for PR links, so a
|
|
190
|
+
session that only reviews a PR gets it too. Interactive sessions aren't
|
|
191
|
+
scanned; `P` sets the link by hand for those, or to correct a bad scan.
|
|
192
|
+
|
|
193
|
+
State comes from whatever Claude Code last saw, then `gh pr view` on the
|
|
194
|
+
poller thread, at most once a minute per PR. Without `gh` the cached state is
|
|
195
|
+
all you get. The list goes up before gh is asked, so the inbox is up in under
|
|
196
|
+
a second.
|
|
197
|
+
|
|
198
|
+
## Setup
|
|
288
199
|
|
|
289
200
|
The right end of the header shows how much of your Claude subscription's
|
|
290
|
-
5-hour and 7-day rate limit windows you have used, as
|
|
291
|
-
|
|
201
|
+
5-hour and 7-day rate limit windows you have used, as a ten-cell bar per
|
|
202
|
+
window that turns yellow at 70% and red at 90%, like the context bar many
|
|
203
|
+
status lines draw:
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
session ██░░░░░░░░ 24% · 3h left week ████░░░░░░ 41% · 2d left
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`session` is the 5-hour window and `week` the 7-day one, as Claude's own
|
|
210
|
+
`/usage` names them; `3h left` is how long until that window resets. It needs
|
|
211
|
+
a Pro or Max account and a one-time setup.
|
|
292
212
|
|
|
293
213
|
Claude Code passes the numbers to your [status line](https://code.claude.com/docs/en/statusline)
|
|
294
214
|
script every turn, and the inbox reads them from a file that script writes.
|
|
@@ -305,138 +225,9 @@ writes one for you, then add the lines to that. Keep the `-n` check: a
|
|
|
305
225
|
session's first status line run has no numbers yet, and writing anyway would
|
|
306
226
|
blank the file.
|
|
307
227
|
|
|
308
|
-
The
|
|
228
|
+
The bars stay off until the file exists, and goes off again once the file
|
|
309
229
|
is more than fifteen minutes old.
|
|
310
230
|
|
|
311
|
-
##
|
|
312
|
-
|
|
313
|
-
`~/.config/claude-inbox/state.json`, keyed by session id, atomic writes.
|
|
314
|
-
Holds `wake_at`, `snoozed_at`, `alias`, `pr`, `pinned`, `pinned_at`, `settled_at`, `acknowledged_at`, `revived_at`, `last_state`, `state_since`, `last_seen`,
|
|
315
|
-
and `reap_failed_at` / `reap_error` for a session `claude rm` has refused.
|
|
316
|
-
`Store::Entry` is where those names live; everything else reads an entry
|
|
317
|
-
through it. Entries not seen in a poll for 7 days are pruned. Pruning only reaches entries
|
|
318
|
-
the daemon has *forgotten*, which is a different thing from the reaper: the
|
|
319
|
-
daemon still lists sessions a month old, so those keep their entry and it is
|
|
320
|
-
the 14-day reap that clears them.
|
|
321
|
-
|
|
322
|
-
## Layout
|
|
323
|
-
|
|
324
|
-
```
|
|
325
|
-
Sessions.load: AgentsClient → JobState → PullRequests → Poller → Store → Renderer → App
|
|
326
|
-
(shells out) (jobs dir) (known states) (thread, then gh) (pure) (strings) (terminal + key loop)
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
- `AgentsClient` is the only thing that runs `claude`. `FixtureClient` swaps in a JSON file.
|
|
330
|
-
- `Sessions.load` is the one place the list is put together: `AgentsClient`,
|
|
331
|
-
then `JobState`, then `PullRequests#enrich`, in that order because the links
|
|
332
|
-
`PullRequests` wants are the ones `JobState` read. `Session` is immutable; a
|
|
333
|
-
step that adds something hands back a copy.
|
|
334
|
-
- `JobState` reads `~/.claude/jobs/<id>/state.json`, the daemon's own file: the scanned
|
|
335
|
-
links `PullRequests` wants, the open work behind a `working` state, the session's
|
|
336
|
-
own line (`detail`, `needs`, `output.result`) and the `/color` each session carries. Never cached. `enrich` returns each background session
|
|
337
|
-
with its file attached, for `Sessions.load`.
|
|
338
|
-
- `PullRequests` pairs each session with the PRs its `job_state` links and keeps
|
|
339
|
-
their state fresh through `gh`. `enrich` never asks gh and runs inside
|
|
340
|
-
`Sessions.load`; `refresh` is the slow half and runs after the list has gone
|
|
341
|
-
up. `--fixture` points it at `test/fixtures/jobs` with `gh` off.
|
|
342
|
-
- `Palette` maps a session color to an escape sequence and knows nothing else.
|
|
343
|
-
- `RateLimits` reads `~/.claude/rate_limits.json`, the file a status line script writes, and gives the header its usage label. Nil without the file.
|
|
344
|
-
- `Store` holds the last poll and the entry table behind a mutex, and folds each poll in.
|
|
345
|
-
A key it was told to `hide` or `forget` stays out of every poll until it is released or the daemon stops listing it.
|
|
346
|
-
`Store::Entry` is what is remembered about one session, and the only place the state file's key names appear.
|
|
347
|
-
`Store::Row` is one session with its entry, and the rules are its methods.
|
|
348
|
-
`Store::Sections` is one poll sorted into sections and knows where the cursor can land: the `/` filter, the fold-or-rows rule and which section a key is in live there.
|
|
349
|
-
- `Renderer` turns sections into an array of fixed-width strings. `Painter` diffs frames
|
|
350
|
-
and repaints only changed rows.
|
|
351
|
-
- `Reaper` runs `claude rm` over whatever `Row#reapable?` picks and appends a
|
|
352
|
-
line to the log for each one. `due` names them without touching anything;
|
|
353
|
-
`sweep` does the deleting. Both run on the poller, and the list goes up
|
|
354
|
-
between them with the rows `due` named hidden in the store, so a slow `rm`
|
|
355
|
-
stalls neither a frame nor the first one and a row never paints while it is
|
|
356
|
-
being deleted.
|
|
357
|
-
- `Poller` is the thread that asks `Sessions.load` for the list, every four
|
|
358
|
-
seconds and on demand, runs it past the `Reaper` and then the gh refresh,
|
|
359
|
-
and hands each result to `App` over a queue. An on-demand poll queues onto
|
|
360
|
-
that same thread rather than racing it, so a burst of them while it is idle
|
|
361
|
-
is one `claude agents` call, one that lands mid-poll is a single follow-up,
|
|
362
|
-
and no two polls overlap. `App` pauses it while `claude
|
|
363
|
-
attach` has the terminal.
|
|
364
|
-
- `Terminal` is the screen: the alt screen with its mouse and wheel modes, raw
|
|
365
|
-
mode, the cached size and the `Painter` that diffs frames onto it. `release`
|
|
366
|
-
lends it to `claude attach` and takes it back.
|
|
367
|
-
- `App` owns the key loop, the poller thread and the logs thread, and is the
|
|
368
|
-
only place that spawns a child.
|
|
369
|
-
- `Peek` is the peek pane: whether it is open, how far back it is scrolled and
|
|
370
|
-
what to paint for the selected row. `Logs` is where the lines come from: the
|
|
371
|
-
`claude logs` replay of each session, fetched off the main thread, debounced
|
|
372
|
-
and cached.
|
|
373
|
-
- `VtScreen` is a small cursor-addressed grid used to turn the `claude logs` replay
|
|
374
|
-
into readable lines for the peek pane.
|
|
375
|
-
- `SlashCommands` reads the skills and commands `claude` would offer from the
|
|
376
|
-
same directories it reads them, front matter included, for the new-session
|
|
377
|
-
prompt's menu. Pure filesystem; it never runs `claude`.
|
|
378
|
-
- `Dialog` is a box over the list that claims every key until it answers:
|
|
379
|
-
the snooze menu, the stop and delete confirms, the alias and pull request
|
|
380
|
-
prompts. Pure, like `NewSessionForm`; `App` acts on the answer.
|
|
381
|
-
- `Mouse` turns the SGR escape sequences the terminal sends for clicks and
|
|
382
|
-
wheel ticks into `Event`s; `App` maps a click's row back to whatever
|
|
383
|
-
`Renderer` painted there.
|
|
384
|
-
|
|
385
|
-
## Things learned from the real CLI (2.1.273)
|
|
386
|
-
|
|
387
|
-
- Terminal.app puts the tty's active process in the tab title, so a poller that
|
|
388
|
-
forks `claude` every few seconds makes the title flicker. Every helper
|
|
389
|
-
subprocess here is started with `setsid` so it has no controlling tty.
|
|
390
|
-
|
|
391
|
-
- `claude agents --json --all` matches the documented shape exactly. A `done`
|
|
392
|
-
session can still carry a `pid` and `status: idle`.
|
|
393
|
-
- `claude logs <id>` is **not** plain text. It is a replay of the session's terminal
|
|
394
|
-
output: cursor positioning, erase-line, color. Words are frequently separated by
|
|
395
|
-
cursor motion rather than spaces, so stripping escapes yields run-together garbage.
|
|
396
|
-
Feeding it through a screen grid produces readable text.
|
|
397
|
-
- `claude logs` fails with "job not found" for a finished session whose process the
|
|
398
|
-
supervisor has reaped. The peek pane shows a notice instead.
|
|
399
|
-
- `claude attach --help` says `←` returns to agent view and `Ctrl+Z` drops to the
|
|
400
|
-
shell. Under the hood `←` makes the attach process exec `claude agents` in place,
|
|
401
|
-
same pid, using its own executable path, so a PATH shim never sees it.
|
|
402
|
-
- `CLAUDE_CODE_DISABLE_AGENT_VIEW=1` disables `agents`, `attach` and `logs` alike.
|
|
403
|
-
- A prompt beginning with a slash command is expanded, `$ARGUMENTS` and all,
|
|
404
|
-
in `-p` and `--bg` alike (checked on 2.1.274 with a project command).
|
|
405
|
-
Installed plugins are listed in `~/.claude/plugins/installed_plugins.json`,
|
|
406
|
-
keyed `name@marketplace`, each pointing at its `installPath`; claude.ai's
|
|
407
|
-
synced skills sit in `~/.claude/skills/synced/<bucket>/<name>/SKILL.md`
|
|
408
|
-
and show up as `anthropic-skills:<name>`.
|
|
409
|
-
- Interactive sessions (a `claude` you started in a terminal yourself) appear in
|
|
410
|
-
the JSON with no `id` and no `state`, only `status`, and cannot be attached,
|
|
411
|
-
peeked or stopped from outside.
|
|
412
|
-
- `~/.claude/jobs/<id>/state.json` is where the daemon keeps what the JSON
|
|
413
|
-
leaves out: `children` (scanned PR and issue links), `intent`, `worktreePath`,
|
|
414
|
-
`worktreeBranch`, token count and the transcript path, plus the line the
|
|
415
|
-
agents view prints under a row: `detail` is the session's status line,
|
|
416
|
-
`needs` what it is waiting on while blocked, `output.result` its closing
|
|
417
|
-
summary once done. `~/.claude/gh-pr-status-cache.json`
|
|
418
|
-
is keyed by PR url and calls an open draft `DRAFT`; `gh pr view` reports
|
|
419
|
-
`OPEN` plus `isDraft`.
|
|
420
|
-
|
|
421
|
-
- The job file is also where `/color` ends up, as `color`. `claude agents --json`
|
|
422
|
-
does not carry it, so a color is only ever visible by reading the file. The CLI
|
|
423
|
-
offers eight, and its own tmux code is the authority on what they mean to a
|
|
424
|
-
terminal: six are ansi names, `orange` and `pink` are 256-color indexes 208 and
|
|
425
|
-
205. Interactive sessions have no job file and so never have a color.
|
|
426
|
-
|
|
427
|
-
## Development
|
|
428
|
-
|
|
429
|
-
```
|
|
430
|
-
bundle install
|
|
431
|
-
bin/ci # full signoff: lint, gem audit, tests (see CONTRIBUTING.md)
|
|
432
|
-
bin/release --dry-run # the version the commits since the last tag call for
|
|
433
|
-
bundle exec rake test # minitest/spec, test/**/*_spec.rb
|
|
434
|
-
bundle exec standardrb
|
|
435
|
-
CLAUDE_INBOX_STDERR=/tmp/err.log bin/claude-inbox # crash traces off the alt screen
|
|
436
|
-
DEBUG=1 bin/claude-inbox # slow-frame notes in /tmp/inbox-debug.log
|
|
437
|
-
CLAUDE_INBOX_NO_REAP=1 bin/claude-inbox # never delete an idle session
|
|
438
|
-
bin/screens # drive the fixture in a pty, print every screen
|
|
439
|
-
```
|
|
231
|
+
## Contributing
|
|
440
232
|
|
|
441
|
-
|
|
442
|
-
`main` and on the branch and diff the two.
|
|
233
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
data/exe/claude-inbox
CHANGED
|
@@ -19,7 +19,6 @@ if (i = ARGV.index("--fixture"))
|
|
|
19
19
|
fixtures = File.dirname(agents)
|
|
20
20
|
opts[:client] = ClaudeInbox::FixtureClient.new(agents, logs: File.binread(File.join(fixtures, "logs_raw.txt")))
|
|
21
21
|
opts[:store] = ClaudeInbox::Store.new(path: nil)
|
|
22
|
-
opts[:jobs_dir] = File.join(fixtures, "jobs")
|
|
23
22
|
opts[:pull_requests] = ClaudeInbox::PullRequests.new(cache_path: File.join(fixtures, "gh-pr-status-cache.json"), resolved_path: nil, gh: nil)
|
|
24
23
|
else
|
|
25
24
|
# The only place the reaper is armed. Everything else — tests, --fixture,
|