sessiontools 0.1.0__tar.gz → 0.3.0__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.
Files changed (89) hide show
  1. sessiontools-0.3.0/PKG-INFO +317 -0
  2. sessiontools-0.3.0/README.md +301 -0
  3. {sessiontools-0.1.0 → sessiontools-0.3.0}/pyproject.toml +21 -16
  4. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/__init__.py +18 -4
  5. sessiontools-0.3.0/src/sessiontools/account/__init__.py +15 -0
  6. sessiontools-0.3.0/src/sessiontools/account/cli.py +372 -0
  7. sessiontools-0.3.0/src/sessiontools/account/keychain.py +318 -0
  8. sessiontools-0.3.0/src/sessiontools/account/leakscan.py +190 -0
  9. sessiontools-0.3.0/src/sessiontools/account/limits.py +248 -0
  10. sessiontools-0.3.0/src/sessiontools/account/movehome.py +312 -0
  11. sessiontools-0.3.0/src/sessiontools/account/new.py +458 -0
  12. sessiontools-0.3.0/src/sessiontools/account/registry.py +589 -0
  13. sessiontools-0.3.0/src/sessiontools/account/setuptoken.py +1373 -0
  14. sessiontools-0.3.0/src/sessiontools/account/status.py +647 -0
  15. sessiontools-0.3.0/src/sessiontools/account/tokenfile.py +385 -0
  16. sessiontools-0.3.0/src/sessiontools/account/tokenstore.py +462 -0
  17. sessiontools-0.3.0/src/sessiontools/account/use.py +831 -0
  18. sessiontools-0.3.0/src/sessiontools/band.py +130 -0
  19. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/busy.py +468 -80
  20. sessiontools-0.3.0/src/sessiontools/claude_code.py +457 -0
  21. sessiontools-0.3.0/src/sessiontools/cli.py +805 -0
  22. sessiontools-0.3.0/src/sessiontools/clockline.py +180 -0
  23. sessiontools-0.3.0/src/sessiontools/completion.py +400 -0
  24. sessiontools-0.3.0/src/sessiontools/context_parts/__init__.py +50 -0
  25. sessiontools-0.3.0/src/sessiontools/context_parts/boot.py +534 -0
  26. sessiontools-0.3.0/src/sessiontools/context_parts/breakdown.py +218 -0
  27. sessiontools-0.3.0/src/sessiontools/context_parts/hooks.py +269 -0
  28. sessiontools-0.3.0/src/sessiontools/context_parts/nnls.py +112 -0
  29. sessiontools-0.3.0/src/sessiontools/context_parts/render.py +478 -0
  30. sessiontools-0.3.0/src/sessiontools/context_parts/slash_context.py +137 -0
  31. sessiontools-0.3.0/src/sessiontools/context_parts/tools.py +118 -0
  32. sessiontools-0.3.0/src/sessiontools/context_parts/transcript.py +401 -0
  33. sessiontools-0.3.0/src/sessiontools/default_model.py +480 -0
  34. sessiontools-0.3.0/src/sessiontools/default_model_cli.py +280 -0
  35. sessiontools-0.3.0/src/sessiontools/default_model_watch.py +314 -0
  36. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/get.py +6 -4
  37. sessiontools-0.3.0/src/sessiontools/ledger/__init__.py +338 -0
  38. sessiontools-0.3.0/src/sessiontools/ledger/_tiktoken_child.py +65 -0
  39. sessiontools-0.3.0/src/sessiontools/ledger/count.py +176 -0
  40. sessiontools-0.3.0/src/sessiontools/ledger/render.py +262 -0
  41. sessiontools-0.3.0/src/sessiontools/ledger/transcript.py +268 -0
  42. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/listing.py +71 -18
  43. sessiontools-0.3.0/src/sessiontools/operator.py +223 -0
  44. sessiontools-0.3.0/src/sessiontools/pane_state.py +663 -0
  45. sessiontools-0.3.0/src/sessiontools/pane_state_agent.py +522 -0
  46. sessiontools-0.3.0/src/sessiontools/park.py +1359 -0
  47. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/procs.py +46 -8
  48. sessiontools-0.3.0/src/sessiontools/reap.py +925 -0
  49. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/send.py +2 -2
  50. sessiontools-0.3.0/src/sessiontools/session_info.py +174 -0
  51. sessiontools-0.3.0/src/sessiontools/settings.py +1526 -0
  52. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/settle.py +85 -5
  53. sessiontools-0.3.0/src/sessiontools/snapshot.py +324 -0
  54. sessiontools-0.3.0/src/sessiontools/sound/__init__.py +117 -0
  55. sessiontools-0.3.0/src/sessiontools/sound/engine.swift +2020 -0
  56. sessiontools-0.3.0/src/sessiontools/sound/event.swift +248 -0
  57. sessiontools-0.3.0/src/sessiontools/sound/service.py +596 -0
  58. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/__init__.py +11 -12
  59. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/agentfile.py +44 -13
  60. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/agents.py +10 -14
  61. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/args.py +19 -16
  62. sessiontools-0.3.0/src/sessiontools/start/claude_settings.py +333 -0
  63. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/context.py +101 -29
  64. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/environment.py +133 -14
  65. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/errors.py +2 -2
  66. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/holders.py +6 -6
  67. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/identity.py +83 -18
  68. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/placement.py +19 -8
  69. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/plan.py +342 -72
  70. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/policy.py +65 -56
  71. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/record.py +15 -16
  72. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/resume.py +17 -15
  73. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/run.py +26 -13
  74. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/secrets.py +110 -4
  75. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/start/vendor.py +19 -6
  76. sessiontools-0.3.0/src/sessiontools/statusline/__init__.py +1195 -0
  77. sessiontools-0.3.0/src/sessiontools/statusline/sample.json +20 -0
  78. sessiontools-0.3.0/src/sessiontools/statusline/statusline.sh +185 -0
  79. sessiontools-0.3.0/src/sessiontools/tmux.py +178 -0
  80. sessiontools-0.3.0/src/sessiontools/turn.py +191 -0
  81. sessiontools-0.3.0/src/sessiontools/verbs.py +67 -0
  82. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/window.py +90 -15
  83. sessiontools-0.1.0/PKG-INFO +0 -11
  84. sessiontools-0.1.0/src/sessiontools/claude_code.py +0 -179
  85. sessiontools-0.1.0/src/sessiontools/cli.py +0 -347
  86. sessiontools-0.1.0/src/sessiontools/settings.py +0 -764
  87. sessiontools-0.1.0/src/sessiontools/tmux.py +0 -90
  88. {sessiontools-0.1.0 → sessiontools-0.3.0}/LICENSE +0 -0
  89. {sessiontools-0.1.0 → sessiontools-0.3.0}/src/sessiontools/py.typed +0 -0
