claude-inbox 0.14.4 → 0.15.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 +17 -10
- data/lib/claude_inbox/actions.rb +1 -1
- data/lib/claude_inbox/app/attaching.rb +25 -17
- data/lib/claude_inbox/app/navigating.rb +22 -9
- data/lib/claude_inbox/app/spawning.rb +3 -3
- data/lib/claude_inbox/app/triaging.rb +15 -0
- data/lib/claude_inbox/app.rb +11 -6
- data/lib/claude_inbox/claude/agents_client.rb +353 -0
- data/lib/claude_inbox/claude/job_state.rb +126 -0
- data/lib/claude_inbox/claude/roster.rb +34 -0
- data/lib/claude_inbox/claude/transcript.rb +113 -0
- data/lib/claude_inbox/cli.rb +17 -11
- data/lib/claude_inbox/codex/daemon.rb +102 -0
- data/lib/claude_inbox/codex/relay.rb +148 -0
- data/lib/claude_inbox/codex/threads_client.rb +181 -0
- data/lib/claude_inbox/codex/web_socket.rb +123 -0
- data/lib/claude_inbox/dialog.rb +12 -10
- data/lib/claude_inbox/harnesses.rb +40 -0
- data/lib/claude_inbox/headless.rb +3 -2
- data/lib/claude_inbox/images.rb +9 -7
- data/lib/claude_inbox/keymap.rb +3 -10
- data/lib/claude_inbox/mod.rb +1 -1
- data/lib/claude_inbox/new_session_form.rb +50 -17
- data/lib/claude_inbox/notifier.rb +1 -1
- data/lib/claude_inbox/palette.rb +4 -8
- data/lib/claude_inbox/paste.rb +2 -6
- data/lib/claude_inbox/poller.rb +33 -18
- data/lib/claude_inbox/pull_requests.rb +63 -15
- data/lib/claude_inbox/reaper.rb +3 -3
- data/lib/claude_inbox/records.rb +2 -5
- data/lib/claude_inbox/remote/listener.rb +3 -3
- data/lib/claude_inbox/remote/pairing_dialog.rb +6 -6
- data/lib/claude_inbox/remote/start.rb +8 -9
- data/lib/claude_inbox/renderer.rb +48 -33
- data/lib/claude_inbox/session.rb +37 -67
- data/lib/claude_inbox/session_request.rb +7 -7
- data/lib/claude_inbox/snapshot.rb +2 -2
- data/lib/claude_inbox/store/entry.rb +6 -1
- data/lib/claude_inbox/store/row.rb +13 -8
- data/lib/claude_inbox/store/sections.rb +1 -1
- data/lib/claude_inbox/store.rb +19 -18
- data/lib/claude_inbox/subprocess.rb +3 -2
- data/lib/claude_inbox/text.rb +0 -3
- data/lib/claude_inbox/theme.rb +1 -3
- data/lib/claude_inbox/titler.rb +62 -0
- data/lib/claude_inbox/updater.rb +1 -2
- data/lib/claude_inbox/vt_screen.rb +1 -2
- data/lib/claude_inbox.rb +2 -1
- data/mod/hooks/register.tsx +8 -7
- data/mod/tests/pane.test.tsx +25 -0
- data/mod/tests/snapshot.fixture.ts +4 -0
- data/mod/types/index.d.ts +2 -2
- metadata +11 -5
- data/lib/claude_inbox/agents_client.rb +0 -268
- data/lib/claude_inbox/job_state.rb +0 -130
- data/lib/claude_inbox/sessions.rb +0 -19
- data/lib/claude_inbox/transcript.rb +0 -76
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ab2c702ba985e433dfdef1a421fc112a430338f30e82bc0ccd60c7f3a44bc810
|
|
4
|
+
data.tar.gz: 4d89ccc91011ca3e6230ab936bd6e0e799566384942734dc47f940b9db615c4d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b551a056e1a92981e3dcd6d29a1268ea741e0156eae308006d1869f2430e14418a36b42a679fda4d497fc839a5c718a5a7ccb15a8b3ee5cdc74170182233143d
|
|
7
|
+
data.tar.gz: 8237ef30f1eaab21723588d0c3e4203471de666afb3b4163f69a9e6d4ba7201af37db1796001566c885c7fef266fafd40c095065a5e4ce5b269affdfdd7af04d
|
data/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# claude-inbox
|
|
2
2
|
|
|
3
|
-
An inbox for Claude Code's background sessions. It lists
|
|
4
|
-
lists and adds snooze, settle, pinning and each session's
|
|
3
|
+
An inbox for Claude Code's background sessions and Codex threads. It lists
|
|
4
|
+
what `claude agents` lists and adds snooze, settle, pinning and each session's
|
|
5
|
+
pull request.
|
|
5
6
|
|
|
6
7
|
Inspired by [T3 Code](https://github.com/pingdotgg/t3code)'s functionality and
|
|
7
8
|
inbox-style UX, I wanted a better way to manage my Claude agents, so I built
|
|
@@ -40,7 +41,7 @@ a fullscreen terminal.
|
|
|
40
41
|
|
|
41
42
|
`/inbox` opens it, `ctrl+x tab` focuses it and `esc` leaves. The keys are the
|
|
42
43
|
ones below, with `ge` for `G`, `n` `p` for `Tab` `Shift+Tab`, and no `a`,
|
|
43
|
-
`P`, `X`, `Ctrl-x`, `/`, `R` or `U`, and `s` has no `5`. `Enter`, or a click on a row, switches to the
|
|
44
|
+
`r`, `P`, `X`, `Ctrl-x`, `/`, `R` or `U`, and `s` has no `5`. `Enter`, or a click on a row, switches to the
|
|
44
45
|
session when you attached through `claude-inbox`.
|
|
45
46
|
|
|
46
47
|
It opens when a session starts. To stop that, set
|
|
@@ -55,8 +56,12 @@ It opens when a session starts. To stop that, set
|
|
|
55
56
|
failing checks or changes requested, that you haven't attached to yet.
|
|
56
57
|
3. **Active.** Everything else that's running or not yet settled, including
|
|
57
58
|
interactive terminals.
|
|
58
|
-
4. **
|
|
59
|
-
|
|
59
|
+
4. **Working.** With `--fold-working`, sessions still at work, until they
|
|
60
|
+
stop or you pin them. A sub-agent still running keeps a session at work;
|
|
61
|
+
one waiting only on a shell stays in Active. Collapsed; one you start
|
|
62
|
+
with `n` lights the fold line with its name instead of opening it.
|
|
63
|
+
5. **Snoozed.** Sorted by wake time. Collapsed.
|
|
64
|
+
6. **Settled.** Settled with `x`, or every pull request is merged or closed.
|
|
60
65
|
Collapsed.
|
|
61
66
|
|
|
62
67
|
`--sort=activity` puts the session that spoke most recently first in every
|
|
@@ -68,7 +73,7 @@ status line. A row with a pull request shows it as `#885 open`, `#885 merged`
|
|
|
68
73
|
and so on, with `✗ checks`, `changes requested` or `conflicts` after it when
|
|
69
74
|
something is wrong, and `+2` after it when the session has two more.
|
|
70
75
|
A steady `◌` with `idle · 1 shell` means the agent stopped
|
|
71
|
-
and is waiting on a shell
|
|
76
|
+
and is waiting on a shell it started. A blue `●` marks a session you
|
|
72
77
|
haven't attached to since it got blocked or finished; one you have attached
|
|
73
78
|
to since is dimmed, unless `/color` tints it. The age after the badge is how
|
|
74
79
|
long since the session last said anything, read from its transcript: a
|
|
@@ -83,7 +88,7 @@ Vim bindings. Arrows and the mouse work too.
|
|
|
83
88
|
| `j` `k` | move down / up |
|
|
84
89
|
| `gg` `G` | first / last row |
|
|
85
90
|
| `Ctrl-d` `Ctrl-u` | half page down / up (`Ctrl-f` `Ctrl-b` full page) |
|
|
86
|
-
| `Enter` `l` | attach (`←` or `Ctrl+Z` returns here), adopt a remote session, or expand Snoozed / Settled |
|
|
91
|
+
| `Enter` `l` | attach (`←` or `Ctrl+Z` returns here; in Codex, `←` at the start of the prompt), adopt a remote session, or expand Snoozed / Settled |
|
|
87
92
|
| `Tab` `Shift+Tab` | next / previous section |
|
|
88
93
|
| `h` | collapse the current fold |
|
|
89
94
|
| `za` `zo` `zc` | toggle / open / close the fold under the cursor |
|
|
@@ -94,6 +99,7 @@ Vim bindings. Arrows and the mouse work too.
|
|
|
94
99
|
| `u` | wake a snoozed session, or bring back a settled one |
|
|
95
100
|
| `x` | settle by hand |
|
|
96
101
|
| `a` | set a local alias |
|
|
102
|
+
| `r` | ask Claude for a new title, kept as the alias |
|
|
97
103
|
| `o` | open the pull request in the browser; with several, `o` again opens the one that matters, `1`… another |
|
|
98
104
|
| `w` | open the session at claude.ai/code |
|
|
99
105
|
| `y` | copy the pull request url, or the session's address; with several, `y` again copies the one that matters, `1`… another |
|
|
@@ -101,7 +107,7 @@ Vim bindings. Arrows and the mouse work too.
|
|
|
101
107
|
| `X` | stop the session; `Enter` resumes it later |
|
|
102
108
|
| `Ctrl-x` | delete the session, transcript and worktree for good |
|
|
103
109
|
| `/` | filter by name, directory, prompt, status or pull request (`#885` or its title); `Enter` keeps it, `Esc` clears |
|
|
104
|
-
| `R` | poll now |
|
|
110
|
+
| `R` | poll now (the list also refreshes on its own when a session's status changes) |
|
|
105
111
|
| `U` | update claude-inbox when the header says a newer one is out |
|
|
106
112
|
| `q` | quit |
|
|
107
113
|
|
|
@@ -109,8 +115,9 @@ Vim bindings. Arrows and the mouse work too.
|
|
|
109
115
|
|
|
110
116
|
### New session
|
|
111
117
|
|
|
112
|
-
`n` opens a form for the prompt, name, directory, model,
|
|
113
|
-
worktree, Remote Control and color, which tints the row
|
|
118
|
+
`n` opens a form for the prompt, Claude or Codex, name, directory, model,
|
|
119
|
+
effort, permissions, worktree, Remote Control and color, which tints the row
|
|
120
|
+
as `/color` would.
|
|
114
121
|
`Tab` moves between fields, `Ctrl-S` starts the session and `Ctrl-O` starts
|
|
115
122
|
it and attaches. `Option-S` or `Option-O` by mistake only says which key you
|
|
116
123
|
meant; it no longer reads as `Esc` and asks to discard the prompt.
|
data/lib/claude_inbox/actions.rb
CHANGED
|
@@ -39,7 +39,7 @@ module ClaudeInbox
|
|
|
39
39
|
# Applies every request on disk to `store` and returns how many it took.
|
|
40
40
|
def drain(store)
|
|
41
41
|
return 0 unless @dir && File.directory?(@dir)
|
|
42
|
-
keys = store.
|
|
42
|
+
keys = store.attachable_keys
|
|
43
43
|
Dir.glob(File.join(@dir, "*.json")).sort.count { |path| take(path, store, keys) }
|
|
44
44
|
end
|
|
45
45
|
|
|
@@ -9,14 +9,15 @@ module ClaudeInbox
|
|
|
9
9
|
|
|
10
10
|
def activate
|
|
11
11
|
return set_expanded(true) if @selected&.fold?
|
|
12
|
-
return @modal = Dialog::Confirm.new(:adopt, @selected.key) if selected_session&.
|
|
13
|
-
attach(
|
|
12
|
+
return @modal = Dialog::Confirm.new(:adopt, @selected.key) if selected_session&.can?(:adopt)
|
|
13
|
+
attach(selected_session) if allowed?(:attach)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
# Guard for attach/stop: refuse politely on a terminal or remote
|
|
17
|
-
|
|
16
|
+
# Guard for attach/stop/delete: refuse politely on a terminal or remote
|
|
17
|
+
# row, and say nothing for an action the row's harness has not got.
|
|
18
|
+
def allowed?(action)
|
|
18
19
|
s = selected_session
|
|
19
|
-
return true if s&.
|
|
20
|
+
return true if s&.can?(action)
|
|
20
21
|
if s&.interactive?
|
|
21
22
|
notice(s.remote? ? "that's a remote session — Enter adopts it, w opens it at claude.ai/code" : "that's your own terminal — switch to that window")
|
|
22
23
|
end
|
|
@@ -27,49 +28,56 @@ module ClaudeInbox
|
|
|
27
28
|
# attach ends and the next starts without passing through the list. The
|
|
28
29
|
# poller keeps running meanwhile, so the pane's snapshot stays fresh. A
|
|
29
30
|
# failed attach leaves its reason on screen until a key is pressed.
|
|
30
|
-
def attach(
|
|
31
|
-
while
|
|
31
|
+
def attach(session)
|
|
32
|
+
while session
|
|
32
33
|
@switch.clear
|
|
33
|
-
from =
|
|
34
|
-
@store.visit(
|
|
34
|
+
from = session.session_id
|
|
35
|
+
@store.visit(session.key) do
|
|
35
36
|
@terminal.release do
|
|
36
|
-
@terminal.pause unless @client.attach(
|
|
37
|
+
@terminal.pause unless @client.attach(session) { @switch.requested_for?(from) }
|
|
37
38
|
end
|
|
38
39
|
end
|
|
39
40
|
@poller.soon
|
|
40
|
-
|
|
41
|
+
session = session_for(@switch.take(@store.attachable_keys))
|
|
41
42
|
end
|
|
42
43
|
end
|
|
43
44
|
|
|
45
|
+
# A session just started or adopted, which no poll has listed yet.
|
|
46
|
+
def started(id, harness: :claude, cwd: nil) = Session.new(harness: harness, id: id, cwd: cwd)
|
|
47
|
+
|
|
44
48
|
def adopt_session(key)
|
|
45
49
|
s = session_for(key)
|
|
46
|
-
return unless s&.
|
|
50
|
+
return unless s&.can?(:adopt)
|
|
47
51
|
notice("adopting #{s.display_name}…")
|
|
48
52
|
in_background do
|
|
49
53
|
id = @client.adopt(session_id: s.session_id, cwd: s.cwd, pid: s.pid)
|
|
50
54
|
@queue << [:notice, "adopted as #{id}"]
|
|
51
55
|
@queue << [:select, id]
|
|
52
|
-
@queue << [:attach, id]
|
|
56
|
+
@queue << [:attach, started(id)]
|
|
53
57
|
end
|
|
54
58
|
end
|
|
55
59
|
|
|
56
60
|
def open_confirm(kind)
|
|
57
|
-
return unless
|
|
58
|
-
@modal = Dialog::Confirm.new(kind, @selected.key, label_for(@selected.key))
|
|
61
|
+
return unless allowed?(kind)
|
|
62
|
+
@modal = Dialog::Confirm.new(kind, @selected.key, label_for(@selected.key), worktree: !selected_session.worktree_path.nil?)
|
|
59
63
|
end
|
|
60
64
|
|
|
65
|
+
# A Codex thread between turns has nothing to stop, which is no failure.
|
|
61
66
|
def stop_session(id)
|
|
67
|
+
return unless (s = session_for(id))
|
|
68
|
+
label = label_for(id)
|
|
62
69
|
in_background do
|
|
63
|
-
@client.stop(
|
|
70
|
+
@queue << [:notice, "#{label} has no turn running"] unless @client.stop(s)
|
|
64
71
|
@poller.soon
|
|
65
72
|
end
|
|
66
73
|
end
|
|
67
74
|
|
|
68
75
|
def delete_session(id)
|
|
76
|
+
return unless (s = session_for(id))
|
|
69
77
|
label = label_for(id)
|
|
70
78
|
notice("deleting #{label}…")
|
|
71
79
|
in_background do
|
|
72
|
-
@client.rm(
|
|
80
|
+
@client.rm(s)
|
|
73
81
|
@store.forget(id)
|
|
74
82
|
@queue << [:notice, "deleted #{label}"]
|
|
75
83
|
@poller.soon
|
|
@@ -5,21 +5,36 @@ module ClaudeInbox
|
|
|
5
5
|
# Where the cursor is in the sectioned, foldable list and how it moves.
|
|
6
6
|
# The only writer of the selection; the rest of App reads it.
|
|
7
7
|
module Navigating
|
|
8
|
+
ARRIVAL_SECONDS = 4
|
|
9
|
+
|
|
8
10
|
private
|
|
9
11
|
|
|
12
|
+
def arrival(now) = @arrival&.then { |a, until_at| a if now < until_at }
|
|
13
|
+
|
|
10
14
|
# The row to land on once a poll lists it, as after a spawn.
|
|
11
15
|
def select_later(id)
|
|
12
16
|
@pending_select = id
|
|
13
17
|
end
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
# A requested row is settled the poll it first appears in, filtered
|
|
20
|
+
# out or not: left pending, it would fire whenever the row next
|
|
21
|
+
# surfaced, taking the cursor from wherever you had moved it since.
|
|
22
|
+
# Behind a fold it lights the fold line for a few seconds and leaves
|
|
23
|
+
# both the fold and the cursor alone, since a folded session is one
|
|
24
|
+
# you chose not to watch.
|
|
25
|
+
def ensure_selection(all)
|
|
26
|
+
sections = filtered(all)
|
|
17
27
|
pending = @pending_select && Store::Selection.row(@pending_select)
|
|
18
|
-
if pending &&
|
|
19
|
-
select(pending)
|
|
28
|
+
if pending && all.section_of(pending)
|
|
20
29
|
@pending_select = nil
|
|
21
|
-
|
|
30
|
+
name = sections.section_of(pending)
|
|
31
|
+
if name && Store.folded?(name, @expanded)
|
|
32
|
+
@arrival = [Renderer::Arrival.new(section: name, label: label_for(pending.key)), Time.now + ARRIVAL_SECONDS]
|
|
33
|
+
elsif name
|
|
34
|
+
return select(pending)
|
|
35
|
+
end
|
|
22
36
|
end
|
|
37
|
+
stops = sections.selections(@expanded)
|
|
23
38
|
return if stops.include?(@selected)
|
|
24
39
|
select(stops.first)
|
|
25
40
|
end
|
|
@@ -63,7 +78,6 @@ module ClaudeInbox
|
|
|
63
78
|
select(heads[(idx + dir) % heads.size].last)
|
|
64
79
|
end
|
|
65
80
|
|
|
66
|
-
# The foldable section the cursor is currently on or inside, if any.
|
|
67
81
|
def current_fold_section
|
|
68
82
|
name = filtered.section_of(@selected)
|
|
69
83
|
name if Store::FOLDABLE_SECTIONS.include?(name)
|
|
@@ -80,9 +94,8 @@ module ClaudeInbox
|
|
|
80
94
|
|
|
81
95
|
def toggle_expanded = set_expanded(!@expanded[current_fold_section])
|
|
82
96
|
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
# or refuse a terminal/remote row, so this doesn't repeat that.
|
|
97
|
+
# Left to activate, as Enter is, to expand a fold or refuse a
|
|
98
|
+
# terminal or remote row.
|
|
86
99
|
def click_row(row)
|
|
87
100
|
item = row_item_at(row)
|
|
88
101
|
return unless item
|
|
@@ -9,7 +9,7 @@ module ClaudeInbox
|
|
|
9
9
|
def open_new_session
|
|
10
10
|
cwd = selected_session&.cwd || Dir.pwd
|
|
11
11
|
dirs = SessionRequest.directories(@store.sessions, @trust.call)
|
|
12
|
-
@modal = NewSessionForm.new(cwd: SessionRequest.strip_worktree(cwd), dirs: dirs)
|
|
12
|
+
@modal = NewSessionForm.new(cwd: SessionRequest.strip_worktree(cwd), dirs: dirs, choices: @client.choices)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
# `:start`/`:start_and_attach` leave the form as `@modal`, now busy: it
|
|
@@ -35,8 +35,8 @@ module ClaudeInbox
|
|
|
35
35
|
@queue << [:notice, ["started #{id}", note].compact.join(" — ")]
|
|
36
36
|
@queue << [:form_started, form]
|
|
37
37
|
@queue << [:select, id]
|
|
38
|
-
attach ? @queue << [:attach, id] : @poller.soon
|
|
39
|
-
rescue
|
|
38
|
+
attach ? @queue << [:attach, started(id, harness: v[:harness], cwd: v[:cwd])] : @poller.soon
|
|
39
|
+
rescue Harnesses::Error => e
|
|
40
40
|
@queue << [:form_failed, form, e.message]
|
|
41
41
|
end
|
|
42
42
|
end
|
|
@@ -62,6 +62,21 @@ module ClaudeInbox
|
|
|
62
62
|
@modal = Dialog::Prompt.new(:alias, @selected.key, current)
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
def retitle_selected
|
|
66
|
+
return unless require_storable && require_writable("retitle")
|
|
67
|
+
key, session = @selected.key, selected_session
|
|
68
|
+
current = @store.alias_for(key) || session.display_name
|
|
69
|
+
notice("asking claude for a title…")
|
|
70
|
+
in_background do
|
|
71
|
+
title = @titler.title(session, current: current)
|
|
72
|
+
next @queue << [:notice, "“#{current}” still fits"] if title == current
|
|
73
|
+
@store.set_alias(key, title)
|
|
74
|
+
@queue << [:notice, "titled “#{title}” — a edits it"]
|
|
75
|
+
rescue Titler::Error => e
|
|
76
|
+
@queue << [:notice, e.message]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
65
80
|
def open_pr_editor
|
|
66
81
|
return unless require_storable && require_writable("link a pull request")
|
|
67
82
|
current = @store.pr_for(@selected.key) || selected_session&.pr&.url || ""
|
data/lib/claude_inbox/app.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "agents_client"
|
|
3
|
+
require_relative "claude/agents_client"
|
|
4
|
+
require_relative "harnesses"
|
|
4
5
|
require_relative "debug"
|
|
5
6
|
require_relative "dialog"
|
|
6
7
|
require_relative "input"
|
|
@@ -12,6 +13,7 @@ require_relative "keymap"
|
|
|
12
13
|
require_relative "remote/listener"
|
|
13
14
|
require_relative "new_session_form"
|
|
14
15
|
require_relative "pull_requests"
|
|
16
|
+
|
|
15
17
|
require_relative "reaper"
|
|
16
18
|
require_relative "notifier"
|
|
17
19
|
require_relative "poller"
|
|
@@ -25,6 +27,7 @@ require_relative "switch"
|
|
|
25
27
|
require_relative "trust"
|
|
26
28
|
require_relative "mod"
|
|
27
29
|
require_relative "updater"
|
|
30
|
+
require_relative "titler"
|
|
28
31
|
require_relative "app/updating"
|
|
29
32
|
require_relative "app/filtering"
|
|
30
33
|
require_relative "app/triaging"
|
|
@@ -47,13 +50,14 @@ module ClaudeInbox
|
|
|
47
50
|
|
|
48
51
|
# The reaper deletes sessions and the listener lets other machines in,
|
|
49
52
|
# so both are off unless `bin/claude-inbox` switches them on.
|
|
50
|
-
def initialize(client: AgentsClient.new, store: Store.new, pull_requests: PullRequests.new,
|
|
53
|
+
def initialize(client: Harnesses.new(claude: Claude::AgentsClient.new), store: Store.new, pull_requests: PullRequests.new,
|
|
51
54
|
rate_limits: RateLimits.new, reaper: Reaper.disabled, snapshot: Snapshot.disabled, switch: Switch.disabled,
|
|
52
55
|
actions: Actions.disabled, notifier: Notifier.disabled, listen: nil, updater: nil, argv: [], out: $stdout, input: $stdin, color: true,
|
|
53
56
|
trust: Trust.method(:projects), terminal: Terminal.new(out, input), queue: Queue.new, pane_offer: Mod::Offer.disabled,
|
|
54
|
-
clipboard: Clipboard.method(:copy))
|
|
57
|
+
clipboard: Clipboard.method(:copy), titler: Titler.new)
|
|
55
58
|
@client = client
|
|
56
59
|
@clipboard = clipboard
|
|
60
|
+
@titler = titler
|
|
57
61
|
@store = store
|
|
58
62
|
@snapshot = snapshot
|
|
59
63
|
@switch = switch
|
|
@@ -67,7 +71,7 @@ module ClaudeInbox
|
|
|
67
71
|
@renderer = Renderer.new
|
|
68
72
|
@input = Input.new(input, out)
|
|
69
73
|
@queue = queue
|
|
70
|
-
@poller = Poller.new(client: client, store: store, pull_requests: pull_requests,
|
|
74
|
+
@poller = Poller.new(client: client, store: store, pull_requests: pull_requests,
|
|
71
75
|
reaper: reaper, queue: @queue, snapshot: snapshot, actions: actions, notifier: notifier)
|
|
72
76
|
@listener = listen ? Remote::Listener.new(client: client, store: store, queue: @queue, **listen) : Remote::Listener.disabled
|
|
73
77
|
@selected = nil
|
|
@@ -253,6 +257,7 @@ module ClaudeInbox
|
|
|
253
257
|
when :toggle_pin then toggle_pin_selected
|
|
254
258
|
when :settle then settle_selected
|
|
255
259
|
when :alias then open_alias_editor
|
|
260
|
+
when :retitle then retitle_selected
|
|
256
261
|
when :link_pr then open_pr_editor
|
|
257
262
|
when :open_pr then open_pr
|
|
258
263
|
when :open_remote then open_remote
|
|
@@ -283,13 +288,13 @@ module ClaudeInbox
|
|
|
283
288
|
sections = filtered(all)
|
|
284
289
|
width, height = @terminal.size
|
|
285
290
|
body_h = Renderer.body_height(height)
|
|
286
|
-
ensure_selection(
|
|
291
|
+
ensure_selection(all)
|
|
287
292
|
@tick += 1
|
|
288
293
|
view = Renderer::View.new(
|
|
289
294
|
width: width, height: height, now: now, selected: @selected&.key, top: @frame.top, expanded: @expanded,
|
|
290
295
|
modal: modal_lines(width), screen: screen_lines(width, body_h),
|
|
291
296
|
status: status_text(now), usage: @rate_limits.windows(now), filter: filter, filter_editing: filter_editing?,
|
|
292
|
-
tick: @tick / 2, loading: loading_for, listening: @listener.snapshot
|
|
297
|
+
tick: @tick / 2, loading: loading_for, listening: @listener.snapshot, arrival: arrival(now)
|
|
293
298
|
)
|
|
294
299
|
@frame = @renderer.frame(sections, view)
|
|
295
300
|
@frame = @frame.plain unless @color # --no-color: decided here and nowhere else
|