swarph-cli 0.7.9__tar.gz → 0.9.0__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 (52) hide show
  1. {swarph_cli-0.7.9/src/swarph_cli.egg-info → swarph_cli-0.9.0}/PKG-INFO +2 -2
  2. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/pyproject.toml +2 -2
  3. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/__init__.py +1 -1
  4. swarph_cli-0.9.0/src/swarph_cli/commands/memory_sync.py +194 -0
  5. swarph_cli-0.9.0/src/swarph_cli/commands/mesh.py +514 -0
  6. swarph_cli-0.9.0/src/swarph_cli/commands/spawn.py +910 -0
  7. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/main.py +4 -1
  8. {swarph_cli-0.7.9 → swarph_cli-0.9.0/src/swarph_cli.egg-info}/PKG-INFO +2 -2
  9. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli.egg-info/SOURCES.txt +6 -0
  10. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_cell_loader.py +10 -2
  11. swarph_cli-0.9.0/tests/test_memory_sync.py +57 -0
  12. swarph_cli-0.9.0/tests/test_mesh_command.py +233 -0
  13. swarph_cli-0.9.0/tests/test_mesh_sidecar.py +206 -0
  14. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_spawn_command.py +408 -6
  15. swarph_cli-0.9.0/tests/test_spawn_windows_relaunch.py +129 -0
  16. swarph_cli-0.7.9/src/swarph_cli/commands/spawn.py +0 -532
  17. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/LICENSE +0 -0
  18. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/README.md +0 -0
  19. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/setup.cfg +0 -0
  20. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/caller.py +0 -0
  21. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/cell.py +0 -0
  22. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/commands/__init__.py +0 -0
  23. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/commands/chat.py +0 -0
  24. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/commands/daemon.py +0 -0
  25. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/commands/hook_output.py +0 -0
  26. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/commands/import_session.py +0 -0
  27. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/commands/install_hook.py +0 -0
  28. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/commands/onboard.py +0 -0
  29. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/commands/ratify.py +0 -0
  30. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/commands/watchdog.py +0 -0
  31. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/parsers/__init__.py +0 -0
  32. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/parsers/claude.py +0 -0
  33. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  34. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  35. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  36. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  37. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  38. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli.egg-info/requires.txt +0 -0
  39. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
  40. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_chat_command.py +0 -0
  41. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_claude_parser.py +0 -0
  42. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_daemon_command.py +0 -0
  43. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_hook_output.py +0 -0
  44. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_import_command.py +0 -0
  45. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_install_hook.py +0 -0
  46. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_main.py +0 -0
  47. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_onboard_command.py +0 -0
  48. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_ratify_command.py +0 -0
  49. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_smoke_chat.py +0 -0
  50. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_smoke_one_shot.py +0 -0
  51. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_smoke_phase_5_5.py +0 -0
  52. {swarph_cli-0.7.9 → swarph_cli-0.9.0}/tests/test_watchdog.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.7.9
4
- Summary: The `swarph` binary — multi-LLM CLI with mesh-gateway integration: one-shot + chat REPL, multi-provider routing (gemini/claude/deepseek/openai/grok), cell.yaml spawn, session import, and watchdog. v0.7.9 reverts the v0.7.8 pyreadline3 dependency + chat-REPL shim: the Windows 'Enter renders m' symptom traced to Claude Code's own native-Windows TUI (no hooks, recent build, fails at the trust prompt — Anthropic-side), NOT this REPL, so the shim addressed the wrong layer. Bare input() + a CR-strip guard remain.
3
+ Version: 0.9.0
4
+ Summary: The `swarph` binary — multi-LLM CLI + mesh-gateway integration: multi-provider spawn (claude/codex/antigravity per cell.provider, via a ProviderMembrane), cell.yaml, session import, watchdog. v0.9.0 adds `swarph mesh` (send/inbox/register with per-peer tokens) + a provider-agnostic inbox sidecar, and `assisted_memory` (git-backed durable memory: restore-on-spawn + saver loop + current-task anchor) toggled per cell.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/darw007d/swarph-cli
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.7.9"
8
- description = "The `swarph` binary — multi-LLM CLI with mesh-gateway integration: one-shot + chat REPL, multi-provider routing (gemini/claude/deepseek/openai/grok), cell.yaml spawn, session import, and watchdog. v0.7.9 reverts the v0.7.8 pyreadline3 dependency + chat-REPL shim: the Windows 'Enter renders m' symptom traced to Claude Code's own native-Windows TUI (no hooks, recent build, fails at the trust prompt — Anthropic-side), NOT this REPL, so the shim addressed the wrong layer. Bare input() + a CR-strip guard remain."
7
+ version = "0.9.0"
8
+ description = "The `swarph` binary — multi-LLM CLI + mesh-gateway integration: multi-provider spawn (claude/codex/antigravity per cell.provider, via a ProviderMembrane), cell.yaml, session import, watchdog. v0.9.0 adds `swarph mesh` (send/inbox/register with per-peer tokens) + a provider-agnostic inbox sidecar, and `assisted_memory` (git-backed durable memory: restore-on-spawn + saver loop + current-task anchor) toggled per cell."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
11
11
  requires-python = ">=3.10"
