pocketshell 0.5.4__tar.gz → 0.5.6__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.
- pocketshell-0.5.6/LICENSE +21 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/PKG-INFO +51 -179
- {pocketshell-0.5.4 → pocketshell-0.5.6}/README.md +46 -173
- {pocketshell-0.5.4 → pocketshell-0.5.6}/pyproject.toml +11 -23
- {pocketshell-0.5.4 → pocketshell-0.5.6}/scheduler/pocketshell-usage-capture.service +1 -1
- {pocketshell-0.5.4 → pocketshell-0.5.6}/scheduler/pocketshell-usage-capture.timer +1 -1
- pocketshell-0.5.6/scripts/check-cgroup-cap-proof.sh +191 -0
- pocketshell-0.5.6/scripts/check-product-tmux-absent.sh +62 -0
- pocketshell-0.5.6/scripts/check-python-ci-lock.sh +73 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/scripts/sync-readme-exit-codes.py +2 -2
- pocketshell-0.5.6/scripts/test-usage-missing-window-mutation.sh +215 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/cli.py +4 -6
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/sessions.py +28 -1
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_sessions_create_routing.py +31 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_usage.py +7 -55
- {pocketshell-0.5.4 → pocketshell-0.5.6}/uv.lock +2 -110
- pocketshell-0.5.4/src/pocketshell/qr_share.py +0 -432
- pocketshell-0.5.4/tests/test_agents_fixture_aplexer.py +0 -158
- pocketshell-0.5.4/tests/test_qr_share.py +0 -327
- pocketshell-0.5.4/tests/test_ssh_fixture_penalties.py +0 -966
- pocketshell-0.5.4/tests/test_watch_ci.py +0 -948
- pocketshell-0.5.4/tests/test_watch_ci_cancelled.py +0 -745
- {pocketshell-0.5.4 → pocketshell-0.5.6}/.gitignore +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/scheduler/README.md +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/__init__.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/__main__.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/agent_card_push.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/agent_log.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/agents.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/agents_kind.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/aplexer.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/cards.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/cgroup_agents.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/daemon.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/engines.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/env.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/github.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/hooks.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/logs.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/memcap.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/profiles.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/prune_attachments.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/push.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/repos.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/serve.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/session_enum.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/tree.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/usage.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/usage_capture.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/usage_reset.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/src/pocketshell/workspaces.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/__init__.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/conftest.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/data/quse-0.0.11-usage.json +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/data/quse-0.0.13-usage.json +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/data/quse-0.0.15-usage.json +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/data/quse-0.0.15-usage.ndjson +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/data/quse-0.0.15-usage.provenance.txt +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/data/quse-0.0.9-usage.json +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/data/sshd_anchor/Dockerfile.from-unanchored-base +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/fixtures/aplexer/snapshot-agent.json +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/fixtures/aplexer/snapshot-crashed-start.json +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/fixtures/aplexer/snapshot-liveness-mix.json +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/fixtures/aplexer/snapshot-mid-create.json +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/fixtures/aplexer/snapshot-post-kill-window.json +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/fixtures/aplexer/snapshot-reported-state.json +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/fixtures/aplexer/snapshot.json +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_agent_card_push.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_agent_log.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_agents.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_agents_kind.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_aplexer_contract.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_aplexer_resolution.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_cards.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_cards_push_notify.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_cgroup_agents.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_cli.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_daemon.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_daemon_fallback.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_engines.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_env.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_github.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_hooks.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_logs.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_profiles.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_prune_attachments.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_push.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_repos.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_serve.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_session_cgroup_kernel_proof.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_session_enum.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_sessions.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_sessions_attach.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_sessions_kill.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_sessions_mem_cap.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_tree.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_usage_capture.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_usage_reset.py +0 -0
- {pocketshell-0.5.4 → pocketshell-0.5.6}/tests/test_workspaces.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexey Grigorev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pocketshell
|
|
3
|
-
Version: 0.5.
|
|
4
|
-
Summary: Unified server-side Python utility for the PocketShell Android
|
|
5
|
-
Project-URL: Homepage, https://github.com/
|
|
6
|
-
Project-URL: Issues, https://github.com/
|
|
3
|
+
Version: 0.5.6
|
|
4
|
+
Summary: Unified server-side Python utility for the PocketShell Android and desktop clients.
|
|
5
|
+
Project-URL: Homepage, https://github.com/PocketShell-io/pocketshell-cli
|
|
6
|
+
Project-URL: Issues, https://github.com/PocketShell-io/pocketshell-cli/issues
|
|
7
7
|
Author: Alexey Grigorev
|
|
8
8
|
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
9
10
|
Keywords: agents,pocketshell,ssh,usage
|
|
10
11
|
Classifier: Development Status :: 3 - Alpha
|
|
11
12
|
Classifier: Environment :: Console
|
|
@@ -26,16 +27,17 @@ Requires-Dist: quse==0.0.15
|
|
|
26
27
|
Provides-Extra: dev
|
|
27
28
|
Requires-Dist: pytest>=8.4.0; extra == 'dev'
|
|
28
29
|
Requires-Dist: ruff>=0.15.0; extra == 'dev'
|
|
29
|
-
Provides-Extra: qr
|
|
30
|
-
Requires-Dist: qrcode[pil]>=7.4; extra == 'qr'
|
|
31
30
|
Description-Content-Type: text/markdown
|
|
32
31
|
|
|
33
32
|
# pocketshell
|
|
34
33
|
|
|
35
|
-
Unified server-side Python utility for the [PocketShell](https://github.com/
|
|
36
|
-
Android
|
|
34
|
+
Unified server-side Python utility for the [PocketShell](https://github.com/PocketShell-io/pocketshell)
|
|
35
|
+
Android and desktop clients. This is the host-side companion CLI, maintained in its own
|
|
36
|
+
repo with an independent release cycle; before v0.5.5 it lived at
|
|
37
|
+
`tools/pocketshell/` in the [monorepo](https://github.com/PocketShell-io/pocketshell)
|
|
38
|
+
(versioning was coupled to the app tag there — see [Release flow](#release-flow)). The app probes for this single helper on each remote
|
|
37
39
|
host and uses its subcommands for usage, aplexer session lifecycle, agent
|
|
38
|
-
conversations,
|
|
40
|
+
conversations, repository discovery, environment files, hooks,
|
|
39
41
|
logs, and daemon lifecycle checks.
|
|
40
42
|
|
|
41
43
|
## Durable workspaces
|
|
@@ -66,7 +68,7 @@ uv tool install pocketshell
|
|
|
66
68
|
For local development from a clone:
|
|
67
69
|
|
|
68
70
|
```bash
|
|
69
|
-
cd
|
|
71
|
+
cd pocketshell-cli
|
|
70
72
|
uv venv
|
|
71
73
|
uv pip install -e .
|
|
72
74
|
pocketshell --help
|
|
@@ -76,21 +78,6 @@ pocketshell --help
|
|
|
76
78
|
pipx. Both install paths produce a `pocketshell` binary that the
|
|
77
79
|
PocketShell app's bootstrap probe detects.
|
|
78
80
|
|
|
79
|
-
### Optional extras
|
|
80
|
-
|
|
81
|
-
`pocketshell qr-share` requires the `qrcode[pil]` package (Pillow) to
|
|
82
|
-
render QR images. Because Pillow is heavy and not needed by any other
|
|
83
|
-
subcommand, it ships behind an optional `qr` extra:
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
uv tool install pocketshell --with qrcode[pil]
|
|
87
|
-
# or
|
|
88
|
-
pip install pocketshell[qr]
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Without the extra, every other subcommand keeps working; only
|
|
92
|
-
`pocketshell qr-share` exits 127 with a friendly install hint.
|
|
93
|
-
|
|
94
81
|
## Usage
|
|
95
82
|
|
|
96
83
|
Top-level commands in the current helper:
|
|
@@ -109,7 +96,6 @@ pocketshell hooks ... # Claude/Codex/OpenCode hooks
|
|
|
109
96
|
pocketshell logs ... # server-side trace sink
|
|
110
97
|
pocketshell daemon ... # IPC daemon lifecycle
|
|
111
98
|
pocketshell serve --dir PATH [--port N] # foreground static HTTP server
|
|
112
|
-
pocketshell qr-share ... # SSH host QR import payloads
|
|
113
99
|
```
|
|
114
100
|
|
|
115
101
|
Run `pocketshell --help` or `pocketshell <command> --help` for the live
|
|
@@ -180,15 +166,15 @@ Schema (every entry):
|
|
|
180
166
|
{
|
|
181
167
|
"owner": "alexeygrigorev", // null when remote URL is non-GitHub
|
|
182
168
|
"name": "pocketshell", // local dir basename, or GH repo name
|
|
183
|
-
"full_name": "
|
|
169
|
+
"full_name": "PocketShell-io/pocketshell", // null when owner unknown
|
|
184
170
|
"local": { // populated by --local scans
|
|
185
171
|
"path": "/home/alexey/git/pocketshell",
|
|
186
172
|
"head": "main"
|
|
187
173
|
},
|
|
188
174
|
"remote": { // populated by --remote scans
|
|
189
175
|
"default_branch": "main",
|
|
190
|
-
"html_url": "https://github.com/
|
|
191
|
-
"ssh_url": "git@github.com:
|
|
176
|
+
"html_url": "https://github.com/PocketShell-io/pocketshell",
|
|
177
|
+
"ssh_url": "git@github.com:PocketShell-io/pocketshell.git",
|
|
192
178
|
"updated_at": "2026-05-27T12:00:00Z"
|
|
193
179
|
}
|
|
194
180
|
}
|
|
@@ -273,47 +259,6 @@ to its `index.html` when present; paths are resolved before the containment
|
|
|
273
259
|
check, so parent traversal and symlinks that leave the selected directory are
|
|
274
260
|
rejected rather than served.
|
|
275
261
|
|
|
276
|
-
### `pocketshell qr-share`
|
|
277
|
-
|
|
278
|
-
Builds a `pocketshell.ssh-import.v1` payload from an `~/.ssh/config`
|
|
279
|
-
alias (resolved via `ssh -G`) or from explicit flags, wraps it in one or
|
|
280
|
-
more `pocketshell.qr.v1` chunked envelopes (matching the Kotlin
|
|
281
|
-
`QrChunkCodec` byte-for-byte), and emits QR codes for the phone-side
|
|
282
|
-
scanner to consume (issue #129).
|
|
283
|
-
|
|
284
|
-
```bash
|
|
285
|
-
pocketshell qr-share prod # ssh-config alias
|
|
286
|
-
pocketshell qr-share --host h --user u --key ~/.ssh/id_ed25519 --name h
|
|
287
|
-
pocketshell qr-share prod --png --out-dir /tmp/qr # write PNGs
|
|
288
|
-
pocketshell qr-share prod --print-only --id deadbeef # debug envelopes
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
When stdout is a TTY the QRs are drawn inline as Unicode blocks; between
|
|
292
|
-
multi-part transmissions the command pauses on "Press Enter for next
|
|
293
|
-
QR" so the user can scan each in turn. When stdout is not a TTY (or
|
|
294
|
-
`--png` is passed) a numbered PNG sequence (`qr-share-01.png`,
|
|
295
|
-
`qr-share-02.png`, ...) is written to `--out-dir`.
|
|
296
|
-
|
|
297
|
-
Requires the optional `qr` extra (see [Optional extras](#optional-extras)).
|
|
298
|
-
Without it, the command exits 127 with the install hint and every other
|
|
299
|
-
subcommand keeps working.
|
|
300
|
-
|
|
301
|
-
#### Running from a repo clone (no install)
|
|
302
|
-
|
|
303
|
-
To run `qr-share` straight from a checkout without installing the tool,
|
|
304
|
-
use `uv run` from `tools/pocketshell` and include the `qr` extra:
|
|
305
|
-
|
|
306
|
-
```bash
|
|
307
|
-
cd tools/pocketshell
|
|
308
|
-
uv run --extra qr pocketshell qr-share prod
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
The first run creates `.venv` and installs the QR dependency; later runs
|
|
312
|
-
are instant. Run it in an interactive terminal so stdout is a TTY and the
|
|
313
|
-
QR renders inline — otherwise it falls back to writing PNGs (add
|
|
314
|
-
`--png --out-dir ./qr` to force PNGs). Omitting `--extra qr` makes the
|
|
315
|
-
command exit 127 with the install hint.
|
|
316
|
-
|
|
317
262
|
### `pocketshell hooks`
|
|
318
263
|
|
|
319
264
|
Installs agent **stop / idle-detection** hooks across Claude Code,
|
|
@@ -388,7 +333,7 @@ generated executables, and durable ownership metadata are cleaned up.
|
|
|
388
333
|
## Development
|
|
389
334
|
|
|
390
335
|
```bash
|
|
391
|
-
cd
|
|
336
|
+
cd pocketshell-cli
|
|
392
337
|
uv venv
|
|
393
338
|
uv pip install -e ".[dev]"
|
|
394
339
|
uv run pytest
|
|
@@ -406,120 +351,47 @@ they run in seconds without invoking a real host session.
|
|
|
406
351
|
|
|
407
352
|
## Release flow
|
|
408
353
|
|
|
409
|
-
`pocketshell`
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
and **also** builds the Python sdist + wheel and publishes them to PyPI.
|
|
413
|
-
|
|
414
|
-
### Version coupling (tag-derived, issue #2356)
|
|
415
|
-
|
|
416
|
-
Neither side is a hand-maintained literal any more. Both derive from the git
|
|
417
|
-
tag being built, via the single shared script
|
|
418
|
-
[`scripts/derive-version.sh`](../../scripts/derive-version.sh):
|
|
419
|
-
|
|
420
|
-
- `app/build.gradle.kts` computes `versionCode`/`versionName` at Gradle
|
|
421
|
-
configuration time by shelling out to `scripts/derive-version.sh`.
|
|
422
|
-
- `tools/pocketshell/pyproject.toml`'s committed `version` field is a
|
|
423
|
-
placeholder. The release workflow's "Stamp pyproject.toml version from
|
|
424
|
-
tag" step overwrites it (in the ephemeral CI checkout, never committed)
|
|
425
|
-
from `scripts/derive-version.sh version-name --ref <tag>` immediately
|
|
426
|
-
before building the sdist/wheel.
|
|
427
|
-
|
|
428
|
-
[`scripts/check-version-coupling.sh`](../../scripts/check-version-coupling.sh)
|
|
429
|
-
verifies the derivation script is the SOLE source of truth (its own
|
|
430
|
-
self-test, Gradle's resolved version matching a direct script invocation,
|
|
431
|
-
and both consumers referencing the script by path rather than an
|
|
432
|
-
independent reimplementation) — it runs per-push in `tests.yml`.
|
|
433
|
-
[`scripts/check-pypi-version.sh`](../../scripts/check-pypi-version.sh)
|
|
434
|
-
verifies, at tag-publish time, that the freshly-stamped
|
|
435
|
-
`pyproject.toml` version equals what `scripts/derive-version.sh` derives
|
|
436
|
-
for the tag being published:
|
|
354
|
+
`pocketshell` is released from THIS repo on its own cycle, independent of the
|
|
355
|
+
Android app (extraction: PocketShell-io/pocketshell#2643). The `version` in
|
|
356
|
+
`pyproject.toml` is the single source of truth:
|
|
437
357
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
358
|
+
1. Bump `version` in `pyproject.toml` (one small commit — the release
|
|
359
|
+
declaration; org convention, cf. PocketShell-io/quse).
|
|
360
|
+
2. Tag that commit `vX.Y.Z` — the tag MUST equal the pyproject version — and
|
|
361
|
+
push the tag. `.github/workflows/publish.yml` builds the sdist + wheel,
|
|
362
|
+
refuses to publish if `dist/` does not carry exactly the tagged version,
|
|
363
|
+
and publishes to PyPI.
|
|
364
|
+
3. Hosts pick it up with `uv tool upgrade pocketshell`.
|
|
441
365
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
the
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
The PyPI publish job depends on the APK build job, so a broken APK
|
|
467
|
-
build also aborts the PyPI publish. If only the PyPI publish fails the
|
|
468
|
-
maintainer can re-trigger the workflow at the same tag from the
|
|
469
|
-
Actions tab; the APK build is idempotent against an existing release
|
|
470
|
-
(`softprops/action-gh-release` updates the existing release rather
|
|
471
|
-
than failing).
|
|
472
|
-
|
|
473
|
-
## PyPI trusted publishing setup (one-time)
|
|
474
|
-
|
|
475
|
-
The `publish-pypi` job uses GitHub's OIDC token instead of a long-lived
|
|
476
|
-
API token. This avoids storing a `PYPI_API_TOKEN` secret in the repo
|
|
477
|
-
and means there is nothing to rotate. The trade-off is that the
|
|
478
|
-
project owner must complete one configuration step on pypi.org before
|
|
479
|
-
the first automated tag publish:
|
|
480
|
-
|
|
481
|
-
1. Sign in to https://pypi.org/ with the project owner account.
|
|
482
|
-
2. Open the `pocketshell` project page ->
|
|
483
|
-
**Manage** -> **Publishing**.
|
|
484
|
-
3. Under **Trusted publishers**, click **Add a new pending publisher**
|
|
485
|
-
(if the project is empty) or **Add a new publisher**, then fill in:
|
|
486
|
-
- **PyPI Project Name**: `pocketshell`
|
|
487
|
-
- **Owner**: `alexeygrigorev`
|
|
488
|
-
- **Repository name**: `pocketshell`
|
|
489
|
-
- **Workflow name**: `build.yml`
|
|
490
|
-
- **Environment name**: `pypi`
|
|
491
|
-
4. Save the publisher.
|
|
492
|
-
5. In this repository on GitHub, open
|
|
493
|
-
**Settings** -> **Environments** -> **New environment** -> name it
|
|
494
|
-
`pypi`. No secrets or reviewers are required; the environment exists
|
|
495
|
-
purely to scope the OIDC token. (If the environment already exists,
|
|
496
|
-
confirm it has no protection rules that would block the workflow
|
|
497
|
-
from running.)
|
|
498
|
-
6. Push the next release tag. The `Publish to PyPI via trusted
|
|
499
|
-
publishing` step should succeed without any token configuration.
|
|
500
|
-
|
|
501
|
-
### Why trusted publishing (and not `PYPI_API_TOKEN`)?
|
|
502
|
-
|
|
503
|
-
- No long-lived secret to rotate, leak, or accidentally print in logs.
|
|
504
|
-
- The OIDC subject is scoped to `repo=alexeygrigorev/pocketshell`,
|
|
505
|
-
`workflow=build.yml`, `environment=pypi`, so a compromised fork or
|
|
506
|
-
a different workflow file in this repo cannot reuse it.
|
|
507
|
-
- D22 (no backwards-compat): we do not also maintain a token-fallback
|
|
508
|
-
path. If trusted publishing breaks, fix it; do not add a token
|
|
509
|
-
branch alongside.
|
|
510
|
-
|
|
511
|
-
If trusted publishing is ever unavailable for a tag (e.g. PyPI outage
|
|
512
|
-
on the OIDC verifier), the recommended manual escape hatch is:
|
|
366
|
+
CI (`.github/workflows/ci.yml`) runs the pytest suite plus the packaging and
|
|
367
|
+
frozen-lock guards on every PR and every push to `main`. The first
|
|
368
|
+
self-governed release is **0.5.5**, continuing from the last app-coupled
|
|
369
|
+
release 0.5.4 so `uv tool upgrade` stays monotonic.
|
|
370
|
+
|
|
371
|
+
### PyPI publish setup (one-time)
|
|
372
|
+
|
|
373
|
+
The `publish-pypi` job publishes with a PyPI API token stored as the
|
|
374
|
+
`PYPI_API_TOKEN` secret on this repo (same mechanism as
|
|
375
|
+
[PocketShell-io/quse](https://github.com/PocketShell-io/quse)):
|
|
376
|
+
|
|
377
|
+
1. On pypi.org, create an API token scoped to the `pocketshell` project (or
|
|
378
|
+
reuse the existing project-scoped token from the monorepo era).
|
|
379
|
+
2. On GitHub: this repo → Settings → Secrets and variables → Actions →
|
|
380
|
+
New repository secret → name `PYPI_API_TOKEN`. The `pypi` environment
|
|
381
|
+
already exists; the publish job runs inside it.
|
|
382
|
+
|
|
383
|
+
If a publish fails after the tag is pushed, fix the cause and re-run
|
|
384
|
+
`publish.yml` from the Actions tab (it has a `workflow_dispatch` trigger) —
|
|
385
|
+
the tag does not need to move.
|
|
386
|
+
|
|
387
|
+
Manual escape hatch (maintainer account, only when CI publishing is
|
|
388
|
+
unavailable):
|
|
513
389
|
|
|
514
390
|
```bash
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
python -m twine upload dist/*
|
|
391
|
+
uv build
|
|
392
|
+
uv run --with twine twine upload dist/*
|
|
518
393
|
```
|
|
519
394
|
|
|
520
|
-
with the maintainer's account. Do not re-add a `PYPI_API_TOKEN` secret
|
|
521
|
-
as a permanent fallback.
|
|
522
|
-
|
|
523
395
|
## Why a unified CLI?
|
|
524
396
|
|
|
525
397
|
The PocketShell app previously depended on multiple host-side tools.
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# pocketshell
|
|
2
2
|
|
|
3
|
-
Unified server-side Python utility for the [PocketShell](https://github.com/
|
|
4
|
-
Android
|
|
3
|
+
Unified server-side Python utility for the [PocketShell](https://github.com/PocketShell-io/pocketshell)
|
|
4
|
+
Android and desktop clients. This is the host-side companion CLI, maintained in its own
|
|
5
|
+
repo with an independent release cycle; before v0.5.5 it lived at
|
|
6
|
+
`tools/pocketshell/` in the [monorepo](https://github.com/PocketShell-io/pocketshell)
|
|
7
|
+
(versioning was coupled to the app tag there — see [Release flow](#release-flow)). The app probes for this single helper on each remote
|
|
5
8
|
host and uses its subcommands for usage, aplexer session lifecycle, agent
|
|
6
|
-
conversations,
|
|
9
|
+
conversations, repository discovery, environment files, hooks,
|
|
7
10
|
logs, and daemon lifecycle checks.
|
|
8
11
|
|
|
9
12
|
## Durable workspaces
|
|
@@ -34,7 +37,7 @@ uv tool install pocketshell
|
|
|
34
37
|
For local development from a clone:
|
|
35
38
|
|
|
36
39
|
```bash
|
|
37
|
-
cd
|
|
40
|
+
cd pocketshell-cli
|
|
38
41
|
uv venv
|
|
39
42
|
uv pip install -e .
|
|
40
43
|
pocketshell --help
|
|
@@ -44,21 +47,6 @@ pocketshell --help
|
|
|
44
47
|
pipx. Both install paths produce a `pocketshell` binary that the
|
|
45
48
|
PocketShell app's bootstrap probe detects.
|
|
46
49
|
|
|
47
|
-
### Optional extras
|
|
48
|
-
|
|
49
|
-
`pocketshell qr-share` requires the `qrcode[pil]` package (Pillow) to
|
|
50
|
-
render QR images. Because Pillow is heavy and not needed by any other
|
|
51
|
-
subcommand, it ships behind an optional `qr` extra:
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
uv tool install pocketshell --with qrcode[pil]
|
|
55
|
-
# or
|
|
56
|
-
pip install pocketshell[qr]
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Without the extra, every other subcommand keeps working; only
|
|
60
|
-
`pocketshell qr-share` exits 127 with a friendly install hint.
|
|
61
|
-
|
|
62
50
|
## Usage
|
|
63
51
|
|
|
64
52
|
Top-level commands in the current helper:
|
|
@@ -77,7 +65,6 @@ pocketshell hooks ... # Claude/Codex/OpenCode hooks
|
|
|
77
65
|
pocketshell logs ... # server-side trace sink
|
|
78
66
|
pocketshell daemon ... # IPC daemon lifecycle
|
|
79
67
|
pocketshell serve --dir PATH [--port N] # foreground static HTTP server
|
|
80
|
-
pocketshell qr-share ... # SSH host QR import payloads
|
|
81
68
|
```
|
|
82
69
|
|
|
83
70
|
Run `pocketshell --help` or `pocketshell <command> --help` for the live
|
|
@@ -148,15 +135,15 @@ Schema (every entry):
|
|
|
148
135
|
{
|
|
149
136
|
"owner": "alexeygrigorev", // null when remote URL is non-GitHub
|
|
150
137
|
"name": "pocketshell", // local dir basename, or GH repo name
|
|
151
|
-
"full_name": "
|
|
138
|
+
"full_name": "PocketShell-io/pocketshell", // null when owner unknown
|
|
152
139
|
"local": { // populated by --local scans
|
|
153
140
|
"path": "/home/alexey/git/pocketshell",
|
|
154
141
|
"head": "main"
|
|
155
142
|
},
|
|
156
143
|
"remote": { // populated by --remote scans
|
|
157
144
|
"default_branch": "main",
|
|
158
|
-
"html_url": "https://github.com/
|
|
159
|
-
"ssh_url": "git@github.com:
|
|
145
|
+
"html_url": "https://github.com/PocketShell-io/pocketshell",
|
|
146
|
+
"ssh_url": "git@github.com:PocketShell-io/pocketshell.git",
|
|
160
147
|
"updated_at": "2026-05-27T12:00:00Z"
|
|
161
148
|
}
|
|
162
149
|
}
|
|
@@ -241,47 +228,6 @@ to its `index.html` when present; paths are resolved before the containment
|
|
|
241
228
|
check, so parent traversal and symlinks that leave the selected directory are
|
|
242
229
|
rejected rather than served.
|
|
243
230
|
|
|
244
|
-
### `pocketshell qr-share`
|
|
245
|
-
|
|
246
|
-
Builds a `pocketshell.ssh-import.v1` payload from an `~/.ssh/config`
|
|
247
|
-
alias (resolved via `ssh -G`) or from explicit flags, wraps it in one or
|
|
248
|
-
more `pocketshell.qr.v1` chunked envelopes (matching the Kotlin
|
|
249
|
-
`QrChunkCodec` byte-for-byte), and emits QR codes for the phone-side
|
|
250
|
-
scanner to consume (issue #129).
|
|
251
|
-
|
|
252
|
-
```bash
|
|
253
|
-
pocketshell qr-share prod # ssh-config alias
|
|
254
|
-
pocketshell qr-share --host h --user u --key ~/.ssh/id_ed25519 --name h
|
|
255
|
-
pocketshell qr-share prod --png --out-dir /tmp/qr # write PNGs
|
|
256
|
-
pocketshell qr-share prod --print-only --id deadbeef # debug envelopes
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
When stdout is a TTY the QRs are drawn inline as Unicode blocks; between
|
|
260
|
-
multi-part transmissions the command pauses on "Press Enter for next
|
|
261
|
-
QR" so the user can scan each in turn. When stdout is not a TTY (or
|
|
262
|
-
`--png` is passed) a numbered PNG sequence (`qr-share-01.png`,
|
|
263
|
-
`qr-share-02.png`, ...) is written to `--out-dir`.
|
|
264
|
-
|
|
265
|
-
Requires the optional `qr` extra (see [Optional extras](#optional-extras)).
|
|
266
|
-
Without it, the command exits 127 with the install hint and every other
|
|
267
|
-
subcommand keeps working.
|
|
268
|
-
|
|
269
|
-
#### Running from a repo clone (no install)
|
|
270
|
-
|
|
271
|
-
To run `qr-share` straight from a checkout without installing the tool,
|
|
272
|
-
use `uv run` from `tools/pocketshell` and include the `qr` extra:
|
|
273
|
-
|
|
274
|
-
```bash
|
|
275
|
-
cd tools/pocketshell
|
|
276
|
-
uv run --extra qr pocketshell qr-share prod
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
The first run creates `.venv` and installs the QR dependency; later runs
|
|
280
|
-
are instant. Run it in an interactive terminal so stdout is a TTY and the
|
|
281
|
-
QR renders inline — otherwise it falls back to writing PNGs (add
|
|
282
|
-
`--png --out-dir ./qr` to force PNGs). Omitting `--extra qr` makes the
|
|
283
|
-
command exit 127 with the install hint.
|
|
284
|
-
|
|
285
231
|
### `pocketshell hooks`
|
|
286
232
|
|
|
287
233
|
Installs agent **stop / idle-detection** hooks across Claude Code,
|
|
@@ -356,7 +302,7 @@ generated executables, and durable ownership metadata are cleaned up.
|
|
|
356
302
|
## Development
|
|
357
303
|
|
|
358
304
|
```bash
|
|
359
|
-
cd
|
|
305
|
+
cd pocketshell-cli
|
|
360
306
|
uv venv
|
|
361
307
|
uv pip install -e ".[dev]"
|
|
362
308
|
uv run pytest
|
|
@@ -374,120 +320,47 @@ they run in seconds without invoking a real host session.
|
|
|
374
320
|
|
|
375
321
|
## Release flow
|
|
376
322
|
|
|
377
|
-
`pocketshell`
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
and **also** builds the Python sdist + wheel and publishes them to PyPI.
|
|
381
|
-
|
|
382
|
-
### Version coupling (tag-derived, issue #2356)
|
|
383
|
-
|
|
384
|
-
Neither side is a hand-maintained literal any more. Both derive from the git
|
|
385
|
-
tag being built, via the single shared script
|
|
386
|
-
[`scripts/derive-version.sh`](../../scripts/derive-version.sh):
|
|
387
|
-
|
|
388
|
-
- `app/build.gradle.kts` computes `versionCode`/`versionName` at Gradle
|
|
389
|
-
configuration time by shelling out to `scripts/derive-version.sh`.
|
|
390
|
-
- `tools/pocketshell/pyproject.toml`'s committed `version` field is a
|
|
391
|
-
placeholder. The release workflow's "Stamp pyproject.toml version from
|
|
392
|
-
tag" step overwrites it (in the ephemeral CI checkout, never committed)
|
|
393
|
-
from `scripts/derive-version.sh version-name --ref <tag>` immediately
|
|
394
|
-
before building the sdist/wheel.
|
|
395
|
-
|
|
396
|
-
[`scripts/check-version-coupling.sh`](../../scripts/check-version-coupling.sh)
|
|
397
|
-
verifies the derivation script is the SOLE source of truth (its own
|
|
398
|
-
self-test, Gradle's resolved version matching a direct script invocation,
|
|
399
|
-
and both consumers referencing the script by path rather than an
|
|
400
|
-
independent reimplementation) — it runs per-push in `tests.yml`.
|
|
401
|
-
[`scripts/check-pypi-version.sh`](../../scripts/check-pypi-version.sh)
|
|
402
|
-
verifies, at tag-publish time, that the freshly-stamped
|
|
403
|
-
`pyproject.toml` version equals what `scripts/derive-version.sh` derives
|
|
404
|
-
for the tag being published:
|
|
323
|
+
`pocketshell` is released from THIS repo on its own cycle, independent of the
|
|
324
|
+
Android app (extraction: PocketShell-io/pocketshell#2643). The `version` in
|
|
325
|
+
`pyproject.toml` is the single source of truth:
|
|
405
326
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
327
|
+
1. Bump `version` in `pyproject.toml` (one small commit — the release
|
|
328
|
+
declaration; org convention, cf. PocketShell-io/quse).
|
|
329
|
+
2. Tag that commit `vX.Y.Z` — the tag MUST equal the pyproject version — and
|
|
330
|
+
push the tag. `.github/workflows/publish.yml` builds the sdist + wheel,
|
|
331
|
+
refuses to publish if `dist/` does not carry exactly the tagged version,
|
|
332
|
+
and publishes to PyPI.
|
|
333
|
+
3. Hosts pick it up with `uv tool upgrade pocketshell`.
|
|
409
334
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
the
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
The PyPI publish job depends on the APK build job, so a broken APK
|
|
435
|
-
build also aborts the PyPI publish. If only the PyPI publish fails the
|
|
436
|
-
maintainer can re-trigger the workflow at the same tag from the
|
|
437
|
-
Actions tab; the APK build is idempotent against an existing release
|
|
438
|
-
(`softprops/action-gh-release` updates the existing release rather
|
|
439
|
-
than failing).
|
|
440
|
-
|
|
441
|
-
## PyPI trusted publishing setup (one-time)
|
|
442
|
-
|
|
443
|
-
The `publish-pypi` job uses GitHub's OIDC token instead of a long-lived
|
|
444
|
-
API token. This avoids storing a `PYPI_API_TOKEN` secret in the repo
|
|
445
|
-
and means there is nothing to rotate. The trade-off is that the
|
|
446
|
-
project owner must complete one configuration step on pypi.org before
|
|
447
|
-
the first automated tag publish:
|
|
448
|
-
|
|
449
|
-
1. Sign in to https://pypi.org/ with the project owner account.
|
|
450
|
-
2. Open the `pocketshell` project page ->
|
|
451
|
-
**Manage** -> **Publishing**.
|
|
452
|
-
3. Under **Trusted publishers**, click **Add a new pending publisher**
|
|
453
|
-
(if the project is empty) or **Add a new publisher**, then fill in:
|
|
454
|
-
- **PyPI Project Name**: `pocketshell`
|
|
455
|
-
- **Owner**: `alexeygrigorev`
|
|
456
|
-
- **Repository name**: `pocketshell`
|
|
457
|
-
- **Workflow name**: `build.yml`
|
|
458
|
-
- **Environment name**: `pypi`
|
|
459
|
-
4. Save the publisher.
|
|
460
|
-
5. In this repository on GitHub, open
|
|
461
|
-
**Settings** -> **Environments** -> **New environment** -> name it
|
|
462
|
-
`pypi`. No secrets or reviewers are required; the environment exists
|
|
463
|
-
purely to scope the OIDC token. (If the environment already exists,
|
|
464
|
-
confirm it has no protection rules that would block the workflow
|
|
465
|
-
from running.)
|
|
466
|
-
6. Push the next release tag. The `Publish to PyPI via trusted
|
|
467
|
-
publishing` step should succeed without any token configuration.
|
|
468
|
-
|
|
469
|
-
### Why trusted publishing (and not `PYPI_API_TOKEN`)?
|
|
470
|
-
|
|
471
|
-
- No long-lived secret to rotate, leak, or accidentally print in logs.
|
|
472
|
-
- The OIDC subject is scoped to `repo=alexeygrigorev/pocketshell`,
|
|
473
|
-
`workflow=build.yml`, `environment=pypi`, so a compromised fork or
|
|
474
|
-
a different workflow file in this repo cannot reuse it.
|
|
475
|
-
- D22 (no backwards-compat): we do not also maintain a token-fallback
|
|
476
|
-
path. If trusted publishing breaks, fix it; do not add a token
|
|
477
|
-
branch alongside.
|
|
478
|
-
|
|
479
|
-
If trusted publishing is ever unavailable for a tag (e.g. PyPI outage
|
|
480
|
-
on the OIDC verifier), the recommended manual escape hatch is:
|
|
335
|
+
CI (`.github/workflows/ci.yml`) runs the pytest suite plus the packaging and
|
|
336
|
+
frozen-lock guards on every PR and every push to `main`. The first
|
|
337
|
+
self-governed release is **0.5.5**, continuing from the last app-coupled
|
|
338
|
+
release 0.5.4 so `uv tool upgrade` stays monotonic.
|
|
339
|
+
|
|
340
|
+
### PyPI publish setup (one-time)
|
|
341
|
+
|
|
342
|
+
The `publish-pypi` job publishes with a PyPI API token stored as the
|
|
343
|
+
`PYPI_API_TOKEN` secret on this repo (same mechanism as
|
|
344
|
+
[PocketShell-io/quse](https://github.com/PocketShell-io/quse)):
|
|
345
|
+
|
|
346
|
+
1. On pypi.org, create an API token scoped to the `pocketshell` project (or
|
|
347
|
+
reuse the existing project-scoped token from the monorepo era).
|
|
348
|
+
2. On GitHub: this repo → Settings → Secrets and variables → Actions →
|
|
349
|
+
New repository secret → name `PYPI_API_TOKEN`. The `pypi` environment
|
|
350
|
+
already exists; the publish job runs inside it.
|
|
351
|
+
|
|
352
|
+
If a publish fails after the tag is pushed, fix the cause and re-run
|
|
353
|
+
`publish.yml` from the Actions tab (it has a `workflow_dispatch` trigger) —
|
|
354
|
+
the tag does not need to move.
|
|
355
|
+
|
|
356
|
+
Manual escape hatch (maintainer account, only when CI publishing is
|
|
357
|
+
unavailable):
|
|
481
358
|
|
|
482
359
|
```bash
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
python -m twine upload dist/*
|
|
360
|
+
uv build
|
|
361
|
+
uv run --with twine twine upload dist/*
|
|
486
362
|
```
|
|
487
363
|
|
|
488
|
-
with the maintainer's account. Do not re-add a `PYPI_API_TOKEN` secret
|
|
489
|
-
as a permanent fallback.
|
|
490
|
-
|
|
491
364
|
## Why a unified CLI?
|
|
492
365
|
|
|
493
366
|
The PocketShell app previously depended on multiple host-side tools.
|