swarph-cli 0.26.0__tar.gz → 0.27.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 (157) hide show
  1. {swarph_cli-0.26.0/src/swarph_cli.egg-info → swarph_cli-0.27.0}/PKG-INFO +1 -1
  2. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/pyproject.toml +1 -1
  3. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/highlight.py +64 -2
  5. {swarph_cli-0.26.0 → swarph_cli-0.27.0/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  6. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_brain_ask_command.py +2 -2
  7. swarph_cli-0.27.0/tests/test_highlight_command.py +140 -0
  8. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_watchdog.py +2 -2
  9. swarph_cli-0.26.0/tests/test_highlight_command.py +0 -67
  10. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/LICENSE +0 -0
  11. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/README.md +0 -0
  12. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/setup.cfg +0 -0
  13. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/caller.py +0 -0
  14. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/capture/__init__.py +0 -0
  15. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/capture/harden.py +0 -0
  16. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/capture/lineage.py +0 -0
  17. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/capture/liveness.py +0 -0
  18. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/capture/manifest.py +0 -0
  19. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/capture/paths.py +0 -0
  20. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/capture/verify.py +0 -0
  21. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/cell.py +0 -0
  22. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/chain_token.py +0 -0
  23. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/__init__.py +0 -0
  24. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/_gateway_client.py +0 -0
  25. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/add.py +0 -0
  26. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/brain.py +0 -0
  27. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/brain_ask.py +0 -0
  28. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/cell.py +0 -0
  29. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/channel.py +0 -0
  30. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/chat.py +0 -0
  31. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/codegraph.py +0 -0
  32. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/compress.py +0 -0
  33. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/daemon.py +0 -0
  34. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/event.py +0 -0
  35. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/gateway.py +0 -0
  36. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/hook_output.py +0 -0
  37. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/hooks.py +0 -0
  38. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/import_session.py +0 -0
  39. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/init.py +0 -0
  40. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/install_hook.py +0 -0
  41. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  42. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/lane.py +0 -0
  43. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/mcp_server.py +0 -0
  44. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/memory_sync.py +0 -0
  45. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/mesh.py +0 -0
  46. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/onboard.py +0 -0
  47. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
  48. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/ratify.py +0 -0
  49. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/schedule.py +0 -0
  50. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/security.py +0 -0
  51. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/service.py +0 -0
  52. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/spawn.py +0 -0
  53. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/commands/watchdog.py +0 -0
  54. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/compress/__init__.py +0 -0
  55. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/compress/levers.py +0 -0
  56. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/compress/marker.py +0 -0
  57. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/compress/verify.py +0 -0
  58. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/gateway/__init__.py +0 -0
  59. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
  60. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
  61. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/gateway/schema.sql +0 -0
  62. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/gateway/server.py +0 -0
  63. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/gateway/services_control.py +0 -0
  64. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/main.py +0 -0
  65. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/multiplexer.py +0 -0
  66. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/parsers/__init__.py +0 -0
  67. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/parsers/claude.py +0 -0
  68. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/service/__init__.py +0 -0
  69. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/service/app.py +0 -0
  70. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/service/providers.py +0 -0
  71. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  72. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  73. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  74. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli.egg-info/SOURCES.txt +0 -0
  75. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  76. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  77. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli.egg-info/requires.txt +0 -0
  78. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
  79. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_add_security_gate.py +0 -0
  80. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_artifact_add.py +0 -0
  81. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_artifact_hash.py +0 -0
  82. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_artifact_lib.py +0 -0
  83. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_artifact_mcp.py +0 -0
  84. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_artifact_skill.py +0 -0
  85. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_artifact_tool.py +0 -0
  86. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_artifact_uri.py +0 -0
  87. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_brain_command.py +0 -0
  88. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_caller_meta_guard.py +0 -0
  89. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_capture_lineage.py +0 -0
  90. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_capture_liveness.py +0 -0
  91. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_capture_manifest.py +0 -0
  92. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_capture_paths.py +0 -0
  93. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_capture_security.py +0 -0
  94. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_cell_command_dispatch.py +0 -0
  95. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_cell_harden.py +0 -0
  96. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_cell_loader.py +0 -0
  97. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_cell_verify.py +0 -0
  98. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_chain_token.py +0 -0
  99. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_channel_command.py +0 -0
  100. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_chat_command.py +0 -0
  101. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_claude_parser.py +0 -0
  102. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_claude_tmux_template.py +0 -0
  103. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_codegraph_command.py +0 -0
  104. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_codegraph_mcp.py +0 -0
  105. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_compress_caller_convention.py +0 -0
  106. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_compress_command.py +0 -0
  107. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_compress_levers.py +0 -0
  108. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_compress_marker.py +0 -0
  109. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_compress_verify.py +0 -0
  110. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_daemon_command.py +0 -0
  111. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_event_emit.py +0 -0
  112. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_feature_to_uri.py +0 -0
  113. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_gateway_brain.py +0 -0
  114. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_gateway_client.py +0 -0
  115. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_gateway_command.py +0 -0
  116. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_hook_output.py +0 -0
  117. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_hooks_add.py +0 -0
  118. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_hooks_bundle.py +0 -0
  119. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_hooks_init.py +0 -0
  120. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_hooks_lifecycle.py +0 -0
  121. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_hooks_merge.py +0 -0
  122. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_import_command.py +0 -0
  123. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_init_command.py +0 -0
  124. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_install_hook.py +0 -0
  125. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_install_multiplexer_command.py +0 -0
  126. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_lane_command.py +0 -0
  127. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_main.py +0 -0
  128. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_mcp_server.py +0 -0
  129. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_memory_sync.py +0 -0
  130. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_mesh_command.py +0 -0
  131. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_mesh_sidecar.py +0 -0
  132. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_meta_edge_identity.py +0 -0
  133. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_multiplexer.py +0 -0
  134. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_onboard_command.py +0 -0
  135. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_protocol_handler.py +0 -0
  136. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_ratify_command.py +0 -0
  137. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_repl_caller_convention.py +0 -0
  138. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_schedule_command.py +0 -0
  139. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_security.py +0 -0
  140. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_service_app.py +0 -0
  141. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_service_command.py +0 -0
  142. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_service_providers.py +0 -0
  143. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_session_path_role_gate.py +0 -0
  144. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_smoke_chat.py +0 -0
  145. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_smoke_one_shot.py +0 -0
  146. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_smoke_phase_5_5.py +0 -0
  147. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_spawn_command.py +0 -0
  148. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_spawn_live_pin.py +0 -0
  149. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_spawn_mitosis_lineage.py +0 -0
  150. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_spawn_tmux_session.py +0 -0
  151. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_spawn_windows_relaunch.py +0 -0
  152. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_version_consistency.py +0 -0
  153. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_watchdog_dm_wake.py +0 -0
  154. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  155. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  156. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_watchdog_model_rung.py +0 -0
  157. {swarph_cli-0.26.0 → swarph_cli-0.27.0}/tests/test_watchdog_stale_peers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.26.0
3
+ Version: 0.27.0
4
4
  Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, session `import`, and a stranded-session `watchdog`.
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.26.0"
7
+ version = "0.27.0"
8
8
  description = "The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, session `import`, and a stranded-session `watchdog`."
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.26.0"
19
+ __version__ = "0.27.0"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -27,9 +27,54 @@ import sys
27
27
  from datetime import datetime, timezone
28
28
  from pathlib import Path
29
29
 
30
+ from swarph_cli.commands.mesh import _post_json, _resolve_token
31
+
30
32
  _PUSH_RETRIES = 8
31
33
 
32
34
 
35
+ def _resolve_gateway(arg: str | None) -> str:
36
+ """Gateway base URL for the peer-token ingest path. Reuses the mesh-wide
37
+ SWARPH_BRAIN_GATEWAY (already set on every cell from the brain-ask rollout)
38
+ as the final fallback, so `swarph highlight` goes peer-token-by-default with
39
+ ZERO new per-cell config. `--gateway` / `--local` override."""
40
+ return (arg
41
+ or os.environ.get("SWARPH_HIGHLIGHT_GATEWAY")
42
+ or os.environ.get("SWARPH_GATEWAY")
43
+ or os.environ.get("SWARPH_BRAIN_GATEWAY")
44
+ or "").strip()
45
+
46
+
47
+ def _log_via_gateway(gateway: str, cell: str, highlight: str,
48
+ memory: str, when: str, token_file: str | None) -> int:
49
+ """POST the highlight to the gateway `/highlights` — the gateway holds the git
50
+ push credential, so the cell needs only its mesh peer token (no GitHub PAT).
51
+ Fail-loud: a non-200 or connection error returns 1 (never a silent git double-write)."""
52
+ url = gateway.rstrip("/") + "/highlights"
53
+ body: dict = {"highlight": highlight, "cell": cell}
54
+ if memory:
55
+ body["memory"] = memory
56
+ if when:
57
+ body["when"] = when
58
+ try:
59
+ token = _resolve_token(cell, token_file)
60
+ except RuntimeError as exc:
61
+ print(f"swarph highlight: {exc}", file=sys.stderr)
62
+ return 1
63
+ status, resp = _post_json(url, body, token)
64
+ if status == 200:
65
+ ts = resp.get("ts", "")
66
+ print(f"logged -> TIMELINE.md @ {ts} (via gateway)"
67
+ + (f" -> {memory}" if memory else ""))
68
+ if resp.get("pushed") is False:
69
+ print("swarph highlight: committed at the gateway but NOT yet pushed "
70
+ "(converges on a later push)", file=sys.stderr)
71
+ return 0
72
+ detail = resp.get("detail") if isinstance(resp, dict) else resp
73
+ where = f"HTTP {status}" if status else "connection failed"
74
+ print(f"swarph highlight: gateway POST failed ({where}): {detail}", file=sys.stderr)
75
+ return 1
76
+
77
+
33
78
  def _collapse(s: str) -> str:
34
79
  """One-line invariant — an embedded newline can't forge a second
35
80
  attributed entry."""
@@ -120,14 +165,31 @@ def run_highlight(argv: list) -> int:
120
165
  help="ISO8601 event time for a backfilled highlight; default now")
