claude-inbox 0.2.0 → 0.4.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 +107 -177
- data/exe/claude-inbox +67 -4
- data/lib/claude_inbox/actions.rb +91 -0
- data/lib/claude_inbox/agents_client.rb +68 -62
- data/lib/claude_inbox/app.rb +88 -38
- data/lib/claude_inbox/config.rb +24 -0
- data/lib/claude_inbox/dialog.rb +3 -1
- data/lib/claude_inbox/headless.rb +31 -0
- data/lib/claude_inbox/images.rb +37 -6
- data/lib/claude_inbox/job_state.rb +25 -10
- data/lib/claude_inbox/keymap.rb +1 -3
- data/lib/claude_inbox/mod.rb +71 -0
- data/lib/claude_inbox/new_session_form.rb +43 -52
- data/lib/claude_inbox/painter.rb +31 -0
- data/lib/claude_inbox/palette.rb +4 -7
- data/lib/claude_inbox/paste.rb +2 -0
- data/lib/claude_inbox/peek.rb +2 -2
- data/lib/claude_inbox/poller.rb +48 -24
- data/lib/claude_inbox/pull_requests.rb +36 -22
- data/lib/claude_inbox/rate_limits.rb +1 -3
- data/lib/claude_inbox/records.rb +7 -2
- data/lib/claude_inbox/remote/http.rb +140 -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 +435 -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 +201 -0
- data/lib/claude_inbox/remote.rb +10 -0
- data/lib/claude_inbox/renderer.rb +61 -61
- data/lib/claude_inbox/session.rb +7 -21
- data/lib/claude_inbox/session_request.rb +151 -0
- data/lib/claude_inbox/sessions.rb +0 -1
- data/lib/claude_inbox/settings.rb +20 -7
- data/lib/claude_inbox/slash_commands.rb +1 -0
- data/lib/claude_inbox/snapshot.rb +48 -0
- data/lib/claude_inbox/store/entry.rb +0 -2
- data/lib/claude_inbox/store/row.rb +7 -3
- data/lib/claude_inbox/store.rb +24 -11
- data/lib/claude_inbox/subprocess.rb +9 -1
- data/lib/claude_inbox/switch.rb +40 -0
- data/lib/claude_inbox/terminal.rb +9 -3
- data/lib/claude_inbox/text.rb +7 -1
- data/lib/claude_inbox/text_buffer.rb +84 -33
- data/lib/claude_inbox/trust.rb +23 -0
- data/lib/claude_inbox/vt_screen.rb +1 -1
- data/lib/claude_inbox/writer_lock.rb +65 -0
- data/lib/claude_inbox.rb +1 -10
- data/mod/.claude-plugin/plugin.json +20 -0
- data/mod/hooks/hooks.json +1 -0
- data/mod/hooks/register.tsx +470 -0
- data/mod/tests/contract.test.tsx +23 -0
- data/mod/tests/pane.test.tsx +199 -0
- data/mod/tests/snapshot.fixture.ts +47 -0
- data/mod/tests/snooze.test.tsx +31 -0
- data/mod/tsconfig.json +3 -0
- data/mod/types/index.d.ts +30 -0
- metadata +29 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { On } from 'claude-code'
|
|
2
|
+
import { expect, mock, test } from 'claude-code/testing'
|
|
3
|
+
|
|
4
|
+
const PANE = {
|
|
5
|
+
plugin: 'inbox-pane',
|
|
6
|
+
component: 'Pane',
|
|
7
|
+
requestId: 'inbox',
|
|
8
|
+
props: { title: 'Inbox', isFocused: true, bodyColumns: 48, placement: 'dock', scroll: { offset: 0, bodyRows: 30 }, view: {} },
|
|
9
|
+
viewport: { columns: 160, rows: 40 },
|
|
10
|
+
} as const
|
|
11
|
+
|
|
12
|
+
const ROWS = [
|
|
13
|
+
{ section: 'needs_you', id: 'a1', label: 'needs an answer', state: 'blocked', actionable: true, line: 'confirm: drop it?' },
|
|
14
|
+
{ section: 'active', id: 'b2', label: 'still going', state: 'working', actionable: true, line: 'running bin/ci', pr: { short: '#7', state: 'open', url: 'https://github.com/o/r/pull/7' } },
|
|
15
|
+
{ section: 'active', id: 'uuid-term', label: 'a terminal', state: 'working', actionable: false, isTerminal: true },
|
|
16
|
+
{ section: 'settled', id: 'c3', label: 'put away', state: 'done', actionable: true },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
// The op's result rides under `value`, so a seeded read is `{ value: { value, version } }`.
|
|
20
|
+
const seeded = (value: unknown) => ({ value: { value, version: 1 } })
|
|
21
|
+
|
|
22
|
+
// Answers what a session start asks of the engine, with a fresh snapshot so no headless
|
|
23
|
+
// inbox starts, and records the panes it opens and the state it sets. `polled` settles
|
|
24
|
+
// with the start's first poll.
|
|
25
|
+
const startable = (on: On, sections: Record<string, unknown[]> = {}) => {
|
|
26
|
+
const opened: string[] = []
|
|
27
|
+
const set: Record<string, unknown> = {}
|
|
28
|
+
let done = () => {}
|
|
29
|
+
const polled = new Promise<void>(resolve => (done = resolve))
|
|
30
|
+
on('session.start', ($, e) => ({ cwd: e.cwd }))
|
|
31
|
+
on('command.register', () => ({ value: undefined }))
|
|
32
|
+
on('session.id', () => ({ value: 'u-test' }))
|
|
33
|
+
on('fs.read', () => ({ value: JSON.stringify({ written_at: 1_700_000_000, sections }) }))
|
|
34
|
+
on('state.set', async ($, e, next) => {
|
|
35
|
+
set[e.key] = e.value
|
|
36
|
+
const result = await next(e)
|
|
37
|
+
if (e.key === 'writtenAt') done()
|
|
38
|
+
return result
|
|
39
|
+
})
|
|
40
|
+
on('ui.open', ($, e) => {
|
|
41
|
+
opened.push(e.id)
|
|
42
|
+
return { value: { isPlaced: true } }
|
|
43
|
+
})
|
|
44
|
+
return { opened, polled, set }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test('without the gem the pane says so, on every surface that seats a pane', async ($, on) => {
|
|
48
|
+
mock.env(on, { HOME: '/Users/me' })
|
|
49
|
+
mock.clock(on, { now: 1_700_000_000_000 })
|
|
50
|
+
for (const surface of ['terminal', 'desktop', 'vscode', 'mobile'] as const) {
|
|
51
|
+
const ui = await $.ui.mount({ ...PANE, surface })
|
|
52
|
+
expect(await ui.find({ type: 'Text', text: /claude-inbox is not running/ })).toBeDefined()
|
|
53
|
+
await ui.unmount()
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('a snapshot older than thirty seconds is called stale, just now before a minute', async ($, on) => {
|
|
58
|
+
mock.env(on, { HOME: '/Users/me' })
|
|
59
|
+
mock.clock(on, { now: 1_700_000_000_000 })
|
|
60
|
+
on('state.get', ($, e, next) => (e.key === 'writtenAt' ? seeded(1_700_000_000 - 45) : next(e)))
|
|
61
|
+
const ui = await $.ui.mount({ ...PANE, surface: 'terminal' })
|
|
62
|
+
expect(await ui.find({ type: 'Text', text: /stopped just now; rows are stale/ })).toBeDefined()
|
|
63
|
+
await ui.unmount()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test("rows sit under the gem's sections, Settled folded until za opens it", async ($, on) => {
|
|
67
|
+
mock.env(on, { HOME: '/Users/me' })
|
|
68
|
+
mock.clock(on, { now: 1_700_000_000_000 })
|
|
69
|
+
on('state.get', ($, e, next) => {
|
|
70
|
+
if (e.key === 'rows') return seeded(ROWS)
|
|
71
|
+
if (e.key === 'writtenAt') return seeded(1_700_000_000)
|
|
72
|
+
return next(e)
|
|
73
|
+
})
|
|
74
|
+
for (const surface of ['terminal', 'desktop'] as const) {
|
|
75
|
+
const ui = await $.ui.mount({ ...PANE, surface })
|
|
76
|
+
expect(await ui.find({ type: 'Text', text: /Needs you · 1/ })).toBeDefined()
|
|
77
|
+
expect(await ui.find({ type: 'Text', text: /Active · 2/ })).toBeDefined()
|
|
78
|
+
expect(await ui.find({ type: 'Text', text: /Settled · 1/ })).toBeDefined()
|
|
79
|
+
expect(await ui.find({ type: 'Text', text: /confirm: drop it\?/ })).toBeDefined()
|
|
80
|
+
expect(await ui.find({ type: 'Text', text: /#7 open/ })).toBeDefined()
|
|
81
|
+
expect(await ui.find({ type: 'Text', text: /1 folded/ })).toBeDefined()
|
|
82
|
+
expect(await ui.find({ type: 'Text', text: /put away/ })).toBeUndefined()
|
|
83
|
+
await ui.press({ key: 'g' })
|
|
84
|
+
await ui.press({ key: 'e' })
|
|
85
|
+
await ui.press({ key: 'z' })
|
|
86
|
+
expect(await ui.find({ type: 'Button', key: 'x' })).toBeUndefined()
|
|
87
|
+
await ui.press({ key: 'a' })
|
|
88
|
+
expect(await ui.find({ type: 'Text', text: /put away/ })).toBeDefined()
|
|
89
|
+
expect(await ui.find({ type: 'Button', key: 'x' })).toBeDefined()
|
|
90
|
+
await ui.press({ key: 'z' })
|
|
91
|
+
await ui.press({ key: 'c' })
|
|
92
|
+
expect(await ui.find({ type: 'Text', text: /put away/ })).toBeUndefined()
|
|
93
|
+
// Cursor and folds live in session state, so put them back for the next surface.
|
|
94
|
+
await ui.press({ key: 'g' })
|
|
95
|
+
await ui.press({ key: 'g' })
|
|
96
|
+
await ui.unmount()
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('the session the pane sits in leads the list under This session, with its own keys', async ($, on) => {
|
|
101
|
+
mock.env(on, { HOME: '/Users/me' })
|
|
102
|
+
mock.clock(on, { now: 1_700_000_000_000 })
|
|
103
|
+
const [viewing, ...others] = ROWS
|
|
104
|
+
on('state.get', ($, e, next) => {
|
|
105
|
+
if (e.key === 'rows') return seeded(others)
|
|
106
|
+
if (e.key === 'current') return seeded(viewing)
|
|
107
|
+
if (e.key === 'writtenAt') return seeded(1_700_000_000)
|
|
108
|
+
return next(e)
|
|
109
|
+
})
|
|
110
|
+
const written: string[] = []
|
|
111
|
+
on('fs.write', ($, e) => {
|
|
112
|
+
written.push(e.path)
|
|
113
|
+
return { value: undefined }
|
|
114
|
+
})
|
|
115
|
+
const ui = await $.ui.mount({ ...PANE, surface: 'terminal' })
|
|
116
|
+
expect(await ui.find({ type: 'Text', text: /^This session$/ })).toBeDefined()
|
|
117
|
+
expect(await ui.find({ type: 'Text', text: /needs an answer/ })).toBeDefined()
|
|
118
|
+
expect(await ui.find({ type: 'Text', text: /Needs you/ })).toBeUndefined()
|
|
119
|
+
await ui.press({ key: 'row:a1' })
|
|
120
|
+
expect(written).toEqual([])
|
|
121
|
+
await ui.press({ key: 'x' })
|
|
122
|
+
expect(written).toEqual(['/Users/me/.config/claude-inbox/actions/pane-1700000000000.json'])
|
|
123
|
+
await ui.unmount()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('a poll lifts the row whose session is this one out of the list', async ($, on) => {
|
|
127
|
+
mock.env(on, { HOME: '/Users/me' })
|
|
128
|
+
mock.clock(on, { now: 1_700_000_000_000 })
|
|
129
|
+
const { polled, set } = startable(on, {
|
|
130
|
+
needs_you: [{ id: 'a1', session: 'u-test', label: 'mine' }],
|
|
131
|
+
active: [{ id: 'b2', session: 'u-b2', label: 'other' }],
|
|
132
|
+
})
|
|
133
|
+
await $.session.start({ cwd: '/tmp/proj', surface: 'terminal', isInteractive: true })
|
|
134
|
+
await polled
|
|
135
|
+
expect((set.current as { id?: string } | null)?.id).toBe('a1')
|
|
136
|
+
expect((set.rows as { id?: string }[]).map(row => row.id)).toEqual(['b2'])
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('x leaves a settle request for the inbox to apply', async ($, on) => {
|
|
140
|
+
mock.env(on, { HOME: '/Users/me' })
|
|
141
|
+
mock.clock(on, { now: 1_700_000_000_000 })
|
|
142
|
+
on('state.get', ($, e, next) => (e.key === 'rows' ? seeded(ROWS) : e.key === 'writtenAt' ? seeded(1_700_000_000) : next(e)))
|
|
143
|
+
const written: { path: string; text: string }[] = []
|
|
144
|
+
on('fs.write', ($, e) => {
|
|
145
|
+
written.push({ path: e.path, text: e.text })
|
|
146
|
+
return { value: undefined }
|
|
147
|
+
})
|
|
148
|
+
const ui = await $.ui.mount({ ...PANE, surface: 'terminal' })
|
|
149
|
+
await ui.press({ key: 'x' })
|
|
150
|
+
expect(written.length).toBe(1)
|
|
151
|
+
expect(written[0]?.path).toBe('/Users/me/.config/claude-inbox/actions/pane-1700000000000.json')
|
|
152
|
+
expect(JSON.parse(written[0]?.text ?? '{}')).toEqual({ action: 'settle', id: 'a1', at: 1_700_000_000_000 })
|
|
153
|
+
await ui.unmount()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('Enter asks the attached inbox for the row, naming this session, and refuses a terminal', async ($, on) => {
|
|
157
|
+
mock.env(on, { HOME: '/Users/me' })
|
|
158
|
+
mock.clock(on, { now: 1_700_000_000_000 })
|
|
159
|
+
on('state.get', ($, e, next) => (e.key === 'rows' ? seeded(ROWS) : e.key === 'writtenAt' ? seeded(1_700_000_000) : next(e)))
|
|
160
|
+
const written: { path: string; text: string }[] = []
|
|
161
|
+
on('fs.write', ($, e) => {
|
|
162
|
+
written.push({ path: e.path, text: e.text })
|
|
163
|
+
return { value: undefined }
|
|
164
|
+
})
|
|
165
|
+
const ui = await $.ui.mount({ ...PANE, surface: 'terminal' })
|
|
166
|
+
await ui.press({ key: 'row:uuid-term' })
|
|
167
|
+
expect(written.length).toBe(0)
|
|
168
|
+
await ui.press({ key: 'row:b2' })
|
|
169
|
+
expect(written.length).toBe(1)
|
|
170
|
+
expect(written[0]?.path).toBe('/Users/me/.config/claude-inbox/switch.json')
|
|
171
|
+
expect(JSON.parse(written[0]?.text ?? '{}')).toEqual({ id: 'b2', from: 'pane', at: 1_700_000_000 })
|
|
172
|
+
await ui.unmount()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test('an unfocused pane says how to focus it', async ($, on) => {
|
|
176
|
+
mock.env(on, { HOME: '/Users/me' })
|
|
177
|
+
mock.clock(on, { now: 1_700_000_000_000 })
|
|
178
|
+
const ui = await $.ui.mount({ ...PANE, surface: 'terminal', props: { ...PANE.props, isFocused: false } })
|
|
179
|
+
expect(await ui.find({ type: 'Text', text: /takes you here/ })).toBeDefined()
|
|
180
|
+
await ui.unmount()
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
test('the pane opens when a session starts', async ($, on) => {
|
|
184
|
+
mock.env(on, { HOME: '/Users/me' })
|
|
185
|
+
mock.clock(on, { now: 1_700_000_000_000 })
|
|
186
|
+
const { opened, polled } = startable(on)
|
|
187
|
+
await $.session.start({ cwd: '/tmp/proj', surface: 'terminal', isInteractive: true })
|
|
188
|
+
await polled
|
|
189
|
+
expect(opened).toEqual(['inbox'])
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('with openOnStart off only /inbox opens it', { options: { openOnStart: false } }, async ($, on) => {
|
|
193
|
+
mock.env(on, { HOME: '/Users/me' })
|
|
194
|
+
mock.clock(on, { now: 1_700_000_000_000 })
|
|
195
|
+
const { opened, polled } = startable(on)
|
|
196
|
+
await $.session.start({ cwd: '/tmp/proj', surface: 'terminal', isInteractive: true })
|
|
197
|
+
await polled
|
|
198
|
+
expect(opened).toEqual([])
|
|
199
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// What the gem's Snapshot writes for three sessions, a blocked one, a working one
|
|
2
|
+
// with an open pull request and a hand-settled one. The gem's spec/mod_spec.rb
|
|
3
|
+
// writes the same sessions through Snapshot and compares them to this text, so a
|
|
4
|
+
// key renamed on either side fails a test on both.
|
|
5
|
+
export const SNAPSHOT = `{
|
|
6
|
+
"version": 1,
|
|
7
|
+
"written_at": 1789600000,
|
|
8
|
+
"sections": {
|
|
9
|
+
"pinned": [],
|
|
10
|
+
"needs_you": [
|
|
11
|
+
{
|
|
12
|
+
"id": "a1",
|
|
13
|
+
"session": "u-a1",
|
|
14
|
+
"label": "needs an answer",
|
|
15
|
+
"state": "blocked",
|
|
16
|
+
"actionable": true,
|
|
17
|
+
"cwd": "/tmp/proj"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"active": [
|
|
21
|
+
{
|
|
22
|
+
"id": "b2",
|
|
23
|
+
"session": "u-b2",
|
|
24
|
+
"label": "still going",
|
|
25
|
+
"state": "working",
|
|
26
|
+
"actionable": true,
|
|
27
|
+
"cwd": "/tmp/proj",
|
|
28
|
+
"pr": {
|
|
29
|
+
"short": "#7",
|
|
30
|
+
"state": "open",
|
|
31
|
+
"url": "https://github.com/o/r/pull/7"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"snoozed": [],
|
|
36
|
+
"settled": [
|
|
37
|
+
{
|
|
38
|
+
"id": "c3",
|
|
39
|
+
"session": "u-c3",
|
|
40
|
+
"label": "put away",
|
|
41
|
+
"state": "done",
|
|
42
|
+
"actionable": true,
|
|
43
|
+
"cwd": "/tmp/proj"
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}`
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { expect, mock, test } from 'claude-code/testing'
|
|
2
|
+
|
|
3
|
+
const PANE = {
|
|
4
|
+
plugin: 'inbox-pane',
|
|
5
|
+
component: 'Pane',
|
|
6
|
+
requestId: 'inbox',
|
|
7
|
+
props: { title: 'Inbox', isFocused: true, bodyColumns: 48, placement: 'dock', scroll: { offset: 0, bodyRows: 30 }, view: {} },
|
|
8
|
+
viewport: { columns: 160, rows: 40 },
|
|
9
|
+
} as const
|
|
10
|
+
const ROWS = [{ section: 'needs_you', id: 'a1', label: 'needs an answer', state: 'blocked', actionable: true, line: 'confirm: drop it?' }]
|
|
11
|
+
const seeded = (value: unknown) => ({ value: { value, version: 1 } })
|
|
12
|
+
|
|
13
|
+
test('s then 1 leaves a fifteen-minute snooze request', async ($, on) => {
|
|
14
|
+
mock.env(on, { HOME: '/Users/me' })
|
|
15
|
+
mock.clock(on, { now: 1_700_000_000_000 })
|
|
16
|
+
on('state.get', ($, e, next) => (e.key === 'rows' ? seeded(ROWS) : e.key === 'writtenAt' ? seeded(1_700_000_000) : next(e)))
|
|
17
|
+
const written: string[] = []
|
|
18
|
+
on('fs.write', ($, e) => {
|
|
19
|
+
written.push(e.text)
|
|
20
|
+
return { value: undefined }
|
|
21
|
+
})
|
|
22
|
+
const ui = await $.ui.mount({ ...PANE, surface: 'terminal' })
|
|
23
|
+
expect(await ui.find({ type: 'Button', key: '1' })).toBeUndefined()
|
|
24
|
+
await ui.press({ key: 's' })
|
|
25
|
+
expect(await ui.find({ type: 'Button', key: '1' })).toBeDefined()
|
|
26
|
+
expect(await ui.find({ type: 'Text', text: /15 minutes/ })).toBeDefined()
|
|
27
|
+
await ui.press({ key: '1' })
|
|
28
|
+
expect(written.length).toBe(1)
|
|
29
|
+
expect(JSON.parse(written[0] ?? '{}')).toEqual({ action: 'snooze', id: 'a1', choice: 'm15', at: 1_700_000_000_000 })
|
|
30
|
+
await ui.unmount()
|
|
31
|
+
})
|
data/mod/tsconfig.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type Section = 'pinned' | 'needs_you' | 'active' | 'snoozed' | 'settled'
|
|
2
|
+
|
|
3
|
+
// One row of the gem's snapshot.json, as its Snapshot class writes it.
|
|
4
|
+
export type InboxRow = {
|
|
5
|
+
section: Section
|
|
6
|
+
id?: string
|
|
7
|
+
// The daemon's sessionId, the name $.session.id() gives the session the pane sits in.
|
|
8
|
+
session?: string
|
|
9
|
+
label: string
|
|
10
|
+
state: string
|
|
11
|
+
actionable: boolean
|
|
12
|
+
isWaiting: boolean
|
|
13
|
+
isTerminal: boolean
|
|
14
|
+
cwd?: string
|
|
15
|
+
line?: string
|
|
16
|
+
wakeAt?: number | 'until_woken'
|
|
17
|
+
remote?: string
|
|
18
|
+
pr?: { short: string; state: string; url: string }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type Folds = { snoozed: boolean; settled: boolean }
|
|
22
|
+
|
|
23
|
+
// The first key of a chord (g, z, s) waiting for its second; null between chords.
|
|
24
|
+
export type Pending = 'g' | 'z' | 's' | null
|
|
25
|
+
|
|
26
|
+
declare module 'claude-code' {
|
|
27
|
+
interface PluginState {
|
|
28
|
+
'inbox-pane': { rows: InboxRow[]; current: InboxRow | null; writtenAt: number; cursor: number; folds: Folds; pending: Pending }
|
|
29
|
+
}
|
|
30
|
+
}
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: claude-inbox
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jordan Byron
|
|
@@ -105,16 +105,21 @@ files:
|
|
|
105
105
|
- README.md
|
|
106
106
|
- exe/claude-inbox
|
|
107
107
|
- lib/claude_inbox.rb
|
|
108
|
+
- lib/claude_inbox/actions.rb
|
|
108
109
|
- lib/claude_inbox/agents_client.rb
|
|
109
110
|
- lib/claude_inbox/app.rb
|
|
111
|
+
- lib/claude_inbox/config.rb
|
|
110
112
|
- lib/claude_inbox/debug.rb
|
|
111
113
|
- lib/claude_inbox/dialog.rb
|
|
114
|
+
- lib/claude_inbox/headless.rb
|
|
112
115
|
- lib/claude_inbox/images.rb
|
|
113
116
|
- lib/claude_inbox/job_state.rb
|
|
114
117
|
- lib/claude_inbox/keymap.rb
|
|
115
118
|
- lib/claude_inbox/logs.rb
|
|
119
|
+
- lib/claude_inbox/mod.rb
|
|
116
120
|
- lib/claude_inbox/mouse.rb
|
|
117
121
|
- lib/claude_inbox/new_session_form.rb
|
|
122
|
+
- lib/claude_inbox/painter.rb
|
|
118
123
|
- lib/claude_inbox/palette.rb
|
|
119
124
|
- lib/claude_inbox/paste.rb
|
|
120
125
|
- lib/claude_inbox/peek.rb
|
|
@@ -123,22 +128,45 @@ files:
|
|
|
123
128
|
- lib/claude_inbox/rate_limits.rb
|
|
124
129
|
- lib/claude_inbox/reaper.rb
|
|
125
130
|
- lib/claude_inbox/records.rb
|
|
131
|
+
- lib/claude_inbox/remote.rb
|
|
132
|
+
- lib/claude_inbox/remote/http.rb
|
|
133
|
+
- lib/claude_inbox/remote/icon.png
|
|
134
|
+
- lib/claude_inbox/remote/icon.svg
|
|
135
|
+
- lib/claude_inbox/remote/listener.rb
|
|
136
|
+
- lib/claude_inbox/remote/page.html
|
|
137
|
+
- lib/claude_inbox/remote/pairing.rb
|
|
138
|
+
- lib/claude_inbox/remote/pairing_dialog.rb
|
|
139
|
+
- lib/claude_inbox/remote/start.rb
|
|
126
140
|
- lib/claude_inbox/renderer.rb
|
|
127
141
|
- lib/claude_inbox/session.rb
|
|
142
|
+
- lib/claude_inbox/session_request.rb
|
|
128
143
|
- lib/claude_inbox/sessions.rb
|
|
129
144
|
- lib/claude_inbox/settings.rb
|
|
130
145
|
- lib/claude_inbox/slash_commands.rb
|
|
146
|
+
- lib/claude_inbox/snapshot.rb
|
|
131
147
|
- lib/claude_inbox/store.rb
|
|
132
148
|
- lib/claude_inbox/store/entry.rb
|
|
133
149
|
- lib/claude_inbox/store/row.rb
|
|
134
150
|
- lib/claude_inbox/store/sections.rb
|
|
135
151
|
- lib/claude_inbox/store/selection.rb
|
|
136
152
|
- lib/claude_inbox/subprocess.rb
|
|
153
|
+
- lib/claude_inbox/switch.rb
|
|
137
154
|
- lib/claude_inbox/terminal.rb
|
|
138
155
|
- lib/claude_inbox/text.rb
|
|
139
156
|
- lib/claude_inbox/text_buffer.rb
|
|
140
157
|
- lib/claude_inbox/theme.rb
|
|
158
|
+
- lib/claude_inbox/trust.rb
|
|
141
159
|
- lib/claude_inbox/vt_screen.rb
|
|
160
|
+
- lib/claude_inbox/writer_lock.rb
|
|
161
|
+
- mod/.claude-plugin/plugin.json
|
|
162
|
+
- mod/hooks/hooks.json
|
|
163
|
+
- mod/hooks/register.tsx
|
|
164
|
+
- mod/tests/contract.test.tsx
|
|
165
|
+
- mod/tests/pane.test.tsx
|
|
166
|
+
- mod/tests/snapshot.fixture.ts
|
|
167
|
+
- mod/tests/snooze.test.tsx
|
|
168
|
+
- mod/tsconfig.json
|
|
169
|
+
- mod/types/index.d.ts
|
|
142
170
|
homepage: https://github.com/jordanbyron/claude-inbox
|
|
143
171
|
licenses:
|
|
144
172
|
- MIT
|