swarph-cli 0.72.17__tar.gz → 0.72.18__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 (509) hide show
  1. {swarph_cli-0.72.17/src/swarph_cli.egg-info → swarph_cli-0.72.18}/PKG-INFO +9 -1
  2. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/README.md +8 -0
  3. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/pyproject.toml +1 -1
  4. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/__init__.py +1 -1
  5. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/board.py +117 -17
  6. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/mesh.py +70 -0
  7. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/roster.py +196 -0
  8. swarph_cli-0.72.18/src/swarph_cli/quota_sync.py +252 -0
  9. {swarph_cli-0.72.17 → swarph_cli-0.72.18/src/swarph_cli.egg-info}/PKG-INFO +9 -1
  10. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli.egg-info/SOURCES.txt +4 -0
  11. swarph_cli-0.72.18/tests/test_1068_structured_accept_cli.py +104 -0
  12. swarph_cli-0.72.18/tests/test_1071_quota_sync.py +307 -0
  13. swarph_cli-0.72.18/tests/test_copilot_pane_reader.py +165 -0
  14. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/LICENSE +0 -0
  15. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/setup.cfg +0 -0
  16. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/__main__.py +0 -0
  17. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/bench/__init__.py +0 -0
  18. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/bench/backends.py +0 -0
  19. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/bench/claude_cli.py +0 -0
  20. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/bench/codex_cli.py +0 -0
  21. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/bench/pack.py +0 -0
  22. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/bench/prices.py +0 -0
  23. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/bench/providers.py +0 -0
  24. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/bench/quality.py +0 -0
  25. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/bench/refresh_prices.py +0 -0
  26. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/bench/rules.py +0 -0
  27. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/bench/runner.py +0 -0
  28. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/bench/signal_score.py +0 -0
  29. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/bench/validate.py +0 -0
  30. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/caller.py +0 -0
  31. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/capture/__init__.py +0 -0
  32. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/capture/harden.py +0 -0
  33. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/capture/lineage.py +0 -0
  34. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/capture/liveness.py +0 -0
  35. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/capture/manifest.py +0 -0
  36. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/capture/paths.py +0 -0
  37. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/capture/verify.py +0 -0
  38. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/cell.py +0 -0
  39. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/chain_token.py +0 -0
  40. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/channel.py +0 -0
  41. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/__init__.py +0 -0
  42. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/_content.py +0 -0
  43. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/_display.py +0 -0
  44. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/_gateway_client.py +0 -0
  45. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/add.py +0 -0
  46. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/bench.py +0 -0
  47. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/board_merge_check.py +0 -0
  48. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/brain.py +0 -0
  49. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/brain_ask.py +0 -0
  50. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/cell.py +0 -0
  51. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/cell_probe.py +0 -0
  52. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  53. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/channel.py +0 -0
  54. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/chat.py +0 -0
  55. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/codegraph.py +0 -0
  56. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  57. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  58. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/codex_waker.py +0 -0
  59. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/compress.py +0 -0
  60. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/daemon.py +0 -0
  61. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/dreaming.py +0 -0
  62. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/event.py +0 -0
  63. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/gateway.py +0 -0
  64. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/gh_route.py +0 -0
  65. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/group.py +0 -0
  66. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/guide.py +0 -0
  67. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/highlight.py +0 -0
  68. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/hook_interpreter.py +0 -0
  69. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/hook_output.py +0 -0
  70. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/hooks.py +0 -0
  71. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/import_session.py +0 -0
  72. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/init.py +0 -0
  73. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  74. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/install_hook.py +0 -0
  75. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  76. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  77. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  78. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  79. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/lane.py +0 -0
  80. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/mcp_server.py +0 -0
  81. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/memory.py +0 -0
  82. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  83. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/memory_sync.py +0 -0
  84. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/monitor.py +0 -0
  85. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/okf_links.py +0 -0
  86. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/onboard.py +0 -0
  87. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  88. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  89. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/protocol_handler.py +0 -0
  90. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/ratify.py +0 -0
  91. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/rowclear_hook.py +0 -0
  92. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/schedule.py +0 -0
  93. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/security.py +0 -0
  94. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/service.py +0 -0
  95. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/spawn.py +0 -0
  96. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/sweep.py +0 -0
  97. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/timeline.py +0 -0
  98. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/triage.py +0 -0
  99. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/version.py +0 -0
  100. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  101. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/commands/watchdog.py +0 -0
  102. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/compress/__init__.py +0 -0
  103. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/compress/levers.py +0 -0
  104. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/compress/marker.py +0 -0
  105. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/compress/verify.py +0 -0
  106. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/console_safe.py +0 -0
  107. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/delivery_queue.py +0 -0
  108. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/dm_frame.py +0 -0
  109. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/dreaming/__init__.py +0 -0
  110. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/dreaming/candidates.py +0 -0
  111. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/dreaming/clone.py +0 -0
  112. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/dreaming/enrich.py +0 -0
  113. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/dreaming/index_check.py +0 -0
  114. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/dreaming/notify.py +0 -0
  115. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/dreaming/organize.py +0 -0
  116. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/dreaming/probes.py +0 -0
  117. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/dreaming/report.py +0 -0
  118. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/dreaming/run.py +0 -0
  119. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/dreaming/slm.py +0 -0
  120. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/dreaming/transcripts.py +0 -0
  121. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/dreaming/verify.py +0 -0
  122. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/gateway/__init__.py +0 -0
  123. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/gateway/feature_registry.py +0 -0
  124. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/gateway/lanes_control.py +0 -0
  125. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/gateway/schema.sql +0 -0
  126. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/gateway/server.py +0 -0
  127. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/gateway/services_control.py +0 -0
  128. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/gateway_default.py +0 -0
  129. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/gh_identity.py +0 -0
  130. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/guide/GUIDE.md +0 -0
  131. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/identity.py +0 -0
  132. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/main.py +0 -0
  133. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/multiplexer.py +0 -0
  134. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/operator/__init__.py +0 -0
  135. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/operator/__main__.py +0 -0
  136. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/operator/api.py +0 -0
  137. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/operator/cli.py +0 -0
  138. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/operator/config.py +0 -0
  139. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/operator/fmt.py +0 -0
  140. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/orphan_daemons.py +0 -0
  141. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/pane_probe.py +0 -0
  142. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/parsers/__init__.py +0 -0
  143. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/parsers/claude.py +0 -0
  144. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  145. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  146. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  147. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  148. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  149. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  150. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  151. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  152. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  153. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  154. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  155. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  156. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/peer_executor.py +0 -0
  157. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/peer_executor_windows.py +0 -0
  158. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/peer_reconciliation.py +0 -0
  159. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/peer_reply_outbox.py +0 -0
  160. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/peer_service_host.py +0 -0
  161. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/peer_service_payload.py +0 -0
  162. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/peer_service_reply.py +0 -0
  163. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/peer_staging.py +0 -0
  164. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/query_filters.py +0 -0
  165. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/rowclear.py +0 -0
  166. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/scripts/__init__.py +0 -0
  167. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  168. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  169. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  170. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  171. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
  172. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/scrub.py +0 -0
  173. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/service/__init__.py +0 -0
  174. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/service/app.py +0 -0
  175. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/service/providers.py +0 -0
  176. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/session_bridge.py +0 -0
  177. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  178. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  179. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/source_text.py +0 -0
  180. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/stall_alert.py +0 -0
  181. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/codex-waker.default +0 -0
  182. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  183. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  184. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  185. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  186. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  187. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  188. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  189. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  190. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  191. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  192. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-stuck-sweep.service +0 -0
  193. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-stuck-sweep.timer +0 -0
  194. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-triage.service +0 -0
  195. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-triage.timer +0 -0
  196. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  197. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  198. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  199. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/timeline_paths.py +0 -0
  200. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/tokens.py +0 -0
  201. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/triage/__init__.py +0 -0
  202. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/triage/backend.py +0 -0
  203. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/triage/job.py +0 -0
  204. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/triage/owners.json +0 -0
  205. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/triage/router.py +0 -0
  206. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/triage/rows.py +0 -0
  207. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli/triage/sensor.py +0 -0
  208. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  209. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  210. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli.egg-info/requires.txt +0 -0
  211. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/src/swarph_cli.egg-info/top_level.txt +0 -0
  212. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1006_board_publisher.py +0 -0
  213. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1006_session_board.py +0 -0
  214. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1010_strand_only.py +0 -0
  215. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1013_spawn_channel.py +0 -0
  216. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1017_muse_tui.py +0 -0
  217. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1050_surrogate_wake.py +0 -0
  218. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1052_opencode_deaf.py +0 -0
  219. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1059_watchdog_unread_env.py +0 -0
  220. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1061_roster_cli.py +0 -0
  221. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1064_stuck_sweep.py +0 -0
  222. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1065_release.py +0 -0
  223. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1066_quiet_wakes.py +0 -0
  224. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1073_triage.py +0 -0
  225. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1073_triage_board.py +0 -0
  226. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1073_wheel_install.py +0 -0
  227. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1075_task_cli.py +0 -0
  228. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_1083_openai_decisions.py +0 -0
  229. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_124_register_merge.py +0 -0
  230. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_126_pending_from_complete.py +0 -0
  231. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  232. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_138_memory_navigate.py +0 -0
  233. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_139_tool_probe.py +0 -0
  234. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_183_card_anchor.py +0 -0
  235. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_184_notify_honesty.py +0 -0
  236. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_201_win32_bash_refusal.py +0 -0
  237. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_243_onboard_per_peer_token.py +0 -0
  238. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_307_task4_ask_reply.py +0 -0
  239. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  240. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_319_refusal_exit_codes.py +0 -0
  241. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_323_mistral_backend.py +0 -0
  242. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_332_report_organize_findings.py +0 -0
  243. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_356_query_filters.py +0 -0
  244. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_397_gh_identity_router.py +0 -0
  245. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_397_router_preflight.py +0 -0
  246. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_402_one_identity_resolver.py +0 -0
  247. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_432_rowclear.py +0 -0
  248. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_467b_onboard_operator_credential.py +0 -0
  249. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_472_register_caller_binding.py +0 -0
  250. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_484_codex_queue_sink.py +0 -0
  251. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_523_guide.py +0 -0
  252. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_524_conftest_hermeticity.py +0 -0
  253. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_532_accept_flag.py +0 -0
  254. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_535_version_capability.py +0 -0
  255. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_538_signal_bench.py +0 -0
  256. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_546_default_gateway.py +0 -0
  257. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_547_verbs_from_registry.py +0 -0
  258. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_548_gateway_remainder.py +0 -0
  259. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_552_codegraph_gateway.py +0 -0
  260. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_555_relay_row_shape.py +0 -0
  261. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_556_relay_freshness.py +0 -0
  262. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_559_edges_truncated.py +0 -0
  263. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_564_bootstrap_first_peer.py +0 -0
  264. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  265. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_579_register_bootstrap_message.py +0 -0
  266. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_591_cli_step_graph.py +0 -0
  267. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_644_monitor_supervision.py +0 -0
  268. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_648_install_unit.py +0 -0
  269. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_654_reexec.py +0 -0
  270. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_656_owned_slm.py +0 -0
  271. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_684_dreaming_verb.py +0 -0
  272. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_689_pkg_version_order.py +0 -0
  273. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_715_grok_wake.py +0 -0
  274. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_716_timeline_one_path.py +0 -0
  275. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_723_grok_0251.py +0 -0
  276. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  277. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_725_stdio_reconfigure.py +0 -0
  278. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_726_grok_limit_screen.py +0 -0
  279. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_729_arm_check_delivery_gap.py +0 -0
  280. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_729_dm_once.py +0 -0
  281. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_729_wake_watchdog.py +0 -0
  282. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  283. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_735_dm_mcp_tools.py +0 -0
  284. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_739_wake_writer.py +0 -0
  285. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_740_edit_project.py +0 -0
  286. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_762_cards_edit_priority.py +0 -0
  287. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_764_enrich_collapse.py +0 -0
  288. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_801_confirm_flow_cli.py +0 -0
  289. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_802_stage_history.py +0 -0
  290. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_825_codegraph_hook_noise.py +0 -0
  291. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_825_install_posttooluse_only.py +0 -0
  292. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_825_remaining_cap_shred_bind.py +0 -0
  293. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_829_counterfactual_column.py +0 -0
  294. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_830_hook_binding_version.py +0 -0
  295. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_84_cli_labels.py +0 -0
  296. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_866_doctrine_guide.py +0 -0
  297. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_872_identity_refuses_not_warns.py +0 -0
  298. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_874_source_text.py +0 -0
  299. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  300. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_937_dreaming_notify.py +0 -0
  301. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_938_organize_line_limit.py +0 -0
  302. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_941_bench_backends.py +0 -0
  303. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_941_harness_342.py +0 -0
  304. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_945_same_line_pairing.py +0 -0
  305. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_955_prompt_audit.py +0 -0
  306. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_957_auth_http.py +0 -0
  307. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_959_venv_interpreter.py +0 -0
  308. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_960_channel.py +0 -0
  309. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
  310. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_961_cursor_grok_footer.py +0 -0
  311. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_961_loud_stop.py +0 -0
  312. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_963_plugin_manifest.py +0 -0
  313. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_964_channel_cell.py +0 -0
  314. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_965_mesh_wait.py +0 -0
  315. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_975_channel_hop.py +0 -0
  316. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_add_security_gate.py +0 -0
  317. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_artifact_add.py +0 -0
  318. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_artifact_hash.py +0 -0
  319. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_artifact_lib.py +0 -0
  320. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_artifact_mcp.py +0 -0
  321. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_artifact_skill.py +0 -0
  322. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_artifact_tool.py +0 -0
  323. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_artifact_uri.py +0 -0
  324. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_bench_backends.py +0 -0
  325. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_bench_claude_cli.py +0 -0
  326. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_bench_codex_cli.py +0 -0
  327. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_bench_command.py +0 -0
  328. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_bench_pack.py +0 -0
  329. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_bench_prices.py +0 -0
  330. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_bench_quality.py +0 -0
  331. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_bench_runner.py +0 -0
  332. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_bench_validate.py +0 -0
  333. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_board_card_thread.py +0 -0
  334. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_board_command.py +0 -0
  335. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_board_merge_check.py +0 -0
  336. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_board_obligation_close.py +0 -0
  337. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_brain_ask_command.py +0 -0
  338. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_brain_ask_env_diagnosis.py +0 -0
  339. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_brain_ask_identity_explicitness.py +0 -0
  340. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_brain_command.py +0 -0
  341. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_caller_meta_guard.py +0 -0
  342. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_capture_lineage.py +0 -0
  343. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_capture_liveness.py +0 -0
  344. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_capture_manifest.py +0 -0
  345. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_capture_paths.py +0 -0
  346. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_capture_security.py +0 -0
  347. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_cell_command_dispatch.py +0 -0
  348. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_cell_harden.py +0 -0
  349. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_cell_loader.py +0 -0
  350. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_cell_selfcheck.py +0 -0
  351. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_cell_verify.py +0 -0
  352. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_chain_token.py +0 -0
  353. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_channel_command.py +0 -0
  354. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  355. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_channel_post.py +0 -0
  356. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_channel_wake_policy_filter.py +0 -0
  357. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_chat_command.py +0 -0
  358. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_claude_parser.py +0 -0
  359. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_claude_tmux_template.py +0 -0
  360. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_codegraph_caller_scope.py +0 -0
  361. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_codegraph_command.py +0 -0
  362. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_codegraph_hook.py +0 -0
  363. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_codegraph_mcp.py +0 -0
  364. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_codex_waker.py +0 -0
  365. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_compress_caller_convention.py +0 -0
  366. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_compress_command.py +0 -0
  367. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_compress_levers.py +0 -0
  368. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_compress_marker.py +0 -0
  369. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_compress_verify.py +0 -0
  370. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_console_encoding_e2e.py +0 -0
  371. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_content_composition.py +0 -0
  372. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_copilot_membrane.py +0 -0
  373. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_cursor_membrane.py +0 -0
  374. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_daemon_command.py +0 -0
  375. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_daemon_console_encoding.py +0 -0
  376. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_daemon_delivery.py +0 -0
  377. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_delivery_queue.py +0 -0
  378. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_display_sanitize.py +0 -0
  379. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  380. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_event_emit.py +0 -0
  381. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_feature_to_uri.py +0 -0
  382. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_gateway_brain.py +0 -0
  383. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_gateway_client.py +0 -0
  384. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_gateway_command.py +0 -0
  385. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_gateway_message_idempotency.py +0 -0
  386. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  387. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_group_command.py +0 -0
  388. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_guide_commands_parse.py +0 -0
  389. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_guide_selfcheck.py +0 -0
  390. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_heartbeat_check.py +0 -0
  391. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_highlight_command.py +0 -0
  392. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_hook_output.py +0 -0
  393. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_hooks_activity_marker.py +0 -0
  394. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_hooks_add.py +0 -0
  395. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_hooks_bundle.py +0 -0
  396. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_hooks_init.py +0 -0
  397. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_hooks_legacy_generation_removal.py +0 -0
  398. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_hooks_lifecycle.py +0 -0
  399. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_hooks_merge.py +0 -0
  400. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_hooks_verify.py +0 -0
  401. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  402. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_hooks_win32_command_path.py +0 -0
  403. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_import_command.py +0 -0
  404. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_inbox_identity_provenance.py +0 -0
  405. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_init_command.py +0 -0
  406. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_init_provider_menu.py +0 -0
  407. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_install_codex_hooks.py +0 -0
  408. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_install_hook.py +0 -0
  409. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_install_multiplexer_command.py +0 -0
  410. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_install_postcompact_hook.py +0 -0
  411. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_install_wake_hook.py +0 -0
  412. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_lane_command.py +0 -0
  413. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_main.py +0 -0
  414. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_main_unknown_verb_guard.py +0 -0
  415. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_mcp_missing_hint.py +0 -0
  416. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_mcp_server.py +0 -0
  417. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_memory_command.py +0 -0
  418. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_memory_emit_snippet.py +0 -0
  419. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_memory_gateway.py +0 -0
  420. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_memory_sync.py +0 -0
  421. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_memory_sync_membrane.py +0 -0
  422. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_mesh_command.py +0 -0
  423. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_mesh_inbox_mark_read.py +0 -0
  424. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_mesh_inbox_sanitize.py +0 -0
  425. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_mesh_originate_thread_623.py +0 -0
  426. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_mesh_send_unknown_peer.py +0 -0
  427. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_mesh_sidecar.py +0 -0
  428. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  429. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_mesh_sidecar_muse_sink.py +0 -0
  430. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  431. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_meta_edge_identity.py +0 -0
  432. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_monitor_channel_poll.py +0 -0
  433. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_monitor_cli.py +0 -0
  434. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_monitor_daemon_collision.py +0 -0
  435. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_monitor_self_name_verification.py +0 -0
  436. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_monitor_state_split.py +0 -0
  437. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_multiplexer.py +0 -0
  438. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_muse_membrane.py +0 -0
  439. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_muse_own_cli.py +0 -0
  440. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_obligation_close_is_surfaced.py +0 -0
  441. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_okf_links.py +0 -0
  442. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_onboard_command.py +0 -0
  443. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_opencode_membrane.py +0 -0
  444. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_opencode_plugin.py +0 -0
  445. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_operator_me.py +0 -0
  446. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_orphan_daemons.py +0 -0
  447. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_packaged_artifacts_exist.py +0 -0
  448. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_packaged_skills.py +0 -0
  449. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_peer_executor.py +0 -0
  450. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_peer_executor_windows.py +0 -0
  451. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_peer_reply_drain_command.py +0 -0
  452. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_peer_reply_drain_supervisor.py +0 -0
  453. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_peer_reply_outbox.py +0 -0
  454. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_peer_reply_transport.py +0 -0
  455. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_peer_service_e2e.py +0 -0
  456. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_peer_service_host.py +0 -0
  457. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_peer_service_payload.py +0 -0
  458. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_peer_service_reply.py +0 -0
  459. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_postcompact_recall.py +0 -0
  460. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_process_liveness.py +0 -0
  461. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_protocol_handler.py +0 -0
  462. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_providers_isolation.py +0 -0
  463. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_ratify_command.py +0 -0
  464. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_readme_claims_no_gateway_default.py +0 -0
  465. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_repl_caller_convention.py +0 -0
  466. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_schedule_command.py +0 -0
  467. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_schedule_create_contract.py +0 -0
  468. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_scrub.py +0 -0
  469. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_security.py +0 -0
  470. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_service_app.py +0 -0
  471. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_service_command.py +0 -0
  472. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_service_providers.py +0 -0
  473. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_session_bridge_inject.py +0 -0
  474. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_session_bridge_probe.py +0 -0
  475. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_session_bridge_resolve.py +0 -0
  476. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_session_path_role_gate.py +0 -0
  477. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_shipped_units_match_the_cli.py +0 -0
  478. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_smoke_chat.py +0 -0
  479. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_smoke_one_shot.py +0 -0
  480. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_smoke_phase_5_5.py +0 -0
  481. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_spawn_base_hoist.py +0 -0
  482. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_spawn_command.py +0 -0
  483. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_spawn_identity_stamp.py +0 -0
  484. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_spawn_live_pin.py +0 -0
  485. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_spawn_mitosis_lineage.py +0 -0
  486. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_spawn_tmux_session.py +0 -0
  487. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_spawn_windows_relaunch.py +0 -0
  488. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_stall_alert.py +0 -0
  489. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_subprocess_decode_encoding.py +0 -0
  490. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_timeline_command.py +0 -0
  491. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_tmux_wake_runstate_defer.py +0 -0
  492. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_tmux_wake_session_reanchor.py +0 -0
  493. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_tmux_wake_stale.py +0 -0
  494. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_tmux_wake_submit_verify.py +0 -0
  495. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_token_file_one_parser.py +0 -0
  496. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_token_precedence_explicit_first.py +0 -0
  497. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_unified_token_resolver.py +0 -0
  498. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_version_command.py +0 -0
  499. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_version_consistency.py +0 -0
  500. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_vibe_membrane.py +0 -0
  501. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_watchdog.py +0 -0
  502. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_watchdog_dm_wake.py +0 -0
  503. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  504. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  505. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_watchdog_emit_health.py +0 -0
  506. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_watchdog_install_flags.py +0 -0
  507. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_watchdog_model_rung.py +0 -0
  508. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/tests/test_watchdog_stale_peers.py +0 -0
  509. {swarph_cli-0.72.17 → swarph_cli-0.72.18}/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.72.17