@@ -0,0 +1,317 @@
1
+ Metadata-Version: 2.4
2
+ Name: sessiontools
3
+ Version: 0.3.0
4
+ Summary: The harness layer: sessions, status and idle, one driver per harness - a library first
5
+ Author: Mike Bailey
6
+ Author-email: Mike Bailey <mike@sessionkit.ai>
7
+ License-Expression: AGPL-3.0-or-later
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Requires-Dist: pyyaml>=6
11
+ Requires-Dist: jsonschema>=4
12
+ Requires-Python: >=3.11, <3.15
13
+ Project-URL: Homepage, https://github.com/sessionkit/sessiontools
14
+ Project-URL: Repository, https://github.com/sessionkit/sessiontools
15
+ Description-Content-Type: text/markdown
16
+
17
+ # sessiontools
18
+
19
+ `session`: one command for the coding-agent sessions on this machine.
20
+ Which are live, whether each is busy or idle, how full its context is,
21
+ and how to start one from an agent file. For anyone running several
22
+ Claude Code sessions side by side, and for the tools that watch them.
23
+
24
+ ## Install
25
+
26
+ This puts `session` on the PATH:
27
+
28
+ ```console
29
+ uv tool install sessiontools
30
+ ```
31
+
32
+ Python 3.11 to 3.14. Optional: [the Claude Code plugin][plugin] adds
33
+ the `session` skill and four hooks.
34
+
35
+ Shell completion, one line per shell ([shell completion][completion]).
36
+ For zsh, in `~/.zshrc` after compinit:
37
+
38
+ ```console
39
+ eval "$(session completion zsh)"
40
+ ```
41
+
42
+ For bash with the bash-completion package (2.x), once:
43
+
44
+ ```console
45
+ session completion bash > ~/.local/share/bash-completion/completions/session
46
+ ```
47
+
48
+ For bash 3.2, or any bash without that package, in `~/.bashrc`:
49
+
50
+ ```console
51
+ eval "$(session completion bash)"
52
+ ```
53
+
54
+ ## Quick start
55
+
56
+ - `session list`: the live sessions, with their panes, state and context.
57
+ - `session get state`: this session's state: busy, idle with children,
58
+ or idle.
59
+ - `session context`: how full this session's context window is.
60
+ - `session context --parts`: what filled it: boot, then growth by
61
+ category.
62
+ - `session ledger --short`: each file read and command run, by tokens.
63
+ - `session account status`: which Claude account this Mac is on.
64
+ - `session start --list-agents`: the agents it can start.
65
+ - `session reap`: seats idle past 1h, each CLOSE or KEEP, and why. A dry
66
+ run.
67
+ - `session default-model`: the model every new `claude` starts on, and
68
+ whether a pin keeps it there. Off until you set `model.default`.
69
+ - `session --help`: every verb.
70
+
71
+ ## Switching accounts
72
+
73
+ `session account use NAME` moves this Mac to another Claude account from
74
+ a token you already hold: no browser, no restart, and it never prints a
75
+ token. On a Keychain host it moves **every** seat, not one:
76
+
77
+ | route | moves | cache cost | reversible by | gate |
78
+ |---|---|---|---|---|
79
+ | `account use` (Keychain) | every seat on the Mac with no env token, within ~50 s | the sum of their contexts (2.07M / 1.10M measured) | `use` back, or restore from the backup | witness, `--yes`, preflight, backup |
80
+ | `account use` (token file) | no live seat; each one that relaunches from a shell that reads the file | none now; each seat's context when it restarts | `use` back, or restore from the backup | witness, `--yes`, preflight, backup |
81
+
82
+ A host whose shell exports `CLAUDE_CODE_OAUTH_TOKEN` from a file names it
83
+ in the `session` setting `account.token_file`, and `use` swaps the file.
84
+ A seat `session start` launches does not follow the file unless its agent
85
+ file says `inherit: [CLAUDE_CODE_OAUTH_TOKEN]`.
86
+ Without `--yes`, `use` shows the plan and writes nothing. To add an
87
+ account, `session account new NAME --email E` signs in through `claude
88
+ setup-token`, saves the token to passage, reads it back and registers
89
+ it; the token is never shown. See [Switching Claude accounts][account].
90
+
91
+ If you sign in by token, `CLAUDE_CODE_OAUTH_TOKEN` in your shell, a
92
+ session `session start` launches does not get it by default. To carry
93
+ it, name it under `inherit:` in the agent file ([credentials are never
94
+ handed down][credentials]). A `/login` sign-in needs nothing:
95
+
96
+ ```yaml
97
+ environment:
98
+ inherit:
99
+ - CLAUDE_CODE_OAUTH_TOKEN
100
+ ```
101
+
102
+ ## What an install changes
103
+
104
+ `uv tool install sessiontools` changes two places and fills uv's own
105
+ cache, and it touches no shell startup file and nothing in `~/.claude`.
106
+ Everything else below appears only when you run the verb named beside
107
+ it. Each line says what puts it there and what takes it away.
108
+
109
+ - **PATH:** `~/.local/bin/session`, the one command, or in
110
+ `UV_TOOL_BIN_DIR` when that is set. uv says so if the folder is not
111
+ on your PATH, and edits no shell file. `uv tool uninstall` removes it.
112
+ - **The program:** `~/.local/share/uv/tools/sessiontools/`, its own
113
+ Python environment with PyYAML and jsonschema, or in `UV_TOOL_DIR`.
114
+ `uv tool uninstall` removes it.
115
+ - **uv's cache:** the install fills it with what it downloaded and
116
+ built. It is uv's, shared by everything uv installs, not sessiontools'.
117
+ `uv cache dir` prints where it is (`~/.cache/uv` by default, or
118
+ `UV_CACHE_DIR`). `uv tool uninstall` leaves it; `uv cache clean
119
+ sessiontools` drops this package's entries, and `uv cache prune` drops
120
+ the ones nothing uses.
121
+ - **Folders, each made by a verb you run:**
122
+ - `~/.session/state/list/`: the list cache, `snapshot.json` and
123
+ `list.lock`, at the first `session list`.
124
+ - `~/.session/sessions/<session-id>/`: one for each `session start`,
125
+ holding `launch/`, `log/settings.jsonl` and `session-id`. It also
126
+ gets `settings/<tool>.d/50-env.json`, one for each tool whose
127
+ settings name a variable that is set when the session starts. Each
128
+ is written once, so a resumed session keeps its file, and
129
+ `log/settings.jsonl` names each variable it read, never its value.
130
+ The plugin's hooks and `session park` write only inside a folder
131
+ that is already there.
132
+ - `~/.session/state/reap.log`: `session reap --yes`.
133
+ - `~/.session/state/pane-state/watch.lock`: the pane signal's service.
134
+ - `~/.session/state/default-model/`: `backups/` and `log.jsonl`, the
135
+ first time `session default-model enforce` changes settings.json.
136
+ - `~/.sessionkit/tools/accounts/`: `session account new` and `use`.
137
+ - `~/.local/share/sessiontools/sound/bin/`: `session sound engine on`
138
+ builds the sound engine there with `swiftc`.
139
+ - `~/Library/Logs/sessiontools/`: the two services' logs.
140
+ - `SESSIONS_DIR`, `SESSION_STATE_DIR` and `SESSIONTOOLS_SOUND_HOME`
141
+ move these folders when set.
142
+ - **Services (launchd agents):** none until you turn one on. Each `on`
143
+ writes a plist in `~/Library/LaunchAgents/` and loads it, and it
144
+ starts again at every login. Each `off` unloads it and deletes the
145
+ plist. Both `on` and `off` also retire a job an older release set up
146
+ under its old label: they unload it and delete its plist. `status`
147
+ names one that is still there, and `--dry-run` lists the retire and
148
+ changes nothing.
149
+ - `dev.sessionkit.tools.sound-engine`: `session sound engine on`.
150
+ `off --purge` also deletes the engine it built.
151
+ - `dev.sessionkit.tools.pane-state`: `session pane-state agent on`.
152
+ It sets `@session_state` and `@session_state_why` on each seat's
153
+ tmux pane.
154
+ - `dev.sessionkit.tools.default-model`: no verb writes or loads it.
155
+ `session default-model unit launchd` prints it, and it is there only
156
+ if you save and load it yourself
157
+ ([the default model][default-model]).
158
+ - **Plugin and hooks, optional:** `claude plugin marketplace add` and
159
+ `claude plugin install` ([the Claude Code plugin][plugin]). Claude
160
+ Code names the marketplace and the plugin in `~/.claude/settings.json`
161
+ (`extraKnownMarketplaces`, `enabledPlugins`), lists them in
162
+ `~/.claude/plugins/`, and keeps a copy in
163
+ `~/.claude/plugins/cache/sessiontools/`. The plugin brings the
164
+ `session` skill and nine hook entries: SessionStart (two),
165
+ PreToolUse, PostToolBatch (three), Stop, UserPromptSubmit and
166
+ SessionEnd. Each is silent unless set.
167
+ Uninstalling the plugin takes it out of the settings and leaves the
168
+ cached copy.
169
+ - **Settings:** the install writes none, and none are needed.
170
+ `session statusline install` writes `statusline.sh` and
171
+ `claude-code.d/50-statusline.json` in `~/.sessionkit/tools/settings/`;
172
+ with `--force` it first keeps a `.bak-<stamp>` copy of each file it
173
+ replaces. Any other file there is one you wrote.
174
+ - **Outside these folders, only on a verb:** `session account use NAME
175
+ --yes` replaces the Keychain item `Claude Code-credentials`, or the
176
+ token file named by `account.token_file`, after a backup in the
177
+ accounts folder. `session account new` saves a token to passage.
178
+ With `model.default` set, `session default-model enforce` (and the
179
+ unit or `watch` that runs it) rewrites the top-level `"model"` in
180
+ `~/.claude/settings.json`, after a backup.
181
+
182
+ ## Uninstall
183
+
184
+ Turn off any service you turned on first: the pane signal's service
185
+ and the default model's agent run the `session` command that the last
186
+ line removes. Then remove the
187
+ plugin, if you installed it, and the command. Skip the lines for what
188
+ you never installed:
189
+
190
+ ```console
191
+ session pane-state agent off
192
+ session sound engine off --purge
193
+ launchctl bootout gui/$(id -u)/dev.sessionkit.tools.default-model
194
+ rm ~/Library/LaunchAgents/dev.sessionkit.tools.default-model.plist
195
+ claude plugin uninstall sessiontools@sessiontools
196
+ claude plugin marketplace remove sessiontools
197
+ uv tool uninstall sessiontools
198
+ ```
199
+
200
+ That leaves what the verbs made, listed above. The plugin's cached
201
+ copy, the services' logs and the engine's folder are sessiontools'
202
+ alone. To delete them:
203
+
204
+ ```bash
205
+ rm -rf "${HOME:?}/.claude/plugins/cache/sessiontools"
206
+ rm -rf "${HOME:?}/Library/Logs/sessiontools"
207
+ rm -rf "${HOME:?}/.local/share/sessiontools"
208
+ ```
209
+
210
+ uv's cache stays too, with the other things uv installed; `uv cache
211
+ clean sessiontools` drops this package's entries.
212
+
213
+ The rest is your data: delete it by hand only if you want it gone.
214
+ `~/.sessionkit/tools/` holds your settings and accounts, and the
215
+ accounts folder keeps the backups of every token `session account use`
216
+ replaced. `~/.session/` holds the session folders and the list cache,
217
+ and the kit's other packages keep their files in it too.
218
+
219
+ ## Docs
220
+
221
+ - [The `session` command][verbs]: what it does, every verb, and an
222
+ example of each
223
+ - [Starting a session][start]: agent files, flags, tmux, one holder
224
+ - [Switching Claude accounts][account]: `session account`, its blast
225
+ radius, and `new`, which adds an account
226
+ - [The `session list` cache][cache]: one computation, many readers
227
+ - [Operator and privileged seats][operator]: who seats ask
228
+ - [Closing idle seats][reap]: `session reap`, a dry run by default
229
+ - [Shell completion][completion]: `session completion bash|zsh`, a static
230
+ script built from the verbs' own parsers
231
+ - [The Claude Code plugin][plugin]: the skill and the hooks
232
+ - [The settle alert][settle]: one line past a context threshold
233
+ - [The clock line][clock]: the time and the context on every tool batch
234
+ - [The busy sound][busy]: a sound while a tool batch runs long
235
+ - [The session-info hooks][session-info]: the short session id as the
236
+ session title, and the session's facts in context, both off by default
237
+ - [The sound engine][engine]: the busy sound from one resident process
238
+ - [The status line][status-line]: `session statusline install` puts it in the
239
+ kit's claude-code layer, never `~/.claude`. `session start` seats show it;
240
+ a bare `claude` does not (`alias claude='session start -'`)
241
+ - [The pane signal][pane-state]: BUSY / FREE on each seat's tmux pane,
242
+ and `session pane-state agent`, the launchd agent that publishes it
243
+ - [The default model][default-model]: `session default-model`, opt-in,
244
+ puts settings.json's `"model"` back when something rewrites it
245
+ - [The Python library][library]: `from sessiontools import sessions`
246
+ - [Building and testing][building]: make targets, specs, releases
247
+ - [CHANGELOG][changelog]; licence AGPL-3.0-or-later ([LICENSE][license])
248
+
249
+ ## Configuration
250
+
251
+ Nothing needs configuring: unset, every part uses its default. What can
252
+ be set, and where each one lives ([configuration][configuration]):
253
+
254
+ - **Settings files**, JSON in layers: `~/.sessionkit/tools/settings/session.json`
255
+ (the operator, the list cache, the pane signal, accounts, a session's
256
+ home, the default model), `~/.sessionkit/tools/settings/sounds.json` (the busy sound), and
257
+ `~/.sessionkit/tools/settings/claude-code.json` (the model, effort
258
+ and status line `session start` passes on). Every key, its default and
259
+ which layers it is read from: [settings][settings].
260
+ - **Folders of its own:** `~/.session/` (sessions, seats, agent files, the
261
+ state dir) and `~/.sessionkit/tools/accounts/` (`session account`).
262
+ - **Environment variables:** the settle alert's `SESSION_SETTLE_AT`, the
263
+ clock line's `SESSION_CLOCK_WARN_AT`, the hooks' `SESSIONTOOLS_HOOK_*`
264
+ switches (`SESSIONTOOLS_HOOK_CLOCK=on` switches the clock line on), and
265
+ the paths it reads, such as `SESSIONS_DIR` and `SESSION_STATE_DIR`.
266
+ The configuration page lists each one with its default, under the part
267
+ that reads it.
268
+
269
+ ## Logs
270
+
271
+ - **The sound engine:** `~/Library/Logs/sessiontools/sound-engine.log`,
272
+ once `session sound engine on` has run.
273
+ - **The pane signal's agent:** `~/Library/Logs/sessiontools/pane-state.log`,
274
+ once `session pane-state agent on` has run.
275
+ - **The default model:** `~/.session/state/default-model/log.jsonl`, one
276
+ line per write to settings.json, and
277
+ `~/Library/Logs/sessiontools/default-model.log`, once you have loaded
278
+ the launchd agent `session default-model unit launchd` prints.
279
+ - **`session reap --yes`:** `~/.session/state/reap.log`, one line for each
280
+ seat it closed or failed to close.
281
+ - **Each session's settings at birth:**
282
+ `~/.session/sessions/<session-id>/log/settings.jsonl`, what `session
283
+ start` read into that session's settings: each environment variable
284
+ by name, never its value.
285
+ - **The status line:** `~/.config/session/statusline-log.jsonl`, one line
286
+ per render, only when `~/.config/session/` already exists.
287
+
288
+ Follow one as it grows with `tail -F`, for example
289
+ `tail -F ~/Library/Logs/sessiontools/pane-state.log`.
290
+
291
+ - **Health:** `session list` prints a row for each live session, with its
292
+ state and context. A session you know is running that is not listed is
293
+ one sessiontools cannot see.
294
+
295
+ [verbs]: docs/session.md
296
+ [start]: docs/session-start-launcher.md
297
+ [credentials]: docs/session-start-launcher.md#credentials-are-never-handed-down
298
+ [account]: docs/account.md
299
+ [cache]: docs/list-snapshot-cache.md
300
+ [operator]: docs/operator-privileged-seats.md
301
+ [reap]: docs/session-reap.md
302
+ [completion]: docs/shell-completion.md
303
+ [plugin]: docs/claude-code-plugin.md
304
+ [settle]: docs/settle-alert-hook.md
305
+ [clock]: docs/clock-line-hook.md
306
+ [busy]: docs/busy-sound-hook.md
307
+ [session-info]: docs/session-info-hook.md
308
+ [engine]: docs/sound-engine-agent.md
309
+ [status-line]: docs/status-line-script.md
310
+ [pane-state]: docs/pane-state-signal.md
311
+ [default-model]: docs/default-model.md
312
+ [library]: docs/python-library-api.md
313
+ [building]: docs/building-and-testing.md
314
+ [changelog]: CHANGELOG.md
315
+ [license]: LICENSE
316
+ [configuration]: docs/configuration.md
317
+ [settings]: docs/settings.md
@@ -0,0 +1,301 @@
1
+ # sessiontools
2
+
3
+ `session`: one command for the coding-agent sessions on this machine.
4
+ Which are live, whether each is busy or idle, how full its context is,
5
+ and how to start one from an agent file. For anyone running several
6
+ Claude Code sessions side by side, and for the tools that watch them.
7
+
8
+ ## Install
9
+
10
+ This puts `session` on the PATH:
11
+
12
+ ```console
13
+ uv tool install sessiontools
14
+ ```
15
+
16
+ Python 3.11 to 3.14. Optional: [the Claude Code plugin][plugin] adds
17
+ the `session` skill and four hooks.
18
+
19
+ Shell completion, one line per shell ([shell completion][completion]).
20
+ For zsh, in `~/.zshrc` after compinit:
21
+
22
+ ```console
23
+ eval "$(session completion zsh)"
24
+ ```
25
+
26
+ For bash with the bash-completion package (2.x), once:
27
+
28
+ ```console
29
+ session completion bash > ~/.local/share/bash-completion/completions/session
30
+ ```
31
+
32
+ For bash 3.2, or any bash without that package, in `~/.bashrc`:
33
+
34
+ ```console
35
+ eval "$(session completion bash)"
36
+ ```
37
+
38
+ ## Quick start
39
+
40
+ - `session list`: the live sessions, with their panes, state and context.
41
+ - `session get state`: this session's state: busy, idle with children,
42
+ or idle.
43
+ - `session context`: how full this session's context window is.
44
+ - `session context --parts`: what filled it: boot, then growth by
45
+ category.
46
+ - `session ledger --short`: each file read and command run, by tokens.
47
+ - `session account status`: which Claude account this Mac is on.
48
+ - `session start --list-agents`: the agents it can start.
49
+ - `session reap`: seats idle past 1h, each CLOSE or KEEP, and why. A dry
50
+ run.
51
+ - `session default-model`: the model every new `claude` starts on, and
52
+ whether a pin keeps it there. Off until you set `model.default`.
53
+ - `session --help`: every verb.
54
+
55
+ ## Switching accounts
56
+
57
+ `session account use NAME` moves this Mac to another Claude account from
58
+ a token you already hold: no browser, no restart, and it never prints a
59
+ token. On a Keychain host it moves **every** seat, not one:
60
+
61
+ | route | moves | cache cost | reversible by | gate |
62
+ |---|---|---|---|---|
63
+ | `account use` (Keychain) | every seat on the Mac with no env token, within ~50 s | the sum of their contexts (2.07M / 1.10M measured) | `use` back, or restore from the backup | witness, `--yes`, preflight, backup |
64
+ | `account use` (token file) | no live seat; each one that relaunches from a shell that reads the file | none now; each seat's context when it restarts | `use` back, or restore from the backup | witness, `--yes`, preflight, backup |
65
+
66
+ A host whose shell exports `CLAUDE_CODE_OAUTH_TOKEN` from a file names it
67
+ in the `session` setting `account.token_file`, and `use` swaps the file.
68
+ A seat `session start` launches does not follow the file unless its agent
69
+ file says `inherit: [CLAUDE_CODE_OAUTH_TOKEN]`.
70
+ Without `--yes`, `use` shows the plan and writes nothing. To add an
71
+ account, `session account new NAME --email E` signs in through `claude
72
+ setup-token`, saves the token to passage, reads it back and registers
73
+ it; the token is never shown. See [Switching Claude accounts][account].
74
+
75
+ If you sign in by token, `CLAUDE_CODE_OAUTH_TOKEN` in your shell, a
76
+ session `session start` launches does not get it by default. To carry
77
+ it, name it under `inherit:` in the agent file ([credentials are never
78
+ handed down][credentials]). A `/login` sign-in needs nothing:
79
+
80
+ ```yaml
81
+ environment:
82
+ inherit:
83
+ - CLAUDE_CODE_OAUTH_TOKEN
84
+ ```
85
+
86
+ ## What an install changes
87
+
88
+ `uv tool install sessiontools` changes two places and fills uv's own
89
+ cache, and it touches no shell startup file and nothing in `~/.claude`.
90
+ Everything else below appears only when you run the verb named beside
91
+ it. Each line says what puts it there and what takes it away.
92
+
93
+ - **PATH:** `~/.local/bin/session`, the one command, or in
94
+ `UV_TOOL_BIN_DIR` when that is set. uv says so if the folder is not
95
+ on your PATH, and edits no shell file. `uv tool uninstall` removes it.
96
+ - **The program:** `~/.local/share/uv/tools/sessiontools/`, its own
97
+ Python environment with PyYAML and jsonschema, or in `UV_TOOL_DIR`.
98
+ `uv tool uninstall` removes it.
99
+ - **uv's cache:** the install fills it with what it downloaded and
100
+ built. It is uv's, shared by everything uv installs, not sessiontools'.
101
+ `uv cache dir` prints where it is (`~/.cache/uv` by default, or
102
+ `UV_CACHE_DIR`). `uv tool uninstall` leaves it; `uv cache clean
103
+ sessiontools` drops this package's entries, and `uv cache prune` drops
104
+ the ones nothing uses.
105
+ - **Folders, each made by a verb you run:**
106
+ - `~/.session/state/list/`: the list cache, `snapshot.json` and
107
+ `list.lock`, at the first `session list`.
108
+ - `~/.session/sessions/<session-id>/`: one for each `session start`,
109
+ holding `launch/`, `log/settings.jsonl` and `session-id`. It also
110
+ gets `settings/<tool>.d/50-env.json`, one for each tool whose
111
+ settings name a variable that is set when the session starts. Each
112
+ is written once, so a resumed session keeps its file, and
113
+ `log/settings.jsonl` names each variable it read, never its value.
114
+ The plugin's hooks and `session park` write only inside a folder
115
+ that is already there.
116
+ - `~/.session/state/reap.log`: `session reap --yes`.
117
+ - `~/.session/state/pane-state/watch.lock`: the pane signal's service.
118
+ - `~/.session/state/default-model/`: `backups/` and `log.jsonl`, the
119
+ first time `session default-model enforce` changes settings.json.
120
+ - `~/.sessionkit/tools/accounts/`: `session account new` and `use`.
121
+ - `~/.local/share/sessiontools/sound/bin/`: `session sound engine on`
122
+ builds the sound engine there with `swiftc`.
123
+ - `~/Library/Logs/sessiontools/`: the two services' logs.
124
+ - `SESSIONS_DIR`, `SESSION_STATE_DIR` and `SESSIONTOOLS_SOUND_HOME`
125
+ move these folders when set.
126
+ - **Services (launchd agents):** none until you turn one on. Each `on`
127
+ writes a plist in `~/Library/LaunchAgents/` and loads it, and it
128
+ starts again at every login. Each `off` unloads it and deletes the
129
+ plist. Both `on` and `off` also retire a job an older release set up
130
+ under its old label: they unload it and delete its plist. `status`
131
+ names one that is still there, and `--dry-run` lists the retire and
132
+ changes nothing.
133
+ - `dev.sessionkit.tools.sound-engine`: `session sound engine on`.
134
+ `off --purge` also deletes the engine it built.
135
+ - `dev.sessionkit.tools.pane-state`: `session pane-state agent on`.
136
+ It sets `@session_state` and `@session_state_why` on each seat's
137
+ tmux pane.
138
+ - `dev.sessionkit.tools.default-model`: no verb writes or loads it.
139
+ `session default-model unit launchd` prints it, and it is there only
140
+ if you save and load it yourself
141
+ ([the default model][default-model]).
142
+ - **Plugin and hooks, optional:** `claude plugin marketplace add` and
143
+ `claude plugin install` ([the Claude Code plugin][plugin]). Claude
144
+ Code names the marketplace and the plugin in `~/.claude/settings.json`
145
+ (`extraKnownMarketplaces`, `enabledPlugins`), lists them in
146
+ `~/.claude/plugins/`, and keeps a copy in
147
+ `~/.claude/plugins/cache/sessiontools/`. The plugin brings the
148
+ `session` skill and nine hook entries: SessionStart (two),
149
+ PreToolUse, PostToolBatch (three), Stop, UserPromptSubmit and
150
+ SessionEnd. Each is silent unless set.
151
+ Uninstalling the plugin takes it out of the settings and leaves the
152
+ cached copy.
153
+ - **Settings:** the install writes none, and none are needed.
154
+ `session statusline install` writes `statusline.sh` and
155
+ `claude-code.d/50-statusline.json` in `~/.sessionkit/tools/settings/`;
156
+ with `--force` it first keeps a `.bak-<stamp>` copy of each file it
157
+ replaces. Any other file there is one you wrote.
158
+ - **Outside these folders, only on a verb:** `session account use NAME
159
+ --yes` replaces the Keychain item `Claude Code-credentials`, or the
160
+ token file named by `account.token_file`, after a backup in the
161
+ accounts folder. `session account new` saves a token to passage.
162
+ With `model.default` set, `session default-model enforce` (and the
163
+ unit or `watch` that runs it) rewrites the top-level `"model"` in
164
+ `~/.claude/settings.json`, after a backup.
165
+
166
+ ## Uninstall
167
+
168
+ Turn off any service you turned on first: the pane signal's service
169
+ and the default model's agent run the `session` command that the last
170
+ line removes. Then remove the
171
+ plugin, if you installed it, and the command. Skip the lines for what
172
+ you never installed:
173
+
174
+ ```console
175
+ session pane-state agent off
176
+ session sound engine off --purge
177
+ launchctl bootout gui/$(id -u)/dev.sessionkit.tools.default-model
178
+ rm ~/Library/LaunchAgents/dev.sessionkit.tools.default-model.plist
179
+ claude plugin uninstall sessiontools@sessiontools
180
+ claude plugin marketplace remove sessiontools
181
+ uv tool uninstall sessiontools
182
+ ```
183
+
184
+ That leaves what the verbs made, listed above. The plugin's cached
185
+ copy, the services' logs and the engine's folder are sessiontools'
186
+ alone. To delete them:
187
+
188
+ ```bash
189
+ rm -rf "${HOME:?}/.claude/plugins/cache/sessiontools"
190
+ rm -rf "${HOME:?}/Library/Logs/sessiontools"
191
+ rm -rf "${HOME:?}/.local/share/sessiontools"
192
+ ```
193
+
194
+ uv's cache stays too, with the other things uv installed; `uv cache
195
+ clean sessiontools` drops this package's entries.
196
+
197
+ The rest is your data: delete it by hand only if you want it gone.
198
+ `~/.sessionkit/tools/` holds your settings and accounts, and the
199
+ accounts folder keeps the backups of every token `session account use`
200
+ replaced. `~/.session/` holds the session folders and the list cache,
201
+ and the kit's other packages keep their files in it too.
202
+
203
+ ## Docs
204
+
205
+ - [The `session` command][verbs]: what it does, every verb, and an
206
+ example of each
207
+ - [Starting a session][start]: agent files, flags, tmux, one holder
208
+ - [Switching Claude accounts][account]: `session account`, its blast
209
+ radius, and `new`, which adds an account
210
+ - [The `session list` cache][cache]: one computation, many readers
211
+ - [Operator and privileged seats][operator]: who seats ask
212
+ - [Closing idle seats][reap]: `session reap`, a dry run by default
213
+ - [Shell completion][completion]: `session completion bash|zsh`, a static
214
+ script built from the verbs' own parsers
215
+ - [The Claude Code plugin][plugin]: the skill and the hooks
216
+ - [The settle alert][settle]: one line past a context threshold
217
+ - [The clock line][clock]: the time and the context on every tool batch
218
+ - [The busy sound][busy]: a sound while a tool batch runs long
219
+ - [The session-info hooks][session-info]: the short session id as the
220
+ session title, and the session's facts in context, both off by default
221
+ - [The sound engine][engine]: the busy sound from one resident process
222
+ - [The status line][status-line]: `session statusline install` puts it in the
223
+ kit's claude-code layer, never `~/.claude`. `session start` seats show it;
224
+ a bare `claude` does not (`alias claude='session start -'`)
225
+ - [The pane signal][pane-state]: BUSY / FREE on each seat's tmux pane,
226
+ and `session pane-state agent`, the launchd agent that publishes it
227
+ - [The default model][default-model]: `session default-model`, opt-in,
228
+ puts settings.json's `"model"` back when something rewrites it
229
+ - [The Python library][library]: `from sessiontools import sessions`
230
+ - [Building and testing][building]: make targets, specs, releases
231
+ - [CHANGELOG][changelog]; licence AGPL-3.0-or-later ([LICENSE][license])
232
+
233
+ ## Configuration
234
+
235
+ Nothing needs configuring: unset, every part uses its default. What can
236
+ be set, and where each one lives ([configuration][configuration]):
237
+
238
+ - **Settings files**, JSON in layers: `~/.sessionkit/tools/settings/session.json`
239
+ (the operator, the list cache, the pane signal, accounts, a session's
240
+ home, the default model), `~/.sessionkit/tools/settings/sounds.json` (the busy sound), and
241
+ `~/.sessionkit/tools/settings/claude-code.json` (the model, effort
242
+ and status line `session start` passes on). Every key, its default and
243
+ which layers it is read from: [settings][settings].
244
+ - **Folders of its own:** `~/.session/` (sessions, seats, agent files, the
245
+ state dir) and `~/.sessionkit/tools/accounts/` (`session account`).
246
+ - **Environment variables:** the settle alert's `SESSION_SETTLE_AT`, the
247
+ clock line's `SESSION_CLOCK_WARN_AT`, the hooks' `SESSIONTOOLS_HOOK_*`
248
+ switches (`SESSIONTOOLS_HOOK_CLOCK=on` switches the clock line on), and
249
+ the paths it reads, such as `SESSIONS_DIR` and `SESSION_STATE_DIR`.
250
+ The configuration page lists each one with its default, under the part
251
+ that reads it.
252
+
253
+ ## Logs
254
+
255
+ - **The sound engine:** `~/Library/Logs/sessiontools/sound-engine.log`,
256
+ once `session sound engine on` has run.
257
+ - **The pane signal's agent:** `~/Library/Logs/sessiontools/pane-state.log`,
258
+ once `session pane-state agent on` has run.
259
+ - **The default model:** `~/.session/state/default-model/log.jsonl`, one
260
+ line per write to settings.json, and
261
+ `~/Library/Logs/sessiontools/default-model.log`, once you have loaded
262
+ the launchd agent `session default-model unit launchd` prints.
263
+ - **`session reap --yes`:** `~/.session/state/reap.log`, one line for each
264
+ seat it closed or failed to close.
265
+ - **Each session's settings at birth:**
266
+ `~/.session/sessions/<session-id>/log/settings.jsonl`, what `session
267
+ start` read into that session's settings: each environment variable
268
+ by name, never its value.
269
+ - **The status line:** `~/.config/session/statusline-log.jsonl`, one line
270
+ per render, only when `~/.config/session/` already exists.
271
+
272
+ Follow one as it grows with `tail -F`, for example
273
+ `tail -F ~/Library/Logs/sessiontools/pane-state.log`.
274
+
275
+ - **Health:** `session list` prints a row for each live session, with its
276
+ state and context. A session you know is running that is not listed is
277
+ one sessiontools cannot see.
278
+
279
+ [verbs]: docs/session.md
280
+ [start]: docs/session-start-launcher.md
281
+ [credentials]: docs/session-start-launcher.md#credentials-are-never-handed-down
282
+ [account]: docs/account.md
283
+ [cache]: docs/list-snapshot-cache.md
284
+ [operator]: docs/operator-privileged-seats.md
285
+ [reap]: docs/session-reap.md
286
+ [completion]: docs/shell-completion.md
287
+ [plugin]: docs/claude-code-plugin.md
288
+ [settle]: docs/settle-alert-hook.md
289
+ [clock]: docs/clock-line-hook.md
290
+ [busy]: docs/busy-sound-hook.md
291
+ [session-info]: docs/session-info-hook.md
292
+ [engine]: docs/sound-engine-agent.md
293
+ [status-line]: docs/status-line-script.md
294
+ [pane-state]: docs/pane-state-signal.md
295
+ [default-model]: docs/default-model.md
296
+ [library]: docs/python-library-api.md
297
+ [building]: docs/building-and-testing.md
298
+ [changelog]: CHANGELOG.md
299
+ [license]: LICENSE
300
+ [configuration]: docs/configuration.md
301
+ [settings]: docs/settings.md
@@ -1,38 +1,43 @@
1
1
  [project]
2
2
  name = "sessiontools"
3
- version = "0.1.0"
3
+ version = "0.3.0"
4
4
  description = "The harness layer: sessions, status and idle, one driver per harness - a library first"
5
+ readme = "README.md"
5
6
  license = "AGPL-3.0-or-later"
6
7
  license-files = ["LICENSE"]
7
8
  authors = [
8
- { name = "Cora 7", email = "ai-cora@failmode.com" }
9
+ { name = "Mike Bailey", email = "mike@sessionkit.ai" }
10
+ ]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
9
13
  ]
10
14
  # >=3.11, not the recipe's >=3.13: sessionmail (the first consumer) declares
11
15
  # >=3.11, and uv resolves for EVERY version a project allows, so a stricter
12
- # floor here made sessionmail's `uv sync` unsatisfiable (Pip, 13:02 Thu
13
- # 2026-09-17). Measured: the suite passes on 3.11, 3.12 and 3.13; nothing in
14
- # the tree needs 3.13.
15
- requires-python = ">=3.11"
16
- # ONE runtime dependency, and only for `session start`: agent front matter
17
- # is YAML (the same YAML Claude Code reads itself). Mike, voice 00:12 Tue
18
- # 2026-09-29, asked "Can start pull in the YAML library, just for itself?":
19
- # "Um, yes." So PyYAML is declared here and imported by exactly one module,
16
+ # floor here made sessionmail's `uv sync` unsatisfiable.
17
+ # Measured: the suite passes on 3.11, 3.12 and 3.13; nothing in
18
+ # the tree needs 3.13. The ceiling, <3.15, makes 3.14 the newest supported
19
+ # version.
20
+ requires-python = ">=3.11,<3.15"
21
+ # TWO runtime dependencies. The FIRST, PyYAML, for agent front matter, which
22
+ # is YAML (the same YAML Claude Code reads itself), and `session start`
23
+ # may pull in the YAML library for itself alone. So PyYAML is declared here and imported by exactly one module,
20
24
  # `sessiontools.start.agentfile`; `import sessiontools` (the library
21
25
  # sessionmail depends on) still imports nothing outside the standard library.
22
26
  # tests/test_cli.py pins both halves.
23
27
  # The SECOND: jsonschema, for `session settings` alone - each tool ships a
24
28
  # JSON Schema and the reader checks the merged result against it. No
25
- # configuration library for the layering (Mike, voice 19:15 Sat 2026-10-03,
26
- # Cora /RULINGS 4600); imported inside `sessiontools.settings.check_schema`,
29
+ # configuration library for the layering, by design; imported inside `sessiontools.settings.check_schema`,
27
30
  # so `import sessiontools` still reaches nothing outside the standard library.
28
31
  dependencies = ["PyYAML>=6", "jsonschema>=4"]
29
32
 
30
- # THE `session` COMMAND - ruled at the cutover. Mike, voice 23:25-23:28 Mon
31
- # 2026-09-28, the session repo just moved aside: sessiontools provides it
32
- # ("Ah, yes, it should"), same verbs, ported as package code ("port it
33
- # nicely"), superseding his 12:18 Thu 2026-09-17 "not on our path today".
33
+ # THE `session` COMMAND - settled at the cutover, when the session repo moved
34
+ # aside: sessiontools provides it, same verbs, ported as package code.
34
35
  # Exactly one package owns `session` on the PATH; from here it is this one.
35
36
  # argparse and the library; PyYAML for `session start` alone (above).
37
+ [project.urls]
38
+ Homepage = "https://github.com/sessionkit/sessiontools"
39
+ Repository = "https://github.com/sessionkit/sessiontools"
40
+
36
41
  [project.scripts]
37
42
  session = "sessiontools.cli:main"
38
43