swarph-cli 0.48.2__tar.gz → 0.49.1__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 (344) hide show
  1. {swarph_cli-0.48.2/src/swarph_cli.egg-info → swarph_cli-0.49.1}/PKG-INFO +3 -3
  2. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/README.md +1 -1
  3. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/pyproject.toml +6 -3
  4. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/__init__.py +1 -1
  5. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/add.py +66 -6
  6. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/brain_ask.py +27 -11
  7. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/cell_selfcheck.py +10 -4
  8. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/codegraph.py +2 -2
  9. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/codegraph_hook.py +10 -2
  10. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/daemon.py +6 -1
  11. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/event.py +5 -2
  12. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/init.py +6 -3
  13. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/mesh.py +71 -15
  14. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/monitor.py +3 -1
  15. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/onboard.py +6 -2
  16. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/ratify.py +5 -1
  17. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/watchdog.py +39 -14
  18. swarph_cli-0.49.1/src/swarph_cli/gateway_default.py +70 -0
  19. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/guide/GUIDE.md +1 -1
  20. swarph_cli-0.49.1/src/swarph_cli/skills/i-have-adhd/LICENSE +21 -0
  21. swarph_cli-0.49.1/src/swarph_cli/skills/i-have-adhd/SKILL.md +176 -0
  22. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/systemd/codex-waker.default +6 -2
  23. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/systemd/peer-reply-drain.default +6 -2
  24. {swarph_cli-0.48.2 → swarph_cli-0.49.1/src/swarph_cli.egg-info}/PKG-INFO +3 -3
  25. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli.egg-info/SOURCES.txt +7 -0
  26. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli.egg-info/requires.txt +1 -1
  27. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_546_default_gateway.py +22 -32
  28. swarph_cli-0.49.1/tests/test_578_no_machine_specific_host_defaults.py +458 -0
  29. swarph_cli-0.49.1/tests/test_579_register_bootstrap_message.py +98 -0
  30. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_brain_ask_command.py +30 -5
  31. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_content_composition.py +4 -0
  32. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_daemon_command.py +2 -0
  33. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_mesh_command.py +9 -2
  34. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_onboard_command.py +4 -0
  35. swarph_cli-0.49.1/tests/test_packaged_skills.py +162 -0
  36. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_ratify_command.py +5 -0
  37. swarph_cli-0.49.1/tests/test_readme_claims_no_gateway_default.py +202 -0
  38. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_shipped_units_match_the_cli.py +40 -8
  39. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_watchdog.py +8 -4
  40. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/LICENSE +0 -0
  41. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/setup.cfg +0 -0
  42. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/__main__.py +0 -0
  43. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/bench/__init__.py +0 -0
  44. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/bench/backends.py +0 -0
  45. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/bench/pack.py +0 -0
  46. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/bench/prices.py +0 -0
  47. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/bench/quality.py +0 -0
  48. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/bench/refresh_prices.py +0 -0
  49. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/bench/runner.py +0 -0
  50. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/bench/validate.py +0 -0
  51. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/caller.py +0 -0
  52. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/capture/__init__.py +0 -0
  53. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/capture/harden.py +0 -0
  54. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/capture/lineage.py +0 -0
  55. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/capture/liveness.py +0 -0
  56. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/capture/manifest.py +0 -0
  57. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/capture/paths.py +0 -0
  58. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/capture/verify.py +0 -0
  59. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/cell.py +0 -0
  60. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/chain_token.py +0 -0
  61. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/__init__.py +0 -0
  62. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/_content.py +0 -0
  63. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/_display.py +0 -0
  64. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/_gateway_client.py +0 -0
  65. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/bench.py +0 -0
  66. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/board.py +0 -0
  67. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/board_merge_check.py +0 -0
  68. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/brain.py +0 -0
  69. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/cell.py +0 -0
  70. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/channel.py +0 -0
  71. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/chat.py +0 -0
  72. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  73. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/codex_waker.py +0 -0
  74. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/compress.py +0 -0
  75. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/gateway.py +0 -0
  76. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/gh_route.py +0 -0
  77. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/group.py +0 -0
  78. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/guide.py +0 -0
  79. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/highlight.py +0 -0
  80. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/hook_output.py +0 -0
  81. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/hooks.py +0 -0
  82. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/import_session.py +0 -0
  83. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  84. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/install_hook.py +0 -0
  85. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  86. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  87. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  88. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/lane.py +0 -0
  89. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/mcp_server.py +0 -0
  90. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/memory.py +0 -0
  91. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  92. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/memory_sync.py +0 -0
  93. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/okf_links.py +0 -0
  94. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  95. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  96. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/protocol_handler.py +0 -0
  97. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/schedule.py +0 -0
  98. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/security.py +0 -0
  99. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/service.py +0 -0
  100. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/spawn.py +0 -0
  101. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/timeline.py +0 -0
  102. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  103. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/compress/__init__.py +0 -0
  104. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/compress/levers.py +0 -0
  105. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/compress/marker.py +0 -0
  106. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/compress/verify.py +0 -0
  107. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/console_safe.py +0 -0
  108. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/delivery_queue.py +0 -0
  109. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/gateway/__init__.py +0 -0
  110. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/gateway/feature_registry.py +0 -0
  111. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/gateway/lanes_control.py +0 -0
  112. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/gateway/schema.sql +0 -0
  113. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/gateway/server.py +0 -0
  114. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/gateway/services_control.py +0 -0
  115. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/gh_identity.py +0 -0
  116. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/main.py +0 -0
  117. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/multiplexer.py +0 -0
  118. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/parsers/__init__.py +0 -0
  119. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/parsers/claude.py +0 -0
  120. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  121. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  122. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  123. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  124. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  125. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  126. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  127. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  128. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  129. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  130. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  131. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/peer_executor.py +0 -0
  132. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/peer_executor_windows.py +0 -0
  133. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/peer_reconciliation.py +0 -0
  134. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/peer_reply_outbox.py +0 -0
  135. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/peer_service_host.py +0 -0
  136. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/peer_service_payload.py +0 -0
  137. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/peer_service_reply.py +0 -0
  138. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/peer_staging.py +0 -0
  139. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/scripts/__init__.py +0 -0
  140. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  141. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  142. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  143. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/service/__init__.py +0 -0
  144. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/service/app.py +0 -0
  145. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/service/providers.py +0 -0
  146. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/session_bridge.py +0 -0
  147. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/stall_alert.py +0 -0
  148. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  149. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  150. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  151. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  152. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  153. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  154. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  155. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  156. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  157. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli/tokens.py +0 -0
  158. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  159. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  160. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/src/swarph_cli.egg-info/top_level.txt +0 -0
  161. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_124_register_merge.py +0 -0
  162. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_201_win32_bash_refusal.py +0 -0
  163. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_243_onboard_per_peer_token.py +0 -0
  164. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_307_task4_ask_reply.py +0 -0
  165. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_319_refusal_exit_codes.py +0 -0
  166. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_323_mistral_backend.py +0 -0
  167. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_397_gh_identity_router.py +0 -0
  168. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_397_router_preflight.py +0 -0
  169. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_467b_onboard_operator_credential.py +0 -0
  170. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_523_guide.py +0 -0
  171. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_524_conftest_hermeticity.py +0 -0
  172. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_532_accept_flag.py +0 -0
  173. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_535_version_capability.py +0 -0
  174. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_548_gateway_remainder.py +0 -0
  175. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_552_codegraph_gateway.py +0 -0
  176. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_555_relay_row_shape.py +0 -0
  177. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_556_relay_freshness.py +0 -0
  178. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_84_cli_labels.py +0 -0
  179. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_add_security_gate.py +0 -0
  180. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_artifact_add.py +0 -0
  181. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_artifact_hash.py +0 -0
  182. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_artifact_lib.py +0 -0
  183. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_artifact_mcp.py +0 -0
  184. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_artifact_skill.py +0 -0
  185. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_artifact_tool.py +0 -0
  186. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_artifact_uri.py +0 -0
  187. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_bench_backends.py +0 -0
  188. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_bench_command.py +0 -0
  189. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_bench_pack.py +0 -0
  190. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_bench_prices.py +0 -0
  191. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_bench_quality.py +0 -0
  192. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_bench_runner.py +0 -0
  193. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_bench_validate.py +0 -0
  194. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_board_card_thread.py +0 -0
  195. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_board_command.py +0 -0
  196. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_board_merge_check.py +0 -0
  197. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_board_obligation_close.py +0 -0
  198. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_brain_ask_env_diagnosis.py +0 -0
  199. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_brain_ask_identity_explicitness.py +0 -0
  200. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_brain_command.py +0 -0
  201. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_caller_meta_guard.py +0 -0
  202. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_capture_lineage.py +0 -0
  203. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_capture_liveness.py +0 -0
  204. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_capture_manifest.py +0 -0
  205. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_capture_paths.py +0 -0
  206. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_capture_security.py +0 -0
  207. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_cell_command_dispatch.py +0 -0
  208. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_cell_harden.py +0 -0
  209. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_cell_loader.py +0 -0
  210. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_cell_selfcheck.py +0 -0
  211. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_cell_verify.py +0 -0
  212. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_chain_token.py +0 -0
  213. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_channel_command.py +0 -0
  214. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  215. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_channel_post.py +0 -0
  216. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_channel_wake_policy_filter.py +0 -0
  217. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_chat_command.py +0 -0
  218. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_claude_parser.py +0 -0
  219. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_claude_tmux_template.py +0 -0
  220. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_codegraph_caller_scope.py +0 -0
  221. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_codegraph_command.py +0 -0
  222. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_codegraph_hook.py +0 -0
  223. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_codegraph_mcp.py +0 -0
  224. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_codex_waker.py +0 -0
  225. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_compress_caller_convention.py +0 -0
  226. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_compress_command.py +0 -0
  227. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_compress_levers.py +0 -0
  228. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_compress_marker.py +0 -0
  229. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_compress_verify.py +0 -0
  230. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_console_encoding_e2e.py +0 -0
  231. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_cursor_membrane.py +0 -0
  232. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_daemon_console_encoding.py +0 -0
  233. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_daemon_delivery.py +0 -0
  234. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_delivery_queue.py +0 -0
  235. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_display_sanitize.py +0 -0
  236. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  237. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_event_emit.py +0 -0
  238. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_feature_to_uri.py +0 -0
  239. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_gateway_brain.py +0 -0
  240. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_gateway_client.py +0 -0
  241. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_gateway_command.py +0 -0
  242. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_gateway_message_idempotency.py +0 -0
  243. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  244. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_group_command.py +0 -0
  245. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_heartbeat_check.py +0 -0
  246. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_highlight_command.py +0 -0
  247. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_hook_output.py +0 -0
  248. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_hooks_activity_marker.py +0 -0
  249. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_hooks_add.py +0 -0
  250. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_hooks_bundle.py +0 -0
  251. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_hooks_init.py +0 -0
  252. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_hooks_legacy_generation_removal.py +0 -0
  253. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_hooks_lifecycle.py +0 -0
  254. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_hooks_merge.py +0 -0
  255. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_hooks_verify.py +0 -0
  256. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  257. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_hooks_win32_command_path.py +0 -0
  258. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_import_command.py +0 -0
  259. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_inbox_identity_provenance.py +0 -0
  260. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_init_command.py +0 -0
  261. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_init_provider_menu.py +0 -0
  262. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_install_codex_hooks.py +0 -0
  263. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_install_hook.py +0 -0
  264. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_install_multiplexer_command.py +0 -0
  265. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_install_postcompact_hook.py +0 -0
  266. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_install_wake_hook.py +0 -0
  267. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_lane_command.py +0 -0
  268. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_main.py +0 -0
  269. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_main_unknown_verb_guard.py +0 -0
  270. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_mcp_server.py +0 -0
  271. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_memory_command.py +0 -0
  272. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_memory_gateway.py +0 -0
  273. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_memory_sync.py +0 -0
  274. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_memory_sync_membrane.py +0 -0
  275. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_mesh_inbox_mark_read.py +0 -0
  276. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_mesh_inbox_sanitize.py +0 -0
  277. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_mesh_send_unknown_peer.py +0 -0
  278. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_mesh_sidecar.py +0 -0
  279. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  280. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  281. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_meta_edge_identity.py +0 -0
  282. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_monitor_channel_poll.py +0 -0
  283. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_monitor_cli.py +0 -0
  284. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_monitor_daemon_collision.py +0 -0
  285. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_monitor_self_name_verification.py +0 -0
  286. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_monitor_state_split.py +0 -0
  287. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_multiplexer.py +0 -0
  288. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_muse_membrane.py +0 -0
  289. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_muse_own_cli.py +0 -0
  290. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_obligation_close_is_surfaced.py +0 -0
  291. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_okf_links.py +0 -0
  292. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_packaged_artifacts_exist.py +0 -0
  293. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_peer_executor.py +0 -0
  294. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_peer_executor_windows.py +0 -0
  295. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_peer_reply_drain_command.py +0 -0
  296. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_peer_reply_drain_supervisor.py +0 -0
  297. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_peer_reply_outbox.py +0 -0
  298. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_peer_reply_transport.py +0 -0
  299. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_peer_service_e2e.py +0 -0
  300. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_peer_service_host.py +0 -0
  301. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_peer_service_payload.py +0 -0
  302. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_peer_service_reply.py +0 -0
  303. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_postcompact_recall.py +0 -0
  304. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_process_liveness.py +0 -0
  305. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_protocol_handler.py +0 -0
  306. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_providers_isolation.py +0 -0
  307. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_repl_caller_convention.py +0 -0
  308. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_schedule_command.py +0 -0
  309. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_schedule_create_contract.py +0 -0
  310. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_security.py +0 -0
  311. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_service_app.py +0 -0
  312. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_service_command.py +0 -0
  313. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_service_providers.py +0 -0
  314. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_session_bridge_inject.py +0 -0
  315. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_session_bridge_probe.py +0 -0
  316. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_session_bridge_resolve.py +0 -0
  317. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_session_path_role_gate.py +0 -0
  318. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_smoke_chat.py +0 -0
  319. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_smoke_one_shot.py +0 -0
  320. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_smoke_phase_5_5.py +0 -0
  321. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_spawn_base_hoist.py +0 -0
  322. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_spawn_command.py +0 -0
  323. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_spawn_identity_stamp.py +0 -0
  324. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_spawn_live_pin.py +0 -0
  325. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_spawn_mitosis_lineage.py +0 -0
  326. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_spawn_tmux_session.py +0 -0
  327. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_spawn_windows_relaunch.py +0 -0
  328. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_stall_alert.py +0 -0
  329. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_subprocess_decode_encoding.py +0 -0
  330. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_timeline_command.py +0 -0
  331. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_tmux_wake_submit_verify.py +0 -0
  332. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_token_file_one_parser.py +0 -0
  333. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_token_precedence_explicit_first.py +0 -0
  334. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_unified_token_resolver.py +0 -0
  335. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_version_consistency.py +0 -0
  336. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_vibe_membrane.py +0 -0
  337. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_watchdog_dm_wake.py +0 -0
  338. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  339. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  340. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_watchdog_emit_health.py +0 -0
  341. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_watchdog_install_flags.py +0 -0
  342. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_watchdog_model_rung.py +0 -0
  343. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_watchdog_stale_peers.py +0 -0
  344. {swarph_cli-0.48.2 → swarph_cli-0.49.1}/tests/test_windows_waker_installer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.48.2
