sessiontools 0.3.2__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 (126) hide show
  1. {sessiontools-0.3.2 → sessiontools-0.4.0}/PKG-INFO +83 -50
  2. {sessiontools-0.3.2 → sessiontools-0.4.0}/README.md +80 -49
  3. sessiontools-0.4.0/pyproject.toml +42 -0
  4. sessiontools-0.3.2/pyproject.toml → sessiontools-0.4.0/pyproject.toml.orig +11 -1
  5. sessiontools-0.4.0/src/sessiontools/argv_flags.py +32 -0
  6. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/band.py +43 -21
  7. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/busy.py +283 -22
  8. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/cli.py +80 -14
  9. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/clockline.py +12 -60
  10. sessiontools-0.4.0/src/sessiontools/commands/__init__.py +110 -0
  11. sessiontools-0.4.0/src/sessiontools/commands/cli.py +127 -0
  12. sessiontools-0.4.0/src/sessiontools/commands/shell.py +691 -0
  13. sessiontools-0.4.0/src/sessiontools/compact_point.py +400 -0
  14. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/completion.py +4 -0
  15. sessiontools-0.4.0/src/sessiontools/default_model_agent.py +363 -0
  16. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/default_model_cli.py +32 -13
  17. sessiontools-0.4.0/src/sessiontools/get.py +150 -0
  18. sessiontools-0.4.0/src/sessiontools/hidden_failure.py +610 -0
  19. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/ledger/transcript.py +7 -3
  20. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/listing.py +7 -1
  21. sessiontools-0.4.0/src/sessiontools/md_lint.py +262 -0
  22. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/operator.py +66 -13
  23. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/pane_state.py +9 -1
  24. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/pane_state_agent.py +43 -9
  25. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/park.py +67 -15
  26. sessiontools-0.4.0/src/sessiontools/past.py +158 -0
  27. sessiontools-0.4.0/src/sessiontools/paths.py +322 -0
  28. sessiontools-0.4.0/src/sessiontools/respawn.py +645 -0
  29. sessiontools-0.4.0/src/sessiontools/sed_guard.py +294 -0
  30. sessiontools-0.4.0/src/sessiontools/service_file.py +214 -0
  31. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/settings.py +305 -22
  32. sessiontools-0.4.0/src/sessiontools/settle.py +357 -0
  33. sessiontools-0.4.0/src/sessiontools/settle_guard.py +181 -0
  34. sessiontools-0.4.0/src/sessiontools/settle_settings.py +542 -0
  35. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/sound/service.py +58 -13
  36. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/__init__.py +167 -51
  37. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/agentfile.py +26 -14
  38. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/agents.py +35 -9
  39. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/args.py +12 -1
  40. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/boot_identity.py +46 -92
  41. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/claude_settings.py +56 -23
  42. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/context.py +287 -35
  43. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/default_seat.py +2 -45
  44. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/environment.py +5 -3
  45. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/errors.py +25 -1
  46. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/identity.py +2 -117
  47. sessiontools-0.4.0/src/sessiontools/start/pane.py +276 -0
  48. sessiontools-0.4.0/src/sessiontools/start/placement.py +379 -0
  49. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/plan.py +251 -136
  50. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/policy.py +0 -2
  51. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/record.py +212 -19
  52. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/run.py +140 -25
  53. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/secrets.py +5 -3
  54. sessiontools-0.4.0/src/sessiontools/start/user_memory.py +245 -0
  55. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/vendor.py +32 -33
  56. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/statusline/statusline.sh +6 -1
  57. sessiontools-0.4.0/src/sessiontools/web/__init__.py +23 -0
  58. sessiontools-0.4.0/src/sessiontools/web/auth.py +756 -0
  59. sessiontools-0.4.0/src/sessiontools/web/bind.py +69 -0
  60. sessiontools-0.4.0/src/sessiontools/web/cli.py +255 -0
  61. sessiontools-0.4.0/src/sessiontools/web/passkey.js +94 -0
  62. sessiontools-0.4.0/src/sessiontools/web/passkey.py +187 -0
  63. sessiontools-0.4.0/src/sessiontools/web/proxy.py +189 -0
  64. sessiontools-0.4.0/src/sessiontools/web/rooms.py +256 -0
  65. sessiontools-0.4.0/src/sessiontools/web/server.py +600 -0
  66. sessiontools-0.4.0/src/sessiontools/web/settings_page.py +351 -0
  67. sessiontools-0.4.0/src/sessiontools/web/supervisor.py +216 -0
  68. sessiontools-0.4.0/src/sessiontools/web/unixhttp.py +197 -0
  69. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/window.py +121 -16
  70. sessiontools-0.3.2/src/sessiontools/get.py +0 -91
  71. sessiontools-0.3.2/src/sessiontools/paths.py +0 -165
  72. sessiontools-0.3.2/src/sessiontools/settle.py +0 -230
  73. sessiontools-0.3.2/src/sessiontools/start/placement.py +0 -272
  74. {sessiontools-0.3.2 → sessiontools-0.4.0}/LICENSE +0 -0
  75. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/__init__.py +0 -0
  76. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/__init__.py +0 -0
  77. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/cli.py +0 -0
  78. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/keychain.py +0 -0
  79. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/leakscan.py +0 -0
  80. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/limits.py +0 -0
  81. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/movehome.py +0 -0
  82. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/new.py +0 -0
  83. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/profile.py +0 -0
  84. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/registry.py +0 -0
  85. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/setuptoken.py +0 -0
  86. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/status.py +0 -0
  87. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/tokenfile.py +0 -0
  88. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/tokenstore.py +0 -0
  89. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/use.py +0 -0
  90. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/account/verify.py +0 -0
  91. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/claude_code.py +0 -0
  92. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/context_parts/__init__.py +0 -0
  93. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/context_parts/boot.py +0 -0
  94. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/context_parts/breakdown.py +0 -0
  95. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/context_parts/hooks.py +0 -0
  96. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/context_parts/nnls.py +0 -0
  97. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/context_parts/render.py +0 -0
  98. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/context_parts/slash_context.py +0 -0
  99. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/context_parts/tools.py +0 -0
  100. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/context_parts/transcript.py +0 -0
  101. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/default_model.py +0 -0
  102. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/default_model_watch.py +0 -0
  103. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/identity_block.py +0 -0
  104. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/ledger/__init__.py +0 -0
  105. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/ledger/_tiktoken_child.py +0 -0
  106. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/ledger/count.py +0 -0
  107. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/ledger/render.py +0 -0
  108. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/migrate.py +0 -0
  109. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/procs.py +0 -0
  110. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/py.typed +0 -0
  111. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/reap.py +0 -0
  112. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/send.py +0 -0
  113. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/session_info.py +0 -0
  114. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/snapshot.py +0 -0
  115. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/sound/__init__.py +0 -0
  116. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/sound/engine.swift +0 -0
  117. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/sound/event.swift +0 -0
  118. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/default_seat.md +0 -0
  119. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/holders.py +0 -0
  120. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/homefolder.py +0 -0
  121. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/start/resume.py +0 -0
  122. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/statusline/__init__.py +0 -0
  123. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/statusline/sample.json +0 -0
  124. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/tmux.py +0 -0
  125. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/turn.py +0 -0
  126. {sessiontools-0.3.2 → sessiontools-0.4.0}/src/sessiontools/verbs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sessiontools
