pocketshell 0.5.3__tar.gz → 0.5.5__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.5/LICENSE +21 -0
- pocketshell-0.5.5/PKG-INFO +404 -0
- pocketshell-0.5.5/README.md +373 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/pyproject.toml +30 -55
- {pocketshell-0.5.3 → pocketshell-0.5.5}/scheduler/pocketshell-usage-capture.service +1 -1
- {pocketshell-0.5.3 → pocketshell-0.5.5}/scheduler/pocketshell-usage-capture.timer +1 -1
- pocketshell-0.5.5/scripts/check-cgroup-cap-proof.sh +191 -0
- pocketshell-0.5.5/scripts/check-product-tmux-absent.sh +62 -0
- pocketshell-0.5.5/scripts/check-python-ci-lock.sh +73 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/scripts/sync-readme-exit-codes.py +2 -2
- pocketshell-0.5.5/scripts/test-usage-missing-window-mutation.sh +215 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/__init__.py +2 -3
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/agent_card_push.py +1 -1
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/agent_log.py +1 -1
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/agents.py +26 -333
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/agents_kind.py +3 -3
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/aplexer.py +1 -1
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/cards.py +20 -49
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/cgroup_agents.py +9 -12
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/cli.py +8 -16
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/daemon.py +7 -69
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/engines.py +2 -2
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/env.py +1 -1
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/hooks.py +0 -79
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/memcap.py +13 -36
- pocketshell-0.5.5/src/pocketshell/session_enum.py +374 -0
- pocketshell-0.5.5/src/pocketshell/sessions.py +692 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/tree.py +23 -59
- pocketshell-0.5.5/src/pocketshell/workspaces.py +224 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/conftest.py +0 -5
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_agent_card_push.py +1 -4
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_agents.py +1 -606
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_agents_kind.py +17 -17
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_aplexer_resolution.py +7 -4
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_cards.py +17 -17
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_cards_push_notify.py +0 -1
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_cgroup_agents.py +18 -18
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_daemon.py +3 -47
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_daemon_fallback.py +3 -7
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_engines.py +3 -3
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_hooks.py +7 -84
- pocketshell-0.5.3/tests/test_sessions_mem_cap.py → pocketshell-0.5.5/tests/test_session_cgroup_kernel_proof.py +186 -609
- pocketshell-0.5.5/tests/test_session_enum.py +93 -0
- pocketshell-0.5.5/tests/test_sessions.py +54 -0
- pocketshell-0.5.5/tests/test_sessions_attach.py +55 -0
- pocketshell-0.5.5/tests/test_sessions_create_routing.py +90 -0
- pocketshell-0.5.5/tests/test_sessions_kill.py +57 -0
- pocketshell-0.5.5/tests/test_sessions_mem_cap.py +38 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_tree.py +13 -38
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_usage.py +7 -55
- pocketshell-0.5.5/tests/test_workspaces.py +98 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/uv.lock +10 -200
- pocketshell-0.5.3/PKG-INFO +0 -628
- pocketshell-0.5.3/README.md +0 -595
- pocketshell-0.5.3/src/pocketshell/config.py +0 -163
- pocketshell-0.5.3/src/pocketshell/jobs.py +0 -829
- pocketshell-0.5.3/src/pocketshell/qr_share.py +0 -432
- pocketshell-0.5.3/src/pocketshell/resume.py +0 -698
- pocketshell-0.5.3/src/pocketshell/send.py +0 -1495
- pocketshell-0.5.3/src/pocketshell/session_enum.py +0 -905
- pocketshell-0.5.3/src/pocketshell/sessions.py +0 -1991
- pocketshell-0.5.3/tests/test_agents_fixture_aplexer.py +0 -714
- pocketshell-0.5.3/tests/test_agents_kind_target.py +0 -280
- pocketshell-0.5.3/tests/test_agents_source_target.py +0 -426
- pocketshell-0.5.3/tests/test_jobs.py +0 -896
- pocketshell-0.5.3/tests/test_qr_share.py +0 -327
- pocketshell-0.5.3/tests/test_resume.py +0 -558
- pocketshell-0.5.3/tests/test_send.py +0 -2715
- pocketshell-0.5.3/tests/test_session_enum.py +0 -144
- pocketshell-0.5.3/tests/test_session_enum_schema2.py +0 -1226
- pocketshell-0.5.3/tests/test_sessions.py +0 -448
- pocketshell-0.5.3/tests/test_sessions_attach.py +0 -652
- pocketshell-0.5.3/tests/test_sessions_create_routing.py +0 -1284
- pocketshell-0.5.3/tests/test_sessions_kill.py +0 -748
- pocketshell-0.5.3/tests/test_ssh_fixture_penalties.py +0 -1009
- pocketshell-0.5.3/tests/test_watch_ci.py +0 -948
- pocketshell-0.5.3/tests/test_watch_ci_cancelled.py +0 -745
- {pocketshell-0.5.3 → pocketshell-0.5.5}/.gitignore +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/scheduler/README.md +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/__main__.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/github.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/logs.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/profiles.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/prune_attachments.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/push.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/repos.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/serve.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/usage.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/usage_capture.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/src/pocketshell/usage_reset.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/__init__.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/data/quse-0.0.11-usage.json +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/data/quse-0.0.13-usage.json +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/data/quse-0.0.15-usage.json +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/data/quse-0.0.15-usage.ndjson +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/data/quse-0.0.15-usage.provenance.txt +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/data/quse-0.0.9-usage.json +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/data/sshd_anchor/Dockerfile.from-unanchored-base +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/fixtures/aplexer/snapshot-agent.json +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/fixtures/aplexer/snapshot-crashed-start.json +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/fixtures/aplexer/snapshot-liveness-mix.json +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/fixtures/aplexer/snapshot-mid-create.json +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/fixtures/aplexer/snapshot-post-kill-window.json +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/fixtures/aplexer/snapshot-reported-state.json +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/fixtures/aplexer/snapshot.json +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_agent_log.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_aplexer_contract.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_cli.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_env.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_github.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_logs.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_profiles.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_prune_attachments.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_push.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_repos.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_serve.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_usage_capture.py +0 -0
- {pocketshell-0.5.3 → pocketshell-0.5.5}/tests/test_usage_reset.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.
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pocketshell
|
|
3
|
+
Version: 0.5.5
|
|
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
|
+
Author: Alexey Grigorev
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agents,pocketshell,ssh,usage
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development
|
|
20
|
+
Classifier: Topic :: System :: Monitoring
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: aplexer==0.1.5; sys_platform == 'linux'
|
|
23
|
+
Requires-Dist: click>=8.2.0
|
|
24
|
+
Requires-Dist: google-auth>=2.0.0
|
|
25
|
+
Requires-Dist: pyyaml>=6.0
|
|
26
|
+
Requires-Dist: quse==0.0.15
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=8.4.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.15.0; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# pocketshell
|
|
33
|
+
|
|
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
|
|
39
|
+
host and uses its subcommands for usage, aplexer session lifecycle, agent
|
|
40
|
+
conversations, repository discovery, environment files, hooks,
|
|
41
|
+
logs, and daemon lifecycle checks.
|
|
42
|
+
|
|
43
|
+
## Durable workspaces
|
|
44
|
+
|
|
45
|
+
The Quiet workspace-first client uses the host-side workspace membership
|
|
46
|
+
contract before it has any live session to enumerate:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
pocketshell workspaces list --host <host> --json
|
|
50
|
+
pocketshell workspaces add <path> --host <host> --json
|
|
51
|
+
pocketshell workspaces remove <path> --host <host> --json
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Membership is stored in the existing private tree registry. Each entry has a
|
|
55
|
+
canonical absolute `path` for identity and a separate `display_path` for the
|
|
56
|
+
path spelling shown in the UI. Adding or removing the same path repeatedly is
|
|
57
|
+
safe, and `list` retains empty workspaces.
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
The recommended path is `uv tool install`, which lands the binary on PATH
|
|
62
|
+
under `~/.local/bin/`:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv tool install pocketshell
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
For local development from a clone:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
cd pocketshell-cli
|
|
72
|
+
uv venv
|
|
73
|
+
uv pip install -e .
|
|
74
|
+
pocketshell --help
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`pipx install pocketshell` works the same way for users who prefer
|
|
78
|
+
pipx. Both install paths produce a `pocketshell` binary that the
|
|
79
|
+
PocketShell app's bootstrap probe detects.
|
|
80
|
+
|
|
81
|
+
## Usage
|
|
82
|
+
|
|
83
|
+
Top-level commands in the current helper:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
pocketshell usage [provider] [--json] # provider quota / usage
|
|
87
|
+
pocketshell sessions list --json # schema-3 aplexer session rows
|
|
88
|
+
pocketshell sessions create NAME --json # create or reuse a session
|
|
89
|
+
pocketshell sessions attach NAME # attach to a live session
|
|
90
|
+
pocketshell sessions kill NAME --json # stop and reap a session
|
|
91
|
+
pocketshell agent-log ... # agent conversation logs
|
|
92
|
+
pocketshell repos list ... # local / GitHub repositories
|
|
93
|
+
pocketshell github status [--json] # gh install / auth state
|
|
94
|
+
pocketshell env ... # .env / .envrc management
|
|
95
|
+
pocketshell hooks ... # Claude/Codex/OpenCode hooks
|
|
96
|
+
pocketshell logs ... # server-side trace sink
|
|
97
|
+
pocketshell daemon ... # IPC daemon lifecycle
|
|
98
|
+
pocketshell serve --dir PATH [--port N] # foreground static HTTP server
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Run `pocketshell --help` or `pocketshell <command> --help` for the live
|
|
102
|
+
flag set. Some parity subcommands still proxy through the existing host
|
|
103
|
+
tools internally so their output remains byte-identical to what the app
|
|
104
|
+
already parses.
|
|
105
|
+
|
|
106
|
+
### `pocketshell sessions`
|
|
107
|
+
|
|
108
|
+
The session group is deliberately aplexer-only. `list`, `create`, `attach`, and
|
|
109
|
+
`kill` all use the bundled `a` executable resolved next to the installed
|
|
110
|
+
PocketShell interpreter; a missing or unusable aplexer is reported as an error
|
|
111
|
+
instead of an empty session list.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pocketshell sessions list --json
|
|
115
|
+
pocketshell sessions create my-session --cwd ~/git/project --mem none --json
|
|
116
|
+
pocketshell sessions attach my-session
|
|
117
|
+
pocketshell sessions kill my-session --json
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The list and lifecycle responses use schema 3. Rows carry the aplexer id,
|
|
121
|
+
workspace, tag, phase, attachment state, and agent metadata; there is no
|
|
122
|
+
backend discriminator or legacy session socket. Create is idempotent for the
|
|
123
|
+
same workspace and tag. Kill stops the workload and reaps the aplexer record
|
|
124
|
+
before returning its JSON result.
|
|
125
|
+
|
|
126
|
+
### `pocketshell usage`
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
pocketshell usage # human-readable lines, one per provider
|
|
130
|
+
pocketshell usage --json # machine-readable JSON (consumed by the app)
|
|
131
|
+
pocketshell usage codex # filter to a single provider
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The output shape is byte-identical to `quse [provider] [--json]`. When
|
|
135
|
+
the IPC daemon is running, `usage --json` dispatches `usage.fetch` over
|
|
136
|
+
the daemon socket and uses the daemon's short TTL cache; otherwise an
|
|
137
|
+
absent/unavailable daemon or explicitly supported method skew falls through
|
|
138
|
+
to the one-shot subprocess path. Timeout, malformed-response, and
|
|
139
|
+
daemon-internal failures are surfaced instead of being retried locally.
|
|
140
|
+
|
|
141
|
+
All daemon-backed wrappers (`usage`, `repos`, `tree`, `sessions`, and
|
|
142
|
+
`agents kind`) use one typed fallback boundary. It emits the safe
|
|
143
|
+
`pocketshell.daemon_call` event with `reason`, `method`, `phase`, RPC code, and
|
|
144
|
+
available CLI/daemon versions. It never logs RPC parameters or command output.
|
|
145
|
+
|
|
146
|
+
If `quse` is not installed, `pocketshell usage` exits with code 127 and
|
|
147
|
+
prints an install hint to stderr.
|
|
148
|
+
|
|
149
|
+
### `pocketshell repos list`
|
|
150
|
+
|
|
151
|
+
Enumerate git repositories — either cloned on this host (`--local`) or
|
|
152
|
+
owned by the authenticated GitHub user (`--remote`). The two modes
|
|
153
|
+
share one unified JSON schema so a future merged view can interleave
|
|
154
|
+
them transparently.
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
pocketshell repos list --local # scan ~/git for clones (human)
|
|
158
|
+
pocketshell repos list --local --json # same, JSON output
|
|
159
|
+
pocketshell repos list --remote --json # via owner-only `gh api user/repos`
|
|
160
|
+
pocketshell repos list --remote --limit 20
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Schema (every entry):
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"owner": "alexeygrigorev", // null when remote URL is non-GitHub
|
|
168
|
+
"name": "pocketshell", // local dir basename, or GH repo name
|
|
169
|
+
"full_name": "PocketShell-io/pocketshell", // null when owner unknown
|
|
170
|
+
"local": { // populated by --local scans
|
|
171
|
+
"path": "/home/alexey/git/pocketshell",
|
|
172
|
+
"head": "main"
|
|
173
|
+
},
|
|
174
|
+
"remote": { // populated by --remote scans
|
|
175
|
+
"default_branch": "main",
|
|
176
|
+
"html_url": "https://github.com/PocketShell-io/pocketshell",
|
|
177
|
+
"ssh_url": "git@github.com:PocketShell-io/pocketshell.git",
|
|
178
|
+
"updated_at": "2026-05-27T12:00:00Z"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`--local` scans `~/git` by default (override with one or more `--root`
|
|
184
|
+
flags or the colon-separated `POCKETSHELL_REPOS_ROOTS` env var) and
|
|
185
|
+
populates `local` for every entry. `owner` and `full_name` are
|
|
186
|
+
best-effort from the parsed `remote.origin.url`; non-GitHub remotes
|
|
187
|
+
leave them `null`.
|
|
188
|
+
|
|
189
|
+
`--remote` delegates to `gh api 'user/repos?affiliation=owner&sort=updated' --paginate --slurp`.
|
|
190
|
+
Requires `gh` on PATH (`apt install gh` on Debian/Ubuntu,
|
|
191
|
+
`brew install gh` on macOS) authenticated via
|
|
192
|
+
`gh auth login -s repo:read`. Sorted by `updated_at` descending so the
|
|
193
|
+
picker shows the most-recently-touched repos first. Missing `gh` exits
|
|
194
|
+
127 with an install hint; a non-zero `gh` exit (auth missing,
|
|
195
|
+
rate-limit, etc.) propagates the exit code and stderr verbatim.
|
|
196
|
+
|
|
197
|
+
With neither flag, defaults to `--local` and prints a one-line
|
|
198
|
+
discoverability hint mentioning `--remote`.
|
|
199
|
+
|
|
200
|
+
Daemon mode caches `repos.list_local` for 10 s and `repos.list_remote`
|
|
201
|
+
for 5 min. `--no-daemon` forces the in-process path; `--no-cache`
|
|
202
|
+
forces the daemon to re-run upstream on the next call.
|
|
203
|
+
|
|
204
|
+
### `pocketshell github status`
|
|
205
|
+
|
|
206
|
+
Reports whether the GitHub CLI (`gh`) is installed and authenticated, as
|
|
207
|
+
structured JSON the app consumes to gate GitHub features and prompt the
|
|
208
|
+
user to configure `gh` when it is missing (epic #644, slice #645).
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
pocketshell github status # human-readable summary
|
|
212
|
+
pocketshell github status --json # machine-readable JSON (consumed by the app)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Schema:
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"installed": true, // shutil.which("gh") found the binary
|
|
220
|
+
"authenticated": true, // `gh auth status` exited 0
|
|
221
|
+
"account": "alexeygrigorev", // logged-in username, or null
|
|
222
|
+
"hint": null // actionable hint when something is missing
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The command always exits 0 — "gh missing" and "not authenticated" are
|
|
227
|
+
normal, reportable states (not probe failures), so the app can poll the
|
|
228
|
+
status without treating it as an error. When `gh` is absent the `hint` tells
|
|
229
|
+
the user to install it and run `gh auth login`; when present but
|
|
230
|
+
unauthenticated the `hint` tells them to run `gh auth login`. The only
|
|
231
|
+
network access is whatever `gh auth status` itself performs (a token-validity
|
|
232
|
+
check); the command does NOT call the GitHub API.
|
|
233
|
+
|
|
234
|
+
### `pocketshell serve`
|
|
235
|
+
|
|
236
|
+
Serve a folder over HTTP for a client-owned SSH port forward:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
pocketshell serve --dir /path/to/site
|
|
240
|
+
pocketshell serve --dir /path/to/site --port 8080 --bind 127.0.0.1
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The server binds `127.0.0.1` by default. Omitting `--port` (or passing
|
|
244
|
+
`--port 0`) lets the OS select a free port; after binding, stdout contains
|
|
245
|
+
exactly one stable JSON line with the selected port:
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{"port":43123}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The process stays in the foreground so the caller owns its lifetime: keep the
|
|
252
|
+
SSH exec channel alive while the site is needed and terminate that process
|
|
253
|
+
when the view closes or the connection is lost. There is no detached server
|
|
254
|
+
registry or `--stop` command in this contract. HTTP access logs and errors go
|
|
255
|
+
to stderr, keeping stdout parseable.
|
|
256
|
+
|
|
257
|
+
Requests serve static files with stdlib MIME detection. A directory resolves
|
|
258
|
+
to its `index.html` when present; paths are resolved before the containment
|
|
259
|
+
check, so parent traversal and symlinks that leave the selected directory are
|
|
260
|
+
rejected rather than served.
|
|
261
|
+
|
|
262
|
+
### `pocketshell hooks`
|
|
263
|
+
|
|
264
|
+
Installs agent **stop / idle-detection** hooks across Claude Code,
|
|
265
|
+
Codex, and OpenCode and normalizes their events into a single
|
|
266
|
+
append-only JSONL bus the app can read back. Server-side only;
|
|
267
|
+
integration only — no "tell the agent to continue" action yet (deferred;
|
|
268
|
+
see issue #267 and locked decision **D26** in `docs/decisions.md`).
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
pocketshell hooks install [--engine claude|codex|opencode|all] # default: all
|
|
272
|
+
pocketshell hooks status [--engine ...] [--json] [--last N]
|
|
273
|
+
pocketshell hooks events [--since ISO8601] [--limit N] [--json]
|
|
274
|
+
pocketshell hooks uninstall [--engine ...]
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
`install` is **non-destructive — it merges, it never clobbers**:
|
|
278
|
+
|
|
279
|
+
- **Claude Code** — adds a `{type: "command", command: "python3 <handler>"}`
|
|
280
|
+
entry under the `Stop`, `SubagentStop`, and `Notification` hook events
|
|
281
|
+
in `~/.claude/settings.json`, only when absent. All other top-level
|
|
282
|
+
keys and any pre-existing user hooks are preserved.
|
|
283
|
+
- **Codex** — sets the top-level `notify` program in `~/.codex/config.toml`
|
|
284
|
+
to our handler (Codex hooks do not fire under `codex exec`, so `notify`
|
|
285
|
+
is the headless-safe signal). If `notify` is already set to something
|
|
286
|
+
else, it warns and **skips** rather than overwriting. The rest of the
|
|
287
|
+
TOML is preserved.
|
|
288
|
+
- **OpenCode** — drops a `pocketshell-idle-signal.js` plugin into
|
|
289
|
+
`~/.config/opencode/plugin/` without disturbing other plugins.
|
|
290
|
+
|
|
291
|
+
`install` is idempotent (running twice adds nothing new). Generated handler
|
|
292
|
+
scripts and `.installed` ownership metadata are durable data under
|
|
293
|
+
`$XDG_DATA_HOME/pocketshell/hooks/` (default
|
|
294
|
+
`~/.local/share/pocketshell/hooks/`). The volatile event bus stays at
|
|
295
|
+
`$XDG_CACHE_HOME/pocketshell/hooks/events.jsonl` (default
|
|
296
|
+
`~/.cache/pocketshell/hooks/events.jsonl`). A routine cache cleanup therefore
|
|
297
|
+
starts a fresh bus without breaking the absolute commands retained by Claude or
|
|
298
|
+
Codex; the next event recreates the cache directory and bus.
|
|
299
|
+
|
|
300
|
+
Path overrides are intentionally separate:
|
|
301
|
+
|
|
302
|
+
- `$POCKETSHELL_HOOKS_HANDLER_DIR` overrides the durable generated-handler dir.
|
|
303
|
+
- `$POCKETSHELL_HOOKS_EVENTS_FILE` overrides the event bus file.
|
|
304
|
+
- The historical `$POCKETSHELL_HOOKS_DIR` remains an alias for the **handler
|
|
305
|
+
directory only** when the new handler variable is unset. It no longer moves
|
|
306
|
+
the bus. Use both new variables and rerun `hooks install` when both paths need
|
|
307
|
+
customization.
|
|
308
|
+
|
|
309
|
+
Each generated handler embeds the resolved bus path and appends a normalized
|
|
310
|
+
record `{ts, engine, state, source, session_id, cwd, ...}` there. `install`
|
|
311
|
+
also migrates PocketShell-owned Claude/Codex commands from the old cache path to
|
|
312
|
+
the durable path even when cache cleanup already removed the old scripts;
|
|
313
|
+
foreign hooks and foreign Codex `notify` programs remain untouched.
|
|
314
|
+
|
|
315
|
+
**Per-engine uninstall** (`pocketshell hooks uninstall`) removes only what
|
|
316
|
+
we added and is idempotent:
|
|
317
|
+
|
|
318
|
+
- **Claude Code** — drops our command group from each hook event; an
|
|
319
|
+
event key (and the top-level `hooks` object) is deleted only if we
|
|
320
|
+
created it and it ends up empty. A user's pre-existing hooks always
|
|
321
|
+
survive, so a pre-populated `settings.json` comes back
|
|
322
|
+
byte-equivalent for the unrelated parts.
|
|
323
|
+
- **Codex** — removes the top-level `notify` line only when it still
|
|
324
|
+
points at our handler. A `notify` the user pointed elsewhere is left
|
|
325
|
+
alone.
|
|
326
|
+
- **OpenCode** — deletes our plugin file; other plugins and the dir
|
|
327
|
+
itself are left in place.
|
|
328
|
+
|
|
329
|
+
The event bus (`events.jsonl`) is preserved on uninstall so already-emitted
|
|
330
|
+
records stay readable; only PocketShell-owned current/legacy config entries,
|
|
331
|
+
generated executables, and durable ownership metadata are cleaned up.
|
|
332
|
+
|
|
333
|
+
## Development
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
cd pocketshell-cli
|
|
337
|
+
uv venv
|
|
338
|
+
uv pip install -e ".[dev]"
|
|
339
|
+
uv run pytest
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Or via the dependency-group:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
uv sync --group dev
|
|
346
|
+
uv run pytest
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
The tests stub subprocess boundaries and the bundled aplexer resolver so
|
|
350
|
+
they run in seconds without invoking a real host session.
|
|
351
|
+
|
|
352
|
+
## Release flow
|
|
353
|
+
|
|
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:
|
|
357
|
+
|
|
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`.
|
|
365
|
+
|
|
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):
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
uv build
|
|
392
|
+
uv run --with twine twine upload dist/*
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Why a unified CLI?
|
|
396
|
+
|
|
397
|
+
The PocketShell app previously depended on multiple host-side tools.
|
|
398
|
+
That meant separate installs to keep up to date, separate probes to
|
|
399
|
+
surface failures from, and multiple PATH-discovery edge cases. A single
|
|
400
|
+
`pocketshell` binary collapses that app-facing contract into one install,
|
|
401
|
+
one probe, and one bootstrap row. The Android bootstrap probe now derives
|
|
402
|
+
PATH from the user's shell rc and prepends `$HOME/.local/bin`,
|
|
403
|
+
`$HOME/bin`, and `$HOME/.cargo/bin` before probing, so cloned-repo or
|
|
404
|
+
venv installs can be discovered without a manual app-side PATH field.
|