@@ -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.9"
19
+ __version__ = "0.9.0"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -0,0 +1,194 @@
1
+ """Assisted memory saver loop (Stage 2) + Restore helper."""
2
+
3
+ import argparse
4
+ import datetime
5
+ import os
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Optional
11
+
12
+ from swarph_cli.cell import Cell, load_cell, CellError
13
+
14
+
15
+ def get_memory_repo_path(cell: Cell) -> Path:
16
+ # Memory repos are stored locally under ~/.local/share/swarph/memory/<role>
17
+ return Path.home() / ".local" / "share" / "swarph" / "memory" / cell.role
18
+
19
+
20
+ def _get_files_to_sync(cell: Cell) -> list[tuple[str, Path]]:
21
+ files_to_sync = []
22
+
23
+ # Common
24
+ if (cell.cwd / "CURRENT_TASK.md").exists():
25
+ files_to_sync.append(("CURRENT_TASK.md", cell.cwd / "CURRENT_TASK.md"))
26
+
27
+ if cell.provider == "claude":
28
+ if (cell.cwd / "CLAUDE.md").exists():
29
+ files_to_sync.append(("CLAUDE.md", cell.cwd / "CLAUDE.md"))
30
+ mem_dir = Path.home() / ".claude"
31
+ if (mem_dir / "MEMORY.md").exists():
32
+ files_to_sync.append(("MEMORY.md", mem_dir / "MEMORY.md"))
33
+ for p in mem_dir.glob("memory/*.md"):
34
+ files_to_sync.append((f"memory/{p.name}", p))
35
+ if (mem_dir / "inbox-cursor").exists():
36
+ files_to_sync.append(("inbox-cursor", mem_dir / "inbox-cursor"))
37
+
38
+ elif cell.provider == "codex":
39
+ if (cell.cwd / "AGENTS.md").exists():
40
+ files_to_sync.append(("AGENTS.md", cell.cwd / "AGENTS.md"))
41
+
42
+ elif cell.provider in ("antigravity", "gemini"):
43
+ if (cell.cwd / "GEMINI.md").exists():
44
+ files_to_sync.append(("GEMINI.md", cell.cwd / "GEMINI.md"))
45
+ if (cell.cwd / "inbox-cursor.json").exists():
46
+ files_to_sync.append(("inbox-cursor.json", cell.cwd / "inbox-cursor.json"))
47
+
48
+ gemini_tmp = Path.home() / ".gemini" / "tmp"
49
+ if gemini_tmp.is_dir():
50
+ for proj_dir in gemini_tmp.iterdir():
51
+ if proj_dir.is_dir():
52
+ for p in proj_dir.glob("memory/*.md"):
53
+ rel = f"tmp/{proj_dir.name}/memory/{p.name}"
54
+ files_to_sync.append((rel, p))
55
+
56
+ history_proj = Path.home() / ".gemini" / "history" / ".project_root"
57
+ if history_proj.exists():
58
+ files_to_sync.append(("history/.project_root", history_proj))
59
+
60
+ return files_to_sync
61
+
62
+
63
+ def _clone_if_missing(repo_url: str, repo_dir: Path) -> bool:
64
+ if not (repo_dir / ".git").is_dir():
65
+ repo_dir.parent.mkdir(parents=True, exist_ok=True)
66
+ if "://" not in repo_url and "@" not in repo_url:
67
+ repo_url = f"git@github.com:{repo_url}.git"
68
+ try:
69
+ subprocess.run(["git", "clone", repo_url, str(repo_dir)], check=True)
70
+ except subprocess.CalledProcessError as exc:
71
+ print(f"swarph: git clone failed: {exc}", file=sys.stderr)
72
+ return False
73
+ return True
74
+
75
+
76
+ def perform_restore(cell: Cell) -> Optional[str]:
77
+ """Stage 3: Restore files from memory repo to the filesystem.
78
+
79
+ Returns the text of CURRENT_TASK.md if it exists and was restored,
80
+ so the caller can surface it to the context window.
81
+ """
82
+ am = cell.assisted_memory
83
+ if not am or not am.get("enabled"):
84
+ return None
85
+
86
+ repo_url = am["repo"]
87
+ repo_dir = get_memory_repo_path(cell)
88
+
89
+ if not _clone_if_missing(repo_url, repo_dir):
90
+ return None
91
+
92
+ # pull --ff-only
93
+ try:
94
+ subprocess.run(["git", "-C", str(repo_dir), "pull", "--ff-only", "origin", "main"], check=True, capture_output=True)
95
+ except subprocess.CalledProcessError as exc:
96
+ print(f"swarph: memory pull --ff-only failed (diverged/offline?): {exc}", file=sys.stderr)
97
+
98
+ # Walk the repo dir and copy everything back, EXCEPT .git and .gitignore
99
+ for root, dirs, files in os.walk(repo_dir):
100
+ if ".git" in dirs:
101
+ dirs.remove(".git")
102
+ for f in files:
103
+ if f == ".gitignore":
104
+ continue
105
+
106
+ src = Path(root) / f
107
+ rel = src.relative_to(repo_dir)
108
+
109
+ dest = None
110
+ if rel.parts[0] in ("CURRENT_TASK.md", "CLAUDE.md", "AGENTS.md", "GEMINI.md", "inbox-cursor.json"):
111
+ dest = cell.cwd / rel
112
+ elif cell.provider == "claude" and rel.parts[0] == "MEMORY.md":
113
+ dest = Path.home() / ".claude" / "MEMORY.md"
114
+ elif cell.provider == "claude" and rel.parts[0] == "memory":
115
+ dest = Path.home() / ".claude" / rel
116
+ elif cell.provider == "claude" and rel.parts[0] == "inbox-cursor":
117
+ dest = Path.home() / ".claude" / "inbox-cursor"
118
+ elif cell.provider in ("antigravity", "gemini") and rel.parts[0] == "tmp":
119
+ dest = Path.home() / ".gemini" / rel
120
+ elif cell.provider in ("antigravity", "gemini") and rel.parts[0] == "history":
121
+ dest = Path.home() / ".gemini" / rel
122
+
123
+ if dest:
124
+ dest.parent.mkdir(parents=True, exist_ok=True)
125
+ shutil.copy2(src, dest)
126
+
127
+ current_task = cell.cwd / "CURRENT_TASK.md"
128
+ if current_task.exists():
129
+ return current_task.read_text(encoding="utf-8")
130
+ return None
131
+
132
+
133
+ def run_memory_sync(argv: list[str]) -> int:
134
+ parser = argparse.ArgumentParser(prog="swarph memory-sync")
135
+ parser.add_argument("cell_yaml", help="Path to cell.yaml")
136
+ args = parser.parse_args(argv)
137
+
138
+ try:
139
+ cell = load_cell(Path(args.cell_yaml))
140
+ except CellError as exc:
141
+ print(f"swarph memory-sync: {exc}", file=sys.stderr)
142
+ return 1
143
+
144
+ am = cell.assisted_memory
145
+ if not am or not am.get("enabled"):
146
+ print("swarph memory-sync: assisted_memory not enabled for this cell. Exiting.", file=sys.stderr)
147
+ return 0
148
+
149
+ repo_url = am["repo"]
150
+ repo_dir = get_memory_repo_path(cell)
151
+
152
+ if not _clone_if_missing(repo_url, repo_dir):
153
+ return 1
154
+
155
+ gitignore_path = repo_dir / ".gitignore"
156
+ if not gitignore_path.exists():
157
+ gitignore_path.write_text("secrets/\n.*creds*\n*.token\n", encoding="utf-8")
158
+ subprocess.run(["git", "-C", str(repo_dir), "add", ".gitignore"], check=True)
159
+
160
+ try:
161
+ subprocess.run(["git", "-C", str(repo_dir), "pull", "--ff-only", "origin", "main"], check=True, capture_output=True)
162
+ except subprocess.CalledProcessError:
163
+ pass
164
+
165
+ # EMPTY-GUARD check
166
+ guard_file = None
167
+ if cell.provider == "claude":
168
+ guard_file = cell.cwd / "CLAUDE.md"
169
+ elif cell.provider == "codex":
170
+ guard_file = cell.cwd / "AGENTS.md"
171
+ elif cell.provider in ("antigravity", "gemini"):
172
+ guard_file = cell.cwd / "GEMINI.md"
173
+
174
+ if guard_file and (not guard_file.exists() or guard_file.stat().st_size == 0):
175
+ print(f"[auto_sync] {datetime.datetime.now(datetime.timezone.utc).strftime('%FT%TZ')} SAFETY: {guard_file} missing or empty — skipping sync", file=sys.stderr)
176
+ return 0
177
+
178
+ files_to_sync = _get_files_to_sync(cell)
179
+ for rel_path, abs_path in files_to_sync:
180
+ dest = repo_dir / rel_path
181
+ dest.parent.mkdir(parents=True, exist_ok=True)
182
+ shutil.copy2(abs_path, dest)
183
+ subprocess.run(["git", "-C", str(repo_dir), "add", str(dest)], check=True)
184
+
185
+ diff_check = subprocess.run(["git", "-C", str(repo_dir), "diff", "--cached", "--quiet"])
186
+ if diff_check.returncode != 0:
187
+ ts = datetime.datetime.now(datetime.timezone.utc).strftime('%FT%TZ')
188
+ subprocess.run(["git", "-C", str(repo_dir), "commit", "-m", f"auto-snapshot {ts}"], check=True, capture_output=True)
189
+ try:
190
+ subprocess.run(["git", "-C", str(repo_dir), "push", "origin", "main"], check=True, capture_output=True)
191
+ except subprocess.CalledProcessError as exc:
192
+ print(f"swarph memory-sync: push failed: {exc}", file=sys.stderr)
193
+
194
+ return 0