sessiontools 0.3.3__tar.gz → 0.4.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 (125) hide show
  1. {sessiontools-0.3.3 → sessiontools-0.4.0}/PKG-INFO +71 -45
  2. {sessiontools-0.3.3 → sessiontools-0.4.0}/README.md +70 -44
  3. {sessiontools-0.3.3 → sessiontools-0.4.0}/pyproject.toml +1 -1
  4. {sessiontools-0.3.3 → sessiontools-0.4.0}/pyproject.toml.orig +1 -1
  5. sessiontools-0.4.0/src/sessiontools/argv_flags.py +32 -0
  6. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/band.py +43 -21
  7. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/cli.py +70 -15
  8. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/clockline.py +12 -60
  9. sessiontools-0.4.0/src/sessiontools/commands/__init__.py +110 -0
  10. sessiontools-0.4.0/src/sessiontools/commands/cli.py +127 -0
  11. sessiontools-0.4.0/src/sessiontools/commands/shell.py +691 -0
  12. sessiontools-0.4.0/src/sessiontools/compact_point.py +400 -0
  13. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/completion.py +4 -0
  14. sessiontools-0.4.0/src/sessiontools/default_model_agent.py +363 -0
  15. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/default_model_cli.py +32 -13
  16. sessiontools-0.4.0/src/sessiontools/get.py +150 -0
  17. sessiontools-0.4.0/src/sessiontools/hidden_failure.py +610 -0
  18. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/ledger/transcript.py +7 -3
  19. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/listing.py +7 -1
  20. sessiontools-0.4.0/src/sessiontools/md_lint.py +262 -0
  21. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/pane_state.py +9 -1
  22. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/pane_state_agent.py +43 -9
  23. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/park.py +51 -8
  24. sessiontools-0.4.0/src/sessiontools/past.py +158 -0
  25. sessiontools-0.4.0/src/sessiontools/respawn.py +645 -0
  26. sessiontools-0.4.0/src/sessiontools/sed_guard.py +294 -0
  27. sessiontools-0.4.0/src/sessiontools/service_file.py +214 -0
  28. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/settings.py +112 -12
  29. sessiontools-0.4.0/src/sessiontools/settle.py +357 -0
  30. sessiontools-0.4.0/src/sessiontools/settle_guard.py +181 -0
  31. sessiontools-0.4.0/src/sessiontools/settle_settings.py +542 -0
  32. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/sound/service.py +58 -13
  33. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/__init__.py +167 -51
  34. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/agentfile.py +26 -14
  35. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/args.py +11 -0
  36. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/boot_identity.py +46 -92
  37. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/claude_settings.py +56 -23
  38. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/context.py +287 -35
  39. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/default_seat.py +2 -45
  40. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/environment.py +5 -3
  41. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/errors.py +25 -1
  42. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/identity.py +2 -117
  43. sessiontools-0.4.0/src/sessiontools/start/pane.py +276 -0
  44. sessiontools-0.4.0/src/sessiontools/start/placement.py +379 -0
  45. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/plan.py +251 -136
  46. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/record.py +212 -19
  47. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/run.py +140 -25
  48. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/secrets.py +5 -3
  49. sessiontools-0.4.0/src/sessiontools/start/user_memory.py +245 -0
  50. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/vendor.py +32 -33
  51. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/statusline/statusline.sh +6 -1
  52. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/web/cli.py +65 -6
  53. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/window.py +121 -16
  54. sessiontools-0.3.3/src/sessiontools/get.py +0 -91
  55. sessiontools-0.3.3/src/sessiontools/settle.py +0 -230
  56. sessiontools-0.3.3/src/sessiontools/start/placement.py +0 -272
  57. {sessiontools-0.3.3 → sessiontools-0.4.0}/LICENSE +0 -0
  58. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/__init__.py +0 -0
  59. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/__init__.py +0 -0
  60. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/cli.py +0 -0
  61. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/keychain.py +0 -0
  62. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/leakscan.py +0 -0
  63. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/limits.py +0 -0
  64. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/movehome.py +0 -0
  65. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/new.py +0 -0
  66. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/profile.py +0 -0
  67. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/registry.py +0 -0
  68. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/setuptoken.py +0 -0
  69. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/status.py +0 -0
  70. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/tokenfile.py +0 -0
  71. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/tokenstore.py +0 -0
  72. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/use.py +0 -0
  73. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/account/verify.py +0 -0
  74. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/busy.py +0 -0
  75. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/claude_code.py +0 -0
  76. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/context_parts/__init__.py +0 -0
  77. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/context_parts/boot.py +0 -0
  78. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/context_parts/breakdown.py +0 -0
  79. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/context_parts/hooks.py +0 -0
  80. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/context_parts/nnls.py +0 -0
  81. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/context_parts/render.py +0 -0
  82. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/context_parts/slash_context.py +0 -0
  83. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/context_parts/tools.py +0 -0
  84. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/context_parts/transcript.py +0 -0
  85. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/default_model.py +0 -0
  86. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/default_model_watch.py +0 -0
  87. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/identity_block.py +0 -0
  88. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/ledger/__init__.py +0 -0
  89. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/ledger/_tiktoken_child.py +0 -0
  90. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/ledger/count.py +0 -0
  91. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/ledger/render.py +0 -0
  92. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/migrate.py +0 -0
  93. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/operator.py +0 -0
  94. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/paths.py +0 -0
  95. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/procs.py +0 -0
  96. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/py.typed +0 -0
  97. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/reap.py +0 -0
  98. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/send.py +0 -0
  99. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/session_info.py +0 -0
  100. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/snapshot.py +0 -0
  101. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/sound/__init__.py +0 -0
  102. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/sound/engine.swift +0 -0
  103. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/sound/event.swift +0 -0
  104. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/agents.py +0 -0
  105. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/default_seat.md +0 -0
  106. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/holders.py +0 -0
  107. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/homefolder.py +0 -0
  108. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/policy.py +0 -0
  109. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/start/resume.py +0 -0
  110. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/statusline/__init__.py +0 -0
  111. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/statusline/sample.json +0 -0
  112. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/tmux.py +0 -0
  113. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/turn.py +0 -0
  114. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/verbs.py +0 -0
  115. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/web/__init__.py +0 -0
  116. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/web/auth.py +0 -0
  117. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/web/bind.py +0 -0
  118. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/web/passkey.js +0 -0
  119. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/web/passkey.py +0 -0
  120. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/web/proxy.py +0 -0
  121. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/web/rooms.py +0 -0
  122. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/web/server.py +0 -0
  123. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/web/settings_page.py +0 -0
  124. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/web/supervisor.py +0 -0
  125. {sessiontools-0.3.3 → sessiontools-0.4.0}/src/sessiontools/web/unixhttp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sessiontools
