pocketshell 0.4.13__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.13 → pocketshell-0.4.14}/PKG-INFO +1 -1
  2. {pocketshell-0.4.13 → pocketshell-0.4.14}/pyproject.toml +1 -1
  3. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/agents.py +25 -5
  4. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_agents.py +108 -17
  5. {pocketshell-0.4.13 → pocketshell-0.4.14}/uv.lock +1 -1
  6. {pocketshell-0.4.13 → pocketshell-0.4.14}/.gitignore +0 -0
  7. {pocketshell-0.4.13 → pocketshell-0.4.14}/README.md +0 -0
  8. {pocketshell-0.4.13 → pocketshell-0.4.14}/scheduler/README.md +0 -0
  9. {pocketshell-0.4.13 → pocketshell-0.4.14}/scheduler/pocketshell-usage-capture.service +0 -0
  10. {pocketshell-0.4.13 → pocketshell-0.4.14}/scheduler/pocketshell-usage-capture.timer +0 -0
  11. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/__init__.py +0 -0
  12. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/__main__.py +0 -0
  13. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/agent_log.py +0 -0
  14. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/agents_kind.py +0 -0
  15. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/cards.py +0 -0
  16. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/cgroup_agents.py +0 -0
  17. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/cli.py +0 -0
  18. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/daemon.py +0 -0
  19. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/env.py +0 -0
  20. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/github.py +0 -0
  21. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/hooks.py +0 -0
  22. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/jobs.py +0 -0
  23. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/logs.py +0 -0
  24. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/profiles.py +0 -0
  25. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/prune_attachments.py +0 -0
  26. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/push.py +0 -0
  27. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/qr_share.py +0 -0
  28. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/repos.py +0 -0
  29. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/resume.py +0 -0
  30. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/sessions.py +0 -0
  31. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/tree.py +0 -0
  32. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/usage.py +0 -0
  33. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/usage_capture.py +0 -0
  34. {pocketshell-0.4.13 → pocketshell-0.4.14}/src/pocketshell/usage_reset.py +0 -0
  35. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/__init__.py +0 -0
  36. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_agent_log.py +0 -0
  37. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_agents_kind.py +0 -0
  38. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_cards.py +0 -0
  39. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_cgroup_agents.py +0 -0
  40. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_cli.py +0 -0
  41. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_daemon.py +0 -0
  42. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_env.py +0 -0
  43. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_github.py +0 -0
  44. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_hooks.py +0 -0
  45. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_jobs.py +0 -0
  46. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_logs.py +0 -0
  47. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_profiles.py +0 -0
  48. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_prune_attachments.py +0 -0
  49. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_push.py +0 -0
  50. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_qr_share.py +0 -0
  51. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_repos.py +0 -0
  52. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_resume.py +0 -0
  53. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_sessions.py +0 -0
  54. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_tree.py +0 -0
  55. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_usage.py +0 -0
  56. {pocketshell-0.4.13 → pocketshell-0.4.14}/tests/test_usage_capture.py +0 -0
  57. {pocketshell-0.4.13 → 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.13
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.13"
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"
@@ -379,8 +379,13 @@ def record_agent_kind(
379
379
  profile name is known here, before ``os.execvpe``; #826 record-at-start
380
380
  hard-cut — no detection/parse path). When set, it is written as the
381
381
  per-session ``@ps_agent_profile`` user option alongside ``@ps_agent_kind``.
382
- A default / no-profile launch passes ``None`` and writes NO profile
383
- option, so a default session carries no spurious profile label.
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.
384
389
 
385
390
  The options are session-scoped (not global): ``tmux set-option`` without
386
391
  ``-g`` sets it on the current session, which is the session the agent
@@ -411,12 +416,26 @@ def record_agent_kind(
411
416
  check=False,
412
417
  )
413
418
  if profile:
414
- # Only a non-default profile is recorded; a default/no-profile
415
- # launch writes no option so the tree shows the plain kind.
419
+ # A non-default profile is recorded so the tree shows its label.
416
420
  runner(
417
421
  ["tmux", "set-option", "@ps_agent_profile", profile],
418
422
  check=False,
419
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
+ )
420
439
  except Exception:
421
440
  # Recording the kind is best-effort; never block the launch on it.
422
441
  return False
@@ -522,7 +541,8 @@ def _resolve_config_dir(
522
541
  tree can tell a z.ai Claude apart from a default Claude. The engine's
523
542
  default profile, ``--config-dir`` (which carries no named profile), and
524
543
  omitting both flags all resolve ``profile_label`` to ``None`` — so a
525
- default session records no ``@ps_agent_profile`` option.
544
+ default launch clears any stale ``@ps_agent_profile`` option (issue
545
+ #889) rather than leaving a profile label behind.
526
546
  """
527
547
  if config_dir is not None and profile is not None:
528
548
  click.echo(
@@ -470,11 +470,11 @@ def test_record_agent_kind_sets_session_option_inside_tmux(kind):
470
470
  runner=lambda argv, **kw: calls.append((argv, kw)),
471
471
  )
472
472
  assert ok is True
473
- assert len(calls) == 1
474
- argv, _kw = calls[0]
475
- # Session-scoped (no -g): tmux applies it to the current session, which
476
- # is the one the agent was launched into.
477
- assert argv == ["tmux", "set-option", "@ps_agent_kind", kind]
473
+ # No profile passed -> the kind is set (session-scoped, no -g), and the
474
+ # @ps_agent_profile option is reconciled away by an unset (issue #889).
475
+ argvs = [argv for argv, _kw in calls]
476
+ assert ["tmux", "set-option", "@ps_agent_kind", kind] in argvs
477
+ assert ["tmux", "set-option", "-uq", "@ps_agent_profile"] in argvs
478
478
 
479
479
 
480
480
  def test_record_agent_kind_noop_when_not_in_tmux():
@@ -594,9 +594,12 @@ def test_record_agent_kind_writes_profile_option_when_profile_set():
594
594
  ]
595
595
 
596
596
 
597
- def test_record_agent_kind_no_profile_writes_only_kind():
598
- # A default / no-profile launch records the kind but NO profile option,
599
- # so a default session shows the plain kind with no spurious chip.
597
+ def test_record_agent_kind_no_profile_clears_profile_option():
598
+ # A default / no-profile launch records the kind AND reconciles the
599
+ # @ps_agent_profile option by UNSETTING it (issue #889), so a session
600
+ # previously launched with a non-default profile cannot keep a stale
601
+ # label. ``-uq`` makes unsetting an already-absent option a no-op, so a
602
+ # fresh default session stays clean.
600
603
  calls = []
601
604
  ok = agents.record_agent_kind(
602
605
  "claude",
@@ -605,11 +608,15 @@ def test_record_agent_kind_no_profile_writes_only_kind():
605
608
  profile=None,
606
609
  )
607
610
  assert ok is True
608
- assert calls == [["tmux", "set-option", "@ps_agent_kind", "claude"]]
611
+ assert calls == [
612
+ ["tmux", "set-option", "@ps_agent_kind", "claude"],
613
+ ["tmux", "set-option", "-uq", "@ps_agent_profile"],
614
+ ]
609
615
 
610
616
 
611
- def test_record_agent_kind_empty_profile_writes_only_kind():
612
- # An empty-string profile is treated like no profile (no option written).
617
+ def test_record_agent_kind_empty_profile_clears_profile_option():
618
+ # An empty-string profile is treated like no profile: kind recorded, the
619
+ # @ps_agent_profile option unset (issue #889).
613
620
  calls = []
614
621
  agents.record_agent_kind(
615
622
  "codex",
@@ -617,7 +624,45 @@ def test_record_agent_kind_empty_profile_writes_only_kind():
617
624
  runner=lambda argv, **kw: calls.append(argv),
618
625
  profile="",
619
626
  )
620
- assert calls == [["tmux", "set-option", "@ps_agent_kind", "codex"]]
627
+ assert calls == [
628
+ ["tmux", "set-option", "@ps_agent_kind", "codex"],
629
+ ["tmux", "set-option", "-uq", "@ps_agent_profile"],
630
+ ]
631
+
632
+
633
+ def test_record_agent_kind_default_relaunch_clears_stale_profile():
634
+ # REPRODUCE-FIRST (issue #889): the reported false-z.ai-label bug. A tmux
635
+ # session is launched once with the z.ai profile (sets @ps_agent_profile),
636
+ # then the agent is killed and RELAUNCHED as a regular default Claude in
637
+ # the SAME session. tmux session options persist, so without the #889 fix
638
+ # the stale "Claude (Z.AI)" lingers and the tree mislabels the session.
639
+ # The default relaunch must emit the unset that clears it.
640
+ #
641
+ # Launch 1: z.ai profile.
642
+ zai_calls = []
643
+ agents.record_agent_kind(
644
+ "claude",
645
+ env={"TMUX": "/tmp/tmux-1000/default,1234,0"},
646
+ runner=lambda argv, **kw: zai_calls.append(argv),
647
+ profile="Claude (Z.AI)",
648
+ )
649
+ assert ["tmux", "set-option", "@ps_agent_profile", "Claude (Z.AI)"] in zai_calls
650
+
651
+ # Launch 2: default relaunch in the same session. It MUST issue the unset
652
+ # so the previously-set @ps_agent_profile is reconciled away.
653
+ default_calls = []
654
+ agents.record_agent_kind(
655
+ "claude",
656
+ env={"TMUX": "/tmp/tmux-1000/default,1234,0"},
657
+ runner=lambda argv, **kw: default_calls.append(argv),
658
+ profile=None,
659
+ )
660
+ assert ["tmux", "set-option", "-uq", "@ps_agent_profile"] in default_calls
661
+ # And it must NOT re-set a profile value.
662
+ assert not any(
663
+ argv[:3] == ["tmux", "set-option", "@ps_agent_profile"]
664
+ for argv in default_calls
665
+ )
621
666
 
622
667
 
623
668
  def test_launch_agent_records_profile_before_exec(tmp_path, monkeypatch):
@@ -669,9 +714,10 @@ def test_cli_agent_named_profile_records_profile_option(tmp_path, monkeypatch):
669
714
  assert ["tmux", "set-option", "@ps_agent_profile", "Claude (Z.AI)"] in calls
670
715
 
671
716
 
672
- def test_cli_agent_default_profile_records_no_profile_option(tmp_path, monkeypatch):
673
- """A default Claude profile records the kind but no profile option, so a
674
- default session is the plain kind with no spurious label."""
717
+ def test_cli_agent_default_profile_clears_profile_option(tmp_path, monkeypatch):
718
+ """A default Claude profile records the kind and reconciles the profile
719
+ option by UNSETTING it (issue #889), so a default launch never carries a
720
+ stale label and never sets a spurious one."""
675
721
  _seed_zlaude_home(tmp_path, monkeypatch)
676
722
  workdir = tmp_path / "work"
677
723
  workdir.mkdir()
@@ -684,13 +730,15 @@ def test_cli_agent_default_profile_records_no_profile_option(tmp_path, monkeypat
684
730
  )
685
731
  assert rc == 0
686
732
  assert ["tmux", "set-option", "@ps_agent_kind", "claude"] in calls
733
+ assert ["tmux", "set-option", "-uq", "@ps_agent_profile"] in calls
687
734
  assert not any(
688
735
  argv[:3] == ["tmux", "set-option", "@ps_agent_profile"] for argv in calls
689
736
  )
690
737
 
691
738
 
692
- def test_cli_agent_no_profile_records_no_profile_option(tmp_path, monkeypatch):
693
- """A bare launch (no --profile/--config-dir) records the kind only."""
739
+ def test_cli_agent_no_profile_clears_profile_option(tmp_path, monkeypatch):
740
+ """A bare launch (no --profile/--config-dir) records the kind and unsets
741
+ @ps_agent_profile (issue #889)."""
694
742
  workdir = tmp_path / "work"
695
743
  workdir.mkdir()
696
744
  monkeypatch.setenv("HOME", str(tmp_path))
@@ -701,11 +749,54 @@ def test_cli_agent_no_profile_records_no_profile_option(tmp_path, monkeypatch):
701
749
  rc = main(["agent", "codex", "--dir", str(workdir)])
702
750
  assert rc == 0
703
751
  assert ["tmux", "set-option", "@ps_agent_kind", "codex"] in calls
752
+ assert ["tmux", "set-option", "-uq", "@ps_agent_profile"] in calls
704
753
  assert not any(
705
754
  argv[:3] == ["tmux", "set-option", "@ps_agent_profile"] for argv in calls
706
755
  )
707
756
 
708
757
 
758
+ def test_cli_agent_default_then_zai_relaunch_sets_profile(tmp_path, monkeypatch):
759
+ """Class coverage (issue #889): default-then-relaunched-z.ai. A default
760
+ launch in the session unsets the profile; relaunching the SAME session as
761
+ z.ai sets the correct label (the chip appears)."""
762
+ _seed_zlaude_home(tmp_path, monkeypatch)
763
+ workdir = tmp_path / "work"
764
+ workdir.mkdir()
765
+ monkeypatch.setenv("TMUX", "/tmp/tmux-1000/default,1234,0")
766
+ monkeypatch.setattr(agents.os, "execvpe", lambda f, a, e: None)
767
+
768
+ # Launch 1: default -> unset.
769
+ default_calls = []
770
+ monkeypatch.setattr(
771
+ agents.subprocess, "run", lambda argv, **kw: default_calls.append(argv)
772
+ )
773
+ assert main(["agent", "claude", "--dir", str(workdir), "--profile", "Claude"]) == 0
774
+ assert ["tmux", "set-option", "-uq", "@ps_agent_profile"] in default_calls
775
+
776
+ # Launch 2: z.ai relaunch in the same session -> sets the label.
777
+ zai_calls = []
778
+ monkeypatch.setattr(
779
+ agents.subprocess, "run", lambda argv, **kw: zai_calls.append(argv)
780
+ )
781
+ assert (
782
+ main(
783
+ [
784
+ "agent",
785
+ "claude",
786
+ "--dir",
787
+ str(workdir),
788
+ "--profile",
789
+ "Claude (Z.AI)",
790
+ ]
791
+ )
792
+ == 0
793
+ )
794
+ assert ["tmux", "set-option", "@ps_agent_profile", "Claude (Z.AI)"] in zai_calls
795
+ assert not any(
796
+ argv[:3] == ["tmux", "set-option", "-uq"] for argv in zai_calls
797
+ )
798
+
799
+
709
800
  def test_resolve_config_dir_default_profile_label_is_none(tmp_path, monkeypatch):
710
801
  """The default Claude profile resolves to a None profile label."""
711
802
  _seed_zlaude_home(tmp_path, monkeypatch)
@@ -314,7 +314,7 @@ wheels = [
314
314
 
315
315
  [[package]]
316
316
  name = "pocketshell"
317
- version = "0.4.13"
317
+ version = "0.4.14"
318
318
  source = { editable = "." }
319
319
  dependencies = [
320
320
  { name = "click" },
File without changes
File without changes