pocketshell 0.4.12__tar.gz → 0.4.14__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 (57) hide show
  1. {pocketshell-0.4.12 → pocketshell-0.4.14}/PKG-INFO +1 -1
  2. {pocketshell-0.4.12 → pocketshell-0.4.14}/pyproject.toml +1 -1
  3. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/agents.py +67 -15
  4. pocketshell-0.4.14/src/pocketshell/cards.py +663 -0
  5. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/cli.py +5 -0
  6. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/tree.py +23 -0
  7. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_agents.py +262 -7
  8. pocketshell-0.4.14/tests/test_cards.py +391 -0
  9. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_tree.py +31 -1
  10. {pocketshell-0.4.12 → pocketshell-0.4.14}/uv.lock +2 -2
  11. {pocketshell-0.4.12 → pocketshell-0.4.14}/.gitignore +0 -0
  12. {pocketshell-0.4.12 → pocketshell-0.4.14}/README.md +0 -0
  13. {pocketshell-0.4.12 → pocketshell-0.4.14}/scheduler/README.md +0 -0
  14. {pocketshell-0.4.12 → pocketshell-0.4.14}/scheduler/pocketshell-usage-capture.service +0 -0
  15. {pocketshell-0.4.12 → pocketshell-0.4.14}/scheduler/pocketshell-usage-capture.timer +0 -0
  16. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/__init__.py +0 -0
  17. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/__main__.py +0 -0
  18. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/agent_log.py +0 -0
  19. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/agents_kind.py +0 -0
  20. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/cgroup_agents.py +0 -0
  21. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/daemon.py +0 -0
  22. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/env.py +0 -0
  23. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/github.py +0 -0
  24. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/hooks.py +0 -0
  25. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/jobs.py +0 -0
  26. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/logs.py +0 -0
  27. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/profiles.py +0 -0
  28. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/prune_attachments.py +0 -0
  29. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/push.py +0 -0
  30. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/qr_share.py +0 -0
  31. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/repos.py +0 -0
  32. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/resume.py +0 -0
  33. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/sessions.py +0 -0
  34. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/usage.py +0 -0
  35. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/usage_capture.py +0 -0
  36. {pocketshell-0.4.12 → pocketshell-0.4.14}/src/pocketshell/usage_reset.py +0 -0
  37. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/__init__.py +0 -0
  38. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_agent_log.py +0 -0
  39. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_agents_kind.py +0 -0
  40. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_cgroup_agents.py +0 -0
  41. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_cli.py +0 -0
  42. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_daemon.py +0 -0
  43. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_env.py +0 -0
  44. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_github.py +0 -0
  45. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_hooks.py +0 -0
  46. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_jobs.py +0 -0
  47. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_logs.py +0 -0
  48. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_profiles.py +0 -0
  49. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_prune_attachments.py +0 -0
  50. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_push.py +0 -0
  51. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_qr_share.py +0 -0
  52. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_repos.py +0 -0
  53. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_resume.py +0 -0
  54. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_sessions.py +0 -0
  55. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_usage.py +0 -0
  56. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_usage_capture.py +0 -0
  57. {pocketshell-0.4.12 → pocketshell-0.4.14}/tests/test_usage_reset.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pocketshell
3
- Version: 0.4.12
3
+ Version: 0.4.14
4
4
  Summary: Unified server-side Python utility for the PocketShell Android client.
5
5
  Project-URL: Homepage, https://github.com/alexeygrigorev/pocketshell
6
6
  Project-URL: Issues, https://github.com/alexeygrigorev/pocketshell/issues
@@ -8,7 +8,7 @@ name = "pocketshell"
8
8
  # scripts/check-pypi-version.sh enforces this; .github/workflows/build.yml
9
9
  # runs that check before publishing to PyPI. See
10
10
  # tools/pocketshell/README.md ("Release flow") for the bump procedure.
11
- version = "0.4.12"
11
+ version = "0.4.14"
12
12
  description = "Unified server-side Python utility for the PocketShell Android client."
13
13
  readme = "README.md"
14
14
  requires-python = ">=3.11"
@@ -359,6 +359,7 @@ def record_agent_kind(
359
359
  kind: str,
360
360
  env: Optional[dict[str, str]] = None,
361
361
  runner=None,
362
+ profile: Optional[str] = None,
362
363
  ) -> bool:
