pocketshell 0.5.7__tar.gz → 0.5.8__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 (93) hide show
  1. {pocketshell-0.5.7 → pocketshell-0.5.8}/PKG-INFO +1 -1
  2. {pocketshell-0.5.7 → pocketshell-0.5.8}/pyproject.toml +1 -1
  3. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/profiles.py +43 -0
  4. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/sessions.py +6 -1
  5. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_profiles.py +58 -0
  6. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_sessions_create_routing.py +62 -0
  7. {pocketshell-0.5.7 → pocketshell-0.5.8}/.gitignore +0 -0
  8. {pocketshell-0.5.7 → pocketshell-0.5.8}/LICENSE +0 -0
  9. {pocketshell-0.5.7 → pocketshell-0.5.8}/README.md +0 -0
  10. {pocketshell-0.5.7 → pocketshell-0.5.8}/scheduler/README.md +0 -0
  11. {pocketshell-0.5.7 → pocketshell-0.5.8}/scheduler/pocketshell-usage-capture.service +0 -0
  12. {pocketshell-0.5.7 → pocketshell-0.5.8}/scheduler/pocketshell-usage-capture.timer +0 -0
  13. {pocketshell-0.5.7 → pocketshell-0.5.8}/scripts/check-cgroup-cap-proof.sh +0 -0
  14. {pocketshell-0.5.7 → pocketshell-0.5.8}/scripts/check-product-tmux-absent.sh +0 -0
  15. {pocketshell-0.5.7 → pocketshell-0.5.8}/scripts/check-python-ci-lock.sh +0 -0
  16. {pocketshell-0.5.7 → pocketshell-0.5.8}/scripts/sync-readme-exit-codes.py +0 -0
  17. {pocketshell-0.5.7 → pocketshell-0.5.8}/scripts/test-usage-missing-window-mutation.sh +0 -0
  18. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/__init__.py +0 -0
  19. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/__main__.py +0 -0
  20. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/agent_card_push.py +0 -0
  21. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/agent_log.py +0 -0
  22. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/agents.py +0 -0
  23. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/agents_kind.py +0 -0
  24. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/aplexer.py +0 -0
  25. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/cards.py +0 -0
  26. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/cgroup_agents.py +0 -0
  27. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/cli.py +0 -0
  28. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/daemon.py +0 -0
  29. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/engines.py +0 -0
  30. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/env.py +0 -0
  31. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/github.py +0 -0
  32. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/hooks.py +0 -0
  33. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/logs.py +0 -0
  34. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/memcap.py +0 -0
  35. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/prune_attachments.py +0 -0
  36. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/push.py +0 -0
  37. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/repos.py +0 -0
  38. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/serve.py +0 -0
  39. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/session_enum.py +0 -0
  40. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/tree.py +0 -0
  41. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/usage.py +0 -0
  42. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/usage_capture.py +0 -0
  43. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/usage_reset.py +0 -0
  44. {pocketshell-0.5.7 → pocketshell-0.5.8}/src/pocketshell/workspaces.py +0 -0
  45. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/__init__.py +0 -0
  46. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/conftest.py +0 -0
  47. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/data/quse-0.0.11-usage.json +0 -0
  48. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/data/quse-0.0.13-usage.json +0 -0
  49. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/data/quse-0.0.15-usage.json +0 -0
  50. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/data/quse-0.0.15-usage.ndjson +0 -0
  51. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/data/quse-0.0.15-usage.provenance.txt +0 -0
  52. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/data/quse-0.0.9-usage.json +0 -0
  53. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/data/sshd_anchor/Dockerfile.from-unanchored-base +0 -0
  54. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/fixtures/aplexer/snapshot-agent.json +0 -0
  55. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/fixtures/aplexer/snapshot-crashed-start.json +0 -0
  56. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/fixtures/aplexer/snapshot-liveness-mix.json +0 -0
  57. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/fixtures/aplexer/snapshot-mid-create.json +0 -0
  58. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/fixtures/aplexer/snapshot-post-kill-window.json +0 -0
  59. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/fixtures/aplexer/snapshot-reported-state.json +0 -0
  60. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/fixtures/aplexer/snapshot.json +0 -0
  61. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_agent_card_push.py +0 -0
  62. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_agent_log.py +0 -0
  63. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_agents.py +0 -0
  64. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_agents_kind.py +0 -0
  65. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_aplexer_contract.py +0 -0
  66. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_aplexer_resolution.py +0 -0
  67. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_cards.py +0 -0
  68. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_cards_push_notify.py +0 -0
  69. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_cgroup_agents.py +0 -0
  70. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_cli.py +0 -0
  71. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_daemon.py +0 -0
  72. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_daemon_fallback.py +0 -0
  73. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_engines.py +0 -0
  74. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_env.py +0 -0
  75. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_github.py +0 -0
  76. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_hooks.py +0 -0
  77. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_logs.py +0 -0
  78. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_prune_attachments.py +0 -0
  79. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_push.py +0 -0
  80. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_repos.py +0 -0
  81. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_serve.py +0 -0
  82. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_session_cgroup_kernel_proof.py +0 -0
  83. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_session_enum.py +0 -0
  84. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_sessions.py +0 -0
  85. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_sessions_attach.py +0 -0
  86. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_sessions_kill.py +0 -0
  87. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_sessions_mem_cap.py +0 -0
  88. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_tree.py +0 -0
  89. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_usage.py +0 -0
  90. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_usage_capture.py +0 -0
  91. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_usage_reset.py +0 -0
  92. {pocketshell-0.5.7 → pocketshell-0.5.8}/tests/test_workspaces.py +0 -0
  93. {pocketshell-0.5.7 → pocketshell-0.5.8}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pocketshell