3
- Version: 0.3.2
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>
@@ -9,9 +9,11 @@ License-File: LICENSE
9
9
  Classifier: Development Status :: 3 - Alpha
10
10
  Requires-Dist: pyyaml>=6
11
11
  Requires-Dist: jsonschema>=4
12
+ Requires-Dist: cryptography>=42 ; extra == 'passkey'
12
13
  Requires-Python: >=3.11, <3.15
13
14
  Project-URL: Homepage, https://github.com/sessionkit/sessiontools
14
15
  Project-URL: Repository, https://github.com/sessionkit/sessiontools
16
+ Provides-Extra: passkey
15
17
  Description-Content-Type: text/markdown
16
18
 
17
19
  # sessiontools
@@ -30,7 +32,7 @@ uv tool install sessiontools
30
32
  ```
31
33
 
32
34
  Python 3.11 to 3.14. Optional: [the Claude Code plugin][plugin] adds
33
- the `session` skill and four hooks.
35
+ the `session` skill and five hooks.
34
36
 
35
37
  Shell completion, one line per shell ([shell completion][completion]).
36
38
  For zsh, in `~/.zshrc` after compinit:
@@ -60,12 +62,18 @@ eval "$(session completion bash)"
60
62
  - `session context --parts`: what filled it: boot, then growth by
61
63
  category.
62
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.
63
69
  - `session account status`: which Claude account this Mac is on.
64
70
  - `session account verify`: which account the API bills for this seat's
65
71
  token, from one 1-token request.
66
72
  - `session start --list-agents`: the agents it can start.
67
73
  - `session reap`: seats idle past 1h, each CLOSE or KEEP, and why. A dry
68
74
  run.
75
+ - `session respawn`: the plan to restart this seat in its pane with the
76
+ line it was started with. `--go` acts.
69
77
  - `session default-model`: the model every new `claude` starts on, and
70
78
  whether a pin keeps it there. Off until you set `model.default`.
71
79
  - `session --help`: every verb.
@@ -134,8 +142,8 @@ it. Each line says what puts it there and what takes it away.
134
142
  - `~/.sessionkit/tools/state/list/`: the list cache, `snapshot.json`
135
143
  and `list.lock`, at the first `session list`. The state folders here
136
144
  and below are on a fresh host; a host that still has
137
- `~/.session/state/` keeps using it until it is moved.
138
- - `~/.session/sessions/<session-id>/`: one for each `session start`,
145
+ `~/.session/` keeps using its folders there until they are moved.
146
+ - `~/.sessionkit/tools/state/sessions/<session-id>/`: one for each `session start`,
139
147
  holding `launch/`, `log/settings.jsonl` and `session-id`. It also
140
148
  gets `settings/<tool>.d/50-env.json`, one for each tool whose
141
149
  settings name a variable that is set when the session starts. Each
@@ -149,30 +157,36 @@ it. Each line says what puts it there and what takes it away.
149
157
  `log.jsonl`, the first time `session default-model enforce` changes
150
158
  settings.json.
151
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.
152
166
  - `~/.sessionkit/tools/state/sound/bin/`: `session sound engine on`
153
167
  builds the sound engine there with `swiftc` (in
154
168
  `~/.local/share/sessiontools/sound/bin/` while that old folder is
155
169
  still there).
156
- - `~/.sessionkit/tools/log/`: the services' logs and `reap.log`
157
- (Logs, below). An older release wrote them elsewhere, and those
158
- 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.
159
173
  - `SESSIONS_DIR`, `SESSION_STATE_DIR` and `SESSIONTOOLS_SOUND_HOME`
160
174
  move these folders when set.
161
175
  - **Services (launchd agents):** none until you turn one on. Each `on`
162
176
  writes a plist in `~/Library/LaunchAgents/` and loads it, and it
163
177
  starts again at every login. Each `off` unloads it and deletes the
164
- plist. Both `on` and `off` also retire a job an older release set up
165
- under its old label: they unload it and delete its plist. `status`
166
- names one that is still there, and `--dry-run` lists the retire and
167
- 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.
168
182
  - `dev.sessionkit.tools.sound-engine`: `session sound engine on`.
169
183
  `off --purge` also deletes the engine it built.
170
184
  - `dev.sessionkit.tools.pane-state`: `session pane-state agent on`.
171
185
  It sets `@session_state` and `@session_state_why` on each seat's
172
186
  tmux pane.
173
- - `dev.sessionkit.tools.default-model`: no verb writes or loads it.
174
- `session default-model unit launchd` prints it, and it is there only
175
- 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
176
190
  ([the default model][default-model]).
177
191
  - **Plugin and hooks, optional:** `claude plugin marketplace add` and
178
192
  `claude plugin install` ([the Claude Code plugin][plugin]). Claude
@@ -180,9 +194,10 @@ it. Each line says what puts it there and what takes it away.
180
194
  (`extraKnownMarketplaces`, `enabledPlugins`), lists them in
181
195
  `~/.claude/plugins/`, and keeps a copy in
182
196
  `~/.claude/plugins/cache/sessiontools/`. The plugin brings the
183
- `session` skill and nine hook entries: SessionStart (two),
184
- PreToolUse, PostToolBatch (three), Stop, UserPromptSubmit and
185
- 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.
186
201
  Uninstalling the plugin takes it out of the settings and leaves the
187
202
  cached copy.
188
203
  - **Settings:** the install writes none, and none are needed.
@@ -255,6 +270,7 @@ packages keep their files in it too.
255
270
  - [The `session list` cache][cache]: one computation, many readers
256
271
  - [Operator and privileged seats][operator]: who seats ask
257
272
  - [Closing idle seats][reap]: `session reap`, a dry run by default
273
+ - [Restarting a seat][respawn]: `session respawn`, a plan unless `--go`
258
274
  - [Where tools keeps its state][state-paths]: each folder, old and new,
259
275
  the rule, and `session path`, the verb peers ask
260
276
  - [Moving tools' state][state-migrate]: `session path migrate`, one host
@@ -264,7 +280,13 @@ packages keep their files in it too.
264
280
  - [The Claude Code plugin][plugin]: the skill and the hooks
265
281
  - [The settle alert][settle]: one line past a context threshold
266
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
267
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
268
290
  - [The session-info hooks][session-info]: the short session id as the
269
291
  session title, and the session's facts in context, both off by default
270
292
  - [The identity block][identity]: who a launched session is, in its
@@ -296,8 +318,9 @@ be set, and where each one lives ([configuration][configuration]):
296
318
  which layers it is read from: [settings][settings].
297
319
  - **Folders of its own:** `~/.sessionkit/tools/accounts/` (`session