363
364
  """Record the launched agent ``kind`` as a per-session tmux user option.
364
365
 
@@ -371,10 +372,25 @@ def record_agent_kind(
371
372
  change. The client reads it back through its session enumeration
372
373
  (``tmux list-sessions -F '…#{@ps_agent_kind}'``).
373
374
 
374
- The option is session-scoped (not global): ``tmux set-option`` without
375
+ ``profile`` (issue #858) is the human label of the *non-default* profile
376
+ the agent was launched with — e.g. ``"Claude (Z.AI)"`` for a z.ai Claude
377
+ session, so the tree can distinguish it from a default Anthropic Claude.
378
+ It is the same launch-time-recordable dimension as the kind (the selected
379
+ profile name is known here, before ``os.execvpe``; #826 record-at-start
380
+ hard-cut — no detection/parse path). When set, it is written as the
381
+ per-session ``@ps_agent_profile`` user option alongside ``@ps_agent_kind``.
382
+ A default / no-profile launch passes ``None`` and the option is
383
+ RECONCILED to the current launch by UNSETTING it
384
+ (``tmux set-option -uq @ps_agent_profile``), so a session previously
385
+ launched with a non-default profile and then relaunched as a default
386
+ agent in the SAME tmux session does not keep the stale profile label
387
+ (issue #889). ``@ps_agent_kind`` is always overwritten on every launch
388
+ so it has no equivalent stale hazard.
389
+
390
+ The options are session-scoped (not global): ``tmux set-option`` without
375
391
  ``-g`` sets it on the current session, which is the session the agent
376
392
  was launched into. tmux session options persist for the life of the
377
- session, so the recorded kind survives reconnect / app restart /
393
+ session, so the recorded kind/profile survives reconnect / app restart /
378
394
  app-kill / reinstall — exactly the durability the epic requires.
379
395
 
380
396
  No-op (returns ``False``) when not running inside tmux (``$TMUX``
@@ -399,6 +415,27 @@ def record_agent_kind(
399
415
  ["tmux", "set-option", "@ps_agent_kind", kind],
400
416
  check=False,
401
417
  )
418
+ if profile:
419
+ # A non-default profile is recorded so the tree shows its label.
420
+ runner(
421
+ ["tmux", "set-option", "@ps_agent_profile", profile],
422
+ check=False,
423
+ )
424
+ else:
425
+ # A default / no-profile launch must RECONCILE the option to the
426
+ # current launch by UNSETTING it (issue #889). tmux session
427
+ # options persist for the life of the session, so a session
428
+ # launched once with a non-default profile (e.g. z.ai) and then
429
+ # relaunched as a default agent in the SAME session would keep the
430
+ # stale @ps_agent_profile and be mislabelled in the tree. The
431
+ # ``-u`` unsets the session option; ``-q`` makes unsetting an
432
+ # already-absent option a no-op (a fresh default session stays
433
+ # clean). The kind itself (set above) is always overwritten, so it
434
+ # has no equivalent stale hazard.
435
+ runner(
436
+ ["tmux", "set-option", "-uq", "@ps_agent_profile"],
437
+ check=False,
438
+ )
402
439
  except Exception:
403
440
  # Recording the kind is best-effort; never block the launch on it.
404
441
  return False
@@ -413,6 +450,7 @@ def launch_agent(
413
450
  skip_permissions: bool,
414
451
  config_dir: Optional[str],
415
452
  extra_env: Optional[dict[str, str]] = None,
453
+ profile: Optional[str] = None,
416
454
  execvpe=None,
417
455
  record_kind=None,
418
456
  ) -> None:
@@ -422,6 +460,11 @@ def launch_agent(
422
460
  #732); it layers onto the launch environment under the #703 provider
423
461
  strip (see :func:`build_env`).
424
462
 
463
+ ``profile`` (issue #858) is the human label of the *non-default* profile
464
+ the launch used (``None`` for the engine default). It is recorded
465
+ alongside the kind as the ``@ps_agent_profile`` tmux user option so the
466
+ session tree can distinguish e.g. a z.ai Claude from a default Claude.
467
+
425
468
  ``execvpe`` is injected so tests can assert the exact call without
426
469
  actually replacing the process. When ``None`` (production) it resolves
427
470
  to :func:`os.execvpe` *at call time* — looking it up on the module's
@@ -473,7 +516,7 @@ def launch_agent(
473
516
  # this process (epic #821 Workstream A). Use this wrapper's own
474
517
  # environment (os.environ) for the TMUX detection — `env` is the
475
518
  # provider-stripped launch env that does not necessarily carry $TMUX.
476
- record_kind(kind, dict(os.environ))
519
+ record_kind(kind, dict(os.environ), profile=profile)
477
520
 
478
521
  # Replace this process with the agent so it owns the pty cleanly.
479
522
  execvpe(argv[0], argv, env)
@@ -484,17 +527,22 @@ def _resolve_config_dir(
484
527
  kind: str,
485
528
  config_dir: Optional[str],
486
529
  profile: Optional[str],
487
- ) -> tuple[Optional[str], dict[str, str]]:
488
- """Resolve config dir + extra env from ``--config-dir`` / ``--profile``.
530
+ ) -> tuple[Optional[str], dict[str, str], Optional[str]]:
531
+ """Resolve config dir + extra env + profile label from the launch flags.
489
532
 
490
- Returns ``(config_dir, extra_env)``. ``--config-dir`` and ``--profile``
491
- are mutually exclusive (passing both is an error). When ``--profile`` is
492
- given, it resolves the named host profile (via
533
+ Returns ``(config_dir, extra_env, profile_label)``. ``--config-dir`` and
534
+ ``--profile`` are mutually exclusive (passing both is an error). When
535
+ ``--profile`` is given, it resolves the named host profile (via
493
536
  :func:`pocketshell.profiles.resolve_profile`) to its ``config_dir`` AND
494
- its ``env:`` block (issue #732) — an unknown profile is a clear error. A
495
- default profile resolves to ``None`` config dir (the engine's built-in
496
- location); ``--config-dir`` carries no profile env. Omitting both flags
497
- returns ``(None, {})``.
537
+ its ``env:`` block (issue #732) — an unknown profile is a clear error.
538
+
539
+ ``profile_label`` (issue #858) is the resolved profile's human ``name``
540
+ for a *non-default* profile (e.g. ``"Claude (Z.AI)"``), so the session
541
+ tree can tell a z.ai Claude apart from a default Claude. The engine's
542
+ default profile, ``--config-dir`` (which carries no named profile), and
543
+ omitting both flags all resolve ``profile_label`` to ``None`` — so a
544
+ default launch clears any stale ``@ps_agent_profile`` option (issue
545
+ #889) rather than leaving a profile label behind.
498
546
  """