3
+ Version: 0.49.1
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`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
@@ -29,7 +29,7 @@ Requires-Dist: PyYAML>=6.0
29
29
  Provides-Extra: dev
30
30
  Requires-Dist: pytest>=7.0; extra == "dev"
31
31
  Provides-Extra: mcp
32
- Requires-Dist: mcp>=1.0; extra == "mcp"
32
+ Requires-Dist: mcp<2,>=1.0; extra == "mcp"
33
33
  Provides-Extra: bench
34
34
  Requires-Dist: google-genai>=0.3; extra == "bench"
35
35
  Provides-Extra: mistral
@@ -356,7 +356,7 @@ The load-bearing piece is **billing-scrub**: the subprocess env has every known
356
356
 
357
357
  ### `swarph channel` / `swarph schedule` / `swarph lane`
358
358
 
359
- The gateway's **automation control plane** — channels (pub/sub), scheduled events, and the $0-lane orchestration — as first-class client verbs. They share `mesh`'s auth: identity is `--as` / `SWARPH_SELF`, the bearer is `--token-file` / `MESH_GATEWAY_TOKEN` / the peer-token file, and `--gateway` (default `http://localhost:8788`) points at the hub.
359
+ The gateway's **automation control plane** — channels (pub/sub), scheduled events, and the $0-lane orchestration — as first-class client verbs. They share `mesh`'s auth: identity is `--as` / `SWARPH_SELF`, the bearer is `--token-file` / `MESH_GATEWAY_TOKEN` / the peer-token file, and `--gateway` / `MESH_GATEWAY_URL` points at the hub. **There is no default host** — swarph ships none, and an unconfigured call refuses with an actionable message rather than dialling somewhere (#578).
360
360
 
361
361
  ```bash
362
362
  # channels — converge work into pub/sub rooms
@@ -308,7 +308,7 @@ The load-bearing piece is **billing-scrub**: the subprocess env has every known
308
308
 
309
309
  ### `swarph channel` / `swarph schedule` / `swarph lane`
310
310
 
311
- The gateway's **automation control plane** — channels (pub/sub), scheduled events, and the $0-lane orchestration — as first-class client verbs. They share `mesh`'s auth: identity is `--as` / `SWARPH_SELF`, the bearer is `--token-file` / `MESH_GATEWAY_TOKEN` / the peer-token file, and `--gateway` (default `http://localhost:8788`) points at the hub.
311
+ The gateway's **automation control plane** — channels (pub/sub), scheduled events, and the $0-lane orchestration — as first-class client verbs. They share `mesh`'s auth: identity is `--as` / `SWARPH_SELF`, the bearer is `--token-file` / `MESH_GATEWAY_TOKEN` / the peer-token file, and `--gateway` / `MESH_GATEWAY_URL` points at the hub. **There is no default host** — swarph ships none, and an unconfigured call refuses with an actionable message rather than dialling somewhere (#578).
312
312
 
313
313
  ```bash
314
314
  # channels — converge work into pub/sub rooms
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.48.2"
7
+ version = "0.49.1"
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`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -65,7 +65,9 @@ dev = ["pytest>=7.0"]
65
65
  # toolbelt (swarph_search/add/describe) to any MCP host. The SDK is an extra
66
66
  # so the core one-shot/mesh paths don't pull it in; `swarph mcp-server`
67
67
  # prints a `pip install swarph-cli[mcp]` hint when it's absent.
68
- mcp = ["mcp>=1.0"]
68
+ # mcp 2.0 removed mcp.server.fastmcp (FastMCP left the SDK package); the
69
+ # mcp-server command is written against the 1.x API — pin below 2.
70
+ mcp = ["mcp>=1.0,<2"]
69
71
  # `swarph bench run` (metered backend) calls the google-genai Developer API
70
72
  # directly. The dependency is an extra so `bench`'s pack loader/validate/
71
73
  # distance-engine paths (and every other swarph-cli verb) stay usable without
@@ -121,7 +123,8 @@ where = ["src"]
121
123
  # tree and fails on every installed wheel -- the 0.39.3 bug above, again.
122
124
  swarph_cli = ["systemd/*.service", "systemd/*.timer", "systemd/*.default",
123
125
  "scripts/*.sh", "scripts/*.ps1", "guide/*.md",
124
- "payloads/postcompact/*.sh", "payloads/postcompact/*.cmd"]
126
+ "payloads/postcompact/*.sh", "payloads/postcompact/*.cmd",
127
+ "skills/*/SKILL.md", "skills/*/LICENSE"]
125
128
  # v0.15: ship the bundled mesh-gateway schema so `swarph gateway serve`
126
129
  # can apply it at startup via MESH_SCHEMA_PATH (defaults next to server.py).
127
130
  "swarph_cli.gateway" = ["*.sql"]
@@ -16,6 +16,6 @@ The architecture splits CLI from substrate so:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.48.2"
19
+ __version__ = "0.49.1"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -672,19 +672,79 @@ BUILTIN_SKILLS: dict = {
672
672
  }
673
673
 
674
674
 
675
+ #: Skills shipped as PACKAGE DATA rather than as a Python string constant.
676
+ #:
677
+ #: `swarph-intro` is a ~10-line str literal and that is fine. A 176-line skill
678
+ #: embedded in source is not: it cannot be diffed against its upstream, and the
679
+ #: two copies drift silently — which is the same failure `i-have-adhd`'s own
680
+ #: upstream guards against with a CI check that the `.cursor/` mirror matches.
681
+ #:
682
+ #: Maps skill name -> (description, files-under-src/swarph_cli/skills/<name>/).
683
+ _PACKAGED_SKILLS: dict = {
684
+ "i-have-adhd": (
685
+ "Shape output for a reader with ADHD: lead with the next action, number "
686
+ "multi-step work, restate state across turns, suppress tangents, make "
687
+ "wins visible. Vendored from ayghri/i-have-adhd (MIT) with swarph's "
688
+ "rule 0 (act, do not announce). Invoke with /i-have-adhd.",
689
+ ("SKILL.md", "LICENSE"),
690
+ ),
691
+ }
692
+
693
+ #: Where packaged skill files live inside the installed package.
694
+ _PACKAGED_SKILLS_DIR = Path(__file__).resolve().parent.parent / "skills"
695
+
696
+
697
+ def _resolve_packaged_skill(name: str) -> SkillBundle:
698
+ """Build a :class:`SkillBundle` by READING the packaged files.
699
+
700
+ >>> READ AT RESOLVE TIME, NOT AT IMPORT. <<< A module-level constant is
701
+ evaluated when the package is imported, so a missing or unreadable data file
702
+ would make `swarph --help` fail rather than `swarph add` fail. #578 is the
703
+ same lesson from the other direction: nothing that can fail belongs at import.
704
+
705
+ Raises :class:`FileNotFoundError` naming the absent file — which is the
706
+ honest error for a wheel that did not ship its own package data, a failure
707
+ this repo has had before (see the release checklist: a new module silently
708
+ does not ship).
709
+ """
710
+ description, filenames = _PACKAGED_SKILLS[name]
711
+ files = []
712
+ for relpath in filenames:
713
+ src = _PACKAGED_SKILLS_DIR / name / relpath
714
+ if not src.is_file():
715
+ raise FileNotFoundError(
716
+ f"packaged skill {name!r} is missing {relpath!r} at {src} — the "
717
+ f"wheel did not ship its package data; this is a packaging bug, "
718
+ f"not a user error"
719
+ )
720
+ files.append((relpath, src.read_text(encoding="utf-8")))
721
+ return SkillBundle(
722
+ name=name,
723
+ description=description,
724
+ publisher=_BUILTIN_PUBLISHER,
725
+ trust="builtin",
726
+ files=tuple(files),
727
+ )
728
+
729
+
675
730
  def resolve_builtin_skill(name: str) -> SkillBundle:
676
731
  """Return the bundled :class:`SkillBundle` named ``name``.
677
732
 
733
+ Two sources, one namespace: :data:`BUILTIN_SKILLS` (string constants) and
734
+ :data:`_PACKAGED_SKILLS` (files shipped as package data). A caller cannot
735
+ tell them apart and should not need to.
736
+
678
737
  Raises :class:`ValueError` (listing the available names) for an unknown
679
738
  name — propagated to the CLI layer, which surfaces it; nothing installed.
680
739
  """
681
- try:
740
+ if name in BUILTIN_SKILLS:
682
741
  return BUILTIN_SKILLS[name]
683
- except KeyError:
684
- available = ", ".join(sorted(BUILTIN_SKILLS))
685
- raise ValueError(
686
- f"unknown builtin skill {name!r}; available: {available}"
687
- ) from None
742
+ if name in _PACKAGED_SKILLS:
743
+ return _resolve_packaged_skill(name)
744
+ available = ", ".join(sorted({*BUILTIN_SKILLS, *_PACKAGED_SKILLS}))
745
+ raise ValueError(
746
+ f"unknown builtin skill {name!r}; available: {available}"
747
+ ) from None
688
748
 
689
749
 
690
750
  def _install_skill_files(skills_home, name: str, files) -> None:
@@ -10,7 +10,7 @@ verb so any cell can search the swarm's shared memory the same way. Two modes:
10
10
  Stdlib-only. Config from the environment, mirroring ``swarph mesh``'s token model:
11
11
 
12
12
  GBRAIN_MCP_URL gbrain MCP endpoint; falls back to SWARPH_BRAIN_MCP, else
13
- / SWARPH_BRAIN_MCP http://100.107.222.72:8792/mcp (tailnet IP — gbrain
13
+ / SWARPH_BRAIN_MCP http://<gbrain-host>:8792/mcp (no default — gbrain
14
14
  binds no loopback; measured 2026-08-23, card #548)
15
15
  GBRAIN_TOKEN read token; falls back to SWARPH_BRAIN_TOKEN, then to the
16
16
  / SWARPH_BRAIN_TOKEN mesh per-peer token (~/.config/swarph/<self>.peer_token).
@@ -35,23 +35,34 @@ from swarph_cli import tokens
35
35
  from pathlib import Path
36
36
  from typing import Optional
37
37
 
38
- # TAILNET IP, NOT loopback (card #548): measured 2026-08-23, gbrain binds
39
- # 100.107.222.72:8792 ONLY — 127.0.0.1:8792 refuses on the gateway box itself.
40
- # A loopback default is deaf everywhere, including on the box running gbrain.
41
- _DEFAULT_GBRAIN = "http://100.107.222.72:8792/mcp"
38
+ # NO MODULE-LEVEL ENDPOINT CONSTANT, ON PURPOSE. gbrain binds its tailnet IP
39
+ # only (127.0.0.1:8792 refuses even on gbrain's own box), so SWARPH_BRAIN_MCP must
40
+ # be set explicitly and no host ships as a default (#548 -> #578).
41
+ # `_DEFAULT_GBRAIN = os.environ.get("SWARPH_BRAIN_MCP")` used to live here and was
42
+ # read at IMPORT time, which froze the developer's shell into the module:
43
+ # MEASURED in seat-A review of PR #318 — with SWARPH_BRAIN_MCP exported,
44
+ # test_resolve_endpoint_refuses_rather_than_guessing_a_host FAILED, because the
45
+ # test can delenv the two call-time operands but not a value already captured.
46
+ # The env is read at CALL time in _resolve_endpoint below, and nowhere else.
42
47
  _DEFAULT_TOPK = 6
43
48
 
44
49
 
45
- def _resolve_endpoint() -> str:
46
- """Endpoint precedence: GBRAIN_MCP_URL > SWARPH_BRAIN_MCP > tailnet default.
50
+ def _resolve_endpoint(explicit: str | None = None) -> str:
51
+ """Endpoint precedence: GBRAIN_MCP_URL > SWARPH_BRAIN_MCP. No host default (#578).
47
52
 
48
53
  The SWARPH_BRAIN_MCP fallback keeps the verb config-compatible with the
49
54
  standalone ``swarph-brain-ask`` script (which reads SWARPH_BRAIN_*), so one
50
55
  env config works with both.
51
56
  """
52
- return (os.environ.get("GBRAIN_MCP_URL")
53
- or os.environ.get("SWARPH_BRAIN_MCP")
54
- or _DEFAULT_GBRAIN)
57
+ from swarph_cli.gateway_default import require_gateway
58
+
59
+ return require_gateway(
60
+ explicit
61
+ or os.environ.get("GBRAIN_MCP_URL")
62
+ or os.environ.get("SWARPH_BRAIN_MCP"),
63
+ env="SWARPH_BRAIN_MCP",
64
+ what="gbrain MCP",
65
+ )
55
66
 
56
67
 
57
68
  def _build_query_request(question: str, limit: int = _DEFAULT_TOPK) -> dict:
@@ -237,10 +248,15 @@ def run_brain_ask(argv: list) -> int:
237
248
  help="top-k chunks to retrieve (default 6)")
238
249
  parser.add_argument("--no-synth", action="store_true",
239
250
  help="retrieval only — print raw chunks, skip prose synthesis")
240
- parser.add_argument("--gateway", default=_resolve_endpoint(),
251
+ parser.add_argument("--gateway", default=None,
241
252
  help="gbrain MCP endpoint (env: GBRAIN_MCP_URL or SWARPH_BRAIN_MCP)")
242
253
  parser.add_argument("--token-file", default=None, help="explicit read-token file")
243
254
  args = parser.parse_args(argv)
255
+ # #578/#318-review: resolve the endpoint HERE, not as an argparse default.
256
+ # Evaluating it at parser-BUILD time made `--help` die and made the
257
+ # `--gateway` the error message tells you to pass unreachable, because the
258
+ # parser never got as far as parsing it. Same pattern as ratify:173.
259
+ args.gateway = _resolve_endpoint(args.gateway)
244
260
  question = " ".join(args.question)
245
261
 
246
262
  gw = os.environ.get("SWARPH_BRAIN_GATEWAY")
@@ -290,14 +290,20 @@ def unit_is_relevant(name: str, text: str) -> bool:
290
290
  # cost of the circularity break, stated here rather than discovered.
291
291
 
292
292
  # TAILNET IP, NOT localhost and NOT a MagicDNS name (commander, 2026-08-21).
293
- # The mesh-gateway binds HOST=100.107.222.72 ONLY; localhost has never been bound.
293
+ # The mesh-gateway binds a tailnet IP ONLY; localhost has never been bound.
294
294
  # MESH_GATEWAY_URL overrides — the escape hatch for anyone outside this mesh.
295
- _RESOLVER_DEFAULT_GATEWAY = os.environ.get("MESH_GATEWAY_URL", "http://100.107.222.72:8788")
295
+ # Inlined, NOT imported from swarph_cli.gateway_default: this module is
296
+ # deliberately stdlib-only so it can run as a bare file in isolated mode
297
+ # (test_module_imports_are_stdlib_only). Same policy as #578, no import.
298
+ # NO import-time capture of os.environ here either: `_RESOLVER_DEFAULT_GATEWAY =
299
+ # os.environ.get("MESH_GATEWAY_URL")` used to back this resolver, which made the
300
+ # answer depend on the shell that IMPORTED the module rather than the env passed
301
+ # in. The env is the ARGUMENT — see #578's "model the environment as data".
296
302
 
297
303
 
298
304
  def resolver_gateway(env: dict) -> str:
299
- """What the CLI would use: $MESH_GATEWAY_URL, else the localhost default."""
300
- return (env.get("MESH_GATEWAY_URL") or "").strip() or _RESOLVER_DEFAULT_GATEWAY
305
+ """What the CLI would use: $MESH_GATEWAY_URL, else "" — no host ships (#578)."""
306
+ return (env.get("MESH_GATEWAY_URL") or "").strip()
301
307
 
302
308
 
303
309
  def resolver_token(self_name: str, env: dict, home: Path) -> tuple[Optional[str], str]:
@@ -245,8 +245,8 @@ def _resolve_gateway(arg) -> str:
245
245
 
246
246
  SWARPH_CODEGRAPH_GATEWAY <unset>
247
247
  SWARPH_GATEWAY <unset>
248
- SWARPH_BRAIN_GATEWAY http://100.107.222.72:8788 <- brain-NAMED, MESH value
249
- MESH_GATEWAY_URL http://100.107.222.72:8788 <- correct, and NOT consulted
248
+ SWARPH_BRAIN_GATEWAY http://<host>:8788 <- brain-NAMED, MESH value
249
+ MESH_GATEWAY_URL http://<host>:8788 <- correct, and NOT consulted
250
250
 
251
251
  The relay worked ONLY because a brain-named variable was mis-set to the mesh
252
252
  gateway's address. Every link that resolved was unset or wrong-by-name, and the
@@ -36,8 +36,8 @@ import urllib.error
36
36
  import urllib.request
37
37
  from pathlib import Path
38
38
  from typing import Optional
39
+ from swarph_cli.gateway_default import env_gateway
39
40
 
40
- DEFAULT_GATEWAY = os.environ.get("MESH_GATEWAY_URL", "http://100.107.222.72:8788")
41
41
  TIMEOUT_S = 6
42
42
  MAX_ROWS = 6
43
43
 
@@ -131,6 +131,14 @@ def query_gateway(term: str, gateway: str, token: str, limit: int = MAX_ROWS) ->
131
131
  >>> AN UNREACHABLE OR REFUSING BACKEND IS AN ERROR, NOT AN EMPTY RESULT. <<<
132
132
  Collapsing them is the defect this hook exists to avoid teaching.
133
133
  """
134
+ # #578: with no baked-in host, `gateway` can be "". Request() rejects a
135
+ # relative URL at CONSTRUCTION (below, outside the try), so an unconfigured
136
+ # backend would raise ValueError and take the whole turn down — against this
137
+ # entry point's "ALWAYS exits 0, must never fail a turn" contract.
138
+ # UNCONFIGURED is the same class as UNREACHABLE: an error, not empty.
139
+ if not (gateway or "").strip():
140
+ return {"error": "MESH_GATEWAY_URL is not set and swarph ships no default "
141
+ "gateway host (#578) — the graph was never asked"}
134
142
  req = urllib.request.Request(
135
143
  f"{gateway.rstrip('/')}/codegraph",
136
144
  data=json.dumps({"query": term, "limit": limit}).encode(),
@@ -228,7 +236,7 @@ def run_codegraph_hook(argv: Optional[list] = None) -> int:
228
236
  """PostToolUse(Bash) entry point. ALWAYS exits 0 — it must never fail a turn."""
229
237
  argv = list(argv or [])
230
238
  self_name = os.environ.get("SWARPH_SELF", "").strip()
231
- gateway = DEFAULT_GATEWAY
239
+ gateway = env_gateway()
232
240
  for i, a in enumerate(argv):
233
241
  if a == "--as" and i + 1 < len(argv):
234
242
  self_name = argv[i + 1]
@@ -52,6 +52,7 @@ from typing import Optional
52
52
  from swarph_cli import session_bridge, stall_alert
53
53
  from swarph_cli.console_safe import print_safe
54
54
  from swarph_cli.delivery_queue import DeliveryQueue
55
+ from swarph_cli.gateway_default import env_gateway, require_gateway
55
56
 
56
57
 
57
58
  _DEFAULT_POLL_S = 30
@@ -85,7 +86,7 @@ def _build_parser() -> argparse.ArgumentParser:
85
86
  )
86
87
  p.add_argument(
87
88
  "--gateway",
88
- default=os.environ.get("MESH_GATEWAY_URL", "http://100.107.222.72:8788"),
89
+ default=env_gateway(),
89
90
  help="mesh-gateway base URL.",
90
91
  )
91
92
  p.add_argument(
@@ -564,6 +565,10 @@ def run_daemon(argv: list[str]) -> int:
564
565
  state_dir.mkdir(parents=True, exist_ok=True)
565
566
  # Announce ownership of this state dir BEFORE any cursor work, so a
566
567
  # concurrently-starting `swarph monitor` sees it and refuses (PR #139).
568
+ # #578: identity is resolved above first, so a missing --self reports THAT,
569
+ # not a missing env var. The gateway is only needed for the requests below.
570
+ args.gateway = require_gateway(getattr(args, "gateway", ""))
571
+
567
572
  _write_daemon_pidfile(state_dir)
568
573
  token = _resolve_token(args.token_file)
569
574
  if not token:
@@ -67,9 +67,12 @@ def _post_channel(
67
67
  """
68
68
  name = resolve_self_name(self_name)
69
69
  token = resolve_token(name, token_file)
70
- from .mesh import _DEFAULT_GATEWAY
70
+ from swarph_cli.gateway_default import env_gateway
71
71
 
72
- base = (gateway or _DEFAULT_GATEWAY).rstrip("/")
72
+ # "" when unconfigured — post_json IS mesh._post_json, whose
73
+ # _require_absolute_gateway_url then refuses by name rather than raising
74
+ # `ValueError: unknown url type` from Request(). Checked in review of #318.
75
+ base = (gateway or env_gateway()).rstrip("/")
73
76
  ev: dict = {"event": event, "payload": payload}
74
77
  if chain_token is not None: # carry an explicit "" through; only omit when unset
75
78
  ev["chain_token"] = chain_token
@@ -22,8 +22,8 @@ import yaml
22
22
 
23
23
  from swarph_shared.cell import PEER_NAME_RE, parse_cell_dict
24
24
  from swarph_cli.cell import CLI_ENABLED_PROVIDERS, CellError, cells_dir
25
+ from swarph_cli.gateway_default import env_gateway
25
26
 
26
- _DEFAULT_GATEWAY = os.environ.get("MESH_GATEWAY_URL", "http://100.107.222.72:8788")
27
27
  _CODEX_SANDBOX_DEFAULT = "workspace-write"
28
28
  _CODEX_SANDBOX_VALUES = ("workspace-write", "read-only")
29
29
 
@@ -128,7 +128,7 @@ def _build_parser() -> argparse.ArgumentParser:
128
128
  p.add_argument("--tmux", default=None)
129
129
  p.add_argument("--cursor", default=None)
130
130
  p.add_argument("--sandbox", default=None)
131
- p.add_argument("--gateway", default=_DEFAULT_GATEWAY)
131
+ p.add_argument("--gateway", default=env_gateway())
132
132
  p.add_argument("--assisted-memory", dest="assisted_memory", default=None, metavar="REPO",
133
133
  help="enable git-backed memory at REPO (SSH→HTTPS normalized)")
134
134
  p.add_argument("--starter", default=None)
@@ -197,8 +197,11 @@ def run_init(argv: list[str]) -> int:
197
197
  "cwd": str(cwd),
198
198
  "tmux_session": tmux,
199
199
  "cursor_path": cursor,
200
- "mesh": {"gateway": args.gateway},
201
200
  }
201
+ # Only when one was actually chosen: with the env unset args.gateway is ""
202
+ # (#578 ships no host), and writing that records a decision nobody made.
203
+ if args.gateway:
204
+ cell["mesh"] = {"gateway": args.gateway}
202
205
  if sandbox is not None:
203
206
  cell["sandbox"] = sandbox
204
207
  if args.starter:
@@ -39,6 +39,7 @@ from typing import Optional
39
39
  from .. import tokens
40
40
  from ._content import ContentError, add_content_args, resolve_content
41
41
  from ._display import sanitize_terminal
42
+ from swarph_cli.gateway_default import GatewayNotConfigured, env_gateway
42
43
 
43
44
 
44
45
  def _format_inbox_line(dm: dict) -> str:
@@ -51,17 +52,13 @@ def _format_inbox_line(dm: dict) -> str:
51
52
  return f"id={dm.get('id')} {read} from={who} kind={kind} {content}"
52
53
 
53
54
 
54
- # TAILNET IP, NOT localhost and NOT a MagicDNS name (commander, 2026-08-21).
55
- # The mesh-gateway binds HOST=100.107.222.72 ONLY — localhost has never been bound, so this
56
- # default failed as a bare "Connection refused" with no cause named. It cost 792 silent
57
- # card-export failures over 8 days, and it turned a WORKING hand-started monitor into a
58
- # DEAF SUPERVISED one the moment it was moved to a systemd unit (the unit passes no
59
- # --gateway, so it fell through to this constant).
60
- # An IP over MagicDNS on purpose: a name needs MagicDNS enabled, the right search domain,
61
- # and no local collision; the IP needs only that tailscale is up, which is the real
62
- # precondition anyway. MESH_GATEWAY_URL overrides it — that env var is the escape hatch
63
- # for anyone outside this mesh, and optional inside it.
64
- _DEFAULT_GATEWAY = os.environ.get("MESH_GATEWAY_URL", "http://100.107.222.72:8788")
55
+ # NO MODULE-LEVEL GATEWAY CONSTANT (#578). #546 put the gateway's tailnet IP here,
56
+ # correctly at the time; that box was retired 2026-08-25 and the literal expired.
57
+ # The constant is gone rather than emptied: read at IMPORT time it captured whatever
58
+ # the packaging/importing shell exported, and MEASURED in seat-A review of PR #318 it
59
+ # still returned that value after the environment was cleared — the one channel by
60
+ # which a test could depend on a developer's shell. env_gateway() is called at
61
+ # parser-BUILD time instead (parsers are built per invocation).
65
62
  _DEFAULT_POLL_S = 30
66
63
  _BACKOFF_EMPTY_THRESHOLD = 5
67
64
  _BACKOFF_EMPTY_SECONDS = 60
@@ -170,7 +167,7 @@ def _build_parser() -> argparse.ArgumentParser:
170
167
  )
171
168
  peers.add_argument(
172
169
  "--gateway",
173
- default=os.environ.get("MESH_GATEWAY_URL", _DEFAULT_GATEWAY),
170
+ default=env_gateway(),
174
171
  help="mesh-gateway base URL",
175
172
  )
176
173
  peers.add_argument("--token-file", default=None, help="explicit bearer token file")
@@ -200,7 +197,7 @@ def _add_common(p: argparse.ArgumentParser) -> None:
200
197
  )