298
320
  account`), and its state, each leaf with an old and a new folder. A
299
- fresh host starts `state` and `sound` in the new folder; an existing
300
- one stays put until it is moved, and a moved one is followed
321
+ fresh host, one with no `~/.session/`, starts every leaf in the new
322
+ folder; an existing one stays put until it is moved, and a moved one
323
+ is followed
301
324
  ([where tools keeps its state][state-paths]):
302
325
  - `state`: `~/.session/state/`, new `~/.sessionkit/tools/state/`.
303
326
  - `sessions`, `seats`, `agent` and `backups`: `~/.session/<leaf>/`, new
@@ -306,15 +329,16 @@ be set, and where each one lives ([configuration][configuration]):
306
329
  `~/.sessionkit/tools/state/sound/`.
307
330
  - **The rule, per leaf:** its variable when set; else the old folder
308
331
  while it is real; else the new one when it exists; else the new one
309
- for `state` and `sound`, the old one for the rest.
332
+ for `state` and `sound`, and for the rest the new one on a fresh host
333
+ and the old one on a host that has `~/.session/`.
310
334
  - **To see what is used:** `session path LEAF`, or `session path --json`.
311
335
  - **To move `state` and `sound`:** `session path migrate --dry-run`,
312
336
  then a real run with that host's go ([moving tools' state][state-migrate]).
313
337
  - **Upgrade every copy of sessiontools on a host together,** other
314
338
  packages' copies included: an older copy writes the old `state`
315
339
  folder, and `session path --json` shows `stranded` when one lags.
316
- - **Environment variables:** the settle alert's `SESSION_SETTLE_AT`, the
317
- 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_*`
318
342
  switches (`SESSIONTOOLS_HOOK_CLOCK=on` switches the clock line on), and