121
166
  p.add_argument("--no-push", action="store_true",
122
167
  help="commit locally only, never push")
168
+ p.add_argument("--gateway", default=None,
169
+ help="gateway base URL for the peer-token ingest path "
170
+ "(else SWARPH_HIGHLIGHT_GATEWAY / SWARPH_GATEWAY / SWARPH_BRAIN_GATEWAY)")
171
+ p.add_argument("--token-file", default=None,
172
+ help="explicit bearer token file (else MESH_GATEWAY_TOKEN / peer-token)")
173
+ p.add_argument("--local", action="store_true",
174
+ help="force the local git path even if a gateway is configured")
123
175
  args = p.parse_args(argv)
124
176
 
125
177
  repo = _resolve_dir(args.timeline_dir)
126
178
  cell = _resolve_cell(args.cell, repo)
127
- _ensure_timeline(repo, cell)
128
- ts = _collapse(args.when) if args.when else _now_ts()
129
179
  highlight = _collapse(args.highlight)
130
180
  memory = _collapse(args.memory)
181
+ when = _collapse(args.when) if args.when else ""
182
+
183
+ # Peer-token path (default when a gateway is configured): the gateway holds the
184
+ # git credential, so a cell logs with only its mesh peer token — no GitHub PAT.
185
+ # `--local` forces the legacy git path; with no gateway configured it's the git
186
+ # path too, so existing solo/offline timelines are unaffected.
187
+ gateway = "" if args.local else _resolve_gateway(args.gateway)
188
+ if gateway:
189
+ return _log_via_gateway(gateway, cell, highlight, memory, when, args.token_file)
190
+
191
+ _ensure_timeline(repo, cell)
192
+ ts = when or _now_ts()
131
193
  branch = _current_branch(repo)
