claude-inbox 0.1.0 → 0.3.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 +185 -386
- data/exe/claude-inbox +23 -5
- data/lib/claude_inbox/agents_client.rb +104 -44
- data/lib/claude_inbox/app.rb +114 -62
- data/lib/claude_inbox/config.rb +24 -0
- data/lib/claude_inbox/dialog.rb +10 -4
- data/lib/claude_inbox/images.rb +37 -6
- data/lib/claude_inbox/job_state.rb +32 -9
- data/lib/claude_inbox/keymap.rb +2 -2
- data/lib/claude_inbox/logs.rb +5 -2
- data/lib/claude_inbox/new_session_form.rb +64 -52
- data/lib/claude_inbox/painter.rb +32 -0
- data/lib/claude_inbox/palette.rb +4 -9
- data/lib/claude_inbox/peek.rb +4 -4
- data/lib/claude_inbox/poller.rb +9 -14
- data/lib/claude_inbox/pull_requests.rb +5 -5
- data/lib/claude_inbox/rate_limits.rb +19 -7
- data/lib/claude_inbox/reaper.rb +13 -16
- data/lib/claude_inbox/records.rb +7 -2
- data/lib/claude_inbox/remote/http.rb +137 -0
- data/lib/claude_inbox/remote/icon.png +0 -0
- data/lib/claude_inbox/remote/icon.svg +8 -0
- data/lib/claude_inbox/remote/listener.rb +436 -0
- data/lib/claude_inbox/remote/page.html +477 -0
- data/lib/claude_inbox/remote/pairing.rb +111 -0
- data/lib/claude_inbox/remote/pairing_dialog.rb +93 -0
- data/lib/claude_inbox/remote/start.rb +202 -0
- data/lib/claude_inbox/remote.rb +10 -0
- data/lib/claude_inbox/renderer.rb +135 -113
- data/lib/claude_inbox/session.rb +19 -19
- data/lib/claude_inbox/session_request.rb +151 -0
- data/lib/claude_inbox/sessions.rb +2 -3
- data/lib/claude_inbox/settings.rb +19 -7
- 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 -4
- data/lib/claude_inbox/subprocess.rb +12 -3
- data/lib/claude_inbox/terminal.rb +9 -3
- data/lib/claude_inbox/text.rb +13 -11
- data/lib/claude_inbox/text_buffer.rb +3 -2
- data/lib/claude_inbox/theme.rb +5 -4
- data/lib/claude_inbox/trust.rb +23 -0
- data/lib/claude_inbox/vt_screen.rb +1 -3
- data/lib/claude_inbox.rb +1 -1
- metadata +15 -2
data/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# claude-inbox
|
|
2
2
|
|
|
3
|
-
Inbox-style triage
|
|
4
|
-
`claude agents`,
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Inbox-style triage for Claude Code's background sessions. It reads the same
|
|
4
|
+
daemon state as `claude agents`, through `claude agents --json`, and adds
|
|
5
|
+
snooze, auto-settle and each session's pull request. Use it next to
|
|
6
|
+
`claude agents`, not instead of it.
|
|
7
7
|
|
|
8
8
|