319
343
  the paths it reads, such as `SESSIONS_DIR` and `SESSION_STATE_DIR`.
320
344
  The configuration page lists each one with its default, under the part
@@ -333,6 +357,11 @@ be set, and where each one lives ([configuration][configuration]):
333
357
  the launchd agent `session default-model unit launchd` prints.
334
358
  - **`session reap --yes`:** `~/.sessionkit/tools/log/reap.log`, one line
335
359
  for each seat it closed or failed to close.
360
+ - **Busy detection:** `~/.sessionkit/tools/log/busy.log`, one line each
361
+ time voice could not be asked where its mouth is, naming why and the
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.
336
365
  - **Each session's settings at birth:**
337
366
  `~/.session/sessions/<session-id>/log/settings.jsonl`, what `session
338
367
  start` read into that session's settings: each environment variable
@@ -357,31 +386,35 @@ Follow one as it grows with `tail -F`, for example
357
386
  holder. A session you know is running that is not listed is one
358
387
  sessiontools cannot see.
359
388
 
360
- [verbs]: docs/session.md
361
- [default-seat]: docs/session.md#the-default-seat
362
- [start]: docs/session-start-launcher.md
363
- [credentials]: docs/session-start-launcher.md#credentials-are-never-handed-down
364
- [account]: docs/account.md
365
- [cache]: docs/list-snapshot-cache.md
366
- [operator]: docs/operator-privileged-seats.md
367
- [reap]: docs/session-reap.md
368
- [completion]: docs/shell-completion.md
369
- [state-paths]: docs/tools-state-paths.md
370
- [state-migrate]: docs/tools-state-migrate.md
371
- [plugin]: docs/claude-code-plugin.md
372
- [settle]: docs/settle-alert-hook.md
373
- [clock]: docs/clock-line-hook.md
374
- [busy]: docs/busy-sound-hook.md
375
- [session-info]: docs/session-info-hook.md
376
- [identity]: docs/identity-block.md
377
- [engine]: docs/sound-engine-agent.md
378
- [status-line]: docs/status-line-script.md
379
- [pane-state]: docs/pane-state-signal.md
380
- [default-model]: docs/default-model.md
381
- [library]: docs/python-library-api.md
382
- [building]: docs/building-and-testing.md
383
- [dependencies]: docs/dependencies.md
384
- [changelog]: CHANGELOG.md
385
- [license]: LICENSE
386
- [configuration]: docs/configuration.md
387
- [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.
@@ -118,8 +124,8 @@ it. Each line says what puts it there and what takes it away.
118
124
  - `~/.sessionkit/tools/state/list/`: the list cache, `snapshot.json`
119
125
  and `list.lock`, at the first `session list`. The state folders here
120
126
  and below are on a fresh host; a host that still has
121
- `~/.session/state/` keeps using it until it is moved.
122
- - `~/.session/sessions/<session-id>/`: one for each `session start`,
127
+ `~/.session/` keeps using its folders there until they are moved.
128
+ - `~/.sessionkit/tools/state/sessions/<session-id>/`: one for each `session start`,
123
129
  holding `launch/`, `log/settings.jsonl` and `session-id`. It also
124
130
  gets `settings/<tool>.d/50-env.json`, one for each tool whose
125
131
  settings name a variable that is set when the session starts. Each
@@ -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
@@ -280,8 +300,9 @@ be set, and where each one lives ([configuration][configuration]):
280
300
  which layers it is read from: [settings][settings].
281
301
  - **Folders of its own:** `~/.sessionkit/tools/accounts/` (`session
282
302
  account`), and its state, each leaf with an old and a new folder. A
283
- fresh host starts `state` and `sound` in the new folder; an existing
284
- one stays put until it is moved, and a moved one is followed
303
+ fresh host, one with no `~/.session/`, starts every leaf in the new
304
+ folder; an existing one stays put until it is moved, and a moved one
305
+ is followed
285
306
  ([where tools keeps its state][state-paths]):
286
307
  - `state`: `~/.session/state/`, new `~/.sessionkit/tools/state/`.
287
308
  - `sessions`, `seats`, `agent` and `backups`: `~/.session/<leaf>/`, new
@@ -290,15 +311,16 @@ be set, and where each one lives ([configuration][configuration]):
290
311
  `~/.sessionkit/tools/state/sound/`.
291
312
  - **The rule, per leaf:** its variable when set; else the old folder
292
313
  while it is real; else the new one when it exists; else the new one
293
- for `state` and `sound`, the old one for the rest.
314
+ for `state` and `sound`, and for the rest the new one on a fresh host
315
+ and the old one on a host that has `~/.session/`.
294
316
  - **To see what is used:** `session path LEAF`, or `session path --json`.
295
317
  - **To move `state` and `sound`:** `session path migrate --dry-run`,
296
318
  then a real run with that host's go ([moving tools' state][state-migrate]).