499
547
  if config_dir is not None and profile is not None:
500
548
  click.echo(
@@ -504,7 +552,7 @@ def _resolve_config_dir(
504
552
  )
505
553
  ctx.exit(2)
506
554
  if profile is None:
507
- return config_dir, {}
555
+ return config_dir, {}, None
508
556
 
509
557
  # Lazy import keeps the agent launch path from importing yaml unless a
510
558
  # profile is actually requested.
@@ -519,7 +567,10 @@ def _resolve_config_dir(
519
567
  err=True,
520
568
  )
521
569
  ctx.exit(2)
522
- return resolved.config_dir, dict(resolved.env)
570
+ # Only a non-default profile is surfaced as a label; the default profile
571
+ # is the plain kind (no spurious chip in the tree).
572
+ label = None if resolved.default else resolved.name
573
+ return resolved.config_dir, dict(resolved.env), label
523
574
 
524
575
 
525
576
  def _make_agent_command(kind: str):
@@ -575,7 +626,7 @@ def _make_agent_command(kind: str):
575
626
  config_dir: Optional[str],
576
627
  profile: Optional[str],
577
628
  ) -> None:
578
- config_dir, extra_env = _resolve_config_dir(
629
+ config_dir, extra_env, profile_label = _resolve_config_dir(
579
630
  ctx, kind, config_dir, profile
580
631
  )
581
632
  launch_agent(
@@ -585,6 +636,7 @@ def _make_agent_command(kind: str):
585
636
  skip_permissions=skip_permissions,
586
637
  config_dir=config_dir,
587
638
  extra_env=extra_env,
639
+ profile=profile_label,
588
640
  )
589
641
 
590
642
  return _cmd