roost-top 0.2__tar.gz
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.
- roost_top-0.2/.gitignore +5 -0
- roost_top-0.2/LICENSE +21 -0
- roost_top-0.2/PKG-INFO +235 -0
- roost_top-0.2/README.md +211 -0
- roost_top-0.2/packaging/build-deb.sh +52 -0
- roost_top-0.2/packaging/roost.rb +38 -0
- roost_top-0.2/pyproject.toml +56 -0
- roost_top-0.2/roost.1 +191 -0
- roost_top-0.2/roost.py +1259 -0
- roost_top-0.2/tests/test_roost.py +558 -0
roost_top-0.2/.gitignore
ADDED
roost_top-0.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 George M. Howard
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
roost_top-0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: roost-top
|
|
3
|
+
Version: 0.2
|
|
4
|
+
Summary: top for Claude Code -- live sessions, models, context, and the subagents they spawn
|
|
5
|
+
Project-URL: Homepage, https://github.com/gmhoward9289-ops/roost
|
|
6
|
+
Project-URL: Issues, https://github.com/gmhoward9289-ops/roost/issues
|
|
7
|
+
Author-email: "George M. Howard" <dev@swamplink.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: claude,claude-code,context,monitoring,top,tui
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: System :: Monitoring
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# roost
|
|
26
|
+
|
|
27
|
+
[](https://github.com/gmhoward9289-ops/roost/actions/workflows/ci.yml)
|
|
28
|
+
|
|
29
|
+
`top` for Claude Code. Every live session, the model it is on, how much context
|
|
30
|
+
it has burned — and, unlike anything else, **the subagents it spawned**.
|
|
31
|
+
|
|
32
|
+
One file, no dependencies, Python 3.9+. Runs on macOS, Linux and Windows.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
WORKER MODEL CTX IDLE TASK
|
|
36
|
+
NEAR LIMIT
|
|
37
|
+
demo-a1 opus-5 85% 12s refactor the parser
|
|
38
|
+
PARKED + COSTLY
|
|
39
|
+
demo-b2 opus-5 61% 4h10m audit the build scripts
|
|
40
|
+
WORKING NOW
|
|
41
|
+
demo-c3 fable-5 22% 3s add integration tests
|
|
42
|
+
STARTING
|
|
43
|
+
demo-d4 - - -
|
|
44
|
+
|
|
45
|
+
QUIET (4) demo-e5 . demo-f6 . demo-g7 . demo-h8
|
|
46
|
+
|
|
47
|
+
8 worker(s) | fable-5, opus-5
|
|
48
|
+
|
|
49
|
+
SUBAGENTS
|
|
50
|
+
STATE AGENT MODEL CTX IDLE TASK
|
|
51
|
+
working a812aca59f opus-5 33% 2s survey the config loaders
|
|
52
|
+
idle adaffaba4b sonnet-5 67% 1h22m draft the migration notes
|
|
53
|
+
|
|
54
|
+
2 subagent(s), 1 working
|
|
55
|
+
|
|
56
|
+
INFRA ollama:11434 up qwen2.5-coder:14b (9.2 GB) litellm:4000 up openwebui:8080 DOWN
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Sessions are grouped by what it costs to ignore them, not by size: `NEAR LIMIT`
|
|
60
|
+
is about to stop working, `PARKED + COSTLY` bills its whole context on the next
|
|
61
|
+
turn, and everything quiet collapses to a single line.
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
Homebrew (macOS and Linux):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
brew install gmhoward9289-ops/tap/roost
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Debian and Ubuntu — grab `roost_<version>_all.deb` from the
|
|
72
|
+
[latest release](https://github.com/gmhoward9289-ops/roost/releases/latest):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
sudo apt install ./roost_0.2_all.deb
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
There is no PPA and no apt repository; the `.deb` is a release artifact, and
|
|
79
|
+
`apt install ./file.deb` resolves `python3` exactly as a repo install would.
|
|
80
|
+
|
|
81
|
+
With pipx:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pipx install roost-top
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
PyPI holds the bare name `roost` in reserve — a prior project's name, retained
|
|
88
|
+
after deletion — so the *package* is `roost-top`; the command it installs is
|
|
89
|
+
plain `roost`. Installing straight from the repo skips the index entirely:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pipx install git+https://github.com/gmhoward9289-ops/roost
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Or just take the file. It is one script, stdlib only, no dependencies:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
curl -o roost https://raw.githubusercontent.com/gmhoward9289-ops/roost/main/roost.py
|
|
99
|
+
chmod +x roost && ./roost
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Windows: save it as `roost.py` and run it — `.PY` is in `PATHEXT`, so `roost.py`
|
|
103
|
+
works from anywhere on `PATH`. There is no Windows package; the single file is
|
|
104
|
+
the install.
|
|
105
|
+
|
|
106
|
+
The man page (`man roost`) ships with the Homebrew and `.deb` installs. A pipx
|
|
107
|
+
install puts it under the venv's own `share/man`, which is not on the default
|
|
108
|
+
`MANPATH`; read it in place with
|
|
109
|
+
`man "$(pipx environment --value PIPX_LOCAL_VENVS)/roost-top/share/man/man1/roost.1"`.
|
|
110
|
+
|
|
111
|
+
## Use
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
roost live, refreshing every second
|
|
115
|
+
roost -w 5 slower refresh
|
|
116
|
+
roost -1 one frame, then exit
|
|
117
|
+
roost --json joined records, for piping
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
While running: `space` refresh now · `a` advice panel · `s` subagents panel · `q` quit
|
|
121
|
+
|
|
122
|
+
## Acting on a session
|
|
123
|
+
|
|
124
|
+
> **Experimental.** Interactive mode carries an `EXPERIMENTAL` marker in the
|
|
125
|
+
> top-right corner, and it means it — `x` ends a real process. Reading the
|
|
126
|
+
> dashboard has never been the risky half.
|
|
127
|
+
|
|
128
|
+
`j`/`k` (or the arrow keys) raise a cursor. Raising it expands the `QUIET`
|
|
129
|
+
group, because a session idle for hours is exactly what a sweep is looking for
|
|
130
|
+
and it is unreachable while collapsed.
|
|
131
|
+
|
|
132
|
+
| key | does |
|
|
133
|
+
| --- | --- |
|
|
134
|
+
| `j` `k` `↓` `↑` | move the cursor |
|
|
135
|
+
| `x` | stop the selected session — confirms first, and only `y` proceeds |
|
|
136
|
+
| `y` | copy its sessionId, for `claude --resume <id>` |
|
|
137
|
+
| `esc` | drop the cursor, re-collapse `QUIET` |
|
|
138
|
+
|
|
139
|
+
`x` ends a process. It does not compact, save, or otherwise negotiate with the
|
|
140
|
+
session — **there is no local control channel into a running Claude Code
|
|
141
|
+
session**, so nothing gentler is available from outside it. On Unix that is a
|
|
142
|
+
`SIGTERM` and the session exits on its own terms; on Windows there is no
|
|
143
|
+
cross-process equivalent, so it is a `TerminateProcess` hard kill. Transcripts
|
|
144
|
+
are written a turn at a time, so at most an in-flight turn is lost.
|
|
145
|
+
|
|
146
|
+
Both keys act on the row object that was on screen when you pressed them, never
|
|
147
|
+
on an index re-resolved afterwards. Rows reorder between frames as sessions go
|
|
148
|
+
quiet, and an index that outlived its frame would eventually stop the wrong one.
|
|
149
|
+
|
|
150
|
+
roost refuses to stop its own process or its parent — run it from inside the
|
|
151
|
+
session it is pointed at and the cursor can land on the row that owns your
|
|
152
|
+
terminal.
|
|
153
|
+
|
|
154
|
+
Only one panel is open at a time: `a` and `s` flip between ADVICE and SUBAGENTS
|
|
155
|
+
rather than stacking. With two dozen sessions on screen a stacked second panel
|
|
156
|
+
lands below the bottom of the terminal, which is indistinguishable from the key
|
|
157
|
+
not working. For the same reason the frame now says `... N more line(s) below`
|
|
158
|
+
instead of quietly truncating.
|
|
159
|
+
|
|
160
|
+
## What it logs
|
|
161
|
+
|
|
162
|
+
Every session stopped with `x` appends one JSON line to
|
|
163
|
+
`~/.claude/logs/roost.jsonl` — same shape and the same 5000-line cap as the hook
|
|
164
|
+
logs beside it:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{"ts":"2026-07-31T00:22:57-0400","action":"stop","ok":true,"host":"COOPER",
|
|
168
|
+
"name":"models-ca","pid":4321,"session_id":"abc-123","model":"claude-opus-5",
|
|
169
|
+
"ctx_tokens":484030,"idle_secs":92500}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The session's **task text is deliberately not recorded.** It is free-form prose
|
|
173
|
+
out of a transcript, and an audit trail of what was stopped should not become a
|
|
174
|
+
copy of what was being worked on.
|
|
175
|
+
|
|
176
|
+
Because each record carries the context that session was holding, the log
|
|
177
|
+
answers afterwards what a sweep actually reclaimed rather than just how many
|
|
178
|
+
rows you closed. `--no-log` records nothing. A log that cannot be written is
|
|
179
|
+
ignored rather than raised — losing the log is survivable, losing the display
|
|
180
|
+
is not.
|
|
181
|
+
|
|
182
|
+
## Why subagents are the interesting part
|
|
183
|
+
|
|
184
|
+
Subagents have **no process of their own** — they run as sidechains inside the
|
|
185
|
+
parent's process. Every pid-based view is structurally blind to them.
|
|
186
|
+
|
|
187
|
+
They do each get a transcript, one directory deeper than the session transcripts:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
~/.claude/projects/<slug>/<sessionId>/subagents/agent-<id>.jsonl
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The short task description ("Scout source URLs") lives only in the *parent's*
|
|
194
|
+
`toolUseResult`, keyed by `agentId`. roost joins the two, and falls back to the
|
|
195
|
+
opening words of the subagent's own first message when the parent's record has
|
|
196
|
+
scrolled out of reach.
|
|
197
|
+
|
|
198
|
+
## Where the numbers come from
|
|
199
|
+
|
|
200
|
+
Three local, read-only sources. Nothing is sent anywhere; there is no network
|
|
201
|
+
call except a localhost probe of the infra ports.
|
|
202
|
+
|
|
203
|
+
| source | gives |
|
|
204
|
+
| --- | --- |
|
|
205
|
+
| `~/.claude/sessions/<pid>.json` | live sessions: pid, sessionId, launch cwd, name |
|
|
206
|
+
| `~/.claude/projects/*/<sid>.jsonl` | model in use, token usage |
|
|
207
|
+
| `127.0.0.1` ports | ollama / litellm / openwebui |
|
|
208
|
+
|
|
209
|
+
**Context** is the last assistant turn's `input_tokens + cache_read_input_tokens
|
|
210
|
+
+ cache_creation_input_tokens`. Cross-checked against an independent tool on the
|
|
211
|
+
same session: 77% vs 77.29%.
|
|
212
|
+
|
|
213
|
+
**The context window is inferred, not recorded.** Nothing on disk states which
|
|
214
|
+
window a session opened with, and a session on the 1M window will read 480k+
|
|
215
|
+
cache tokens in a single call — scoring that against 200k yields a nonsense
|
|
216
|
+
"242%". roost picks the smallest standard tier the usage fits and prints it in
|
|
217
|
+
the `WIN` column, so the assumption is visible rather than silent. If a new tier
|
|
218
|
+
ships, `WINDOW_TIERS` is the one line to edit.
|
|
219
|
+
|
|
220
|
+
## Caveats
|
|
221
|
+
|
|
222
|
+
- It reads an **undocumented on-disk format** that can change without warning.
|
|
223
|
+
That is the whole foundation; treat breakage as expected, not exceptional.
|
|
224
|
+
- The window inference above is a heuristic.
|
|
225
|
+
- The `ADVICE` panel's thresholds are tuned to one person's usage. Read
|
|
226
|
+
`EXPENSIVE_TOKENS` and friends before trusting the advice.
|
|
227
|
+
- Daily-driven on macOS and Windows. CI runs the suite and a smoke frame on
|
|
228
|
+
Linux, and it detects live sessions there — but nobody lives on it yet.
|
|
229
|
+
Field reports welcome.
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT — see [LICENSE](LICENSE). Contact: dev@swamplink.com
|
|
234
|
+
|
|
235
|
+
Built in a Digital Swamp. From my swamp to yours.
|
roost_top-0.2/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# roost
|
|
2
|
+
|
|
3
|
+
[](https://github.com/gmhoward9289-ops/roost/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
`top` for Claude Code. Every live session, the model it is on, how much context
|
|
6
|
+
it has burned — and, unlike anything else, **the subagents it spawned**.
|
|
7
|
+
|
|
8
|
+
One file, no dependencies, Python 3.9+. Runs on macOS, Linux and Windows.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
WORKER MODEL CTX IDLE TASK
|
|
12
|
+
NEAR LIMIT
|
|
13
|
+
demo-a1 opus-5 85% 12s refactor the parser
|
|
14
|
+
PARKED + COSTLY
|
|
15
|
+
demo-b2 opus-5 61% 4h10m audit the build scripts
|
|
16
|
+
WORKING NOW
|
|
17
|
+
demo-c3 fable-5 22% 3s add integration tests
|
|
18
|
+
STARTING
|
|
19
|
+
demo-d4 - - -
|
|
20
|
+
|
|
21
|
+
QUIET (4) demo-e5 . demo-f6 . demo-g7 . demo-h8
|
|
22
|
+
|
|
23
|
+
8 worker(s) | fable-5, opus-5
|
|
24
|
+
|
|
25
|
+
SUBAGENTS
|
|
26
|
+
STATE AGENT MODEL CTX IDLE TASK
|
|
27
|
+
working a812aca59f opus-5 33% 2s survey the config loaders
|
|
28
|
+
idle adaffaba4b sonnet-5 67% 1h22m draft the migration notes
|
|
29
|
+
|
|
30
|
+
2 subagent(s), 1 working
|
|
31
|
+
|
|
32
|
+
INFRA ollama:11434 up qwen2.5-coder:14b (9.2 GB) litellm:4000 up openwebui:8080 DOWN
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Sessions are grouped by what it costs to ignore them, not by size: `NEAR LIMIT`
|
|
36
|
+
is about to stop working, `PARKED + COSTLY` bills its whole context on the next
|
|
37
|
+
turn, and everything quiet collapses to a single line.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
Homebrew (macOS and Linux):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
brew install gmhoward9289-ops/tap/roost
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Debian and Ubuntu — grab `roost_<version>_all.deb` from the
|
|
48
|
+
[latest release](https://github.com/gmhoward9289-ops/roost/releases/latest):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
sudo apt install ./roost_0.2_all.deb
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
There is no PPA and no apt repository; the `.deb` is a release artifact, and
|
|
55
|
+
`apt install ./file.deb` resolves `python3` exactly as a repo install would.
|
|
56
|
+
|
|
57
|
+
With pipx:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pipx install roost-top
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
PyPI holds the bare name `roost` in reserve — a prior project's name, retained
|
|
64
|
+
after deletion — so the *package* is `roost-top`; the command it installs is
|
|
65
|
+
plain `roost`. Installing straight from the repo skips the index entirely:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pipx install git+https://github.com/gmhoward9289-ops/roost
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Or just take the file. It is one script, stdlib only, no dependencies:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
curl -o roost https://raw.githubusercontent.com/gmhoward9289-ops/roost/main/roost.py
|
|
75
|
+
chmod +x roost && ./roost
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Windows: save it as `roost.py` and run it — `.PY` is in `PATHEXT`, so `roost.py`
|
|
79
|
+
works from anywhere on `PATH`. There is no Windows package; the single file is
|
|
80
|
+
the install.
|
|
81
|
+
|
|
82
|
+
The man page (`man roost`) ships with the Homebrew and `.deb` installs. A pipx
|
|
83
|
+
install puts it under the venv's own `share/man`, which is not on the default
|
|
84
|
+
`MANPATH`; read it in place with
|
|
85
|
+
`man "$(pipx environment --value PIPX_LOCAL_VENVS)/roost-top/share/man/man1/roost.1"`.
|
|
86
|
+
|
|
87
|
+
## Use
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
roost live, refreshing every second
|
|
91
|
+
roost -w 5 slower refresh
|
|
92
|
+
roost -1 one frame, then exit
|
|
93
|
+
roost --json joined records, for piping
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
While running: `space` refresh now · `a` advice panel · `s` subagents panel · `q` quit
|
|
97
|
+
|
|
98
|
+
## Acting on a session
|
|
99
|
+
|
|
100
|
+
> **Experimental.** Interactive mode carries an `EXPERIMENTAL` marker in the
|
|
101
|
+
> top-right corner, and it means it — `x` ends a real process. Reading the
|
|
102
|
+
> dashboard has never been the risky half.
|
|
103
|
+
|
|
104
|
+
`j`/`k` (or the arrow keys) raise a cursor. Raising it expands the `QUIET`
|
|
105
|
+
group, because a session idle for hours is exactly what a sweep is looking for
|
|
106
|
+
and it is unreachable while collapsed.
|
|
107
|
+
|
|
108
|
+
| key | does |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| `j` `k` `↓` `↑` | move the cursor |
|
|
111
|
+
| `x` | stop the selected session — confirms first, and only `y` proceeds |
|
|
112
|
+
| `y` | copy its sessionId, for `claude --resume <id>` |
|
|
113
|
+
| `esc` | drop the cursor, re-collapse `QUIET` |
|
|
114
|
+
|
|
115
|
+
`x` ends a process. It does not compact, save, or otherwise negotiate with the
|
|
116
|
+
session — **there is no local control channel into a running Claude Code
|
|
117
|
+
session**, so nothing gentler is available from outside it. On Unix that is a
|
|
118
|
+
`SIGTERM` and the session exits on its own terms; on Windows there is no
|
|
119
|
+
cross-process equivalent, so it is a `TerminateProcess` hard kill. Transcripts
|
|
120
|
+
are written a turn at a time, so at most an in-flight turn is lost.
|
|
121
|
+
|
|
122
|
+
Both keys act on the row object that was on screen when you pressed them, never
|
|
123
|
+
on an index re-resolved afterwards. Rows reorder between frames as sessions go
|
|
124
|
+
quiet, and an index that outlived its frame would eventually stop the wrong one.
|
|
125
|
+
|
|
126
|
+
roost refuses to stop its own process or its parent — run it from inside the
|
|
127
|
+
session it is pointed at and the cursor can land on the row that owns your
|
|
128
|
+
terminal.
|
|
129
|
+
|
|
130
|
+
Only one panel is open at a time: `a` and `s` flip between ADVICE and SUBAGENTS
|
|
131
|
+
rather than stacking. With two dozen sessions on screen a stacked second panel
|
|
132
|
+
lands below the bottom of the terminal, which is indistinguishable from the key
|
|
133
|
+
not working. For the same reason the frame now says `... N more line(s) below`
|
|
134
|
+
instead of quietly truncating.
|
|
135
|
+
|
|
136
|
+
## What it logs
|
|
137
|
+
|
|
138
|
+
Every session stopped with `x` appends one JSON line to
|
|
139
|
+
`~/.claude/logs/roost.jsonl` — same shape and the same 5000-line cap as the hook
|
|
140
|
+
logs beside it:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{"ts":"2026-07-31T00:22:57-0400","action":"stop","ok":true,"host":"COOPER",
|
|
144
|
+
"name":"models-ca","pid":4321,"session_id":"abc-123","model":"claude-opus-5",
|
|
145
|
+
"ctx_tokens":484030,"idle_secs":92500}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The session's **task text is deliberately not recorded.** It is free-form prose
|
|
149
|
+
out of a transcript, and an audit trail of what was stopped should not become a
|
|
150
|
+
copy of what was being worked on.
|
|
151
|
+
|
|
152
|
+
Because each record carries the context that session was holding, the log
|
|
153
|
+
answers afterwards what a sweep actually reclaimed rather than just how many
|
|
154
|
+
rows you closed. `--no-log` records nothing. A log that cannot be written is
|
|
155
|
+
ignored rather than raised — losing the log is survivable, losing the display
|
|
156
|
+
is not.
|
|
157
|
+
|
|
158
|
+
## Why subagents are the interesting part
|
|
159
|
+
|
|
160
|
+
Subagents have **no process of their own** — they run as sidechains inside the
|
|
161
|
+
parent's process. Every pid-based view is structurally blind to them.
|
|
162
|
+
|
|
163
|
+
They do each get a transcript, one directory deeper than the session transcripts:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
~/.claude/projects/<slug>/<sessionId>/subagents/agent-<id>.jsonl
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The short task description ("Scout source URLs") lives only in the *parent's*
|
|
170
|
+
`toolUseResult`, keyed by `agentId`. roost joins the two, and falls back to the
|
|
171
|
+
opening words of the subagent's own first message when the parent's record has
|
|
172
|
+
scrolled out of reach.
|
|
173
|
+
|
|
174
|
+
## Where the numbers come from
|
|
175
|
+
|
|
176
|
+
Three local, read-only sources. Nothing is sent anywhere; there is no network
|
|
177
|
+
call except a localhost probe of the infra ports.
|
|
178
|
+
|
|
179
|
+
| source | gives |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| `~/.claude/sessions/<pid>.json` | live sessions: pid, sessionId, launch cwd, name |
|
|
182
|
+
| `~/.claude/projects/*/<sid>.jsonl` | model in use, token usage |
|
|
183
|
+
| `127.0.0.1` ports | ollama / litellm / openwebui |
|
|
184
|
+
|
|
185
|
+
**Context** is the last assistant turn's `input_tokens + cache_read_input_tokens
|
|
186
|
+
+ cache_creation_input_tokens`. Cross-checked against an independent tool on the
|
|
187
|
+
same session: 77% vs 77.29%.
|
|
188
|
+
|
|
189
|
+
**The context window is inferred, not recorded.** Nothing on disk states which
|
|
190
|
+
window a session opened with, and a session on the 1M window will read 480k+
|
|
191
|
+
cache tokens in a single call — scoring that against 200k yields a nonsense
|
|
192
|
+
"242%". roost picks the smallest standard tier the usage fits and prints it in
|
|
193
|
+
the `WIN` column, so the assumption is visible rather than silent. If a new tier
|
|
194
|
+
ships, `WINDOW_TIERS` is the one line to edit.
|
|
195
|
+
|
|
196
|
+
## Caveats
|
|
197
|
+
|
|
198
|
+
- It reads an **undocumented on-disk format** that can change without warning.
|
|
199
|
+
That is the whole foundation; treat breakage as expected, not exceptional.
|
|
200
|
+
- The window inference above is a heuristic.
|
|
201
|
+
- The `ADVICE` panel's thresholds are tuned to one person's usage. Read
|
|
202
|
+
`EXPENSIVE_TOKENS` and friends before trusting the advice.
|
|
203
|
+
- Daily-driven on macOS and Windows. CI runs the suite and a smoke frame on
|
|
204
|
+
Linux, and it detects live sessions there — but nobody lives on it yet.
|
|
205
|
+
Field reports welcome.
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT — see [LICENSE](LICENSE). Contact: dev@swamplink.com
|
|
210
|
+
|
|
211
|
+
Built in a Digital Swamp. From my swamp to yours.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Build a .deb for roost. Usage: packaging/build-deb.sh [version]
|
|
3
|
+
#
|
|
4
|
+
# Deliberately a plain dpkg-deb tree rather than a debian/ source package: roost
|
|
5
|
+
# is one architecture-independent script with no build step and no dependencies
|
|
6
|
+
# beyond python3 itself, so debhelper would add ceremony and no correctness.
|
|
7
|
+
# The result is meant to be attached to a GitHub release and installed with
|
|
8
|
+
# sudo apt install ./roost_<version>_all.deb
|
|
9
|
+
# which resolves python3 the same way an apt repo would. There is no PPA.
|
|
10
|
+
set -eu
|
|
11
|
+
|
|
12
|
+
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
|
13
|
+
VERSION=${1:-$(sed -n 's/^__version__ = "\(.*\)"/\1/p' "$ROOT/roost.py")}
|
|
14
|
+
[ -n "$VERSION" ] || { echo "could not determine version" >&2; exit 1; }
|
|
15
|
+
|
|
16
|
+
BUILD=$(mktemp -d)
|
|
17
|
+
trap 'rm -rf "$BUILD"' EXIT
|
|
18
|
+
PKG="$BUILD/roost_${VERSION}_all"
|
|
19
|
+
|
|
20
|
+
mkdir -p "$PKG/DEBIAN" "$PKG/usr/bin" "$PKG/usr/share/man/man1" \
|
|
21
|
+
"$PKG/usr/share/doc/roost"
|
|
22
|
+
|
|
23
|
+
# Installed as `roost`, not `roost.py`: the shebang and the executable bit are
|
|
24
|
+
# what make it a command, and the .py suffix only matters on Windows.
|
|
25
|
+
install -m 0755 "$ROOT/roost.py" "$PKG/usr/bin/roost"
|
|
26
|
+
gzip -9nc "$ROOT/roost.1" > "$PKG/usr/share/man/man1/roost.1.gz"
|
|
27
|
+
chmod 0644 "$PKG/usr/share/man/man1/roost.1.gz"
|
|
28
|
+
install -m 0644 "$ROOT/LICENSE" "$PKG/usr/share/doc/roost/copyright"
|
|
29
|
+
|
|
30
|
+
cat > "$PKG/DEBIAN/control" <<EOF
|
|
31
|
+
Package: roost
|
|
32
|
+
Version: $VERSION
|
|
33
|
+
Section: utils
|
|
34
|
+
Priority: optional
|
|
35
|
+
Architecture: all
|
|
36
|
+
Depends: python3 (>= 3.9)
|
|
37
|
+
Maintainer: George M. Howard <dev@swamplink.com>
|
|
38
|
+
Homepage: https://github.com/gmhoward9289-ops/roost
|
|
39
|
+
Description: top for Claude Code
|
|
40
|
+
Shows every live Claude Code session on the machine, the model each is
|
|
41
|
+
running, how much of its context window it has consumed, and the subagents
|
|
42
|
+
it has spawned -- which have no process of their own and are invisible to
|
|
43
|
+
any pid-based view.
|
|
44
|
+
.
|
|
45
|
+
Reads only local Claude Code state and probes localhost inference ports.
|
|
46
|
+
It sends nothing anywhere.
|
|
47
|
+
EOF
|
|
48
|
+
|
|
49
|
+
dpkg-deb --build --root-owner-group "$PKG" > /dev/null
|
|
50
|
+
mkdir -p "$ROOT/dist"
|
|
51
|
+
mv "$BUILD/roost_${VERSION}_all.deb" "$ROOT/dist/"
|
|
52
|
+
echo "dist/roost_${VERSION}_all.deb"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Homebrew formula for roost.
|
|
2
|
+
#
|
|
3
|
+
# This is the master copy; it is consumed by copying it to Formula/roost.rb in
|
|
4
|
+
# the tap repo (gmhoward9289-ops/homebrew-tap), which is what `brew install`
|
|
5
|
+
# reads. It lives here so the formula is versioned alongside the code it builds.
|
|
6
|
+
#
|
|
7
|
+
# homebrew-core is not an option yet -- it requires notability thresholds
|
|
8
|
+
# (stars/forks/watchers) that this project has not met.
|
|
9
|
+
#
|
|
10
|
+
# After tagging a release, refresh the checksum with:
|
|
11
|
+
# curl -sL https://github.com/gmhoward9289-ops/roost/archive/refs/tags/v0.2.tar.gz | shasum -a 256
|
|
12
|
+
class Roost < Formula
|
|
13
|
+
include Language::Python::Shebang
|
|
14
|
+
|
|
15
|
+
desc "top for Claude Code: live sessions, context use, and their subagents"
|
|
16
|
+
homepage "https://github.com/gmhoward9289-ops/roost"
|
|
17
|
+
url "https://github.com/gmhoward9289-ops/roost/archive/refs/tags/v0.2.tar.gz"
|
|
18
|
+
sha256 "d7c5c1fec7438a25c4c412ebf716c0e40a70513dcd3bc330cdb3df6c25592402"
|
|
19
|
+
license "MIT"
|
|
20
|
+
|
|
21
|
+
depends_on "python@3.13"
|
|
22
|
+
|
|
23
|
+
def install
|
|
24
|
+
bin.install "roost.py" => "roost"
|
|
25
|
+
# The shipped shebang is `/usr/bin/env python3`, which would resolve to
|
|
26
|
+
# whatever python happens to be first on PATH -- including a virtualenv the
|
|
27
|
+
# user activated for something else. Pin it to the formula's interpreter.
|
|
28
|
+
rewrite_shebang detected_python_shebang(use_python_from_path: false), bin/"roost"
|
|
29
|
+
man1.install "roost.1"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test do
|
|
33
|
+
assert_match "roost #{version}", shell_output("#{bin}/roost --version")
|
|
34
|
+
# -1 renders a frame and exits; with no Claude Code sessions present it
|
|
35
|
+
# still has to produce the empty-state line rather than fail.
|
|
36
|
+
assert_match(/roost|session/i, shell_output("#{bin}/roost -1 --no-color"))
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
# PyPI holds the bare name "roost" in reserve (a prior project's name, retained
|
|
7
|
+
# after deletion), so the distribution is roost-top. The command, the module,
|
|
8
|
+
# the repo, the formula and the .deb are all still plain roost.
|
|
9
|
+
name = "roost-top"
|
|
10
|
+
dynamic = ["version"]
|
|
11
|
+
description = "top for Claude Code -- live sessions, models, context, and the subagents they spawn"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
authors = [{ name = "George M. Howard", email = "dev@swamplink.com" }]
|
|
16
|
+
keywords = ["claude", "claude-code", "tui", "top", "monitoring", "context"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Environment :: Console",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Operating System :: MacOS :: MacOS X",
|
|
23
|
+
"Operating System :: Microsoft :: Windows",
|
|
24
|
+
"Operating System :: POSIX :: Linux",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.9",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Topic :: System :: Monitoring",
|
|
29
|
+
]
|
|
30
|
+
# No dependencies, and there will not be any: roost has to run on whatever
|
|
31
|
+
# Python is already on the box, including a bare system 3.9 on macOS.
|
|
32
|
+
dependencies = []
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/gmhoward9289-ops/roost"
|
|
36
|
+
Issues = "https://github.com/gmhoward9289-ops/roost/issues"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
roost = "roost:main"
|
|
40
|
+
|
|
41
|
+
# One source of truth for the version: __version__ in roost.py, which is also
|
|
42
|
+
# what `roost --version` prints and what the man page header carries.
|
|
43
|
+
[tool.hatch.version]
|
|
44
|
+
path = "roost.py"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.wheel]
|
|
47
|
+
only-include = ["roost.py"]
|
|
48
|
+
|
|
49
|
+
# Installs to <prefix>/share/man/man1. pipx and venv installs put that outside
|
|
50
|
+
# the default MANPATH, so `man roost` works after a system or Homebrew install
|
|
51
|
+
# but may need MANPATH help inside a venv -- see README.
|
|
52
|
+
[tool.hatch.build.targets.wheel.shared-data]
|
|
53
|
+
"roost.1" = "share/man/man1/roost.1"
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.sdist]
|
|
56
|
+
include = ["roost.py", "roost.1", "README.md", "LICENSE", "tests/", "packaging/"]
|