3
+ Version: 0.72.18
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
@@ -497,6 +497,14 @@ swarph monitor stop
497
497
  | `none` | genuinely nothing: observe, append `inbox.log`, no ledger, **no unread tracking**. |
498
498
  | `webhook:<url>` | **held** pending an egress review — exits non-zero rather than silently no-op. |
499
499
 
500
+ **Copilot tmux panes.** The reader recognizes Copilot CLI 1.0.94's color input
501
+ box and `--no-color` composer using the bottom AIC summary, input borders, and
502
+ status footer. It defers while `esc interrupt` indicates a running turn or
503
+ the composer holds a human draft. The wake names `swarph_dm_unread`; delivery
504
+ is recorded only after the composer is observed clear. Missing or incomplete
505
+ pane captures send no keystrokes and leave delivery pending with a visible
506
+ failure. Keep `pull` for layouts that hide or customize this chrome.
507
+
500
508
  **Two pieces of state that never share a variable.** The *observation cursor* (`cursor.json`) is what this monitor has READ from the gateway; it advances on observation, always, gated on nothing. A *delivery ledger* (`ledgers.json`, one per sink) advances only when that sink is satisfied and may lag arbitrarily far. So a dead tmux pane can no longer freeze the cursor, one dead sink cannot stall another, and a sink attached tomorrow starts with an empty ledger and replays from `inbox.log` (bounded, and it reports what it skipped).