3
- Version: 0.3.3
3
+ Version: 0.4.0
4
4
  Summary: The harness layer: sessions, status and idle, one driver per harness - a library first
5
5
  Author: Mike Bailey
6
6
  Author-email: Mike Bailey <mike@sessionkit.ai>
@@ -32,7 +32,7 @@ uv tool install sessiontools
32
32
  ```
33
33
 
34
34
  Python 3.11 to 3.14. Optional: [the Claude Code plugin][plugin] adds
35
- the `session` skill and four hooks.
35
+ the `session` skill and five hooks.
36
36
 
37
37
  Shell completion, one line per shell ([shell completion][completion]).
38
38
  For zsh, in `~/.zshrc` after compinit:
@@ -62,12 +62,18 @@ eval "$(session completion bash)"
62
62
  - `session context --parts`: what filled it: boot, then growth by
63
63
  category.
64
64
  - `session ledger --short`: each file read and command run, by tokens.
65
+ - `session commands SEAT --since 7d --count`: the programs a seat's
66
+ sessions ran in a week, by how often.
67
+ - `session list --past SEAT`: every session recorded as SEAT on this
68
+ host, oldest first.
65
69
  - `session account status`: which Claude account this Mac is on.
66
70
  - `session account verify`: which account the API bills for this seat's
67
71
  token, from one 1-token request.
68
72
  - `session start --list-agents`: the agents it can start.
69
73
  - `session reap`: seats idle past 1h, each CLOSE or KEEP, and why. A dry
70
74
  run.
75
+ - `session respawn`: the plan to restart this seat in its pane with the
76
+ line it was started with. `--go` acts.
71
77
  - `session default-model`: the model every new `claude` starts on, and
72
78
  whether a pin keeps it there. Off until you set `model.default`.
73
79
  - `session --help`: every verb.
@@ -151,30 +157,36 @@ it. Each line says what puts it there and what takes it away.
151
157
  `log.jsonl`, the first time `session default-model enforce` changes
152
158
  settings.json.
153
159
  - `~/.sessionkit/tools/accounts/`: `session account new` and `use`.
160
+ - `~/.sessionkit/tools/services/`, beside `settings/`: one file per
161
+ service, for `sessionkit service list`. `pane-state.json`:
162
+ `session pane-state agent on|off`. `sound-engine.json`: `session
163
+ sound engine on|off`. `default-model.json`: `session default-model
164
+ agent on|off`. `web.json`: `session web declare`, which installs
165
+ nothing. `off` rewrites a file with `enabled: false` and keeps it.
154
166
  - `~/.sessionkit/tools/state/sound/bin/`: `session sound engine on`
155
167
  builds the sound engine there with `swiftc` (in
156
168
  `~/.local/share/sessiontools/sound/bin/` while that old folder is
157
169
  still there).
158
- - `~/.sessionkit/tools/log/`: the services' logs and `reap.log`
159
- (Logs, below). An older release wrote them elsewhere, and those
160
- files stay where they were.
170
+ - `~/.sessionkit/tools/log/`: the services' logs, `reap.log` and
171
+ `respawn.log` (Logs, below). An older release wrote them elsewhere,
172
+ and those files stay where they were.
161
173
  - `SESSIONS_DIR`, `SESSION_STATE_DIR` and `SESSIONTOOLS_SOUND_HOME`
162
174
  move these folders when set.
163
175
  - **Services (launchd agents):** none until you turn one on. Each `on`
164
176
  writes a plist in `~/Library/LaunchAgents/` and loads it, and it
165
177
  starts again at every login. Each `off` unloads it and deletes the
166
- plist. Both `on` and `off` also retire a job an older release set up
167
- under its old label: they unload it and delete its plist. `status`
168
- names one that is still there, and `--dry-run` lists the retire and
169
- changes nothing.
178
+ plist. For pane-state and the sound engine, `on` and `off` also
179
+ retire a job an older release set up under its old label: they unload
180
+ it and delete its plist. `status` names one that is still there, and
181
+ `--dry-run` lists the retire and changes nothing.
170
182
  - `dev.sessionkit.tools.sound-engine`: `session sound engine on`.
171
183
  `off --purge` also deletes the engine it built.
172
184
  - `dev.sessionkit.tools.pane-state`: `session pane-state agent on`.
173
185
  It sets `@session_state` and `@session_state_why` on each seat's
174
186
  tmux pane.
175
- - `dev.sessionkit.tools.default-model`: no verb writes or loads it.
176
- `session default-model unit launchd` prints it, and it is there only
177
- if you save and load it yourself
187
+ - `dev.sessionkit.tools.default-model`: `session default-model agent
188
+ on`. Opt-in: nothing turns it on for you. `session default-model
189
+ unit launchd` prints the same plist for a hand install
178
190
  ([the default model][default-model]).
179
191
  - **Plugin and hooks, optional:** `claude plugin marketplace add` and
180
192
  `claude plugin install` ([the Claude Code plugin][plugin]). Claude
@@ -182,9 +194,10 @@ it. Each line says what puts it there and what takes it away.
182
194
  (`extraKnownMarketplaces`, `enabledPlugins`), lists them in
183
195
  `~/.claude/plugins/`, and keeps a copy in
184
196
  `~/.claude/plugins/cache/sessiontools/`. The plugin brings the
185
- `session` skill and nine hook entries: SessionStart (two),
186
- PreToolUse, PostToolBatch (three), Stop, UserPromptSubmit and
187
- SessionEnd. Each is silent unless set.
197
+ `session` skill and twelve hook entries: SessionStart (three),
198
+ PreToolUse (two), PostToolBatch (three), PreCompact, Stop,
199
+ UserPromptSubmit and SessionEnd. Most are silent unless set; the sed
200
+ guard is on, and refuses only `sed -i ''` against GNU sed.
188
201
  Uninstalling the plugin takes it out of the settings and leaves the
189
202
  cached copy.
190
203
  - **Settings:** the install writes none, and none are needed.
@@ -257,6 +270,7 @@ packages keep their files in it too.
257
270
  - [The `session list` cache][cache]: one computation, many readers
258
271
  - [Operator and privileged seats][operator]: who seats ask
259
272
  - [Closing idle seats][reap]: `session reap`, a dry run by default
273
+ - [Restarting a seat][respawn]: `session respawn`, a plan unless `--go`
260
274
  - [Where tools keeps its state][state-paths]: each folder, old and new,
261
275
  the rule, and `session path`, the verb peers ask
262
276
  - [Moving tools' state][state-migrate]: `session path migrate`, one host
@@ -266,7 +280,13 @@ packages keep their files in it too.
266
280
  - [The Claude Code plugin][plugin]: the skill and the hooks
267
281
  - [The settle alert][settle]: one line past a context threshold
268
282
  - [The clock line][clock]: the time and the context on every tool batch
283
+ - [The Markdown lint][md-lint]: wide table lines and `shape`, named
284
+ after a `.md` write
285
+ - [The hidden-failure note][hidden-failure]: one line when an agent's test
286
+ run piped into `head` or `tail` exits 0 with failures shown
269
287
  - [The busy sound][busy]: a sound while a tool batch runs long
288
+ - [The sed guard][sed-guard]: refuses `sed -i ''` where sed is GNU, and
289
+ names the GNU form
270
290
  - [The session-info hooks][session-info]: the short session id as the
271
291
  session title, and the session's facts in context, both off by default
272
292
  - [The identity block][identity]: who a launched session is, in its
@@ -317,8 +337,8 @@ be set, and where each one lives ([configuration][configuration]):
317
337
  - **Upgrade every copy of sessiontools on a host together,** other
318
338
  packages' copies included: an older copy writes the old `state`
319
339
  folder, and `session path --json` shows `stranded` when one lags.
320
- - **Environment variables:** the settle alert's `SESSION_SETTLE_AT`, the
321
- clock line's `SESSION_CLOCK_WARN_AT`, the hooks' `SESSIONTOOLS_HOOK_*`
340
+ - **Environment variables:** `SESSION_SETTLE_AT`, which `session start`
341
+ copies once into the session layer, the hooks' `SESSIONTOOLS_HOOK_*`
322
342
  switches (`SESSIONTOOLS_HOOK_CLOCK=on` switches the clock line on), and
323
343
  the paths it reads, such as `SESSIONS_DIR` and `SESSION_STATE_DIR`.
324
344
  The configuration page lists each one with its default, under the part
@@ -340,6 +360,8 @@ be set, and where each one lives ([configuration][configuration]):
340
360
  - **Busy detection:** `~/.sessionkit/tools/log/busy.log`, one line each
341
361
  time voice could not be asked where its mouth is, naming why and the
342
362
  folder used instead ([the busy sound][busy]).
363
+ - **`session respawn --go`:** `~/.sessionkit/tools/log/respawn.log`, one line
364
+ for each pane it respawned, written before the kill.
343
365
  - **Each session's settings at birth:**
344
366
  `~/.session/sessions/<session-id>/log/settings.jsonl`, what `session