201
198
  p.add_argument(
202
199
  "--gateway",
203
- default=os.environ.get("MESH_GATEWAY_URL", _DEFAULT_GATEWAY),
200
+ default=env_gateway(),
204
201
  help="mesh-gateway base URL",
205
202
  )
206
203
  p.add_argument("--token-file", default=None, help="explicit bearer token file")
@@ -313,12 +310,69 @@ def _resolve_token(
313
310
  )
314
311
  if res is not None:
315
312
  return res.token
313
+ # #579: the old message named the very file `register` exists to MINT, which
314
+ # reads as a chicken-and-egg to anyone bootstrapping a NEW peer (droplet hit
315
+ # exactly this onboarding friendly-coder, 2026-08-25). `swarph onboard`
316
+ # already explains the operator path; this now says the same thing.
317
+ #
318
+ # >>> THE BOOTSTRAP PARAGRAPH BELONGS TO `register` ALONE. <<< It first shipped
319
+ # unconditionally, so `mesh send`/`reply`/`inbox`/`sidecar` — which all reach here
320
+ # with the default allow_peer_token=True — told a user with a missing token that
321
+ # "minting it is what this command does", which is FALSE for send. Measured in
322
+ # seat-A review of PR #318. allow_peer_token=False is exactly the register path
323
+ # (:848 is its only caller), so it is the condition, not a new flag.
324
+ bootstrap = (
325
+ f"\n"
326
+ f" If you are BOOTSTRAPPING {self_name!r}, that token does not exist yet:\n"
327
+ f" minting it is what this command does, so it cannot also be the way in.\n"
328
+ f" Registering a peer that is not yourself is an OPERATOR action. Where the\n"
329
+ f" gateway enforces caller binding (MESH_CALLER_BINDING_ENFORCE=1, as this\n"
330
+ f" mesh's does) another cell's per-peer token 403s; where it does not, it is\n"
331
+ f" simply the wrong credential. `swarph gateway serve` does NOT enforce it by\n"
332
+ f" default — do not read the 403 as a guarantee.\n"
333
+ f" swarph mesh register --as {self_name} --token-file <operator-token>\n"
334
+ f" MESH_GATEWAY_TOKEN=<operator-token> swarph mesh register --as {self_name}"
335
+ ) if not allow_peer_token else (
336
+ f"\n"
337
+ f" If it does not exist yet, {self_name!r} has no credential on this mesh —\n"
338
+ f" `swarph mesh register` (an OPERATOR action) is what mints one."
339
+ )
316
340
  raise RuntimeError(
317
- "cannot resolve mesh token; set MESH_GATEWAY_TOKEN or create "
318
- f"{_peer_token_path(self_name)}"
341
+ f"cannot resolve a mesh credential for {self_name!r}.\n"
342
+ f"\n"
343
+ f" looked for: ${{MESH_GATEWAY_TOKEN}}\n"
344
+ f" {_peer_token_path(self_name)}\n"
345
+ f"\n"
346
+ f" If {self_name!r} ALREADY EXISTS, its per-peer token is the right\n"
347
+ f" credential — place it at the path above (mode 600) and re-run.\n"
348
+ + bootstrap
319
349
  )