501
509
 
502
510
  **Pull beats push.** Every push sink's liveness is a precondition for hearing anything — if the pane dies, "no wake arrived" is indistinguishable from "no mail arrived". A pull check run *by* the cell lives one layer above tmux and cannot die with it. Designed to drop straight into a SessionStart hook:
@@ -448,6 +448,14 @@ swarph monitor stop
448
448
  | `none` | genuinely nothing: observe, append `inbox.log`, no ledger, **no unread tracking**. |
449
449
  | `webhook:<url>` | **held** pending an egress review — exits non-zero rather than silently no-op. |
450
450
 
451
+ **Copilot tmux panes.** The reader recognizes Copilot CLI 1.0.94's color input
452
+ box and `--no-color` composer using the bottom AIC summary, input borders, and
453
+ status footer. It defers while `esc interrupt` indicates a running turn or
454
+ the composer holds a human draft. The wake names `swarph_dm_unread`; delivery
455
+ is recorded only after the composer is observed clear. Missing or incomplete
456
+ pane captures send no keystrokes and leave delivery pending with a visible
457
+ failure. Keep `pull` for layouts that hide or customize this chrome.
458
+
451
459
  **Two pieces of state that never share a variable.** The *observation cursor* (`cursor.json`) is what this monitor has READ from the gateway; it advances on observation, always, gated on nothing. A *delivery ledger* (`ledgers.json`, one per sink) advances only when that sink is satisfied and may lag arbitrarily far. So a dead tmux pane can no longer freeze the cursor, one dead sink cannot stall another, and a sink attached tomorrow starts with an empty ledger and replays from `inbox.log` (bounded, and it reports what it skipped).