132
194
  pushing = (not args.no_push) and _has_remote(repo)
133
195
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.26.0
3
+ Version: 0.27.0
4
4
  Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, session `import`, and a stranded-session `watchdog`.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
@@ -15,9 +15,9 @@ from swarph_cli.commands import brain_ask as ba
15
15
 
16
16
  # --- version ---------------------------------------------------------------
17
17
 
18
- def test_version_is_0_26_0():
18
+ def test_version_is_0_27_0():
19
19
  import swarph_cli
20
- assert swarph_cli.__version__ == "0.26.0"
20
+ assert swarph_cli.__version__ == "0.27.0"
21
21
 
22
22
 
23
23
  # --- request / response plumbing -------------------------------------------
@@ -0,0 +1,140 @@
1
+ """Tests for ``swarph highlight`` — append to a git-backed timeline.
2
+
3
+ Offline: each test uses a fresh temp timeline dir with no remote (``--no-push``),
4
+ so the real git mechanics run (init + append + commit) without a network.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import subprocess
10
+
11
+ import pytest
12
+
13
+ from swarph_cli.commands import highlight as hl
14
+
15
+
16
+ @pytest.fixture(autouse=True)
17
+ def _no_ambient_gateway(monkeypatch):
18
+ """Isolate git-path tests: clear any gateway env so a developer/CI with
19
+ SWARPH_BRAIN_GATEWAY set doesn't silently flip these into the gateway path.
20
+ Gateway tests set the env explicitly."""
21
+ for v in ("SWARPH_HIGHLIGHT_GATEWAY", "SWARPH_GATEWAY", "SWARPH_BRAIN_GATEWAY"):
22
+ monkeypatch.delenv(v, raising=False)
23
+
24
+
25
+ # --- pure helpers ----------------------------------------------------------
26
+
27
+ def test_collapse_newlines():
28
+ assert hl._collapse("a\nb\rc") == "a b c"
29
+
30
+
31
+ def test_format_line_with_memory():
32
+ line = hl._format_line("2026-06-27T00:00Z", "lab-ovh", "shipped X", "[[mem-x]]")
33
+ assert line == "- 2026-06-27T00:00Z · **lab-ovh** · shipped X · → [[mem-x]]"
34
+
35
+
36
+ def test_format_line_without_memory():
37
+ line = hl._format_line("2026-06-27T00:00Z", "lab-ovh", "shipped X", "")
38
+ assert line == "- 2026-06-27T00:00Z · **lab-ovh** · shipped X"
39
+
40
+
41
+ # --- the verb (real git, no remote) ----------------------------------------
42
+
43
+ def test_highlight_inits_appends_commits(tmp_path, monkeypatch):
44
+ d = tmp_path / "tl"
45
+ monkeypatch.setenv("SWARPH_TIMELINE_DIR", str(d))
46
+ monkeypatch.setenv("SWARPH_CELL", "test-cell")
47
+ rc = hl.run_highlight(["my highlight", "[[mem-x]]", "--no-push"])
48
+ assert rc == 0
49
+ tl = (d / "TIMELINE.md").read_text(encoding="utf-8")
50
+ assert "**test-cell**" in tl and "my highlight" in tl and "→ [[mem-x]]" in tl
51
+ log = subprocess.run(["git", "-C", str(d), "log", "--oneline"],
52
+ capture_output=True, text=True).stdout
53
+ assert "highlight" in log.lower()
54
+ # union-merge attribute set so concurrent cell appends auto-merge
55
+ assert "merge=union" in (d / ".gitattributes").read_text(encoding="utf-8")
56
+
57
+
58
+ def test_highlight_collapses_multiline_anti_spoof(tmp_path, monkeypatch):
59
+ d = tmp_path / "tl"
60
+ monkeypatch.setenv("SWARPH_TIMELINE_DIR", str(d))
61
+ monkeypatch.setenv("SWARPH_CELL", "c")
62
+ rc = hl.run_highlight(["line one\nFORGED **other** entry", "--no-push"])
63
+ assert rc == 0
64
+ entry_lines = [l for l in (d / "TIMELINE.md").read_text(encoding="utf-8").splitlines()
65
+ if l.startswith("- ")]
66
+ assert len(entry_lines) == 1 # the newline can't forge a second attributed entry
67
+ assert "line one FORGED" in entry_lines[0]
68
+
69
+
70
+ def test_highlight_second_append_keeps_both(tmp_path, monkeypatch):
71
+ d = tmp_path / "tl"
72
+ monkeypatch.setenv("SWARPH_TIMELINE_DIR", str(d))
73
+ monkeypatch.setenv("SWARPH_CELL", "c")
74
+ hl.run_highlight(["first", "--no-push"])
75
+ hl.run_highlight(["second", "--no-push"])
76
+ entry_lines = [l for l in (d / "TIMELINE.md").read_text(encoding="utf-8").splitlines()
77
+ if l.startswith("- ")]
78
+ assert len(entry_lines) == 2
79
+
80
+
81
+ # --- gateway (peer-token) path ---------------------------------------------
82
+
83
+ def _fake_post(capture, status=200, resp=None):
84
+ """Return a _post_json stand-in that records the call + returns a fixed reply."""
85
+ def _p(url, body, token, **kw):
86
+ capture.update(url=url, body=body, token=token)
87
+ return status, (resp if resp is not None else
88
+ {"logged": True, "cell": body.get("cell"), "ts": "2026-01-01T00:00Z",
89
+ "line": "x", "pushed": True})
90
+ return _p
91
+
92
+
93
+ def test_resolve_gateway_precedence(monkeypatch):
94
+ monkeypatch.delenv("SWARPH_HIGHLIGHT_GATEWAY", raising=False)
95
+ monkeypatch.setenv("SWARPH_BRAIN_GATEWAY", "http://brain:8788")
96
+ assert hl._resolve_gateway(None) == "http://brain:8788" # brain fallback
97
+ monkeypatch.setenv("SWARPH_GATEWAY", "http://gw:8788")
98
+ assert hl._resolve_gateway(None) == "http://gw:8788" # SWARPH_GATEWAY wins over brain
99
+ monkeypatch.setenv("SWARPH_HIGHLIGHT_GATEWAY", "http://hl:8788")
100
+ assert hl._resolve_gateway(None) == "http://hl:8788" # most-specific wins
101
+ assert hl._resolve_gateway("http://cli:8788") == "http://cli:8788" # --gateway wins over all
102
+
103
+
104
+ def test_gateway_mode_is_default_when_configured(monkeypatch):
105
+ cap = {}
106
+ monkeypatch.setenv("SWARPH_BRAIN_GATEWAY", "http://gw:8788") # the mesh-wide var
107
+ monkeypatch.setenv("SWARPH_CELL", "gridiron")
108
+ monkeypatch.setenv("MESH_GATEWAY_TOKEN", "tok") # _resolve_token source
109
+ monkeypatch.setattr(hl, "_post_json", _fake_post(cap))
110
+ rc = hl.run_highlight(["a real highlight", "[[project_x]]"])
111
+ assert rc == 0
112
+ assert cap["url"] == "http://gw:8788/highlights"
113
+ assert cap["body"] == {"highlight": "a real highlight", "cell": "gridiron",
114
+ "memory": "[[project_x]]"}
115
+ assert cap["token"] == "tok"
116
+
117
+
118
+ def test_gateway_failure_returns_1_no_git_fallback(monkeypatch, tmp_path):
119
+ monkeypatch.setenv("SWARPH_BRAIN_GATEWAY", "http://gw:8788")
120
+ monkeypatch.setenv("SWARPH_CELL", "c")
121
+ monkeypatch.setenv("MESH_GATEWAY_TOKEN", "tok")
122
+ monkeypatch.setenv("SWARPH_TIMELINE_DIR", str(tmp_path / "tl"))
123
+ monkeypatch.setattr(hl, "_post_json", _fake_post({}, status=502, resp={"detail": "boom"}))
124
+ rc = hl.run_highlight(["x"])
125
+ assert rc == 1 # fail loud
126
+ assert not (tmp_path / "tl" / "TIMELINE.md").exists() # no silent git double-write
127
+
128
+
129
+ def test_local_flag_forces_git_even_with_gateway(tmp_path, monkeypatch):
130
+ called = {"n": 0}
131
+ monkeypatch.setenv("SWARPH_BRAIN_GATEWAY", "http://gw:8788") # configured...
132
+ monkeypatch.setenv("SWARPH_TIMELINE_DIR", str(tmp_path / "tl"))
133
+ monkeypatch.setenv("SWARPH_CELL", "c")
134
+ monkeypatch.setattr(hl, "_post_json",
135
+ lambda *a, **k: (called.update(n=called["n"] + 1), (200, {}))[1])
136
+ rc = hl.run_highlight(["local please", "--local", "--no-push"]) # ...but --local wins
137
+ assert rc == 0
138
+ assert called["n"] == 0 # gateway NOT called
139
+ body = (tmp_path / "tl" / "TIMELINE.md").read_text()
140
+ assert "local please" in body and "**c**" in body # git path ran
@@ -1041,6 +1041,6 @@ def test_parser_defaults_process_name_claude_liveness_cmd_none():
1041
1041
  assert ns.liveness_cmd is None
1042
1042
 
1043
1043
 
1044
- def test_version_is_0_26_0():
1044
+ def test_version_is_0_27_0():
1045
1045
  import swarph_cli
1046
- assert swarph_cli.__version__ == "0.26.0"
1046
+ assert swarph_cli.__version__ == "0.27.0"
@@ -1,67 +0,0 @@
1
- """Tests for ``swarph highlight`` — append to a git-backed timeline.
2
-
3
- Offline: each test uses a fresh temp timeline dir with no remote (``--no-push``),
4
- so the real git mechanics run (init + append + commit) without a network.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import subprocess
10
-
11
- from swarph_cli.commands import highlight as hl
12
-
13
-
14
- # --- pure helpers ----------------------------------------------------------
15
-
16
- def test_collapse_newlines():
17
- assert hl._collapse("a\nb\rc") == "a b c"
18
-
19
-
20
- def test_format_line_with_memory():
21
- line = hl._format_line("2026-06-27T00:00Z", "lab-ovh", "shipped X", "[[mem-x]]")
22
- assert line == "- 2026-06-27T00:00Z · **lab-ovh** · shipped X · → [[mem-x]]"
23
-
24
-
25
- def test_format_line_without_memory():
26
- line = hl._format_line("2026-06-27T00:00Z", "lab-ovh", "shipped X", "")
27
- assert line == "- 2026-06-27T00:00Z · **lab-ovh** · shipped X"
28
-
29
-
30
- # --- the verb (real git, no remote) ----------------------------------------
31
-
32
- def test_highlight_inits_appends_commits(tmp_path, monkeypatch):
33
- d = tmp_path / "tl"
34
- monkeypatch.setenv("SWARPH_TIMELINE_DIR", str(d))
35
- monkeypatch.setenv("SWARPH_CELL", "test-cell")
36
- rc = hl.run_highlight(["my highlight", "[[mem-x]]", "--no-push"])
37
- assert rc == 0
38
- tl = (d / "TIMELINE.md").read_text(encoding="utf-8")
39
- assert "**test-cell**" in tl and "my highlight" in tl and "→ [[mem-x]]" in tl
40
- log = subprocess.run(["git", "-C", str(d), "log", "--oneline"],
41
- capture_output=True, text=True).stdout
42
- assert "highlight" in log.lower()
43
- # union-merge attribute set so concurrent cell appends auto-merge
44
- assert "merge=union" in (d / ".gitattributes").read_text(encoding="utf-8")
45
-
46
-
47
- def test_highlight_collapses_multiline_anti_spoof(tmp_path, monkeypatch):
48
- d = tmp_path / "tl"
49
- monkeypatch.setenv("SWARPH_TIMELINE_DIR", str(d))
50
- monkeypatch.setenv("SWARPH_CELL", "c")
51
- rc = hl.run_highlight(["line one\nFORGED **other** entry", "--no-push"])
52
- assert rc == 0
53
- entry_lines = [l for l in (d / "TIMELINE.md").read_text(encoding="utf-8").splitlines()
54
- if l.startswith("- ")]
55
- assert len(entry_lines) == 1 # the newline can't forge a second attributed entry
56
- assert "line one FORGED" in entry_lines[0]
57
-
58
-
59
- def test_highlight_second_append_keeps_both(tmp_path, monkeypatch):
60
- d = tmp_path / "tl"
61
- monkeypatch.setenv("SWARPH_TIMELINE_DIR", str(d))
62
- monkeypatch.setenv("SWARPH_CELL", "c")
63
- hl.run_highlight(["first", "--no-push"])
64
- hl.run_highlight(["second", "--no-push"])
65
- entry_lines = [l for l in (d / "TIMELINE.md").read_text(encoding="utf-8").splitlines()
66
- if l.startswith("- ")]
67
- assert len(entry_lines) == 2
File without changes
File without changes
File without changes