swarph-cli 0.35.0__tar.gz → 0.37.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 (186) hide show
  1. {swarph_cli-0.35.0/src/swarph_cli.egg-info → swarph_cli-0.37.0}/PKG-INFO +1 -1
  2. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/pyproject.toml +1 -1
  3. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/board.py +6 -0
  5. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/memory_sync.py +14 -55
  6. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/spawn.py +94 -0
  7. {swarph_cli-0.35.0 → swarph_cli-0.37.0/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  8. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli.egg-info/SOURCES.txt +1 -0
  9. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_brain_ask_command.py +1 -1
  10. swarph_cli-0.37.0/tests/test_memory_sync_membrane.py +94 -0
  11. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_watchdog.py +1 -1
  12. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/LICENSE +0 -0
  13. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/README.md +0 -0
  14. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/setup.cfg +0 -0
  15. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/caller.py +0 -0
  16. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/capture/__init__.py +0 -0
  17. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/capture/harden.py +0 -0
  18. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/capture/lineage.py +0 -0
  19. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/capture/liveness.py +0 -0
  20. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/capture/manifest.py +0 -0
  21. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/capture/paths.py +0 -0
  22. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/capture/verify.py +0 -0
  23. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/cell.py +0 -0
  24. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/chain_token.py +0 -0
  25. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/__init__.py +0 -0
  26. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/_display.py +0 -0
  27. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/_gateway_client.py +0 -0
  28. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/add.py +0 -0
  29. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/brain.py +0 -0
  30. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/brain_ask.py +0 -0
  31. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/cell.py +0 -0
  32. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/channel.py +0 -0
  33. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/chat.py +0 -0
  34. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/codegraph.py +0 -0
  35. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/compress.py +0 -0
  36. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/daemon.py +0 -0
  37. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/event.py +0 -0
  38. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/gateway.py +0 -0
  39. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/highlight.py +0 -0
  40. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/hook_output.py +0 -0
  41. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/hooks.py +0 -0
  42. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/import_session.py +0 -0
  43. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/init.py +0 -0
  44. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/install_hook.py +0 -0
  45. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  46. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/lane.py +0 -0
  47. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/mcp_server.py +0 -0
  48. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/memory.py +0 -0
  49. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/mesh.py +0 -0
  50. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/okf_links.py +0 -0
  51. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/onboard.py +0 -0
  52. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
  53. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/ratify.py +0 -0
  54. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/schedule.py +0 -0
  55. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/security.py +0 -0
  56. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/service.py +0 -0
  57. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/timeline.py +0 -0
  58. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/commands/watchdog.py +0 -0
  59. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/compress/__init__.py +0 -0
  60. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/compress/levers.py +0 -0
  61. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/compress/marker.py +0 -0
  62. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/compress/verify.py +0 -0
  63. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/delivery_queue.py +0 -0
  64. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/gateway/__init__.py +0 -0
  65. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
  66. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
  67. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/gateway/schema.sql +0 -0
  68. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/gateway/server.py +0 -0
  69. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/gateway/services_control.py +0 -0
  70. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/main.py +0 -0
  71. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/multiplexer.py +0 -0
  72. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/parsers/__init__.py +0 -0
  73. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/parsers/claude.py +0 -0
  74. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/service/__init__.py +0 -0
  75. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/service/app.py +0 -0
  76. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/service/providers.py +0 -0
  77. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/session_bridge.py +0 -0
  78. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/stall_alert.py +0 -0
  79. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  80. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  81. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  82. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  83. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  84. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli.egg-info/requires.txt +0 -0
  85. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
  86. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_add_security_gate.py +0 -0
  87. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_artifact_add.py +0 -0
  88. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_artifact_hash.py +0 -0
  89. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_artifact_lib.py +0 -0
  90. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_artifact_mcp.py +0 -0
  91. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_artifact_skill.py +0 -0
  92. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_artifact_tool.py +0 -0
  93. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_artifact_uri.py +0 -0
  94. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_board_command.py +0 -0
  95. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_brain_command.py +0 -0
  96. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_caller_meta_guard.py +0 -0
  97. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_capture_lineage.py +0 -0
  98. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_capture_liveness.py +0 -0
  99. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_capture_manifest.py +0 -0
  100. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_capture_paths.py +0 -0
  101. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_capture_security.py +0 -0
  102. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_cell_command_dispatch.py +0 -0
  103. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_cell_harden.py +0 -0
  104. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_cell_loader.py +0 -0
  105. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_cell_verify.py +0 -0
  106. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_chain_token.py +0 -0
  107. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_channel_command.py +0 -0
  108. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_channel_post.py +0 -0
  109. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_chat_command.py +0 -0
  110. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_claude_parser.py +0 -0
  111. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_claude_tmux_template.py +0 -0
  112. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_codegraph_command.py +0 -0
  113. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_codegraph_mcp.py +0 -0
  114. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_compress_caller_convention.py +0 -0
  115. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_compress_command.py +0 -0
  116. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_compress_levers.py +0 -0
  117. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_compress_marker.py +0 -0
  118. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_compress_verify.py +0 -0
  119. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_daemon_command.py +0 -0
  120. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_daemon_delivery.py +0 -0
  121. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_delivery_queue.py +0 -0
  122. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_display_sanitize.py +0 -0
  123. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_event_emit.py +0 -0
  124. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_feature_to_uri.py +0 -0
  125. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_gateway_brain.py +0 -0
  126. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_gateway_client.py +0 -0
  127. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_gateway_command.py +0 -0
  128. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_highlight_command.py +0 -0
  129. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_hook_output.py +0 -0
  130. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_hooks_activity_marker.py +0 -0
  131. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_hooks_add.py +0 -0
  132. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_hooks_bundle.py +0 -0
  133. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_hooks_init.py +0 -0
  134. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_hooks_lifecycle.py +0 -0
  135. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_hooks_merge.py +0 -0
  136. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_import_command.py +0 -0
  137. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_init_command.py +0 -0
  138. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_install_hook.py +0 -0
  139. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_install_multiplexer_command.py +0 -0
  140. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_lane_command.py +0 -0
  141. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_main.py +0 -0
  142. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_mcp_server.py +0 -0
  143. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_memory_command.py +0 -0
  144. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_memory_gateway.py +0 -0
  145. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_memory_sync.py +0 -0
  146. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_mesh_command.py +0 -0
  147. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_mesh_inbox_mark_read.py +0 -0
  148. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_mesh_inbox_sanitize.py +0 -0
  149. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_mesh_sidecar.py +0 -0
  150. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  151. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_meta_edge_identity.py +0 -0
  152. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_multiplexer.py +0 -0
  153. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_okf_links.py +0 -0
  154. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_onboard_command.py +0 -0
  155. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_protocol_handler.py +0 -0
  156. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_providers_isolation.py +0 -0
  157. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_ratify_command.py +0 -0
  158. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_repl_caller_convention.py +0 -0
  159. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_schedule_command.py +0 -0
  160. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_security.py +0 -0
  161. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_service_app.py +0 -0
  162. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_service_command.py +0 -0
  163. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_service_providers.py +0 -0
  164. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_session_bridge_inject.py +0 -0
  165. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_session_bridge_probe.py +0 -0
  166. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_session_bridge_resolve.py +0 -0
  167. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_session_path_role_gate.py +0 -0
  168. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_smoke_chat.py +0 -0
  169. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_smoke_one_shot.py +0 -0
  170. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_smoke_phase_5_5.py +0 -0
  171. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_spawn_base_hoist.py +0 -0
  172. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_spawn_command.py +0 -0
  173. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_spawn_live_pin.py +0 -0
  174. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_spawn_mitosis_lineage.py +0 -0
  175. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_spawn_tmux_session.py +0 -0
  176. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_spawn_windows_relaunch.py +0 -0
  177. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_stall_alert.py +0 -0
  178. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_timeline_command.py +0 -0
  179. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_version_consistency.py +0 -0
  180. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_watchdog_dm_wake.py +0 -0
  181. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  182. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  183. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_watchdog_emit_health.py +0 -0
  184. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_watchdog_install_flags.py +0 -0
  185. {swarph_cli-0.35.0 → swarph_cli-0.37.0}/tests/test_watchdog_model_rung.py +0 -0
  186. {swarph_cli-0.35.0 → swarph_cli-0.37.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.35.0
3
+ Version: 0.37.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.35.0"
7
+ version = "0.37.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.35.0"
19
+ __version__ = "0.37.0"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -167,6 +167,9 @@ def _build_parser() -> argparse.ArgumentParser:
167
167
  ck.add_argument("--json", action="store_true"); _add_common(ck)
168
168
  cn = cards.add_parser("assign", help="set a card's assignee")
169
169
  cn.add_argument("id", type=int); cn.add_argument("assignee"); cn.add_argument("--json", action="store_true"); _add_common(cn)
170
+ cr = cards.add_parser("ready", help="flag a card ready-to-advance (move_ready) for the orchestrator")
171
+ cr.add_argument("id", type=int); cr.add_argument("--clear", action="store_true", help="unset move_ready")
172
+ cr.add_argument("--json", action="store_true"); _add_common(cr)
170
173
  return p
171
174
 
172
175
 
@@ -232,6 +235,9 @@ def run_board(argv: list[str]) -> int:
232
235
  if args.command == "assign":
233
236
  st, d = _patch_json(f"{gw}/board/cards/{args.id}", {"actor": self_name, "assignee": args.assignee}, token)
234
237
  return _out(st, d, lambda x: f"card #{x.get('id')} assignee -> {x.get('assignee')}", aj)
238
+ if args.command == "ready":
239
+ st, d = _patch_json(f"{gw}/board/cards/{args.id}", {"actor": self_name, "move_ready": not args.clear}, token)
240
+ return _out(st, d, lambda x: f"card #{x.get('id')} move_ready -> {x.get('move_ready')}", aj)
235
241
  if args.command == "link":
236
242
  gst, gcard = _http_get_json(f"{gw}/board/cards/{args.id}", token)
237
243
  if not (gst and 200 <= gst < 300):
@@ -10,6 +10,7 @@ from pathlib import Path
10
10
  from typing import Optional
11
11
 
12
12
  from swarph_cli.cell import Cell, load_cell, CellError
13
+ from swarph_cli.commands.spawn import MEMBRANES
13
14
 
14
15
 
15
16
  def get_memory_repo_path(cell: Cell) -> Path:
@@ -19,44 +20,13 @@ def get_memory_repo_path(cell: Cell) -> Path:
19
20
 
20
21
  def _get_files_to_sync(cell: Cell) -> list[tuple[str, Path]]:
21
22
  files_to_sync = []
22
-
23
- # Common
23
+ # Common (provider-agnostic)
24
24
  if (cell.cwd / "CURRENT_TASK.md").exists():
25
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 == "antigravity":
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
-
26
+ # Provider-specific memory lives in the membrane (non-discriminatory dispatch)
27
+ membrane = MEMBRANES.get(cell.provider)
28
+ if membrane is not None:
29
+ files_to_sync += membrane.memory_sync_files(cell)
60
30
  return files_to_sync
61
31
 
62
32
 
@@ -110,21 +80,15 @@ def perform_restore(cell: Cell) -> Optional[str]:
110
80
  continue
111
81
 
112
82
  rel = src.relative_to(repo_dir)
113
-
83
+
114
84
  dest = None
115
85
  if rel.parts[0] in ("CURRENT_TASK.md", "CLAUDE.md", "AGENTS.md", "GEMINI.md", "inbox-cursor.json"):
116
86
  dest = cell.cwd / rel
117
- elif cell.provider == "claude" and rel.parts[0] == "MEMORY.md":
118
- dest = Path.home() / ".claude" / "MEMORY.md"
119
- elif cell.provider == "claude" and rel.parts[0] == "memory":
120
- dest = Path.home() / ".claude" / rel
121
- elif cell.provider == "claude" and rel.parts[0] == "inbox-cursor":
122
- dest = Path.home() / ".claude" / "inbox-cursor"
123
- elif cell.provider == "antigravity" and rel.parts[0] == "tmp":
124
- dest = Path.home() / ".gemini" / rel
125
- elif cell.provider == "antigravity" and rel.parts[0] == "history":
126
- dest = Path.home() / ".gemini" / rel
127
-
87
+ else:
88
+ membrane = MEMBRANES.get(cell.provider)
89
+ if membrane is not None:
90
+ dest = membrane.memory_restore_dest(rel.parts, cell)
91
+
128
92
  if dest:
129
93
  dest.parent.mkdir(parents=True, exist_ok=True)
130
94
  shutil.copy2(src, dest)
@@ -168,13 +132,8 @@ def run_memory_sync(argv: list[str]) -> int:
168
132
  pass
169
133
 
170
134
  # EMPTY-GUARD check
171
- guard_file = None
172
- if cell.provider == "claude":
173
- guard_file = cell.cwd / "CLAUDE.md"
174
- elif cell.provider == "codex":
175
- guard_file = cell.cwd / "AGENTS.md"
176
- elif cell.provider == "antigravity":
177
- guard_file = cell.cwd / "GEMINI.md"
135
+ membrane = MEMBRANES.get(cell.provider)
136
+ guard_file = membrane.memory_guard_file(cell) if membrane is not None else None
178
137
 
179
138
  if guard_file and (not guard_file.exists() or guard_file.stat().st_size == 0):
180
139
  print(f"[auto_sync] {datetime.datetime.now(datetime.timezone.utc).strftime('%FT%TZ')} SAFETY: {guard_file} missing or empty — skipping sync", file=sys.stderr)
@@ -1246,6 +1246,22 @@ class ProviderMembrane:
1246
1246
  """chdir + env setup + exec-replace. Only returns on exec failure."""
1247
1247
  raise NotImplementedError
1248
1248
 
1249
+ def memory_sync_files(self, cell) -> list:
1250
+ """Provider-specific memory files to snapshot: (repo-relative, source-abs)
1251
+ pairs. Base = none; each provider declares its own. The COMMON files
1252
+ (CURRENT_TASK.md) are handled provider-agnostically in memory_sync."""
1253
+ return []
1254
+
1255
+ def memory_restore_dest(self, rel_parts, cell):
1256
+ """Where a provider-specific repo-relative path restores to (None = not
1257
+ mine; the common cwd files are restored by memory_sync)."""
1258
+ return None
1259
+
1260
+ def memory_guard_file(self, cell):
1261
+ """The empty-guard file whose emptiness means 'skip sync' (None = no
1262
+ guard, always sync)."""
1263
+ return None
1264
+
1249
1265
 
1250
1266
  class ClaudeMembrane(ProviderMembrane):
1251
1267
  name = "claude"
@@ -1361,6 +1377,31 @@ class ClaudeMembrane(ProviderMembrane):
1361
1377
  return 1
1362
1378
  return 0 # unreachable on POSIX (execve replaces); keeps type checker happy
1363
1379
 
1380
+ def memory_sync_files(self, cell) -> list:
1381
+ files = []
1382
+ if (cell.cwd / "CLAUDE.md").exists():
1383
+ files.append(("CLAUDE.md", cell.cwd / "CLAUDE.md"))
1384
+ mem_dir = Path.home() / ".claude"
1385
+ if (mem_dir / "MEMORY.md").exists():
1386
+ files.append(("MEMORY.md", mem_dir / "MEMORY.md"))
1387
+ for p in mem_dir.glob("memory/*.md"):
1388
+ files.append((f"memory/{p.name}", p))
1389
+ if (mem_dir / "inbox-cursor").exists():
1390
+ files.append(("inbox-cursor", mem_dir / "inbox-cursor"))
1391
+ return files
1392
+
1393
+ def memory_restore_dest(self, rel_parts, cell):
1394
+ if rel_parts[0] == "MEMORY.md":
1395
+ return Path.home() / ".claude" / "MEMORY.md"
1396
+ if rel_parts[0] == "memory":
1397
+ return Path.home() / ".claude" / Path(*rel_parts)
1398
+ if rel_parts[0] == "inbox-cursor":
1399
+ return Path.home() / ".claude" / "inbox-cursor"
1400
+ return None
1401
+
1402
+ def memory_guard_file(self, cell):
1403
+ return cell.cwd / "CLAUDE.md"
1404
+
1364
1405
 
1365
1406
  class CodexMembrane(ProviderMembrane):
1366
1407
  name = "codex"
@@ -1395,6 +1436,15 @@ class CodexMembrane(ProviderMembrane):
1395
1436
  return 1
1396
1437
  return 0 # unreachable, keeps type checker happy
1397
1438
 
1439
+ def memory_sync_files(self, cell) -> list:
1440
+ files = []
1441
+ if (cell.cwd / "AGENTS.md").exists():
1442
+ files.append(("AGENTS.md", cell.cwd / "AGENTS.md"))
1443
+ return files
1444
+
1445
+ def memory_guard_file(self, cell):
1446
+ return cell.cwd / "AGENTS.md"
1447
+
1398
1448
 
1399
1449
  class AntigravityMembrane(ProviderMembrane):
1400
1450
  name = "antigravity"
@@ -1436,6 +1486,31 @@ class AntigravityMembrane(ProviderMembrane):
1436
1486
  return 1
1437
1487
  return 0 # unreachable, keeps type checker happy
1438
1488
 
1489
+ def memory_sync_files(self, cell) -> list:
1490
+ files = []
1491
+ if (cell.cwd / "GEMINI.md").exists():
1492
+ files.append(("GEMINI.md", cell.cwd / "GEMINI.md"))
1493
+ if (cell.cwd / "inbox-cursor.json").exists():
1494
+ files.append(("inbox-cursor.json", cell.cwd / "inbox-cursor.json"))
1495
+ gemini_tmp = Path.home() / ".gemini" / "tmp"
1496
+ if gemini_tmp.is_dir():
1497
+ for proj_dir in gemini_tmp.iterdir():
1498
+ if proj_dir.is_dir():
1499
+ for p in proj_dir.glob("memory/*.md"):
1500
+ files.append((f"tmp/{proj_dir.name}/memory/{p.name}", p))
1501
+ history_proj = Path.home() / ".gemini" / "history" / ".project_root"
1502
+ if history_proj.exists():
1503
+ files.append(("history/.project_root", history_proj))
1504
+ return files
1505
+
1506
+ def memory_restore_dest(self, rel_parts, cell):
1507
+ if rel_parts[0] in ("tmp", "history"):
1508
+ return Path.home() / ".gemini" / Path(*rel_parts)
1509
+ return None
1510
+
1511
+ def memory_guard_file(self, cell):
1512
+ return cell.cwd / "GEMINI.md"
1513
+
1439
1514
 
1440
1515
  class GrokMembrane(ProviderMembrane):
1441
1516
  """Local ``grok`` CLI as a durable swarph CELL ($0 OIDC / subscription).
@@ -1514,6 +1589,25 @@ class GrokMembrane(ProviderMembrane):
1514
1589
  return 1
1515
1590
  return 0 # unreachable on POSIX (execve replaces); keeps type checker happy
1516
1591
 
1592
+ def memory_sync_files(self, cell) -> list:
1593
+ # Grok cells run under an isolated HOME (_GROK_CELL_HOME_SUBDIR); memory
1594
+ # lives at <cell_home>/.grok/memory/**/*.md.
1595
+ files = []
1596
+ mem_dir = cell.cwd / _GROK_CELL_HOME_SUBDIR / ".grok" / "memory"
1597
+ if mem_dir.is_dir():
1598
+ for p in mem_dir.rglob("*.md"):
1599
+ files.append((f"grok-memory/{p.relative_to(mem_dir).as_posix()}", p))
1600
+ return files
1601
+
1602
+ def memory_restore_dest(self, rel_parts, cell):
1603
+ if rel_parts[0] == "grok-memory":
1604
+ return (cell.cwd / _GROK_CELL_HOME_SUBDIR / ".grok" / "memory"
1605
+ / Path(*rel_parts[1:]))
1606
+ return None
1607
+
1608
+ def memory_guard_file(self, cell):
1609
+ return None # grok has no cwd project-doc; always-sync
1610
+
1517
1611
 
1518
1612
  MEMBRANES: dict[str, ProviderMembrane] = {
1519
1613
  "claude": ClaudeMembrane(),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.35.0
3
+ Version: 0.37.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
@@ -139,6 +139,7 @@ tests/test_mcp_server.py
139
139
  tests/test_memory_command.py
140
140
  tests/test_memory_gateway.py
141
141
  tests/test_memory_sync.py
142
+ tests/test_memory_sync_membrane.py
142
143
  tests/test_mesh_command.py
143
144
  tests/test_mesh_inbox_mark_read.py
144
145
  tests/test_mesh_inbox_sanitize.py
@@ -17,7 +17,7 @@ from swarph_cli.commands import brain_ask as ba
17
17
 
18
18
  def test_version_is_0_33_0():
19
19
  import swarph_cli
20
- assert swarph_cli.__version__ == "0.35.0"
20
+ assert swarph_cli.__version__ == "0.37.0"
21
21
 
22
22
 
23
23
  # --- request / response plumbing -------------------------------------------
@@ -0,0 +1,94 @@
1
+ import types
2
+ from pathlib import Path
3
+
4
+ from swarph_cli.commands import spawn
5
+
6
+
7
+ def _cell(tmp_path):
8
+ return types.SimpleNamespace(cwd=tmp_path, provider="x")
9
+
10
+
11
+ def test_base_membrane_returns_empty(tmp_path):
12
+ m = spawn.ProviderMembrane()
13
+ assert m.memory_sync_files(_cell(tmp_path)) == []
14
+ assert m.memory_restore_dest(("anything",), _cell(tmp_path)) is None
15
+ assert m.memory_guard_file(_cell(tmp_path)) is None
16
+
17
+
18
+ def test_claude_membrane_files_and_dests(tmp_path, monkeypatch):
19
+ home = tmp_path / "home"; (home / ".claude" / "memory").mkdir(parents=True)
20
+ (home / ".claude" / "MEMORY.md").write_text("m")
21
+ (home / ".claude" / "memory" / "a.md").write_text("a")
22
+ (home / ".claude" / "inbox-cursor").write_text("c")
23
+ cwd = tmp_path / "cwd"; cwd.mkdir(); (cwd / "CLAUDE.md").write_text("C")
24
+ monkeypatch.setattr(Path, "home", staticmethod(lambda: home))
25
+ m = spawn.MEMBRANES["claude"]
26
+ rels = {r for r, _ in m.memory_sync_files(types.SimpleNamespace(cwd=cwd, provider="claude"))}
27
+ assert rels == {"CLAUDE.md", "MEMORY.md", "memory/a.md", "inbox-cursor"}
28
+ assert m.memory_restore_dest(("MEMORY.md",), None) == home / ".claude" / "MEMORY.md"
29
+ assert m.memory_restore_dest(("memory", "a.md"), None) == home / ".claude" / "memory" / "a.md"
30
+ assert m.memory_restore_dest(("CLAUDE.md",), None) is None # CLAUDE.md restores via common cwd path
31
+ assert m.memory_guard_file(types.SimpleNamespace(cwd=cwd, provider="claude")) == cwd / "CLAUDE.md"
32
+
33
+
34
+ def test_codex_membrane(tmp_path):
35
+ cwd = tmp_path; (cwd / "AGENTS.md").write_text("A")
36
+ m = spawn.MEMBRANES["codex"]
37
+ assert {r for r, _ in m.memory_sync_files(types.SimpleNamespace(cwd=cwd, provider="codex"))} == {"AGENTS.md"}
38
+ assert m.memory_restore_dest(("AGENTS.md",), None) is None
39
+ assert m.memory_guard_file(types.SimpleNamespace(cwd=cwd, provider="codex")) == cwd / "AGENTS.md"
40
+
41
+
42
+ def test_antigravity_restore_dest(tmp_path, monkeypatch):
43
+ home = tmp_path / "home"; monkeypatch.setattr(Path, "home", staticmethod(lambda: home))
44
+ m = spawn.MEMBRANES["antigravity"]
45
+ assert m.memory_restore_dest(("tmp", "proj", "memory", "x.md"), None) == home / ".gemini" / "tmp" / "proj" / "memory" / "x.md"
46
+ assert m.memory_restore_dest(("history", ".project_root"), None) == home / ".gemini" / "history" / ".project_root"
47
+ assert m.memory_restore_dest(("GEMINI.md",), None) is None # common cwd path
48
+ c = tmp_path; (c / "GEMINI.md").write_text("G")
49
+ assert m.memory_guard_file(types.SimpleNamespace(cwd=c, provider="antigravity")) == c / "GEMINI.md"
50
+
51
+
52
+ def test_grok_membrane_isolated_home(tmp_path):
53
+ cwd = tmp_path
54
+ mem = cwd / spawn._GROK_CELL_HOME_SUBDIR / ".grok" / "memory" / "proj"
55
+ mem.mkdir(parents=True)
56
+ (cwd / spawn._GROK_CELL_HOME_SUBDIR / ".grok" / "memory" / "MEMORY.md").write_text("m")
57
+ (mem / "MEMORY.md").write_text("p")
58
+ m = spawn.MEMBRANES["grok"]
59
+ files = m.memory_sync_files(types.SimpleNamespace(cwd=cwd, provider="grok"))
60
+ rels = {r for r, _ in files}
61
+ assert rels == {"grok-memory/MEMORY.md", "grok-memory/proj/MEMORY.md"}
62
+ # Verify no backslashes in keys (cross-OS restore safety)
63
+ assert all("\\" not in r for r, _ in files)
64
+ assert m.memory_restore_dest(("grok-memory", "proj", "MEMORY.md"), types.SimpleNamespace(cwd=cwd, provider="grok")) \
65
+ == cwd / spawn._GROK_CELL_HOME_SUBDIR / ".grok" / "memory" / "proj" / "MEMORY.md"
66
+ assert m.memory_guard_file(types.SimpleNamespace(cwd=cwd, provider="grok")) is None # no cwd doc
67
+
68
+
69
+ # Tests for memory_sync.py dispatch (Task 2)
70
+ from swarph_cli.commands import memory_sync
71
+
72
+
73
+ def test_get_files_to_sync_dispatches_to_membrane(tmp_path):
74
+ cwd = tmp_path; (cwd / "CURRENT_TASK.md").write_text("t"); (cwd / "AGENTS.md").write_text("A")
75
+ cell = types.SimpleNamespace(cwd=cwd, provider="codex")
76
+ rels = {r for r, _ in memory_sync._get_files_to_sync(cell)}
77
+ assert rels == {"CURRENT_TASK.md", "AGENTS.md"} # common + codex membrane
78
+
79
+
80
+ def test_get_files_unknown_provider_is_common_only(tmp_path):
81
+ cwd = tmp_path; (cwd / "CURRENT_TASK.md").write_text("t")
82
+ cell = types.SimpleNamespace(cwd=cwd, provider="nonesuch")
83
+ assert {r for r, _ in memory_sync._get_files_to_sync(cell)} == {"CURRENT_TASK.md"}
84
+
85
+
86
+ def test_grok_roundtrip_sync_then_restore_dest(tmp_path):
87
+ cwd = tmp_path
88
+ mem = cwd / spawn._GROK_CELL_HOME_SUBDIR / ".grok" / "memory"
89
+ mem.mkdir(parents=True); (mem / "MEMORY.md").write_text("m")
90
+ cell = types.SimpleNamespace(cwd=cwd, provider="grok")
91
+ files = memory_sync._get_files_to_sync(cell)
92
+ rel = next(r for r, _ in files if r.startswith("grok-memory"))
93
+ dest = spawn.MEMBRANES["grok"].memory_restore_dest(tuple(Path(rel).parts), cell)
94
+ assert dest == mem / "MEMORY.md" # snapshot rel round-trips to the isolated-HOME source
@@ -1043,4 +1043,4 @@ def test_parser_defaults_process_name_claude_liveness_cmd_none():
1043
1043
 
1044
1044
  def test_version_is_0_33_0():
1045
1045
  import swarph_cli
1046
- assert swarph_cli.__version__ == "0.35.0"
1046
+ assert swarph_cli.__version__ == "0.37.0"
File without changes
File without changes
File without changes