452
460
 
453
461
  **Pull beats push.** Every push sink's liveness is a precondition for hearing anything — if the pane dies, "no wake arrived" is indistinguishable from "no mail arrived". A pull check run *by* the cell lives one layer above tmux and cannot die with it. Designed to drop straight into a SessionStart hook:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.72.17"
7
+ version = "0.72.18"
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" }
@@ -16,6 +16,6 @@ The architecture splits CLI from substrate so:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.72.17"
19
+ __version__ = "0.72.18"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -132,6 +132,12 @@ def _format_obligations(data: dict) -> str:
132
132
  marks.append(f"UNCLOSABLE:{_s(o['unclosable_reason'])}")
133
133
  state = _s(o.get("accept_state"))
134
134
  marks.append(f"accept:{state}" if state else "accept:missing")
135
+ if o.get("accept_format") == "json":
136
+ # #1068: a structured row reads evidenced/total — the reviewer
137
+ # checks condition by condition (--json carries each pair).
138
+ conds = o.get("conditions") or []
139
+ ev = sum(1 for x in conds if x.get("evidence"))
140
+ marks.append(f"conds:{ev}/{len(conds)}")
135
141
  if o.get("status") != "open":
136
142
  # a NULL outcome on a closed row is a pre-#562 legacy close — name
137
143
  # the absence rather than print a blank that reads as recorded
