swarph-cli 0.7.3__tar.gz → 0.7.4__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 (45) hide show
  1. {swarph_cli-0.7.3/src/swarph_cli.egg-info → swarph_cli-0.7.4}/PKG-INFO +1 -1
  2. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/pyproject.toml +1 -1
  3. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/commands/watchdog.py +44 -2
  5. {swarph_cli-0.7.3 → swarph_cli-0.7.4/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  6. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_watchdog.py +88 -0
  7. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/LICENSE +0 -0
  8. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/README.md +0 -0
  9. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/setup.cfg +0 -0
  10. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/caller.py +0 -0
  11. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/cell.py +0 -0
  12. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/commands/__init__.py +0 -0
  13. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/commands/chat.py +0 -0
  14. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/commands/daemon.py +0 -0
  15. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/commands/hook_output.py +0 -0
  16. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/commands/import_session.py +0 -0
  17. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/commands/install_hook.py +0 -0
  18. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/commands/onboard.py +0 -0
  19. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/commands/ratify.py +0 -0
  20. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/commands/spawn.py +0 -0
  21. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/main.py +0 -0
  22. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/parsers/__init__.py +0 -0
  23. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/parsers/claude.py +0 -0
  24. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  25. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  26. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  27. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli.egg-info/SOURCES.txt +0 -0
  28. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  29. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  30. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli.egg-info/requires.txt +0 -0
  31. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/src/swarph_cli.egg-info/top_level.txt +0 -0
  32. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_cell_loader.py +0 -0
  33. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_chat_command.py +0 -0
  34. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_claude_parser.py +0 -0
  35. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_daemon_command.py +0 -0
  36. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_hook_output.py +0 -0
  37. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_import_command.py +0 -0
  38. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_install_hook.py +0 -0
  39. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_main.py +0 -0
  40. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_onboard_command.py +0 -0
  41. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_ratify_command.py +0 -0
  42. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_smoke_chat.py +0 -0
  43. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_smoke_one_shot.py +0 -0
  44. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_smoke_phase_5_5.py +0 -0
  45. {swarph_cli-0.7.3 → swarph_cli-0.7.4}/tests/test_spawn_command.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.7.3
3
+ Version: 0.7.4
4
4
  Summary: The `swarph` binary — multi-LLM CLI with mesh-gateway integration. v0.7.0 ships Phase 7 substrate-doc R7 §11.1.7 operator-tooling layer in 5 increments: PR-A `--new-instance` flag (sibling-spawn case) + PR-B auto-suffix on collision (sibling-slot persistence) + PR-C SessionStart hook (closes bare-claude operator-paste gap) + watchdog (stranded-session recovery) + PR-D swarph-shared cell.yaml relocation (cell-yaml schema graduates to swarph-shared 0.3.0 kernel-tier; substrate-doc R7 §11.1.5 (O5) RESOLVED).
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.7.3"
7
+ version = "0.7.4"
8
8
  description = "The `swarph` binary — multi-LLM CLI with mesh-gateway integration. v0.7.0 ships Phase 7 substrate-doc R7 §11.1.7 operator-tooling layer in 5 increments: PR-A `--new-instance` flag (sibling-spawn case) + PR-B auto-suffix on collision (sibling-slot persistence) + PR-C SessionStart hook (closes bare-claude operator-paste gap) + watchdog (stranded-session recovery) + PR-D swarph-shared cell.yaml relocation (cell-yaml schema graduates to swarph-shared 0.3.0 kernel-tier; substrate-doc R7 §11.1.5 (O5) RESOLVED)."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -16,6 +16,6 @@ The architecture splits CLI from substrate so:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.7.3"
19
+ __version__ = "0.7.4"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -66,6 +66,7 @@ import argparse
66
66
  import json
67
67
  import os
68
68
  import shlex
69
+ import shutil
69
70
  import subprocess
70
71
  import sys
71
72
  import time
@@ -622,6 +623,35 @@ _SYSTEMD_UNIT_DIR = Path("/etc/systemd/system")
622
623
  _SYSTEMD_DEFAULT_DIR = Path("/etc/default")
623
624
  _SYSTEMD_UNIT_NAMES = ("swarph-watchdog.service", "swarph-watchdog.timer")
624
625
  _SYSTEMD_DEFAULT_NAME = "swarph-watchdog" # /etc/default/swarph-watchdog
626
+ # v0.7.3 bundled template's ExecStart placeholder — substituted at install time
627
+ # by `_resolve_swarph_bin()` to the actual binary path on the install host.
628
+ # Fixes the v0.7.3 hardcode that broke pipx-installed peers (binary at
629
+ # ~/.local/bin/swarph not /usr/local/bin/swarph).
630
+ _SWARPH_BIN_PLACEHOLDER = "/usr/local/bin/swarph"
631
+
632
+
633
+ def _resolve_swarph_bin() -> str:
634
+ """Resolve the absolute path of the running swarph binary.
635
+
636
+ Resolution order:
637
+ 1. ``sys.argv[0]`` if it's an absolute path — most reliable, equals
638
+ the path the user invoked
639
+ 2. ``shutil.which(sys.argv[0])`` — bare-name invocation, look up in PATH
640
+ 3. ``shutil.which("swarph")`` — generic PATH lookup as fallback
641
+ 4. ``/usr/local/bin/swarph`` — last-resort default (matches v0.7.3
642
+ hardcode behavior; no regression if all three above fail)
643
+
644
+ ALWAYS returns an absolute path — systemd ExecStart requires absolute.
645
+ Relative inputs (e.g. ``venv/bin/swarph`` from editable installs) get
646
+ abspath'd against cwd. Never raises.
647
+ """
648
+ invoked = sys.argv[0] or "swarph"
649
+ if Path(invoked).is_absolute():
650
+ return invoked
651
+ resolved = shutil.which(invoked) or shutil.which("swarph")
652
+ if not resolved:
653
+ return _SWARPH_BIN_PLACEHOLDER
654
+ return os.path.abspath(resolved)
625
655
 
626
656
 
627
657
  def _bundled_systemd_files() -> dict[str, str]:
@@ -656,6 +686,18 @@ def run_install_service(args: argparse.Namespace) -> int:
656
686
  """
657
687
  files = _bundled_systemd_files()
658
688
 
689
+ # v0.7.4: substitute the bundled service template's ExecStart placeholder
690
+ # with the actual swarph binary path on this host. Fixes the v0.7.3 hardcode
691
+ # that broke pipx-installed peers (binary at ~/.local/bin/swarph not
692
+ # /usr/local/bin/swarph). Pipx is the recommended install path on droplet
693
+ # + lab, so the hardcode bit BOTH peers on first install attempt today.
694
+ swarph_bin = _resolve_swarph_bin()
695
+ service_content = files[_SYSTEMD_UNIT_NAMES[0]].replace(
696
+ f"ExecStart={_SWARPH_BIN_PLACEHOLDER}",
697
+ f"ExecStart={swarph_bin}",
698
+ 1,
699
+ )
700
+
659
701
  # Template the default file with the requested role
660
702
  default_content = files["swarph-watchdog.default"].replace(
661
703
  "SWARPH_CELL=lab",
@@ -664,13 +706,13 @@ def run_install_service(args: argparse.Namespace) -> int:
664
706
  )
665
707
 
666
708
  targets = [
667
- (_SYSTEMD_UNIT_DIR / _SYSTEMD_UNIT_NAMES[0], files[_SYSTEMD_UNIT_NAMES[0]]),
709
+ (_SYSTEMD_UNIT_DIR / _SYSTEMD_UNIT_NAMES[0], service_content),
668
710
  (_SYSTEMD_UNIT_DIR / _SYSTEMD_UNIT_NAMES[1], files[_SYSTEMD_UNIT_NAMES[1]]),
669
711
  (_SYSTEMD_DEFAULT_DIR / _SYSTEMD_DEFAULT_NAME, default_content),
670
712
  ]
671
713
 
672
714
  if args.dry_run:
673
- print(f"# DRY RUN — cell={args.cell}", file=sys.stderr)
715
+ print(f"# DRY RUN — cell={args.cell} swarph_bin={swarph_bin}", file=sys.stderr)
674
716
  for path, content in targets:
675
717
  print(f"\n# would write {path}:", file=sys.stderr)
676
718
  print(content, file=sys.stderr)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.7.3
3
+ Version: 0.7.4
4
4
  Summary: The `swarph` binary — multi-LLM CLI with mesh-gateway integration. v0.7.0 ships Phase 7 substrate-doc R7 §11.1.7 operator-tooling layer in 5 increments: PR-A `--new-instance` flag (sibling-spawn case) + PR-B auto-suffix on collision (sibling-slot persistence) + PR-C SessionStart hook (closes bare-claude operator-paste gap) + watchdog (stranded-session recovery) + PR-D swarph-shared cell.yaml relocation (cell-yaml schema graduates to swarph-shared 0.3.0 kernel-tier; substrate-doc R7 §11.1.5 (O5) RESOLVED).
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
@@ -624,3 +624,91 @@ def test_bundled_systemd_files_readable():
624
624
  assert "OnUnitActiveSec=5min" in files["swarph-watchdog.timer"]
625
625
  # Default file has the SWARPH_CELL=lab template line
626
626
  assert "SWARPH_CELL=lab" in files["swarph-watchdog.default"]
627
+
628
+
629
+ # ---------------------------------------------------------------------------
630
+ # v0.7.4 — _resolve_swarph_bin + ExecStart templating
631
+ # ---------------------------------------------------------------------------
632
+ #
633
+ # v0.7.3 shipped a hardcoded ExecStart=/usr/local/bin/swarph that broke
634
+ # pipx-installed peers (lab + drop both hit this on first install attempt
635
+ # 2026-05-14). v0.7.4 resolves the path at install time via _resolve_swarph_bin
636
+ # and substitutes into the bundled service template.
637
+
638
+
639
+ def test_resolve_swarph_bin_absolute_argv0_wins(monkeypatch):
640
+ """If sys.argv[0] is absolute, it's the most reliable signal — use it."""
641
+ from swarph_cli.commands.watchdog import _resolve_swarph_bin
642
+
643
+ monkeypatch.setattr("sys.argv", ["/home/ubuntu/.local/bin/swarph", "watchdog"])
644
+ assert _resolve_swarph_bin() == "/home/ubuntu/.local/bin/swarph"
645
+
646
+
647
+ def test_resolve_swarph_bin_bare_name_resolved_via_path(monkeypatch, tmp_path):
648
+ """Bare-name argv[0] resolves via PATH."""
649
+ from swarph_cli.commands.watchdog import _resolve_swarph_bin
650
+
651
+ fake_bin = tmp_path / "swarph"
652
+ fake_bin.write_text("#!/bin/sh\nexit 0\n")
653
+ fake_bin.chmod(0o755)
654
+ monkeypatch.setattr("sys.argv", ["swarph", "watchdog"])
655
+ monkeypatch.setattr("shutil.which", lambda name: str(fake_bin) if name == "swarph" else None)
656
+ assert _resolve_swarph_bin() == str(fake_bin)
657
+
658
+
659
+ def test_resolve_swarph_bin_falls_back_to_placeholder_if_unresolvable(monkeypatch):
660
+ """All resolution paths fail → fall back to /usr/local/bin/swarph
661
+ (v0.7.3 hardcode behavior; no regression vs prior version)."""
662
+ from swarph_cli.commands.watchdog import _resolve_swarph_bin, _SWARPH_BIN_PLACEHOLDER
663
+
664
+ monkeypatch.setattr("sys.argv", ["swarph", "watchdog"])
665
+ monkeypatch.setattr("shutil.which", lambda name: None)
666
+ assert _resolve_swarph_bin() == _SWARPH_BIN_PLACEHOLDER
667
+
668
+
669
+ def test_install_service_dry_run_substitutes_swarph_bin(isolated_state, capsys, monkeypatch):
670
+ """Dry-run preview shows resolved swarph path in ExecStart, not hardcoded
671
+ /usr/local/bin/swarph (when the host actually has swarph elsewhere)."""
672
+ pipx_path = "/home/ubuntu/.local/bin/swarph"
673
+ monkeypatch.setattr("sys.argv", [pipx_path, "watchdog"])
674
+ rc = run_watchdog(argv=["--install-service", "--cell", "droplet", "--dry-run"])
675
+ assert rc == 0
676
+ captured = capsys.readouterr()
677
+ # Header surfaces the resolved binary
678
+ assert f"swarph_bin={pipx_path}" in captured.err
679
+ # Service file's ExecStart now points at pipx path, NOT the placeholder
680
+ assert f"ExecStart={pipx_path} watchdog --check" in captured.err
681
+ # And the v0.7.3-hardcoded path no longer appears in the preview
682
+ assert "ExecStart=/usr/local/bin/swarph watchdog --check" not in captured.err
683
+
684
+
685
+ def test_install_service_dry_run_preserves_default_path_when_absolute(isolated_state, capsys, monkeypatch):
686
+ """When sys.argv[0] IS /usr/local/bin/swarph (the v0.7.3 default path),
687
+ the substitution still happens but produces the same line — no diff vs
688
+ v0.7.3 for this case."""
689
+ monkeypatch.setattr("sys.argv", ["/usr/local/bin/swarph", "watchdog"])
690
+ rc = run_watchdog(argv=["--install-service", "--cell", "lab", "--dry-run"])
691
+ assert rc == 0
692
+ captured = capsys.readouterr()
693
+ assert "ExecStart=/usr/local/bin/swarph watchdog --check" in captured.err
694
+
695
+
696
+ def test_resolve_swarph_bin_relative_with_slash_resolves_to_absolute(tmp_path, monkeypatch):
697
+ """Relative path with slash (e.g. editable install's venv/bin/swarph)
698
+ must be absolutized — systemd ExecStart needs absolute. Regression guard
699
+ for the abspath fix on top of v0.7.4 path-autodetect."""
700
+ from swarph_cli.commands.watchdog import _resolve_swarph_bin
701
+
702
+ fake = tmp_path / "swarph"
703
+ fake.write_text("#!/bin/sh\nexit 0\n")
704
+ fake.chmod(0o755)
705
+ # Simulate `./swarph` from cwd=tmp_path
706
+ monkeypatch.chdir(tmp_path)
707
+ monkeypatch.setattr("sys.argv", ["./swarph", "watchdog"])
708
+ # shutil.which('./swarph') returns the relative path as-is when the
709
+ # input contains a slash. _resolve_swarph_bin must abspath it.
710
+ resolved = _resolve_swarph_bin()
711
+ assert Path(resolved).is_absolute(), (
712
+ f"resolver returned non-absolute path: {resolved!r}"
713
+ )
714
+ assert resolved == str(fake)
File without changes
File without changes
File without changes