345
367
  start` read into that session's settings: each environment variable
@@ -364,31 +386,35 @@ Follow one as it grows with `tail -F`, for example
364
386
  holder. A session you know is running that is not listed is one
365
387
  sessiontools cannot see.
366
388
 
367
- [verbs]: docs/session.md
368
- [default-seat]: docs/session.md#the-default-seat
369
- [start]: docs/session-start-launcher.md
370
- [credentials]: docs/session-start-launcher.md#credentials-are-never-handed-down
371
- [account]: docs/account.md
372
- [cache]: docs/list-snapshot-cache.md
373
- [operator]: docs/operator-privileged-seats.md
374
- [reap]: docs/session-reap.md
375
- [completion]: docs/shell-completion.md
376
- [state-paths]: docs/tools-state-paths.md
377
- [state-migrate]: docs/tools-state-migrate.md
378
- [plugin]: docs/claude-code-plugin.md
379
- [settle]: docs/settle-alert-hook.md
380
- [clock]: docs/clock-line-hook.md
381
- [busy]: docs/busy-sound-hook.md
382
- [session-info]: docs/session-info-hook.md
383
- [identity]: docs/identity-block.md
384
- [engine]: docs/sound-engine-agent.md
385
- [status-line]: docs/status-line-script.md
386
- [pane-state]: docs/pane-state-signal.md
387
- [default-model]: docs/default-model.md
388
- [library]: docs/python-library-api.md
389
- [building]: docs/building-and-testing.md
390
- [dependencies]: docs/dependencies.md
391
- [changelog]: CHANGELOG.md
392
- [license]: LICENSE
393
- [configuration]: docs/configuration.md
394
- [settings]: docs/settings.md
389
+ [verbs]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/session.md
390
+ [default-seat]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/session.md#the-default-seat
391
+ [start]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/session-start-launcher.md
392
+ [credentials]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/session-start-launcher.md#credentials-are-never-handed-down
393
+ [account]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/account.md
394
+ [cache]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/list-snapshot-cache.md
395
+ [operator]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/operator-privileged-seats.md
396
+ [reap]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/session-reap.md
397
+ [respawn]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/session-respawn.md
398
+ [completion]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/shell-completion.md
399
+ [state-paths]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/tools-state-paths.md
400
+ [state-migrate]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/tools-state-migrate.md
401
+ [plugin]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/claude-code-plugin.md
402
+ [settle]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/settle-alert-hook.md
403
+ [clock]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/clock-line-hook.md
404
+ [md-lint]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/md-lint-hook.md
405
+ [hidden-failure]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/hidden-failure-hook.md
406
+ [busy]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/busy-sound-hook.md
407
+ [sed-guard]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/sed-guard-hook.md
408
+ [session-info]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/session-info-hook.md
409
+ [identity]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/identity-block.md
410
+ [engine]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/sound-engine-agent.md
411
+ [status-line]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/status-line-script.md
412
+ [pane-state]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/pane-state-signal.md
413
+ [default-model]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/default-model.md
414
+ [library]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/python-library-api.md
415
+ [building]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/building-and-testing.md
416
+ [dependencies]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/dependencies.md
417
+ [changelog]: https://github.com/sessionkit/sessiontools/blob/HEAD/CHANGELOG.md
418
+ [license]: https://github.com/sessionkit/sessiontools/blob/HEAD/LICENSE
419
+ [configuration]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/configuration.md
420
+ [settings]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/settings.md
@@ -14,7 +14,7 @@ uv tool install sessiontools
14
14
  ```