320
350
 
321
351
 
352
+
353
+ def _require_absolute_gateway_url(url: str) -> None:
354
+ """#578: no gateway host ships as a default.
355
+
356
+ Guarding here rather than at the dispatcher is deliberate: the gateway is
357
+ needed only when a REQUEST is made. A dispatcher-level check also fired for
358
+ paths that never touch the network (sidecar target validation, --json shape
359
+ tests) and turned 10 failures into 26. The observable that means
360
+ "unconfigured" is a url that is not absolute — `/peers` instead of
361
+ `http://host:8788/peers` — so key on that.
362
+ """
363
+ if not str(url).startswith(("http://", "https://")):
364
+ # GatewayNotConfigured (a RuntimeError) rather than a bare RuntimeError:
365
+ # cli used to carry TWO refusal shapes for one variable and one remedy —
366
+ # require_gateway at dispatch and this one at request time — so the package
367
+ # disagreed with itself about what "no gateway" raises. One family now.
368
+ raise GatewayNotConfigured(
369
+ "MESH_GATEWAY_URL is not set, and swarph ships no default gateway host.\n"
370
+ " A baked-in address expires the day that box is retired "
371
+ "(card #578; it happened on 2026-08-25).\n"
372
+ " Set MESH_GATEWAY_URL=http://<host>:<port>, or pass --gateway.\n"
373
+ f" (tried to request {url!r})"
374
+ )
375
+
322
376
  def _post_json(
323
377
  url: str,
324
378
  body: dict,
@@ -326,6 +380,7 @@ def _post_json(
326
380
  *,
327
381
  timeout: float = 10.0,
328
382
  ) -> tuple[int, dict]:
383
+ _require_absolute_gateway_url(url)
329
384
  data = json.dumps(body).encode("utf-8")
330
385
  req = urllib.request.Request(
331
386
  url,
@@ -355,6 +410,7 @@ def _http_get_json(
355
410
  *,
356
411
  timeout: float = 10.0,
357
412
  ) -> tuple[int, dict]:
413
+ _require_absolute_gateway_url(url)
358
414
  req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
359
415
  try:
360
416
  with urllib.request.urlopen(req, timeout=timeout) as resp:
@@ -43,6 +43,8 @@ import sys
43
43
  import time
44
44
  from pathlib import Path
45
45
 
46
+ from swarph_cli.gateway_default import env_gateway
47
+
46
48
  from . import mesh
47
49
 
48
50
 
@@ -122,7 +124,7 @@ def _add_common(p: argparse.ArgumentParser) -> None:
122
124
  p.add_argument("--state-dir", default=None, help="state directory")
123
125
  p.add_argument(
124
126
  "--gateway",
125
- default=os.environ.get("MESH_GATEWAY_URL", mesh._DEFAULT_GATEWAY),
127
+ default=env_gateway(),
126
128
  help="mesh-gateway base URL",
127
129
  )
128
130
  p.add_argument("--token-file", default=None, help="explicit bearer token file")
@@ -35,6 +35,7 @@ from pathlib import Path
35
35
  from typing import Optional
36
36
  from swarph_cli import tokens
37
37
  from swarph_cli.console_safe import print_safe
38
+ from swarph_cli.gateway_default import env_gateway, require_gateway
38
39
 
39
40
 
40
41
  _HANDSHAKE_TEMPLATE = """\
@@ -111,8 +112,8 @@ def _build_parser() -> argparse.ArgumentParser:
111
112
  )
112
113
  p.add_argument(
113
114
  "--gateway",
114
- default=os.environ.get("MESH_GATEWAY_URL", "http://100.107.222.72:8788"),
115
- help="mesh-gateway base URL (default: $MESH_GATEWAY_URL or http://100.107.222.72:8788)",
115
+ default=env_gateway(),
116
+ help="mesh-gateway base URL (default: $MESH_GATEWAY_URL; no host is shipped)",
116
117
  )
117
118
  p.add_argument(
118
119
  "--token-file",
@@ -824,6 +825,9 @@ def run_onboard(argv: list[str]) -> int:
824
825
 
825
826
  # ── Step 4: POST /peers/register ─────────────────────────────────
826
827
  peer_url = args.url or f"http://{canonical}:8787"
828
+ # #578: the gateway is needed only for the REQUEST. Validate args first so a
829
+ # typo'd name reports the typo, not a missing env var.
830
+ args.gateway = require_gateway(getattr(args, "gateway", ""))
827
831
  print_safe(f"[4/6] POST {args.gateway}/peers/register")
828
832
  # >>> #124's GUARD, MET CLIENT-SIDE — #294's PATTERN, THE MISSING HALF. <<<
829
833
  # The gateway 409s a re-register whose payload would DROP stored
@@ -26,6 +26,7 @@ import urllib.error
26
26
  import urllib.request
27
27
  from pathlib import Path
28
28
  from typing import Optional
29
+ from swarph_cli.gateway_default import env_gateway, require_gateway
29
30
 
30
31
 
31
32
  def _build_parser() -> argparse.ArgumentParser:
@@ -57,7 +58,7 @@ def _build_parser() -> argparse.ArgumentParser:
57
58
  )
58
59
  p.add_argument(
59
60
  "--gateway",
60
- default=os.environ.get("MESH_GATEWAY_URL", "http://100.107.222.72:8788"),
61
+ default=env_gateway(),
61
62
  help="mesh-gateway base URL.",
62
63
  )
63
64
  p.add_argument(
@@ -167,6 +168,9 @@ def run_ratify(argv: list[str]) -> int:
167
168
  return 1
168
169
 
169
170
  # ── Step 2: caller-side gate (witness must itself be ratified) ───
171
+ # #578: the gateway is needed only for the REQUEST. Arg validation above runs
172
+ # first, so a bad name reports the bad name, not a missing env var.
173
+ args.gateway = require_gateway(getattr(args, "gateway", ""))
170
174
  print(f"[2/6] verify witness {witness!r} is ratified")
171
175
  status, witness_body = _http_json(
172
176
  f"{args.gateway}/peers/{witness}", token=token