|
|
9
9
|
|
|
@@ -18,281 +18,210 @@ Requires Ruby 3.2+ and a `claude` on PATH with the agents feature. Versions
|
|
|
18
18
|
are published to rubygems and listed under GitHub Releases.
|
|
19
19
|
`claude-inbox --version` prints the installed version.
|
|
20
20
|
|
|
21
|
+
Put arguments you want on every launch in `~/.config/claude-inbox/config`.
|
|
22
|
+
|
|
21
23
|
## Sections
|
|
22
24
|
|
|
23
|
-
1. **Pinned.** Parked at the top by hand,
|
|
24
|
-
2. **Needs you.** `blocked` or `failed`,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
can sit there for an hour. Whatever the agent is up to, the open work is named
|
|
54
|
-
next to the state: `working · 2 agents`, `idle · 1 shell`. The count comes from
|
|
55
|
-
the session's own job file, which `claude agents --json` does not expose; the
|
|
56
|
-
inbox still takes the state itself from the daemon, the only thing that knows
|
|
57
|
-
whether a session is alive.
|
|
58
|
-
|
|
59
|
-
Under each row in Pinned, Needs You and Active is the session's own line,
|
|
60
|
-
the one `claude agents` prints too: what it is waiting on while blocked
|
|
61
|
-
(`↳ confirm: drop the top line?`), what it produced once done, otherwise its
|
|
62
|
-
status line. It comes from the job file, like the open work above, so a
|
|
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).
|
|
25
|
+
1. **Pinned.** Parked at the top by hand, whatever its state. `t` toggles it.
|
|
26
|
+
2. **Needs you.** `blocked` or `failed`, until you attach to it.
|
|
27
|
+
3. **Active.** `working`, finished but not yet settled, interactive, or a
|
|
28
|
+
needs-you session you attached to that hasn't resolved. An interactive
|
|
29
|
+
session only reports busy, idle or waiting, which show as working, done
|
|
30
|
+
and needs you. A terminal you're sitting in never settles and can't be
|
|
31
|
+
attached, peeked or stopped from here. Sub-agents and headless `claude -p`
|
|
32
|
+
runs don't show up at all.
|
|
33
|
+
4. **Snoozed.** Sorted by wake time, with "until I wake it" entries last.
|
|
34
|
+
Collapsed. Enter expands it.
|
|
35
|
+
5. **Settled.** Settled with `x`, or every pull request is merged or closed.
|
|
36
|
+
Collapsed. Enter expands it.
|
|
37
|
+
|
|
38
|
+
A row with a pull request shows it after the state, as `#885 open`,
|
|
39
|
+
`#885 draft`, `#885 merged` or `#885 closed`. `o` opens it in the browser.
|
|
40
|
+
|
|
41
|
+
The daemon's `working` covers two cases, and the row tells them apart. A
|
|
42
|
+
spinner and `working` mean the agent is thinking. A steady `◌` and
|
|
43
|
+
`idle · 1 shell` mean it stopped and is waiting on something it started, such
|
|
44
|
+
as a `--watch` shell or a sub-agent. That's why a session with nothing left to
|
|
45
|
+
do can sit there for an hour.
|
|
46
|
+
|
|
47
|
+
Under each row in Pinned, Needs you and Active is the same line
|
|
48
|
+
`claude agents` prints. While blocked it's what the session waits on
|
|
49
|
+
(`↳ confirm: drop the top line?`), once done it's what it produced, and
|
|
50
|
+
otherwise it's the status line. A terminal you opened yourself has none, so
|
|
51
|
+
it shows its directory.
|
|
52
|
+
|
|
53
|
+
A session you gave a color with `/color` wears it on the label. Nothing else
|
|
54
|
+
is tinted, so a blocked session still looks blocked.
|
|
67
55
|
|
|
68
56
|
## Keys
|
|
69
57
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
row you can't be attached to from here). The wheel moves the selection
|
|
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.
|
|
58
|
+
Vim bindings. Arrows work as well as `j`/`k`. In terminals that report the
|
|
59
|
+
mouse, a click selects and attaches and the wheel moves the selection. Apple
|
|
60
|
+
Terminal reports neither.
|
|
79
61
|
|
|
80
62
|
| Key | Action |
|
|
81
63
|
|---|---|
|
|
82
64
|
| `j` `k` | move down / up |
|
|
83
65
|
| `gg` `G` | first / last row |
|
|
84
66
|
| `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 |
|
|
67
|
+
| `Enter` `l` | attach (full-screen handoff; `←` or `Ctrl+Z` return here), adopt a remote session, or expand Snoozed / Settled |
|
|
86
68
|
| `Tab` `Shift+Tab` | jump to the next / previous section |
|
|
87
69
|
| `h` | close the peek pane, else collapse the current Snoozed / Settled fold |
|
|
88
70
|
| `za` `zo` `zc` | toggle / open / close the Snoozed or Settled fold under the cursor |
|
|
89
71
|
| `p` | toggle the read-only peek pane |
|
|
90
72
|
| `J` `K` (`Ctrl-e` `Ctrl-y`) | scroll the peek pane |
|
|
91
|
-
| `n` | new session
|
|
92
|
-
| `
|
|
73
|
+
| `n` | new session |
|
|
74
|
+
| `N` | pair a phone: whether the inbox is listening, and the URL to pair with |
|
|
75
|
+
| `t` | pin / unpin |
|
|
93
76
|
| `s` | snooze: `1` 15m · `2` 1h · `3` tomorrow 9am · `4` until woken |
|
|
94
77
|
| `u` | wake a snoozed session now, or bring back one you settled |
|
|
95
78
|
| `x` | settle a working or needs-you session by hand (returns when it changes state, unless it just finished) |
|
|
96
79
|
| `a` | set a local alias (never touches the real session name) |
|
|
97
80
|
| `o` | open the session's pull request in the browser |
|
|
81
|
+
| `w` | open the session at claude.ai/code |
|
|
98
82
|
| `P` | link a pull request by hand (empty clears; the scanned links return) |
|
|
99
83
|
| `X` | stop the session (`y` to confirm) |
|
|
100
84
|
| `Ctrl-x` | delete the session for good, conversation and worktree with it (`y` to confirm) |
|
|
101
|
-
| `/` | filter by name or
|
|
85
|
+
| `/` | filter by name, cwd, the prompt it started from, or what it is doing now; `Enter` keeps it, `Esc` clears |
|
|
102
86
|
| `R` | poll now |
|
|
103
87
|
| `q` | quit |
|
|
104
88
|
|
|
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.
|
|
89
|
+
`X` and `Ctrl-x` both ask first and both take `y`, but they do different
|
|
90
|
+
things. `X` runs `claude stop`, which ends the process and keeps the
|
|
91
|
+
conversation, and `Enter` resumes it later. `Ctrl-x` runs `claude rm`, which
|
|
92
|
+
deletes the session, its transcript and its worktree. Nothing resumes after
|
|
93
|
+
that, so read the box before answering.
|
|
115
94
|
|
|
116
95
|
### New session
|
|
117
96
|
|
|
118
|
-
`n` opens a form
|
|
119
|
-
|
|
120
|
-
`
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
have no job file, so for them (or to correct a bad scan) `P` sets the link by
|
|
247
|
-
hand; that lives in our own state file as `pr` and replaces the scanned list.
|
|
248
|
-
|
|
249
|
-
State is seeded from `~/.claude/gh-pr-status-cache.json`, whatever Claude Code
|
|
250
|
-
last saw, then refreshed with `gh pr view` on the poller thread, at most once a
|
|
251
|
-
minute per PR and never for one already merged or closed. Without `gh` the
|
|
252
|
-
cached state is all you get.
|
|
253
|
-
|
|
254
|
-
`gh pr view` is a network round trip, so each poll publishes the list first,
|
|
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
|
|
288
|
-
|
|
289
|
-
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 `usage 5h 24% · 7d 41%`.
|
|
291
|
-
It needs a Pro or Max account and a one-time setup.
|
|
292
|
-
|
|
293
|
-
Claude Code passes the numbers to your [status line](https://code.claude.com/docs/en/statusline)
|
|
294
|
-
script every turn, and the inbox reads them from a file that script writes.
|
|
295
|
-
Put these two lines in the script, just after it reads stdin:
|
|
97
|
+
`n` opens a form with prompt, name, directory, model, effort, permissions,
|
|
98
|
+
worktree and Remote Control. `Tab` and `Shift+Tab` move between fields and
|
|
99
|
+
`Esc` cancels. Choice fields cycle with `h` `l` or the arrows, and "default"
|
|
100
|
+
shows what your settings resolve to. The directory starts as the selected
|
|
101
|
+
row's, and `Tab` completes it.
|
|
102
|
+
|
|
103
|
+
`Ctrl-S` starts the session and drops you back in the inbox. `Ctrl-O` starts
|
|
104
|
+
it and attaches. It runs `claude --bg "<prompt>"` in that directory with only
|
|
105
|
+
the flags you changed, and the new row is selected once it shows up.
|
|
106
|
+
|
|
107
|
+
The prompt is multi-line, and `Enter` breaks a line. Paste an image with
|
|
108
|
+
`Cmd-V` (or `Ctrl-V`) or drop a file on the window, and an `[Image #1]` token
|
|
109
|
+
lands at the cursor for the prompt to point at: "make the button look like
|
|
110
|
+
[Image #1]". Pasted images are kept in `~/.config/claude-inbox/images/` for
|
|
111
|
+
14 days. A dropped file is referenced where it is. Pasting images works on
|
|
112
|
+
macOS only.
|
|
113
|
+
|
|
114
|
+
Remote Control set to yes adds `--remote-control`, so the session also shows
|
|
115
|
+
up at claude.ai/code and in the Claude mobile app. It defaults to yes when
|
|
116
|
+
"Enable Remote Control for all sessions" is on in `/config`.
|
|
117
|
+
|
|
118
|
+
Type `/` at the start of a word for a menu of your skills and commands.
|
|
119
|
+
Plugin skills appear as `plugin:name` and claude.ai's synced skills as
|
|
120
|
+
`anthropic-skills:name`. `↑` `↓` choose, `Tab` or `Enter` insert, `Esc`
|
|
121
|
+
closes the menu. Built-ins such as `/init` aren't listed but still work.
|
|
122
|
+
|
|
123
|
+
## How sessions move
|
|
124
|
+
|
|
125
|
+
**Wake.** A snoozed session comes back when its timer runs out, when you
|
|
126
|
+
press `u`, or when it turns blocked or failed after you snoozed it. One that
|
|
127
|
+
was already blocked when you snoozed it stays snoozed, which is the point of
|
|
128
|
+
snoozing. `u` also brings back a settled row.
|
|
129
|
+
|
|
130
|
+
**Acknowledge.** Attaching to a needs-you session marks its state seen and
|
|
131
|
+
moves it to Active. It returns to Needs you when its state changes again. A
|
|
132
|
+
new prompt while still blocked doesn't count.
|
|
133
|
+
|
|
134
|
+
**Settle.** A session settles when you press `x` or when every pull request
|
|
135
|
+
it has is merged or closed. A hand-settled session stays put when it
|
|
136
|
+
finishes, and comes back on any other state change. `failed` never settles.
|
|
137
|
+
A session with no pull request never settles on its own, and one with an
|
|
138
|
+
open or draft PR stays Active.
|
|
139
|
+
|
|
140
|
+
**Reap.** On the next poll, the inbox deletes a background session that has
|
|
141
|
+
been quiet for 14 days with `claude rm`, transcript and worktree included.
|
|
142
|
+
It's the one thing here that deletes without asking. Reaping ignores Settled,
|
|
143
|
+
because an open draft keeps a row in Active for ever and those are exactly
|
|
144
|
+
the rows that need reaping. Idle time is the only clock.
|
|
145
|
+
|
|
146
|
+
The inbox never reaps a `working` session, one that still holds a process, a
|
|
147
|
+
pinned one or a snoozed one. It does reap `failed`, even though failed never
|
|
148
|
+
settles. After a fortnight of not looking at it, you won't. Pin or park a
|
|
149
|
+
session to keep it.
|
|
150
|
+
|
|
151
|
+
Unpushed work is safe, because `claude rm` refuses a worktree with unpushed
|
|
152
|
+
commits and the inbox never overrides that. Every reap adds a line to
|
|
153
|
+
`~/.config/claude-inbox/reaped.log`. `CLAUDE_INBOX_NO_REAP=1` turns reaping
|
|
154
|
+
off.
|
|
155
|
+
|
|
156
|
+
## Remote Control
|
|
157
|
+
|
|
158
|
+
A session with Remote Control on has a page at claude.ai/code, where you can
|
|
159
|
+
follow and drive it from a browser or the Claude mobile app. `w` opens it,
|
|
160
|
+
and the row shows `⇅` after the state. Two kinds of session have it.
|
|
161
|
+
|
|
162
|
+
- A background session started with Remote Control on, from the `n` form or
|
|
163
|
+
as `claude --bg "…" --remote-control`. It behaves like any other row, and
|
|
164
|
+
keeps Remote Control across a stop and a resume.
|
|
165
|
+
- A worker a `claude remote-control` server spawned when you started a
|
|
166
|
+
session from your phone. The daemon can't attach to it, so `Enter` offers
|
|
167
|
+
to adopt it. `y` ends the worker, resumes the conversation as a background
|
|
168
|
+
session under the same id with Remote Control on, and attaches. The phone
|
|
169
|
+
session you were in ends and a new one with the whole conversation takes
|
|
170
|
+
its place. The server keeps running. A worker nobody has messaged yet has
|
|
171
|
+
nothing to adopt.
|
|
172
|
+
|
|
173
|
+
## Starting sessions from your phone
|
|
174
|
+
|
|
175
|
+
With `--listen-lan`, a phone on your Wi-Fi or VPN can start sessions the way
|
|
176
|
+
`n` does. It's off unless you ask for it.
|
|
177
|
+
|
|
178
|
+

|
|
179
|
+
|
|
180
|
+
1. Start the inbox with `claude-inbox --listen-lan`, or put `--listen-lan`
|
|
181
|
+
in `~/.config/claude-inbox/config` or `export CLAUDE_INBOX_LISTEN=lan` in
|
|
182
|
+
your shell rc.
|
|
183
|
+
2. Press `N`, then `c` to copy the pairing URL. Universal Clipboard takes it
|
|
184
|
+
to the phone.
|
|
185
|
+
3. Open it in Safari. To keep it, tap Share, then Add to Home Screen.
|
|
186
|
+
4. Open the new Inbox icon. It has its own storage, apart from Safari's, so
|
|
187
|
+
paste the pairing URL once more and tap Pair.
|
|
188
|
+
|
|
189
|
+
LAN mode is plain HTTP, so the token and every prompt cross the network
|
|
190
|
+
readable by anyone on it. Use it only on a network you control. Anyone with
|
|
191
|
+
the token can start sessions as you in your projects. If the URL leaks, press
|
|
192
|
+
`r` in the `N` dialog to replace it.
|
|
193
|
+
|
|
194
|
+
A remote start may use the `default`, `auto` and `plan` permission modes.
|
|
195
|
+
[docs/remote-start.md](docs/remote-start.md) covers the rest, including ssh,
|
|
196
|
+
the firewall prompt, the JSON API and an iOS Shortcut.
|
|
197
|
+
|
|
198
|
+
## Pull request state
|
|
199
|
+
|
|
200
|
+
The daemon scans each background session's transcript for PR links, so a
|
|
201
|
+
session that only reviews a PR gets it too. It doesn't scan interactive
|
|
202
|
+
sessions. Use `P` to link one by hand, or to fix a bad scan.
|
|
203
|
+
|
|
204
|
+
The state comes from Claude Code's own cache, then from `gh pr view` at most
|
|
205
|
+
once a minute per PR. Without `gh`, you only get the cached state.
|
|
206
|
+
|
|
207
|
+
## Usage bars
|
|
208
|
+
|
|
209
|
+
The right end of the header shows how much of your 5-hour and 7-day rate
|
|
210
|
+
limit windows you've used. Each is a ten-cell bar that turns yellow at 70%
|
|
211
|
+
and red at 90%.
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
session ██░░░░░░░░ 24% · 3h left week ████░░░░░░ 41% · 2d left
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`session` is the 5-hour window and `week` the 7-day one, as `/usage` names
|
|
218
|
+
them. `3h left` is the time until that window resets. The bars need a Pro or
|
|
219
|
+
Max account and a one-time setup.
|
|
220
|
+
|
|
221
|
+
Claude Code passes the numbers to your
|
|
222
|
+
[status line](https://code.claude.com/docs/en/statusline) script every turn,
|
|
223
|
+
and the inbox reads them from a file that script writes. Put these lines in
|
|
224
|
+
the script, just after it reads stdin:
|
|
296
225
|
|
|
297
226
|
```bash
|
|
298
227
|
input=$(cat)
|
|
@@ -300,143 +229,13 @@ limits=$(echo "$input" | jq -c '.rate_limits // empty')
|
|
|
300
229
|
[ -n "$limits" ] && echo "$limits" > ~/.claude/rate_limits.json.tmp && mv ~/.claude/rate_limits.json.tmp ~/.claude/rate_limits.json
|
|
301
230
|
```
|
|
302
231
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
blank the file.
|
|
307
|
-
|
|
308
|
-
The label stays off until the file exists, and goes off again once the file
|
|
309
|
-
is more than fifteen minutes old.
|
|
232
|
+
With no status line script, run `/statusline` in any session to get one,
|
|
233
|
+
then add the lines. Keep the `-n` check. A session's first status line run
|
|
234
|
+
has no numbers yet, and writing anyway would blank the file.
|
|
310
235
|
|
|
311
|
-
|
|
236
|
+
The bars stay hidden until the file exists, and hide again once it's more
|
|
237
|
+
than fifteen minutes old.
|
|
312
238
|
|
|
313
|
-
|
|
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
|
-
```
|
|
239
|
+
## Contributing
|
|
440
240
|
|
|
441
|
-
|
|
442
|
-
`main` and on the branch and diff the two.
|
|
241
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|