15
15
 
16
16
  Python 3.11 to 3.14. Optional: [the Claude Code plugin][plugin] adds
17
- the `session` skill and four hooks.
17
+ the `session` skill and five hooks.
18
18
 
19
19
  Shell completion, one line per shell ([shell completion][completion]).
20
20
  For zsh, in `~/.zshrc` after compinit:
@@ -44,12 +44,18 @@ eval "$(session completion bash)"
44
44
  - `session context --parts`: what filled it: boot, then growth by
45
45
  category.
46
46
  - `session ledger --short`: each file read and command run, by tokens.
47
+ - `session commands SEAT --since 7d --count`: the programs a seat's
48
+ sessions ran in a week, by how often.
49
+ - `session list --past SEAT`: every session recorded as SEAT on this
50
+ host, oldest first.
47
51
  - `session account status`: which Claude account this Mac is on.
48
52
  - `session account verify`: which account the API bills for this seat's
49
53
  token, from one 1-token request.
50
54
  - `session start --list-agents`: the agents it can start.
51
55
  - `session reap`: seats idle past 1h, each CLOSE or KEEP, and why. A dry
52
56
  run.
57
+ - `session respawn`: the plan to restart this seat in its pane with the
58
+ line it was started with. `--go` acts.
53
59
  - `session default-model`: the model every new `claude` starts on, and