297
319
  - **Upgrade every copy of sessiontools on a host together,** other
298
320
  packages' copies included: an older copy writes the old `state`
299
321
  folder, and `session path --json` shows `stranded` when one lags.
300
- - **Environment variables:** the settle alert's `SESSION_SETTLE_AT`, the
301
- 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_*`
302
324
  switches (`SESSIONTOOLS_HOOK_CLOCK=on` switches the clock line on), and
303
325
  the paths it reads, such as `SESSIONS_DIR` and `SESSION_STATE_DIR`.
304
326
  The configuration page lists each one with its default, under the part
@@ -317,6 +339,11 @@ be set, and where each one lives ([configuration][configuration]):
317
339
  the launchd agent `session default-model unit launchd` prints.
318
340
  - **`session reap --yes`:** `~/.sessionkit/tools/log/reap.log`, one line
319
341
  for each seat it closed or failed to close.
342
+ - **Busy detection:** `~/.sessionkit/tools/log/busy.log`, one line each
343
+ time voice could not be asked where its mouth is, naming why and the
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.
320
347
  - **Each session's settings at birth:**
321
348
  `~/.session/sessions/<session-id>/log/settings.jsonl`, what `session
322
349
  start` read into that session's settings: each environment variable
@@ -341,31 +368,35 @@ Follow one as it grows with `tail -F`, for example
341
368
  holder. A session you know is running that is not listed is one
342
369
  sessiontools cannot see.
343
370
 
344
- [verbs]: docs/session.md
345
- [default-seat]: docs/session.md#the-default-seat
346
- [start]: docs/session-start-launcher.md
347
- [credentials]: docs/session-start-launcher.md#credentials-are-never-handed-down
348
- [account]: docs/account.md
349
- [cache]: docs/list-snapshot-cache.md
350
- [operator]: docs/operator-privileged-seats.md
351
- [reap]: docs/session-reap.md
352
- [completion]: docs/shell-completion.md
353
- [state-paths]: docs/tools-state-paths.md
354
- [state-migrate]: docs/tools-state-migrate.md
355
- [plugin]: docs/claude-code-plugin.md
356
- [settle]: docs/settle-alert-hook.md
357
- [clock]: docs/clock-line-hook.md
358
- [busy]: docs/busy-sound-hook.md
359
- [session-info]: docs/session-info-hook.md
360
- [identity]: docs/identity-block.md
361
- [engine]: docs/sound-engine-agent.md
362
- [status-line]: docs/status-line-script.md
363
- [pane-state]: docs/pane-state-signal.md
364
- [default-model]: docs/default-model.md
365
- [library]: docs/python-library-api.md
366
- [building]: docs/building-and-testing.md
367
- [dependencies]: docs/dependencies.md
368
- [changelog]: CHANGELOG.md
369
- [license]: LICENSE
370
- [configuration]: docs/configuration.md
371
- [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
@@ -0,0 +1,42 @@
1
+ [project]
2
+ name = "sessiontools"
3
+ version = "0.4.0"
4
+ description = "The harness layer: sessions, status and idle, one driver per harness - a library first"
5
+ readme = "README.md"
6
+ license = "AGPL-3.0-or-later"
7
+ license-files = ["LICENSE"]
8
+ classifiers = ["Development Status :: 3 - Alpha"]
9
+ requires-python = ">=3.11,<3.15"
10
+ dependencies = [
11
+ "PyYAML>=6",
12
+ "jsonschema>=4",
13
+ ]
14
+
15
+ [[project.authors]]
16
+ name = "Mike Bailey"
17
+ email = "mike@sessionkit.ai"
18
+
19
+ [project.optional-dependencies]
20
+ passkey = ["cryptography>=42"]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/sessionkit/sessiontools"
24
+ Repository = "https://github.com/sessionkit/sessiontools"
25
+
26
+ [project.scripts]
27
+ session = "sessiontools.cli:main"
28
+
29
+ [build-system]
30
+ requires = ["uv_build>=0.11.9,<0.12.0"]
31
+ build-backend = "uv_build"
32
+
33
+ [dependency-groups]
34
+ dev = [
35
+ "pytest>=8",
36
+ "ruff>=0.6",
37
+ "cryptography>=42",
38
+ ]
39
+
40
+ [tool.pytest.ini_options]
41
+ testpaths = ["tests"]
42
+ addopts = "--strict-markers"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sessiontools"
3
- version = "0.3.2"
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"
@@ -34,6 +34,14 @@ dependencies = ["PyYAML>=6", "jsonschema>=4"]
34
34
  # aside: sessiontools provides it, same verbs, ported as package code.
35
35
  # Exactly one package owns `session` on the PATH; from here it is this one.
36
36
  # argparse and the library; PyYAML for `session start` alone (above).
37
+ # The front door's passkey (`session web serve`, PROPOSED door-passkey):
38
+ # WebAuthn verifies ECDSA and RSA signatures, which the standard library
39
+ # cannot, so `cryptography` comes only with this extra and only
40
+ # `sessiontools.web.passkey` imports it. Without it the front door is the
41
+ # pairing code alone. Rule 3: an extra is the maintainer's call too.
42
+ [project.optional-dependencies]
43
+ passkey = ["cryptography>=42"]
44
+
37
45
  [project.urls]
38
46
  Homepage = "https://github.com/sessionkit/sessiontools"
39
47
  Repository = "https://github.com/sessionkit/sessiontools"
@@ -49,6 +57,8 @@ build-backend = "uv_build"
49
57
  dev = [
50
58
  "pytest>=8",
51
59
  "ruff>=0.6",
60
+ # the passkey extra's tests run its ceremonies with a software authenticator
61
+ "cryptography>=42",
52
62
  ]
53
63
 
54
64
  [tool.pytest.ini_options]
@@ -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"]