3
- Version: 0.5.7
3
+ Version: 0.5.8
4
4
  Summary: Unified server-side Python utility for the PocketShell Android and desktop clients.
5
5
  Project-URL: Homepage, https://github.com/PocketShell-io/pocketshell-cli
6
6
  Project-URL: Issues, https://github.com/PocketShell-io/pocketshell-cli/issues
@@ -11,7 +11,7 @@ name = "pocketshell"
11
11
  # since the extraction (PocketShell-io/pocketshell#2643): the first
12
12
  # self-governed release is 0.5.5, continuing from the last app-coupled
13
13
  # release 0.5.4 so `uv tool upgrade pocketshell` stays monotonic.
14
- version = "0.5.7"
14
+ version = "0.5.8"
15
15
  description = "Unified server-side Python utility for the PocketShell Android and desktop clients."
16
16
  readme = "README.md"
17
17
  requires-python = ">=3.11"
@@ -226,6 +226,49 @@ def _aplexer_profiles(
226
226
  return _profiles_from_aplexer_json(payload)
227
227
 
228
228
 
229
+ def resolve_aplexer_profile_arg(
230
+ name: str,
231
+ engine: Optional[str] = None,
232
+ env: Optional[dict[str, str]] = None,
233
+ ) -> Optional[str]:
234
+ """Map a client-facing profile name onto the ``a start --profile`` value.
235
+
236
+ The client picker shows the display names from ``profiles list``
237
+ (``Zcodex``, ``Claude (Z.AI)``), while ``a start --profile`` takes
238
+ aplexer's profile ids — the lowercase dir stems — matched
239
+ case-sensitively, and aplexer deliberately omits the engines' default
240
+ dirs, so the default display names (``Codex`` / ``Claude``) are unknown
241
+ to it (#2661). Resolution order:
242
+
243
+ 1. case-insensitive match on an aplexer profile id → that id;
244
+ 2. case-insensitive match on a profile's display name for ``engine``
245
+ → the aplexer id behind it;
246
+ 3. the engine's default display name → ``None`` (the caller must drop
247
+ ``--profile`` entirely);
248
+ 4. anything else → ``name`` unchanged, so aplexer produces its
249
+ authoritative ``unknown profile`` error.
250
+
251
+ Probe failure or the ``POCKETSHELL_APLEXER`` kill switches return
252
+ ``name`` unchanged (branch 4), degrading to the pre-#2661 behaviour.
253
+ """
254
+ payload = aplexer.run_json(["profiles"], env=env, feature="profiles")
255
+ if not isinstance(payload, dict):
256
+ return name
257
+ lowered = name.strip().lower()
258
+ for stem in payload:
259
+ if isinstance(stem, str) and stem.lower() == lowered:
260
+ return stem
261
+ if engine:
262
+ for stem, entry in payload.items():
263
+ if not isinstance(entry, dict) or entry.get("engine") != engine:
264
+ continue
265
+ if _display_name_for_sibling(engine, str(stem)).lower() == lowered:
266
+ return str(stem)
267
+ if _ENGINE_DEFAULT_DISPLAY.get(engine, "").lower() == lowered:
268
+ return None
269
+ return name
270
+
271
+
229
272
  def _profile_key(profile: Profile) -> tuple[str, Optional[str]]:
230
273
  """Compare native vs aplexer siblings by engine + config_dir."""
231
274
  path = profile.config_dir
@@ -15,6 +15,7 @@ import click
15
15
 
16
16
  from . import aplexer as _aplexer
17
17
  from . import memcap as _memcap
18
+ from . import profiles as _profiles
18
19
  from . import session_enum as _session_enum
19
20
 
20
21
 
@@ -366,7 +367,11 @@ def _create_on_aplexer(
366
367
  workspace=workspace,
367
368
  tag=name,
368
369
  engine=engine,
369
- profile=profile,
370
+ profile=(
371
+ _profiles.resolve_aplexer_profile_arg(profile, engine=engine)
372
+ if profile
373
+ else profile
374
+ ),
370
375
  memory_bytes=memory_bytes,
371
376
  )
372
377
  code, stdout, stderr = _run_aplexer(argv)
@@ -604,3 +604,61 @@ def test_cli_shape_unchanged_with_shadow_probe(
604
604
  assert "SECRET" not in json.dumps(entry)
605
605
  names = {p["name"] for p in parsed["profiles"]}
606
606
  assert {"Claude", "Claude (Z.AI)", "Codex"} <= names
607
+
608
+
609
+ # ---------------------------------------------------------------------------
610
+ # resolve_aplexer_profile_arg (#2661): the client picker sends display names
611
+ # ("Zcodex"), `a start --profile` takes aplexer's dir-stem ids ("zcodex"),
612
+ # and aplexer deliberately omits the engines' default dirs ("Codex").
613
+ # ---------------------------------------------------------------------------
614
+
615
+
616
+ _APLEXER_PROFILES_PAYLOAD = {
617
+ "godex": {"engine": "codex", "env": {"CODEX_HOME": "/home/u/.godex"}},
618
+ "zcodex": {"engine": "codex", "env": {"CODEX_HOME": "/home/u/.zcodex"}},
619
+ "zlaude": {"engine": "claude", "env": {"CLAUDE_CONFIG_DIR": "/home/u/.zlaude"}},
620
+ }
621
+
622
+
623
+ def test_resolve_aplexer_profile_arg_maps_display_name_to_id(tmp_path, monkeypatch):
624
+ _install_fake_a(tmp_path, monkeypatch, payload=_APLEXER_PROFILES_PAYLOAD)
625
+ assert profiles.resolve_aplexer_profile_arg("Zcodex", engine="codex") == "zcodex"
626
+
627
+
628
+ def test_resolve_aplexer_profile_arg_accepts_the_aplexer_id_verbatim(tmp_path, monkeypatch):
629
+ _install_fake_a(tmp_path, monkeypatch, payload=_APLEXER_PROFILES_PAYLOAD)
630
+ assert profiles.resolve_aplexer_profile_arg("zcodex", engine="codex") == "zcodex"
631
+
632
+
633
+ def test_resolve_aplexer_profile_arg_is_case_insensitive_on_the_id(tmp_path, monkeypatch):
634
+ _install_fake_a(tmp_path, monkeypatch, payload=_APLEXER_PROFILES_PAYLOAD)
635
+ assert profiles.resolve_aplexer_profile_arg("Godex", engine="codex") == "godex"
636
+
637
+
638
+ def test_resolve_aplexer_profile_arg_maps_the_known_alias_display_name(tmp_path, monkeypatch):
639
+ _install_fake_a(tmp_path, monkeypatch, payload=_APLEXER_PROFILES_PAYLOAD)
640
+ assert profiles.resolve_aplexer_profile_arg("Claude (Z.AI)", engine="claude") == "zlaude"
641
+
642
+
643
+ def test_resolve_aplexer_profile_arg_drops_the_engine_default_display_name(tmp_path, monkeypatch):
644
+ _install_fake_a(tmp_path, monkeypatch, payload=_APLEXER_PROFILES_PAYLOAD)
645
+ assert profiles.resolve_aplexer_profile_arg("Codex", engine="codex") is None
646
+
647
+
648
+ def test_resolve_aplexer_profile_arg_keeps_unknown_names_verbatim(tmp_path, monkeypatch):
649
+ _install_fake_a(tmp_path, monkeypatch, payload=_APLEXER_PROFILES_PAYLOAD)
650
+ assert profiles.resolve_aplexer_profile_arg("Nope", engine="codex") == "Nope"
651
+
652
+
653
+ def test_resolve_aplexer_profile_arg_degrades_to_passthrough_without_the_probe(
654
+ tmp_path, monkeypatch
655
+ ):
656
+ monkeypatch.setenv("POCKETSHELL_APLEXER_PROFILES", "0")
657
+ assert profiles.resolve_aplexer_profile_arg("Zcodex", engine="codex") == "Zcodex"
658
+
659
+
660
+ def test_resolve_aplexer_profile_arg_degrades_to_passthrough_on_probe_failure(
661
+ tmp_path, monkeypatch
662
+ ):
663
+ _install_fake_a(tmp_path, monkeypatch, exit_code=2)
664
+ assert profiles.resolve_aplexer_profile_arg("Zcodex", engine="codex") == "Zcodex"
@@ -119,3 +119,65 @@ def test_create_fails_loudly_when_aplexer_cannot_resolve(monkeypatch) -> None:
119
119
 
120
120
  assert result.exit_code == 127
121
121
  assert "could not resolve" in result.output
122
+
123
+
124
+ def _start_recording(calls: list[list[str]]):
125
+ def start(argv: list[str]):
126
+ calls.append(list(argv))
127
+ return 0, json.dumps(_record()), ""
128
+
129
+ return start
130
+
131
+
132
+ def test_create_translates_a_profile_display_name_onto_the_aplexer_id(
133
+ monkeypatch, tmp_path: Path
134
+ ) -> None:
135
+ """#2661: the picker sends display names; aplexer wants dir-stem ids."""
136
+ calls: list[list[str]] = []
137
+ monkeypatch.setattr(sessions, "_resolve_aplexer", lambda: _resolution("/fake/a"))
138
+ monkeypatch.setattr(sessions, "_aplexer_snapshot", lambda: [])
139
+ monkeypatch.setattr(sessions._memcap, "resolve_session_mem_bytes", lambda **_: 123)
140
+ monkeypatch.setattr(sessions, "_run_aplexer", _start_recording(calls))
141
+ monkeypatch.setattr(
142
+ sessions._profiles,
143
+ "resolve_aplexer_profile_arg",
144
+ lambda name, engine=None: {"Zcodex": "zcodex"}.get(name, name),
145
+ )
146
+
147
+ result = CliRunner().invoke(
148
+ sessions.sessions_group,
149
+ [
150
+ "create", "shell", "--cwd", str(tmp_path),
151
+ "--engine", "codex", "--profile", "Zcodex", "--json",
152
+ ],
153
+ )
154
+
155
+ assert result.exit_code == 0, result.output
156
+ assert calls[0][calls[0].index("--profile") + 1] == "zcodex"
157
+
158
+
159
+ def test_create_drops_the_profile_flag_for_the_engine_default_display_name(
160
+ monkeypatch, tmp_path: Path
161
+ ) -> None:
162
+ """#2661: aplexer omits default dirs, so `--profile Codex` must go away."""
163
+ calls: list[list[str]] = []
164
+ monkeypatch.setattr(sessions, "_resolve_aplexer", lambda: _resolution("/fake/a"))
165
+ monkeypatch.setattr(sessions, "_aplexer_snapshot", lambda: [])
166
+ monkeypatch.setattr(sessions._memcap, "resolve_session_mem_bytes", lambda **_: 123)
167
+ monkeypatch.setattr(sessions, "_run_aplexer", _start_recording(calls))
168
+ monkeypatch.setattr(
169
+ sessions._profiles,
170
+ "resolve_aplexer_profile_arg",
171
+ lambda name, engine=None: None if name == "Codex" else name,
172
+ )
173
+
174
+ result = CliRunner().invoke(
175
+ sessions.sessions_group,
176
+ [
177
+ "create", "shell", "--cwd", str(tmp_path),
178
+ "--engine", "codex", "--profile", "Codex", "--json",
179
+ ],
180
+ )
181
+
182
+ assert result.exit_code == 0, result.output
183
+ assert "--profile" not in calls[0]
File without changes
File without changes
File without changes
File without changes