54
60
  whether a pin keeps it there. Off until you set `model.default`.
55
61
  - `session --help`: every verb.
@@ -133,30 +139,36 @@ it. Each line says what puts it there and what takes it away.
133
139
  `log.jsonl`, the first time `session default-model enforce` changes
134
140
  settings.json.
135
141
  - `~/.sessionkit/tools/accounts/`: `session account new` and `use`.
142
+ - `~/.sessionkit/tools/services/`, beside `settings/`: one file per
143
+ service, for `sessionkit service list`. `pane-state.json`:
144
+ `session pane-state agent on|off`. `sound-engine.json`: `session
145
+ sound engine on|off`. `default-model.json`: `session default-model
146
+ agent on|off`. `web.json`: `session web declare`, which installs
147
+ nothing. `off` rewrites a file with `enabled: false` and keeps it.
136
148
  - `~/.sessionkit/tools/state/sound/bin/`: `session sound engine on`
137
149
  builds the sound engine there with `swiftc` (in
138
150
  `~/.local/share/sessiontools/sound/bin/` while that old folder is
139
151
  still there).
140
- - `~/.sessionkit/tools/log/`: the services' logs and `reap.log`
141
- (Logs, below). An older release wrote them elsewhere, and those
142
- files stay where they were.
152
+ - `~/.sessionkit/tools/log/`: the services' logs, `reap.log` and
153
+ `respawn.log` (Logs, below). An older release wrote them elsewhere,
154
+ and those files stay where they were.
143
155
  - `SESSIONS_DIR`, `SESSION_STATE_DIR` and `SESSIONTOOLS_SOUND_HOME`
144
156
  move these folders when set.
145
157
  - **Services (launchd agents):** none until you turn one on. Each `on`
146
158
  writes a plist in `~/Library/LaunchAgents/` and loads it, and it
147
159
  starts again at every login. Each `off` unloads it and deletes the
148
- plist. Both `on` and `off` also retire a job an older release set up
149
- under its old label: they unload it and delete its plist. `status`
150
- names one that is still there, and `--dry-run` lists the retire and
151
- changes nothing.
160
+ plist. For pane-state and the sound engine, `on` and `off` also
161
+ retire a job an older release set up under its old label: they unload
162
+ it and delete its plist. `status` names one that is still there, and
163
+ `--dry-run` lists the retire and changes nothing.
152
164
  - `dev.sessionkit.tools.sound-engine`: `session sound engine on`.
153
165
  `off --purge` also deletes the engine it built.
154
166
  - `dev.sessionkit.tools.pane-state`: `session pane-state agent on`.
155
167
  It sets `@session_state` and `@session_state_why` on each seat's
156
168
  tmux pane.
157
- - `dev.sessionkit.tools.default-model`: no verb writes or loads it.
158
- `session default-model unit launchd` prints it, and it is there only
159
- if you save and load it yourself
169
+ - `dev.sessionkit.tools.default-model`: `session default-model agent
170
+ on`. Opt-in: nothing turns it on for you. `session default-model
171
+ unit launchd` prints the same plist for a hand install
160
172
  ([the default model][default-model]).
161
173
  - **Plugin and hooks, optional:** `claude plugin marketplace add` and
162
174
  `claude plugin install` ([the Claude Code plugin][plugin]). Claude
@@ -164,9 +176,10 @@ it. Each line says what puts it there and what takes it away.
164
176
  (`extraKnownMarketplaces`, `enabledPlugins`), lists them in
165
177
  `~/.claude/plugins/`, and keeps a copy in
166
178
  `~/.claude/plugins/cache/sessiontools/`. The plugin brings the
167
- `session` skill and nine hook entries: SessionStart (two),
168
- PreToolUse, PostToolBatch (three), Stop, UserPromptSubmit and
169
- SessionEnd. Each is silent unless set.
179
+ `session` skill and twelve hook entries: SessionStart (three),
180
+ PreToolUse (two), PostToolBatch (three), PreCompact, Stop,
181
+ UserPromptSubmit and SessionEnd. Most are silent unless set; the sed
182
+ guard is on, and refuses only `sed -i ''` against GNU sed.
170
183
  Uninstalling the plugin takes it out of the settings and leaves the