@@ -547,6 +553,28 @@ def _card_say_payload(from_node: str, to_node: str, kind: str, content: str,
547
553
  _NO_TIMEOUT = "NO TIMEOUT — this never goes red on its own; pass --timeout-hours if it should"
548
554
 
549
555
 
556
+ def _parse_accept_conditions(accept):
557
+ """#1068: read a stored accept the way the gateway does — a JSON row
558
+ yields {"pass", "fail"}, prose or missing yields None. Never raises:
559
+ a render helper must not 500 a listing over one odd row."""
560
+ if not accept or not str(accept).strip():
561
+ return None
562
+ try:
563
+ obj = json.loads(str(accept))
564
+ except (ValueError, TypeError):
565
+ return None
566
+ if not isinstance(obj, dict) or not isinstance(obj.get("pass"), list):
567
+ return None
568
+ passes = [p for p in obj["pass"]
569
+ if isinstance(p, dict) and isinstance(p.get("id"), str)
570
+ and isinstance(p.get("text"), str)]
571
+ if len(passes) != len(obj["pass"]) or not passes:
572
+ return None
573
+ fails = obj.get("fail", [])
574
+ return {"pass": [{"id": p["id"], "text": p["text"]} for p in passes],
575
+ "fail": [f for f in fails if isinstance(f, str)]}
576
+
577
+
550
578
  def _accept_line(accept) -> str:
551
579
  """#532's mint-time falsifier readout — shared by the pre-#591 and the §2
552
580
  success lines, because the rule is about the MOMENT, not the response
@@ -556,6 +584,13 @@ def _accept_line(accept) -> str:
556
584
  return ("NO ACCEPT CHECK — reads RED in the sweep; pass --accept "
557
585
  "\"PASS = ... | FAIL = ...\" naming an observable and a way "
558
586
  "it comes out negative")
587
+ parsed = _parse_accept_conditions(accept)
588
+ if parsed is not None:
589
+ # #1068: a structured check reads condition by condition, not as a blob.
590
+ line = "; ".join(f"{p['id']}: {p['text']}" for p in parsed["pass"])
591
+ if parsed["fail"]:
592
+ line += " | FAIL: " + "; ".join(parsed["fail"])
593
+ return f"accept: {line}"
559
594
  if not _FAIL_MARKER_RE.search(str(accept)):
560
595
  return ("accept check has NO FAIL BRANCH — marked NO-FAIL-BRANCH in "
561
596
  "the sweep; a check that cannot come out negative is not one")
@@ -630,15 +665,28 @@ def _step_list(values) -> list[str]:
630
665
  return [n for n in names if n]
631
666
 
632
667
 
668
+ def _json_flag(flag: str, value: str):
669
+ """Parse a --*-json flag locally (#1068): invalid JSON is refused here
670
+ (exit 2 via the caller's ValueError catch), never sent as a string the
671
+ gateway would have to guess at."""
672
+ try:
673
+ return json.loads(value)
674
+ except (ValueError, TypeError) as exc:
675
+ raise ValueError(f"{flag} is not valid JSON: {exc}")
676
+
677
+
633
678
  def _ask_payload(self_name, what, *, holder=None, step=None, needs=None, hours=None,
634
- done=None, accept=None, kind="action", timeout_hours=None,
635
- task=None, task_needs=None) -> dict:
679
+ done=None, accept=None, accept_json=None, kind="action",
680
+ timeout_hours=None, task=None, task_needs=None) -> dict:
636
681
  """POST /board/cards/{id}/ask body. None = not mentioned = key ABSENT (#191's
637
682
  rule; test_532 pins that a null `accept` on the wire would overwrite the
638
683
  gateway's own normalization). No holder → `requested`; `me` → the caller's
639
684
  own `--as` identity (the contract binds `me` to the token, never to an env
640
685
  var). `needs` is whatever `--needs` collected — see `_step_list`.
641
686
  """
687
+ if accept is not None and accept_json is not None:
688
+ raise ValueError("pass --accept or --accept-json, not both — "
689
+ "one check per row")
642
690
  p = {"what": what, "created_by": self_name, "kind": kind}
643
691
  if holder is not None:
644
692
  p["holder"] = self_name if holder == "me" else holder
@@ -652,6 +700,8 @@ def _ask_payload(self_name, what, *, holder=None, step=None, needs=None, hours=N
652
700
  p["done"] = done
653
701
  if accept is not None:
654
702
  p["accept"] = accept
703
+ if accept_json is not None:
704
+ p["accept_json"] = accept_json
655
705
  if timeout_hours is not None:
656
706
  p["timeout_hours"] = timeout_hours
657
707
  if task is not None:
@@ -818,8 +868,25 @@ def _move_line(x) -> str:
818
868
  return line
819
869
 
820
870
 
871
+ def _close_payload(outcome, evidence=None, evidence_json=None) -> dict:
872
+ """POST /board/obligations/{id}/close body (#1068): --evidence and/or
873
+ --evidence-json, at least one. The CLI cannot know the row's stored
874
+ accept, so it sends what it was given — the gateway requires the map
875
+ for a JSON pass, the string everywhere else."""
876
+ if evidence is None and evidence_json is None:
877
+ raise ValueError("nothing observed — pass --evidence and/or "
878
+ "--evidence-json")
879
+ p = {"outcome": outcome}
880
+ if evidence is not None:
881
+ p["evidence"] = evidence
882
+ if evidence_json is not None:
883
+ p["evidence_json"] = evidence_json
884
+ return p
885
+
886
+
821
887
  def _amend_payload(step=None, holder=None, accept=None, hours=None,
822
- needs_add=None, needs_remove=None, needs=None) -> dict:
888
+ needs_add=None, needs_remove=None, needs=None,
889
+ accept_json=None) -> dict:
823
890
  """PATCH /board/obligations/{id}/amend body. None = not mentioned; an empty
824
891
  `holder` is a REAL value (clears → `requested`), so the test is
825
892
  `is not None`, never truthiness (#191). `needs` is the contract's signed
@@ -827,15 +894,19 @@ def _amend_payload(step=None, holder=None, accept=None, hours=None,
827
894
  needs_remove; every edge list flattens through `_step_list`. Refuses the
828
895
  empty patch locally — the gateway 400s it too, but a round trip to learn
829
896
  you said nothing is the #256 shape one hop later."""
897
+ if accept is not None and accept_json is not None:
898
+ raise ValueError("pass --accept or --accept-json, not both — "
899
+ "one check per row")
830
900
  signed = _step_list(needs)
831
901
  add = _step_list(needs_add) + [x for x in signed if not x.startswith("-")]
832
902
  rem = _step_list(needs_remove) + [x[1:] for x in signed if x.startswith("-")]
833
903
  patch = {k: v for k, v in (("step", step), ("holder", holder), ("accept", accept),
904
+ ("accept_json", accept_json),
834
905
  ("hours", hours), ("needs_add", add or None),
835
906
  ("needs_remove", rem or None)) if v is not None}
836
907
  if not patch:
837
- raise ValueError("nothing to amend — pass --step, --holder, --accept, --hours, "
838
- "--needs-add and/or --needs-remove")
908
+ raise ValueError("nothing to amend — pass --step, --holder, --accept, --accept-json, "
909
+ "--hours, --needs-add and/or --needs-remove")
839
910
  return patch
840
911
 
841
912
 
@@ -968,6 +1039,11 @@ def _build_parser() -> argparse.ArgumentParser:
968
1039
  "(#532). Omitting it mints an obligation that reads RED "
969
1040
  "in the sweep; a check with no FAIL branch is marked "
970
1041
  "NO-FAIL-BRANCH — 'verify it works' is not a check")
1042
+ ck.add_argument("--accept-json", default=None, metavar="'{...}'",
1043
+ help="the structured check (#1068): '{\"pass\": [{\"id\", "
1044
+ "\"text\"}], \"fail\": [...]}'. Mutually exclusive "
1045
+ "with --accept; a PASS close of such a row needs "
1046
+ "--evidence-json keyed by condition id")
971
1047
  ck.add_argument("--kind", default="action", help="obligation kind (default: action)")
972
1048
  # #591: the step graph. `--holder` is an OPTION with its own dest — sharing
973
1049
  # the positional's dest makes `--holder me --step build "<what>"` parse
@@ -1063,10 +1139,15 @@ def _build_parser() -> argparse.ArgumentParser:
1063
1139
  choices=("pass", "fail", "cannot_evaluate", "skipped"),
1064
1140
  help="the accept check's OUTCOME (`skipped` only on an "
1065
1141
  "optional step — #591)")
1066
- oc.add_argument("--evidence", required=True,
1142
+ oc.add_argument("--evidence", required=False, default=None,
1067
1143
  help="what you OBSERVED running the check — whitespace is "
1068
1144
  "refused: whitespace evidence is the vibe-close this "
1069
1145
  "endpoint exists to kill")
1146
+ oc.add_argument("--evidence-json", default=None, metavar="'{...}'",
1147
+ help="the per-condition evidence map (#1068): '{\"c1\": "
1148
+ "\"...\", ...}' keyed by the row's condition ids. "
1149
+ "Pass --evidence and/or --evidence-json; the gateway "
1150
+ "requires what the row's stored accept demands")
1070
1151
  oc.add_argument("--json", action="store_true"); _add_common(oc)
1071
1152
 
1072
1153
  ol = obl.add_parser(
@@ -1101,6 +1182,9 @@ def _build_parser() -> argparse.ArgumentParser:
1101
1182
  oa.add_argument("--holder", default=None, metavar="PEER",
1102
1183
  help="reassign; pass an empty string to clear (back to requested)")
1103
1184
  oa.add_argument("--accept", default=None, metavar='"PASS = ... | FAIL = ..."')
1185
+ oa.add_argument("--accept-json", default=None, metavar="'{...}'",
1186
+ help="replace the check with a structured one (#1068); "
1187
+ "mutually exclusive with --accept")
1104
1188
  oa.add_argument("--hours", type=int, default=None, help="clock override, up to 4x the menu value")
1105
1189
  oa.add_argument("--needs-add", action="append", dest="needs_add", default=None,
1106
1190
  metavar="STEP", help="add an edge (repeatable)")
@@ -1172,17 +1256,23 @@ def run_board(argv: list[str]) -> int:
1172
1256
  # #562's CLI half: the explicit close act. The gateway guards the
1173
1257
  # identity (holder, creator, or board orchestrator) and refuses a
1174
1258
  # second close; the CLI's job is to refuse what it can see —
1175
- # whitespace evidence — and to propagate the gateway's refusals
1176
- # with their detail intact.
1259
+ # whitespace evidence, invalid JSON, neither evidence — and to
1260
+ # propagate the gateway's refusals with their detail intact.
1177
1261
  evidence = args.evidence
1178
- if not evidence.strip():
1262
+ if evidence is not None and not evidence.strip():
1179
1263
  print("swarph board obligations close: --evidence is "
1180
1264
  "whitespace-only — the close act records what you "
1181
1265
  "OBSERVED, and nothing was observed", file=sys.stderr)
1182
1266
  return 2
1267
+ try:
1268
+ evidence_json = (_json_flag("--evidence-json", args.evidence_json)
1269
+ if args.evidence_json is not None else None)
1270
+ payload = _close_payload(args.outcome, evidence, evidence_json)
1271
+ except ValueError as exc:
1272
+ print(f"swarph board obligations close: {exc}", file=sys.stderr)
1273
+ return 2
1183
1274
  st, d = _post_json(
1184
- f"{gw}/board/obligations/{args.id}/close",
1185
- {"outcome": args.outcome, "evidence": evidence}, token)
1275
+ f"{gw}/board/obligations/{args.id}/close", payload, token)
1186
1276
  if st < 200 or st >= 300:
1187
1277
  detail = d.get("detail", d) if isinstance(d, dict) else d
1188
1278
  print(f"swarph board obligations close: gateway {st}: {_s(detail)}",
@@ -1212,10 +1302,13 @@ def run_board(argv: list[str]) -> int:
1212
1302
  return _out(st, d, _obligation_act_line, aj)
1213
1303
  if args.command == "amend":
1214
1304
  try:
1305
+ accept_json = (_json_flag("--accept-json", args.accept_json)
1306
+ if args.accept_json is not None else None)
1215
1307
  patch = _amend_payload(step=args.step, holder=args.holder,
1216
1308
  accept=args.accept, hours=args.hours,
1217
1309
  needs_add=args.needs_add,
1218
- needs_remove=args.needs_remove, needs=args.needs)
1310
+ needs_remove=args.needs_remove, needs=args.needs,
1311
+ accept_json=accept_json)
1219
1312
  except ValueError as exc:
1220
1313
  print(f"swarph board obligations amend: {exc}", file=sys.stderr)
1221
1314
  return 2
@@ -1388,11 +1481,18 @@ def run_board(argv: list[str]) -> int:
1388
1481
  f"or positional). If {args.what!r} was meant as the holder, the "
1389
1482
  f"\"<what>\" text is missing", file=sys.stderr)
1390
1483
  return 2
1391
- body = _ask_payload(self_name, args.what, holder=holder,
1392
- step=args.step, needs=args.needs, hours=args.hours,
1393
- done=args.done, accept=args.accept, kind=args.kind,
1394
- timeout_hours=args.timeout_hours,
1395
- task=args.task, task_needs=args.task_needs)
1484
+ try:
1485
+ accept_json = (_json_flag("--accept-json", args.accept_json)
1486
+ if args.accept_json is not None else None)
1487
+ body = _ask_payload(self_name, args.what, holder=holder,
1488
+ step=args.step, needs=args.needs, hours=args.hours,
1489
+ done=args.done, accept=args.accept,
1490
+ accept_json=accept_json, kind=args.kind,
1491
+ timeout_hours=args.timeout_hours,
1492
+ task=args.task, task_needs=args.task_needs)
1493
+ except ValueError as exc:
1494
+ print(f"swarph board cards ask: {exc}", file=sys.stderr)
1495
+ return 2
1396
1496
  st, d = _post_json(f"{gw}/board/cards/{args.id}/ask", body, token)
1397
1497
  return _out(st, d, _ask_line, args.json)
1398
1498
  if args.command == "confirm-flow":
@@ -2350,6 +2350,7 @@ _GROK_WAKE_PROMPT = "check mesh: swarph_dm_unread"
2350
2350
  # Muse 1.4.1 has no push channel either. The wake names the pull tool,
2351
2351
  # same text the grok and opencode sinks inject.
2352
2352
  _MUSE_WAKE_PROMPT = "check mesh: swarph_dm_unread"
2353
+ _COPILOT_WAKE_PROMPT = "check mesh: swarph_dm_unread"
2353
2354
  # A finished turn keeps its ▣ header and adds a duration.
2354
2355
  # Short turns read `· 3.3s` (drop-on-meta-edge, 1.18.33, card #961 post
2355
2356
  # 54557). A longer idle turn reads `· 3m 4s` (opencode pane, 2026-09-29
@@ -2991,6 +2992,8 @@ def _muse_running(lines: list[str]) -> bool:
2991
2992
 
2992
2993
 
2993
2994
  def _wake_prompt_for(lines: Optional[list[str]]) -> str:
2995
+ if lines and _copilot_frame(lines) is not None:
2996
+ return _COPILOT_WAKE_PROMPT
2994
2997
  if lines and _is_grok_pane(lines):
2995
2998
  return _GROK_WAKE_PROMPT
2996
2999
  if lines and _is_opencode_pane(lines):
@@ -3013,6 +3016,67 @@ def _sink_failure_detail(target: str) -> str:
3013
3016
  return "the sink is probably gone (session restart / renamed target)"
3014
3017
 
3015
3018
 
3019
+ def _copilot_frame(
3020
+ lines: list[str],
3021
+ ) -> Optional[tuple[Optional[str], Optional[bool]]]:
3022
+ """Read Copilot's bottom input block, not transcript quotes (#386).
3023
+
3024
+ Both measured 1.0.94 layouts put the AIC summary immediately above
3025
+ the input: ``--no-color`` uses rules and ``❯``; color uses a ``┃``
3026
+ box. An identified but incomplete block stays unknown.
3027
+ """
3028
+ tail = _nonempty_tail(lines, 12)
3029
+ summary = next(
3030
+ (i for i in range(len(tail) - 1, -1, -1)
3031
+ if re.search(r"Plan: .* · Session: .* AIC\b", tail[i])),
3032
+ None,
3033
+ )
3034
+ if summary is None:
3035
+ return None
3036
+ block = [ln.strip() for ln in tail[summary + 1:]]
3037
+ if not block:
3038
+ return (None, None)
3039
+ text = None
3040
+ running = None
3041
+ if block[0].startswith("╻▄"):
3042
+ bottom = next(
3043
+ (i for i, ln in enumerate(block) if ln.startswith("╹▀")), None,
3044
+ )
3045
+ if bottom is not None and bottom + 1 < len(block) - 1:
3046
+ return None # This block is history above another composer.
3047
+ if bottom is not None and bottom + 1 == len(block) - 1:
3048
+ footer = block[-1]
3049
+ rows = block[1:bottom]
3050
+ if rows and all(ln.startswith("┃") for ln in rows):
3051
+ text = "\n".join(ln[1:].strip() for ln in rows).strip()
3052
+ if "esc interrupt" in footer:
3053
+ running = True
3054
+ elif "Interactive ·" in footer or "· autopilot ·" in footer:
3055
+ running = False
3056
+ elif _is_muse_rule(block[0]):
3057
+ bottom = next(
3058
+ (i for i in range(1, len(block))
3059
+ if _is_muse_rule(block[i])), None,
3060
+ )
3061
+ if bottom is not None and bottom + 1 < len(block) - 1:
3062
+ return None
3063
+ if bottom is not None and bottom + 1 == len(block) - 1:
3064
+ rows = block[1:bottom]
3065
+ if rows and rows[0].startswith("❯"):
3066
+ text = "\n".join([rows[0][1:].strip(), *rows[1:]]).strip()
3067
+ footer = block[-1]
3068
+ if "esc interrupt" in footer:
3069
+ running = True
3070
+ elif "Interactive ·" in footer or "· autopilot ·" in footer:
3071
+ running = False
3072
+ else:
3073
+ return None
3074
+ if text is None or running is None:
3075
+ return (None, None)
3076
+ composer = "clear" if not text else "wake" if _only_wake_text(text) else "busy"
3077
+ return (composer, running)
3078
+
3079
+
3016
3080
  def _agent_running(target: str) -> Optional[bool]:
3017
3081
  """True while a keystroke would miss the idle composer.
3018
3082
 
@@ -3030,6 +3094,9 @@ def _agent_running(target: str) -> Optional[bool]:
3030
3094
  lines = _capture_pane_lines(target)
3031
3095
  if lines is None:
3032
3096
  return None
3097
+ copilot = _copilot_frame(lines)
3098
+ if copilot is not None:
3099
+ return copilot[1]
3033
3100
  if _is_opencode_pane(lines):
3034
3101
  return _opencode_running(lines)
3035
3102
  if _is_grok_pane(lines):
@@ -3080,6 +3147,9 @@ def _composer_state(target: str) -> Optional[str]:
3080
3147
  lines = _capture_pane_lines(target)
3081
3148
  if lines is None:
3082
3149
  return None
3150
+ copilot = _copilot_frame(lines)
3151
+ if copilot is not None:
3152
+ return copilot[0]
3083
3153
  if _is_opencode_pane(lines):
3084
3154
  return _opencode_composer_state(lines)
3085
3155
  if _is_grok_pane(lines):
@@ -14,14 +14,26 @@ A quota VALUE without its as_of coordinate is a rumour: --quota-used /
14
14
  --resets without --quota-as-of are refused client-side (exit 2) before the
15
15
  gateway's own 400. The pool NAME needs no coordinate (identifier, not a
16
16
  measurement). --clear-until is the grok-returns flow (explicit null clears).
17
+
18
+ swarph roster sync [--peer PEER]... [--service-log PEER=PATH]...
19
+ [--state PATH] [--json]
20
+
21
+ card #1071: one quota pass over local tmux panes — parse each membrane's
22
+ usage display, PUT the roster on change, DM the commander on a crossing.
23
+ Runs as the quota-writer #417 service (its token via MESH_GATEWAY_TOKEN /
24
+ --token-file); at most one pane read per peer per 5 minutes.
17
25
  """
18
26
  from __future__ import annotations
19
27
 
20
28
  import argparse
21
29
  import json
30
+ import os
31
+ import subprocess
22
32
  import sys
23
33
  import urllib.error
24
34
  import urllib.request
35
+ from datetime import datetime, timezone
36
+ from pathlib import Path
25
37
 
26
38
  # card #1061 role vocabulary — mirrors the gateway's _ROSTER_ROLES. A role
27
39
  # the gateway does not know is refused here (exit 2) before its 400; when the
@@ -219,15 +231,199 @@ def _build_parser():
219
231
  group.add_argument("--clear-until", action="store_true",
220
232
  help="clear unavailable_until (peer is back)")
221
233
  s.add_argument("--note", default=None)
234
+ sy = sub.add_parser("sync", help="one quota pass over local tmux panes "
235
+ "(card #1071): parse, PUT on change, "
236
+ "alert on crossing")
237
+ _add_global(sy)
238
+ sy.add_argument("--json", action="store_true",
239
+ help="print the raw sync summary")
240
+ sy.add_argument("--peer", action="append", default=None,
241
+ help="peer to read (repeatable; default: all local "
242
+ "tmux sessions)")
243
+ sy.add_argument("--service-log", action="append", default=None,
244
+ metavar="PEER=PATH",
245
+ help="provider log tail for the headless error path "
246
+ "(repeatable)")
247
+ sy.add_argument("--state", default=None, metavar="PATH",
248
+ help="state file override (default: "
249
+ "$XDG_STATE_HOME/swarph/quota_sync_state.json)")
222
250
  return p
223
251
 
224
252
 
253
+ def _sync_state_path(explicit=None) -> Path:
254
+ """$XDG_STATE_HOME/swarph/quota_sync_state.json, else ~/.local/state —
255
+ the watchdog dm-wake convention, one file over."""
256
+ if explicit:
257
+ return Path(explicit).expanduser()
258
+ root = os.environ.get("XDG_STATE_HOME", "").strip()
259
+ if root:
260
+ return Path(root) / "swarph" / "quota_sync_state.json"
261
+ return Path.home() / ".local" / "state" / "swarph" / "quota_sync_state.json"
262
+
263
+
264
+ def _load_sync_state(path: Path) -> dict:
265
+ try:
266
+ with Path(path).open(encoding="utf-8") as fp:
267
+ data = json.load(fp)
268
+ except (FileNotFoundError, OSError, ValueError):
269
+ return {}
270
+ return data if isinstance(data, dict) else {}
271
+
272
+
273
+ def _save_sync_state(path: Path, state: dict) -> None:
274
+ """Atomic best-effort persist (temp sibling + os.replace, never raises)."""
275
+ path = Path(path)
276
+ try:
277
+ path.parent.mkdir(parents=True, exist_ok=True)
278
+ tmp = path.with_suffix(".tmp")
279
+ tmp.write_text(json.dumps(state), encoding="utf-8")
280
+ os.replace(tmp, path)
281
+ except OSError:
282
+ pass
283
+
284
+
285
+ def _local_tmux_peers() -> list:
286
+ """Session names on this box. [] when tmux is missing — sync then covers
287
+ only --peer/--service-log peers instead of failing the whole pass."""
288
+ try:
289
+ out = subprocess.run(["tmux", "list-sessions", "-F", "#{session_name}"],
290
+ capture_output=True, timeout=10, text=True,
291
+ encoding="utf-8", errors="replace")
292
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
293
+ return []
294
+ if out.returncode != 0:
295
+ return []
296
+ return [ln.strip() for ln in (out.stdout or "").splitlines() if ln.strip()]
297
+
298
+
299
+ def _capture_peer_pane(peer: str):
300
+ """One read-only full-height capture of the peer's active pane. Never
301
+ sends keys (ruling 6); None on any failure — the peer reads unknown."""
302
+ try:
303
+ out = subprocess.run(["tmux", "capture-pane", "-p", "-t", peer],
304
+ capture_output=True, timeout=10, text=True,
305
+ encoding="utf-8", errors="replace")
306
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
307
+ return None
308
+ if out.returncode != 0:
309
+ return None
310
+ return out.stdout or ""
311
+
312
+
313
+ def _parse_service_logs(values) -> dict:
314
+ """['PEER=PATH', ...] -> {peer: tail text}. A missing/unreadable log is
315
+ skipped (its peer still gets the pane path), never fatal."""
316
+ logs = {}
317
+ for item in values or []:
318
+ if "=" not in item:
319
+ raise ValueError(f"--service-log takes PEER=PATH, got {item!r}")
320
+ peer, _, path = item.partition("=")
321
+ peer, path = peer.strip(), path.strip()
322
+ if not peer or not path:
323
+ raise ValueError(f"--service-log takes PEER=PATH, got {item!r}")
324
+ try:
325
+ with open(path, encoding="utf-8", errors="replace") as fp:
326
+ fp.seek(0, os.SEEK_END)
327
+ size = fp.tell()
328
+ fp.seek(max(0, size - 65536))
329
+ logs[peer] = fp.read()
330
+ except OSError:
331
+ continue
332
+ return logs
333
+
334
+
335
+ def _post_message(gateway: str, token: str, to_node: str, content: str) -> bool:
336
+ """DM the commander as the quota-writer service. True on HTTP 2xx."""
337
+ from swarph_cli.commands.mesh import _require_absolute_gateway_url
338
+ _require_absolute_gateway_url(gateway)
339
+ data = json.dumps({"from_node": "quota-writer", "to_node": to_node,
340
+ "kind": "fyi", "content": content}).encode("utf-8")
341
+ req = urllib.request.Request(
342
+ f"{gateway}/messages", data=data, method="POST",
343
+ headers={"Authorization": f"Bearer {token}",
344
+ "Content-Type": "application/json"})
345
+ try:
346
+ with urllib.request.urlopen(req, timeout=10.0) as resp:
347
+ return 200 <= resp.status < 300
348
+ except (urllib.error.URLError, OSError):
349
+ return False
350
+
351
+
352
+ def _run_sync(args):
353
+ from swarph_cli import quota_sync as qs
354
+ token = _resolve(args)
355
+ if token is None:
356
+ return 2
357
+ gateway = args.gateway.rstrip("/")
358
+ try:
359
+ service_logs = _parse_service_logs(args.service_log)
360
+ except ValueError as exc:
361
+ print(f"swarph roster sync: {exc}", file=sys.stderr)
362
+ return 2
363
+ peers = list(args.peer or []) or _local_tmux_peers()
364
+ for peer in service_logs:
365
+ if peer not in peers:
366
+ peers.append(peer)
367
+ if not peers:
368
+ print("swarph roster sync: no peers (no --peer, no tmux sessions, "
369
+ "no --service-log)", file=sys.stderr)
370
+ return 1
371
+
372
+ def _get_roster():
373
+ status, payload = _http_get_json(f"{gateway}/roster", token)
374
+ if status < 200 or status >= 300 or not isinstance(payload, dict):
375
+ return []
376
+ rows = payload.get("roster")
377
+ return rows if isinstance(rows, list) else []
378
+
379
+ failures = []
380
+
381
+ def _put_roster(peer, body):
382
+ status, payload = _http_put_json(
383
+ f"{gateway}/roster/{peer}", body, token)
384
+ if status < 200 or status >= 300:
385
+ failures.append(peer)
386
+
387
+ def _send_alert(peer, text):
388
+ _post_message(gateway, token, "commander",
389
+ f"quota [{peer}]: {text}")
390
+
391
+ state_path = _sync_state_path(args.state)
392
+ summary = qs.sync_once(
393
+ peers=peers, capture=_capture_peer_pane, get_roster=_get_roster,
394
+ put_roster=_put_roster, send_alert=_send_alert,
395
+ service_logs=service_logs,
396
+ state=_load_sync_state(state_path),
397
+ now=datetime.now(timezone.utc))
398
+ _save_sync_state(state_path, summary.get("state", {}))
399
+ if args.json:
400
+ print(json.dumps(summary, indent=2))
401
+ else:
402
+ for peer, entry in summary.get("peers", {}).items():
403
+ if entry.get("skipped"):
404
+ print(f" {peer}: skipped ({entry['skipped']})")
405
+ continue
406
+ bits = [entry.get("record", "?")]
407
+ if entry.get("put"):
408
+ bits.append("PUT")
409
+ for threshold in entry.get("alerts", []):
410
+ bits.append(f"ALERT {threshold}")
411
+ print(f" {peer}: {' '.join(bits)}")
412
+ if failures:
413
+ print(f"swarph roster sync: PUT failed for: {', '.join(failures)}",
414
+ file=sys.stderr)
415
+ return 1
416
+ return 0
417
+
418
+
225
419
  def run_roster(argv):
226
420
  parser = _build_parser()
227
421
  args = parser.parse_args(argv)
228
422
  try:
229
423
  if args.command == "set":
230
424
  return _run_set(args)
425
+ if args.command == "sync":
426
+ return _run_sync(args)
231
427
  if args.command is None:
232
428
  return _run_list(args)
233
429
  parser.error(f"unknown command: {args.command}")