171
184
  cached copy.
172
185
  - **Settings:** the install writes none, and none are needed.
@@ -239,6 +252,7 @@ packages keep their files in it too.
239
252
  - [The `session list` cache][cache]: one computation, many readers
240
253
  - [Operator and privileged seats][operator]: who seats ask
241
254
  - [Closing idle seats][reap]: `session reap`, a dry run by default
255
+ - [Restarting a seat][respawn]: `session respawn`, a plan unless `--go`
242
256
  - [Where tools keeps its state][state-paths]: each folder, old and new,
243
257
  the rule, and `session path`, the verb peers ask
244
258
  - [Moving tools' state][state-migrate]: `session path migrate`, one host
@@ -248,7 +262,13 @@ packages keep their files in it too.
248
262
  - [The Claude Code plugin][plugin]: the skill and the hooks
249
263
  - [The settle alert][settle]: one line past a context threshold
250
264
  - [The clock line][clock]: the time and the context on every tool batch
265
+ - [The Markdown lint][md-lint]: wide table lines and `shape`, named
266
+ after a `.md` write
267
+ - [The hidden-failure note][hidden-failure]: one line when an agent's test
268
+ run piped into `head` or `tail` exits 0 with failures shown
251
269
  - [The busy sound][busy]: a sound while a tool batch runs long
270
+ - [The sed guard][sed-guard]: refuses `sed -i ''` where sed is GNU, and
271
+ names the GNU form
252
272
  - [The session-info hooks][session-info]: the short session id as the
253
273
  session title, and the session's facts in context, both off by default
254
274
  - [The identity block][identity]: who a launched session is, in its
@@ -299,8 +319,8 @@ be set, and where each one lives ([configuration][configuration]):
299
319
  - **Upgrade every copy of sessiontools on a host together,** other
300
320
  packages' copies included: an older copy writes the old `state`
301
321
  folder, and `session path --json` shows `stranded` when one lags.
302
- - **Environment variables:** the settle alert's `SESSION_SETTLE_AT`, the
303
- clock line's `SESSION_CLOCK_WARN_AT`, the hooks' `SESSIONTOOLS_HOOK_*`
322
+ - **Environment variables:** `SESSION_SETTLE_AT`, which `session start`
323
+ copies once into the session layer, the hooks' `SESSIONTOOLS_HOOK_*`
304
324
  switches (`SESSIONTOOLS_HOOK_CLOCK=on` switches the clock line on), and
305
325
  the paths it reads, such as `SESSIONS_DIR` and `SESSION_STATE_DIR`.
306
326
  The configuration page lists each one with its default, under the part
@@ -322,6 +342,8 @@ be set, and where each one lives ([configuration][configuration]):
322
342
  - **Busy detection:** `~/.sessionkit/tools/log/busy.log`, one line each
323
343
  time voice could not be asked where its mouth is, naming why and the
324
344
  folder used instead ([the busy sound][busy]).
345
+ - **`session respawn --go`:** `~/.sessionkit/tools/log/respawn.log`, one line
346
+ for each pane it respawned, written before the kill.
325
347
  - **Each session's settings at birth:**
326
348
  `~/.session/sessions/<session-id>/log/settings.jsonl`, what `session
327
349
  start` read into that session's settings: each environment variable
@@ -346,31 +368,35 @@ Follow one as it grows with `tail -F`, for example
346
368
  holder. A session you know is running that is not listed is one
347
369
  sessiontools cannot see.
348
370
 
349
- [verbs]: docs/session.md
350
- [default-seat]: docs/session.md#the-default-seat
351
- [start]: docs/session-start-launcher.md
352
- [credentials]: docs/session-start-launcher.md#credentials-are-never-handed-down
353
- [account]: docs/account.md
354
- [cache]: docs/list-snapshot-cache.md
355
- [operator]: docs/operator-privileged-seats.md
356
- [reap]: docs/session-reap.md
357
- [completion]: docs/shell-completion.md
358
- [state-paths]: docs/tools-state-paths.md
359
- [state-migrate]: docs/tools-state-migrate.md
360
- [plugin]: docs/claude-code-plugin.md
361
- [settle]: docs/settle-alert-hook.md
362
- [clock]: docs/clock-line-hook.md
363
- [busy]: docs/busy-sound-hook.md
364
- [session-info]: docs/session-info-hook.md
365
- [identity]: docs/identity-block.md
366
- [engine]: docs/sound-engine-agent.md
367
- [status-line]: docs/status-line-script.md
368
- [pane-state]: docs/pane-state-signal.md
369
- [default-model]: docs/default-model.md
370
- [library]: docs/python-library-api.md
371
- [building]: docs/building-and-testing.md
372
- [dependencies]: docs/dependencies.md
373
- [changelog]: CHANGELOG.md
374
- [license]: LICENSE
375
- [configuration]: docs/configuration.md
376
- [settings]: docs/settings.md
371
+ [verbs]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/session.md
372
+ [default-seat]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/session.md#the-default-seat
373
+ [start]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/session-start-launcher.md
374
+ [credentials]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/session-start-launcher.md#credentials-are-never-handed-down
375
+ [account]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/account.md
376
+ [cache]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/list-snapshot-cache.md
377
+ [operator]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/operator-privileged-seats.md
378
+ [reap]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/session-reap.md
379
+ [respawn]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/session-respawn.md
380
+ [completion]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/shell-completion.md
381
+ [state-paths]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/tools-state-paths.md
382
+ [state-migrate]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/tools-state-migrate.md
383
+ [plugin]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/claude-code-plugin.md
384
+ [settle]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/settle-alert-hook.md
385
+ [clock]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/clock-line-hook.md
386
+ [md-lint]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/md-lint-hook.md
387
+ [hidden-failure]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/hidden-failure-hook.md
388
+ [busy]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/busy-sound-hook.md
389
+ [sed-guard]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/sed-guard-hook.md
390
+ [session-info]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/session-info-hook.md
391
+ [identity]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/identity-block.md
392
+ [engine]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/sound-engine-agent.md
393
+ [status-line]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/status-line-script.md
394
+ [pane-state]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/pane-state-signal.md
395
+ [default-model]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/default-model.md
396
+ [library]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/python-library-api.md
397
+ [building]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/building-and-testing.md
398
+ [dependencies]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/dependencies.md
399
+ [changelog]: https://github.com/sessionkit/sessiontools/blob/HEAD/CHANGELOG.md
400
+ [license]: https://github.com/sessionkit/sessiontools/blob/HEAD/LICENSE
401
+ [configuration]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/configuration.md
402
+ [settings]: https://github.com/sessionkit/sessiontools/blob/HEAD/docs/settings.md
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sessiontools"
3
- version = "0.3.3"
3
+ version = "0.4.0"
4
4
  description = "The harness layer: sessions, status and idle, one driver per harness - a library first"
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-or-later"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sessiontools"
3
- version = "0.3.3"
3
+ version = "0.4.0"
4
4
  description = "The harness layer: sessions, status and idle, one driver per harness - a library first"
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-or-later"
@@ -0,0 +1,32 @@
1
+ """Reading a vendor's flags back off an argv, by the vendor's own rule.
2
+
3
+ claude parses a repeated scalar flag by keeping the LAST one, in either
4
+ spelling: `--flag value` or `--flag=value`. Two readers need that rule:
5
+ the settle hook (`compact_point`, reading a recorded launch line) and
6
+ `session start` (`start.user_memory`, reading the argv it is about to
7
+ render). One definition, here, so the two never disagree.
8
+
9
+ Standard library only, and nothing imported at all: the settle hook
10
+ imports `compact_point`, and so this, on every batch, maybe under a bare
11
+ python3 (it must not pull in `sessiontools.start` or PyYAML).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+
17
+ def last_value(argv: list[str], flag: str) -> str | None:
18
+ """The value of the LAST `FLAG value` or `FLAG=value` in ARGV, or None.
19
+
20
+ The `=` is part of the prefix, so a longer flag that merely starts with
21
+ FLAG's spelling (`--settings-file` for `--settings`) is not FLAG. A
22
+ trailing FLAG with no value after it sets nothing."""
23
+ found = None
24
+ for index, token in enumerate(argv):
25
+ if token == flag and index + 1 < len(argv):
26
+ found = argv[index + 1]
27
+ elif token.startswith(flag + "="):
28
+ found = token[len(flag) + 1:]
29
+ return found
30
+
31
+
32
+ __all__ = ["last_value"]
@@ -1,8 +1,11 @@
1
1
  """`band`: how close a window is to its seat's settle threshold, as data
2
2
  ("thresholds belong to session, the sidebar only paints").
3
3
 
4
- - OVER: at or past the settle threshold (`sessiontools.settle.threshold`,
5
- read-only: this module never writes or re-reads its variable itself).
4
+ - OVER: at or past the settle threshold: the soft line's mark,
5
+ `settle.soft.at`, read through the settings cascade by
6
+ `settle_settings.read` (read-only: no OFF stamp is written here), so
7
+ `session context` and the sidebar agree with the alert. No soft line
8
+ on in any layer: no threshold.
6
9
  - NEAR: within NEAR percent under it: `used >= threshold * (1 - NEAR/100)`.
7
10
  - OK: below that.
8
11
  - None (JSON null): no threshold is set, or `used` is unknown.
@@ -10,15 +13,16 @@
10
13
  THE NEAR CUT IS A SETTING, read through the settings cascade
11
14
  (`sessiontools.settings`: template, host, agent, seat and session layers),
12
15
  under ONE key, NEAR_KEY, of the `session` tool. It is never read from the
13
- environment: the environment is not a layer (settle's own
14
- `SESSION_SETTLE_AT` read is the pattern a later version retires). Unset,
16
+ environment: the environment is not a layer, for the threshold either
17
+ (`SESSION_SETTLE_AT` reaches it only as `session start`'s birth copy into
18
+ the session layer, `settle.soft.at`). Unset,
15
19
  unreadable or not a number from 0 to 100 is the default, DEFAULT_NEAR_PCT
16
20
  (any other default is a one-line change to it).
17
21
 
18
22
  <sessions>/<sid>/settings/session.json {"context": {"near_pct": 10}}
19
23
 
20
- No schema is shipped for the key: the cascade reads a key without one, and
21
- nothing installs a `session` schema today.
24
+ The `session` schema `session start` installs (`settle_settings`) names
25
+ the settle keys only, not this one: the cascade reads a key without one.
22
26
 
23
27
  It sits at the top level, not in `context_parts`: importing any
24
28
  `context_parts` submodule runs that package's `__init__`, which imports the
@@ -64,16 +68,25 @@ def _lookup(merged: Mapping, dotted: str) -> Any:
64
68
  return node
65
69
 
66
70
 
71
+ def _sid(sid: str | None, env: Mapping[str, str] | None) -> str | None:
72
+ """SID as the full id `settings.resolve_sid` makes of a short prefix,
73
+ as `session settings` does: `session_dir` is a literal join, and a
74
+ prefix would otherwise read no session, agent or seat layer and fall
75
+ back without a word. None stays None (template and host layers only).
76
+ An AMBIGUOUS prefix is refused there (SettingsError) and raised here."""
77
+ from sessiontools import settings
78
+ if not sid:
79
+ return None
80
+ sid, _note = settings.resolve_sid(sid, env)
81
+ return sid
82
+
83
+
67
84
  def setting(sid: str | None = None, env: Mapping[str, str] | None = None) -> Any:
68
85
  """NEAR_KEY's raw merged value for session SID (None: template and host
69
86
  layers only), or None when no layer sets it or a layer cannot be read.
70
87
 
71
- SID may be a short prefix: `settings.resolve_sid` turns it into the full
72
- id, as `session settings` does, since `session_dir` is a literal join
73
- and a prefix would otherwise read no session, agent or seat layer and
74
- fall back to the default without a word. An AMBIGUOUS prefix is refused
75
- there (SettingsError) and reads as None here, so the default: never a
76
- guess at which session was meant.
88
+ SID may be a short prefix (`_sid`). An AMBIGUOUS prefix reads as None
89
+ here, so the default: never a guess at which session was meant.
77
90
 
78
91
  `sources` + `merge`, as `busy.values` reads `sounds`: `resolve` would
79
92
  also check an installed schema, which imports jsonschema. The agent
@@ -82,8 +95,8 @@ def setting(sid: str | None = None, env: Mapping[str, str] | None = None) -> Any
82
95
  from sessiontools import settings
83
96
  try:
84
97
  agent = seat = None
98
+ sid = _sid(sid, env)
85
99
  if sid:
86
- sid, _note = settings.resolve_sid(sid, env)
87
100
  agent, seat = settings.identity(sid, env)
88
101
  try:
89
102
  srcs = settings.sources(NEAR_TOOL, sid, agent, seat, env)
@@ -115,16 +128,25 @@ def band(used: float | None, threshold: float | None,
115
128
  return OK
116
129
 
117
130
 
118
- def threshold(env: Mapping[str, str] | None = None) -> int | None:
119
- """The seat's settle threshold in tokens, as settle reads it (read-only)."""
120
- import os
121
-
122
- from sessiontools import settle
123
- return settle.threshold(os.environ if env is None else env)
131
+ def threshold(sid: str | None = None, env: Mapping[str, str] | None = None) -> int | None:
132
+ """Session SID's settle threshold in tokens: `settle.soft.at`, as the
133
+ alert reads it (`settle_settings.read`, every layer, live), or None
134
+ when no layer turns the soft line on, a layer cannot be read, or SID
135
+ is an ambiguous prefix (never a guess). SID may be a short prefix
136
+ (`_sid`); None reads the template and host layers only. Read-only:
137
+ `read`, not `resolve`, so the OFF stamp is the hook's alone."""
138
+ from sessiontools import settings, settle_settings
139
+ try:
140
+ sid = _sid(sid, env)
141
+ except (settings.SettingsError, OSError, ValueError):
142
+ return None
143
+ found = settle_settings.read(sid, env)
144
+ return None if found is None or found.soft is None else found.soft.at
124
145
 
125
146
 
126
147
  def band_for(used: float | None, sid: str | None = None,
127
148
  env: Mapping[str, str] | None = None) -> str | None:
128
149
  """The band of USED tokens for session SID (a full id or a unique
129
- prefix): settle's threshold, and the near cut from the settings cascade."""
130
- return band(used, threshold(env), near_pct(sid, env))
150
+ prefix): the soft line's mark and the near cut, both from the settings
151
+ cascade."""
152
+ return band(used, threshold(sid, env), near_pct(sid, env))