swarph-cli 0.45.2__tar.gz → 0.46.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.
- {swarph_cli-0.45.2/src/swarph_cli.egg-info → swarph_cli-0.46.0}/PKG-INFO +2 -1
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/README.md +1 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/pyproject.toml +1 -1
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/board.py +52 -3
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/gh_route.py +2 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/init.py +7 -1
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/memory_emit_hook.py +15 -5
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/mesh.py +169 -5
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/monitor.py +1 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/onboard.py +1 -1
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/spawn.py +63 -172
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/wake_hook_output.py +3 -1
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/guide/GUIDE.md +3 -2
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/main.py +6 -1
- {swarph_cli-0.45.2 → swarph_cli-0.46.0/src/swarph_cli.egg-info}/PKG-INFO +2 -1
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli.egg-info/SOURCES.txt +4 -0
- swarph_cli-0.46.0/tests/test_124_register_merge.py +112 -0
- swarph_cli-0.46.0/tests/test_319_refusal_exit_codes.py +147 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_532_accept_flag.py +6 -0
- swarph_cli-0.46.0/tests/test_535_version_capability.py +113 -0
- swarph_cli-0.46.0/tests/test_board_obligation_close.py +77 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_mesh_command.py +5 -1
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_postcompact_recall.py +66 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_spawn_base_hoist.py +39 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_spawn_command.py +3 -1
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/LICENSE +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/setup.cfg +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_board_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_group_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_import_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_init_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_main.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_security.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_service_app.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_service_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.45.2 → swarph_cli-0.46.0}/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.
|
|
3
|
+
Version: 0.46.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`, 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
|
|
@@ -186,6 +186,7 @@ swarph board cards thread <id> [--limit N] # the card's conversation (a card
|
|
|
186
186
|
swarph board cards say <id> --content "…" [--to <peer>] [--kind fyi|question|answer|status|unblock]
|
|
187
187
|
swarph board cards ask <id> <holder> "<what>" [--timeout-hours N] # MINT an obligation:
|
|
188
188
|
# name who owes what as a ROW, not a sentence (#307)
|
|
189
|
+
swarph board obligations close <id> --outcome <pass|fail|cannot_evaluate> --evidence "what you observed"
|
|
189
190
|
```
|
|
190
191
|
|
|
191
192
|
`--project` accepts a numeric id **or** a slug (resolved via `projects list`). Note: card creation always starts at `proposed` (the gateway has no stage-on-create) — use `cards move` to advance.
|
|
@@ -138,6 +138,7 @@ swarph board cards thread <id> [--limit N] # the card's conversation (a card
|
|
|
138
138
|
swarph board cards say <id> --content "…" [--to <peer>] [--kind fyi|question|answer|status|unblock]
|
|
139
139
|
swarph board cards ask <id> <holder> "<what>" [--timeout-hours N] # MINT an obligation:
|
|
140
140
|
# name who owes what as a ROW, not a sentence (#307)
|
|
141
|
+
swarph board obligations close <id> --outcome <pass|fail|cannot_evaluate> --evidence "what you observed"
|
|
141
142
|
```
|
|
142
143
|
|
|
143
144
|
`--project` accepts a numeric id **or** a slug (resolved via `projects list`). Note: card creation always starts at `proposed` (the gateway has no stage-on-create) — use `cards move` to advance.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "swarph-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.46.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`, 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" }
|
|
@@ -284,9 +284,10 @@ def _format_ask(d) -> str:
|
|
|
284
284
|
return (f"obligation #{d.get('id')} on card #{d.get('card_id')}: "
|
|
285
285
|
f"{d.get('holder')} owes it, status={d.get('status')}, {deadline}\n"
|
|
286
286
|
f" {falsifier}\n"
|
|
287
|
-
f" thread {d.get('thread_uuid')} —
|
|
288
|
-
f"
|
|
289
|
-
f"
|
|
287
|
+
f" thread {d.get('thread_uuid')} — work lands IN that thread; CLOSE "
|
|
288
|
+
f"it with `swarph board obligations close {d.get('id')}` plus an "
|
|
289
|
+
f"outcome and evidence; a plain `mesh send` cannot close it "
|
|
290
|
+
f"(#509/#562 — the VERB is the mechanism, the thread is where it lands)")
|
|
290
291
|
|
|
291
292
|
|
|
292
293
|
def _format_thread(data) -> str:
|
|
@@ -377,6 +378,25 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
377
378
|
cr = cards.add_parser("ready", help="flag a card ready-to-advance (move_ready) for the orchestrator")
|
|
378
379
|
cr.add_argument("id", type=int); cr.add_argument("--clear", action="store_true", help="unset move_ready")
|
|
379
380
|
cr.add_argument("--json", action="store_true"); _add_common(cr)
|
|
381
|
+
|
|
382
|
+
obl = top.add_parser(
|
|
383
|
+
"obligations",
|
|
384
|
+
help="card obligations — the close act (#562)").add_subparsers(
|
|
385
|
+
dest="command", required=True)
|
|
386
|
+
oc = obl.add_parser(
|
|
387
|
+
"close",
|
|
388
|
+
help="CLOSE an obligation: the DISTINCT ACT that records the check's "
|
|
389
|
+
"outcome plus the evidence observed. A thread reply does NOT "
|
|
390
|
+
"close an obligation (#562) — this does")
|
|
391
|
+
oc.add_argument("id", type=int, help="the obligation id")
|
|
392
|
+
oc.add_argument("--outcome", required=True,
|
|
393
|
+
choices=("pass", "fail", "cannot_evaluate"),
|
|
394
|
+
help="the accept check's OUTCOME")
|
|
395
|
+
oc.add_argument("--evidence", required=True,
|
|
396
|
+
help="what you OBSERVED running the check — whitespace is "
|
|
397
|
+
"refused: whitespace evidence is the vibe-close this "
|
|
398
|
+
"endpoint exists to kill")
|
|
399
|
+
oc.add_argument("--json", action="store_true"); _add_common(oc)
|
|
380
400
|
return p
|
|
381
401
|
|
|
382
402
|
|
|
@@ -425,6 +445,35 @@ def run_board(argv: list[str]) -> int:
|
|
|
425
445
|
st, d = _post_json(f"{gw}/board/projects", _project_add_payload(self_name, args.slug, args.title, goal=args.goal), token)
|
|
426
446
|
return _out(st, d, lambda x: f"created project #{x.get('id')} ({x.get('slug')})", aj)
|
|
427
447
|
|
|
448
|
+
if args.group == "obligations":
|
|
449
|
+
if args.command == "close":
|
|
450
|
+
# #562's CLI half: the explicit close act. The gateway guards the
|
|
451
|
+
# identity (holder, creator, or board orchestrator) and refuses a
|
|
452
|
+
# second close; the CLI's job is to refuse what it can see —
|
|
453
|
+
# whitespace evidence — and to propagate the gateway's refusals
|
|
454
|
+
# with their detail intact.
|
|
455
|
+
evidence = args.evidence
|
|
456
|
+
if not evidence.strip():
|
|
457
|
+
print("swarph board obligations close: --evidence is "
|
|
458
|
+
"whitespace-only — the close act records what you "
|
|
459
|
+
"OBSERVED, and nothing was observed", file=sys.stderr)
|
|
460
|
+
return 2
|
|
461
|
+
st, d = _post_json(
|
|
462
|
+
f"{gw}/board/obligations/{args.id}/close",
|
|
463
|
+
{"outcome": args.outcome, "evidence": evidence}, token)
|
|
464
|
+
if st < 200 or st >= 300:
|
|
465
|
+
detail = d.get("detail", d) if isinstance(d, dict) else d
|
|
466
|
+
print(f"swarph board obligations close: gateway {st}: {_s(detail)}",
|
|
467
|
+
file=sys.stderr)
|
|
468
|
+
return 1
|
|
469
|
+
if aj:
|
|
470
|
+
print(json.dumps(d, indent=2))
|
|
471
|
+
else:
|
|
472
|
+
print(f"obligation #{_s(d.get('id', args.id))} CLOSED by "
|
|
473
|
+
f"{_s(d.get('closed_by', self_name))} — outcome: "
|
|
474
|
+
f"{_s(d.get('close_outcome', args.outcome))}")
|
|
475
|
+
return 0
|
|
476
|
+
|
|
428
477
|
if args.group == "cards":
|
|
429
478
|
if args.command == "list":
|
|
430
479
|
pid, err = _resolve_project(gw, token, args.project)
|
|
@@ -51,6 +51,8 @@ def _allow_unchanged() -> int:
|
|
|
51
51
|
|
|
52
52
|
def run_hook(stdin_text: Optional[str] = None) -> int:
|
|
53
53
|
raw = stdin_text if stdin_text is not None else sys.stdin.read()
|
|
54
|
+
# PS 5.1 native pipes prefix a (double) UTF-8 BOM; strip before parse.
|
|
55
|
+
raw = (raw or "").lstrip("\ufeff")
|
|
54
56
|
try:
|
|
55
57
|
payload = json.loads(raw or "{}")
|
|
56
58
|
except Exception:
|
|
@@ -265,7 +265,13 @@ def run_init(argv: list[str]) -> int:
|
|
|
265
265
|
# its own context); an operator running `swarph onboard` first mints the
|
|
266
266
|
# token into a process that discards it, and the cell's later self-register
|
|
267
267
|
# returns token_status=existing with nothing.
|
|
268
|
-
|
|
268
|
+
# Scoped as CONDITIONAL (#319 d): a standalone outsider has no mesh and
|
|
269
|
+
# cannot ratify — the mesh path is named for the user who IS joining one,
|
|
270
|
+
# not presented as the next thing everyone must do. The outsider's next
|
|
271
|
+
# step is the `ready:` line above (swarph spawn), which works with zero
|
|
272
|
+
# mesh.
|
|
273
|
+
print(f"next: if you're joining a mesh — from the CELL'S OWN context: "
|
|
274
|
+
f"`swarph mesh register --as {name}` "
|
|
269
275
|
f"(mints + stores its once-only per-peer token, mode 600); then a witness "
|
|
270
276
|
f"runs `swarph ratify {name}`. Status at any point: "
|
|
271
277
|
f"`swarph onboard --check {name}`.")
|
|
@@ -17,9 +17,11 @@ A repeat write of the same memory within ``_SUPPRESS_WINDOW`` is suppressed —
|
|
|
17
17
|
the timeline is append-only, not append-always. State is a tiny JSON file,
|
|
18
18
|
best-effort.
|
|
19
19
|
|
|
20
|
-
Failure-mode invariant: exit 0 on EVERY path,
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
Failure-mode invariant: exit 0 on EVERY path, never raise. The hook prints
|
|
21
|
+
nothing OF ITS OWN; the shared ``_log_via_gateway`` prints its one-line
|
|
22
|
+
"logged -> TIMELINE.md" on success (cursor-win, 2026-08-22: the invariant was
|
|
23
|
+
misdescribed, not violated). A hook that fails a tool result because the
|
|
24
|
+
timeline is unreachable inverts every priority this system has.
|
|
23
25
|
"""
|
|
24
26
|
|
|
25
27
|
from __future__ import annotations
|
|
@@ -108,7 +110,12 @@ def _mark_emitted(slug: str) -> None:
|
|
|
108
110
|
|
|
109
111
|
|
|
110
112
|
def _cell() -> str:
|
|
111
|
-
|
|
113
|
+
# SWARPH_SELF outranks SWARPH_CELL — the house order (#332: a self-identity
|
|
114
|
+
# statement outranks an ambient one), and the live reason is #538: psmux
|
|
115
|
+
# leaks SWARPH_CELL from the spawning environment, so a CELL-first order
|
|
116
|
+
# posts this cell's highlight under ANOTHER cell's name and token
|
|
117
|
+
# (measured by cursor-win on the Windows membrane, 2026-08-22).
|
|
118
|
+
return (os.environ.get("SWARPH_SELF") or os.environ.get("SWARPH_CELL")
|
|
112
119
|
or socket.gethostname())
|
|
113
120
|
|
|
114
121
|
|
|
@@ -117,7 +124,10 @@ def run_memory_emit_hook(argv: list[str] | None = None) -> int:
|
|
|
117
124
|
raw = ""
|
|
118
125
|
if not sys.stdin.isatty():
|
|
119
126
|
raw = sys.stdin.read()
|
|
120
|
-
|
|
127
|
+
# PS 5.1 native pipes prefix a (double) UTF-8 BOM — json.loads raises,
|
|
128
|
+
# and the failure invariant converts that into an INVISIBLE no-op
|
|
129
|
+
# (measured by cursor-win, 2026-08-22). Strip before parse.
|
|
130
|
+
payload = json.loads(raw.lstrip("\ufeff")) if raw.strip() else {}
|
|
121
131
|
except Exception:
|
|
122
132
|
return 0
|
|
123
133
|
|
|
@@ -28,6 +28,8 @@ import sys
|
|
|
28
28
|
import time
|
|
29
29
|
import urllib.error
|
|
30
30
|
import urllib.parse
|
|
31
|
+
|
|
32
|
+
import swarph_cli
|
|
31
33
|
import urllib.request
|
|
32
34
|
from collections import deque
|
|
33
35
|
from collections.abc import MutableMapping
|
|
@@ -82,7 +84,8 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
82
84
|
_add_common(send)
|
|
83
85
|
|
|
84
86
|
reply = sub.add_parser(
|
|
85
|
-
"reply", help="reply to a DM IN ITS OWN THREAD (
|
|
87
|
+
"reply", help="reply to a DM IN ITS OWN THREAD (does NOT close the "
|
|
88
|
+
"obligation — the close act is `board obligations close`, #562)")
|
|
86
89
|
reply.add_argument("message_id", type=int, help="the DM being replied to")
|
|
87
90
|
reply.add_argument("--kind", default="answer",
|
|
88
91
|
help="message kind (default: answer). NOT gated — a reply is "
|
|
@@ -136,8 +139,42 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
136
139
|
action="store_true",
|
|
137
140
|
help="allow register when a local per-peer token file already exists",
|
|
138
141
|
)
|
|
142
|
+
register.add_argument(
|
|
143
|
+
"--replace",
|
|
144
|
+
action="store_true",
|
|
145
|
+
help="REPLACE the advertised capability blob with exactly the "
|
|
146
|
+
"--capability keys given (sends full=true). Default is MERGE: "
|
|
147
|
+
"the peer's currently-registered keys are read first and "
|
|
148
|
+
"re-submitted, so updating one field does not destroy the rest "
|
|
149
|
+
"(#124).",
|
|
150
|
+
)
|
|
139
151
|
_add_common(register)
|
|
140
152
|
|
|
153
|
+
peers = sub.add_parser(
|
|
154
|
+
"peers",
|
|
155
|
+
help="list registered peers with their reported swarph_cli_version; "
|
|
156
|
+
"--stale-than X names only peers REPORTING older than X, and "
|
|
157
|
+
"names the UNREPORTED separately (#535)",
|
|
158
|
+
)
|
|
159
|
+
peers.add_argument(
|
|
160
|
+
"--stale-than", default=None, metavar="VERSION",
|
|
161
|
+
help="list only peers whose reported swarph_cli_version is older "
|
|
162
|
+
"than VERSION; peers with no reported version are named "
|
|
163
|
+
"UNREPORTED, never counted as current",
|
|
164
|
+
)
|
|
165
|
+
peers.add_argument(
|
|
166
|
+
"--as", dest="self_name", default=None,
|
|
167
|
+
help="optional peer identity. IF OMITTED the ambient shared token "
|
|
168
|
+
"(MESH_GATEWAY_TOKEN) is used — a registry read does not require "
|
|
169
|
+
"a self identity. Pass --as to use a stored per-peer token instead.",
|
|
170
|
+
)
|
|
171
|
+
peers.add_argument(
|
|
172
|
+
"--gateway",
|
|
173
|
+
default=os.environ.get("MESH_GATEWAY_URL", _DEFAULT_GATEWAY),
|
|
174
|
+
help="mesh-gateway base URL",
|
|
175
|
+
)
|
|
176
|
+
peers.add_argument("--token-file", default=None, help="explicit bearer token file")
|
|
177
|
+
|
|
141
178
|
sidecar = sub.add_parser("sidecar", help="poll inbox and wake a tmux cell")
|
|
142
179
|
sidecar.add_argument("--tmux-target", default=None, help="tmux target pane")
|
|
143
180
|
sidecar.add_argument("--state-dir", default=None, help="state directory")
|
|
@@ -526,12 +563,15 @@ def _find_inbox_message(gateway: str, token: str, self_name: str,
|
|
|
526
563
|
|
|
527
564
|
|
|
528
565
|
def _run_reply(args: argparse.Namespace) -> int:
|
|
529
|
-
"""Reply to a DM IN ITS THREAD
|
|
566
|
+
"""Reply to a DM IN ITS THREAD. Since #562 this does NOT close the
|
|
567
|
+
obligation — closing is the distinct act `board obligations close`
|
|
568
|
+
(POST /board/obligations/{id}/close); the reply path only REMINDS via
|
|
569
|
+
the open_obligations field.
|
|
530
570
|
|
|
531
571
|
>>> A REPLY IS UNIVERSAL ACROSS KINDS, DELIBERATELY. <<< The spec's constraint:
|
|
532
572
|
not gated by kind. Gating would mean a peer could owe you an answer on a DM whose
|
|
533
|
-
kind nobody thought to allow, and the obligation would be
|
|
534
|
-
product — the exact "waiting on a seat" shape #307 exists to kill.
|
|
573
|
+
kind nobody thought to allow, and the obligation would be unanswerable through
|
|
574
|
+
the product — the exact "waiting on a seat" shape #307 exists to kill.
|
|
535
575
|
|
|
536
576
|
>>> AND A THREADLESS REPLY MUST NOT REPORT THE SAME LINE AS A THREADED ONE. <<<
|
|
537
577
|
Most DMs carry no thread. Refusing them would make this verb useless for the
|
|
@@ -776,11 +816,43 @@ def _run_register(args: argparse.Namespace) -> int:
|
|
|
776
816
|
for spec in args.capability:
|
|
777
817
|
key, value = _parse_capability(spec)
|
|
778
818
|
caps[key] = value
|
|
819
|
+
# >>> #124, CLIENT HALF: MERGE OVER THE STORED BLOB BY DEFAULT. <<< The
|
|
820
|
+
# gateway replaces capabilities wholesale, so submitting only the keys
|
|
821
|
+
# this invocation cares about silently destroys the rest (gemini-
|
|
822
|
+
# researcher's role/agent_type, lost to a model_default update). Read
|
|
823
|
+
# the peer's currently-registered blob and re-submit it merged:
|
|
824
|
+
# submitted keys override, stored-only keys survive. --replace opts into
|
|
825
|
+
# the deliberate wholesale replace (full=true, which the gateway's #124
|
|
826
|
+
# guard accepts without its 409). A register that CANNOT READ the
|
|
827
|
+
# registry — first registration, gateway unreachable — proceeds with
|
|
828
|
+
# what it has: the read must never BLOCK the write; there is nothing to
|
|
829
|
+
# destroy on a row that does not exist yet.
|
|
830
|
+
gateway = args.gateway.rstrip("/")
|
|
831
|
+
stored_caps: dict = {}
|
|
832
|
+
found_stored_caps = False
|
|
833
|
+
if not args.replace:
|
|
834
|
+
gstatus, gpayload = _http_get_json(f"{gateway}/peers/{self_name}", token)
|
|
835
|
+
if gstatus == 200 and isinstance(gpayload.get("capabilities"), dict):
|
|
836
|
+
stored_caps = gpayload["capabilities"]
|
|
837
|
+
found_stored_caps = True
|
|
838
|
+
if args.replace:
|
|
839
|
+
merged = caps or {"can_claim_tasks": True}
|
|
840
|
+
elif found_stored_caps or caps:
|
|
841
|
+
merged = {**stored_caps, **caps}
|
|
842
|
+
else:
|
|
843
|
+
merged = {"can_claim_tasks": True}
|
|
844
|
+
# #535: report what version this cell RUNS, on every register — a
|
|
845
|
+
# SUBMITTED key, so the merge refreshes it on re-register and preserves
|
|
846
|
+
# it across partial updates. Without a stored version field, "who is
|
|
847
|
+
# stale" is unanswerable and "the fix propagates" is unfalsifiable.
|
|
848
|
+
merged["swarph_cli_version"] = swarph_cli.__version__
|
|
779
849
|
body = {
|
|
780
850
|
"name": self_name,
|
|
781
851
|
"url": args.url or f"http://{self_name}:8787",
|
|
782
|
-
"capabilities":
|
|
852
|
+
"capabilities": merged,
|
|
783
853
|
}
|
|
854
|
+
if args.replace:
|
|
855
|
+
body["full"] = True
|
|
784
856
|
status, payload = _post_json(
|
|
785
857
|
f"{args.gateway.rstrip('/')}/peers/register",
|
|
786
858
|
body,
|
|
@@ -806,6 +878,96 @@ def _run_register(args: argparse.Namespace) -> int:
|
|
|
806
878
|
return 0
|
|
807
879
|
|
|
808
880
|
|
|
881
|
+
def _version_tuple(text: object) -> Optional[tuple[int, ...]]:
|
|
882
|
+
"""Parse "0.45.1" into (0, 45, 1). Returns None for anything that is not
|
|
883
|
+
a dotted-numeric version — an unparseable version is UNMEASURABLE, not
|
|
884
|
+
current (#535's falsifier clause)."""
|
|
885
|
+
if not isinstance(text, str):
|
|
886
|
+
return None
|
|
887
|
+
parts = text.strip().split(".")
|
|
888
|
+
if not parts or any(not p.isdigit() for p in parts):
|
|
889
|
+
return None
|
|
890
|
+
return tuple(int(p) for p in parts)
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
def _version_is_stale(reported: object, cutoff: str) -> Optional[bool]:
|
|
894
|
+
"""True = reported is older than cutoff; False = current-or-newer;
|
|
895
|
+
None = unmeasurable (missing/unparseable). Numeric tuple compare, so
|
|
896
|
+
0.45.10 > 0.45.9 — lexicographic would call 0.9.0 newer than 0.10.0."""
|
|
897
|
+
have, want = _version_tuple(reported), _version_tuple(cutoff)
|
|
898
|
+
if have is None or want is None:
|
|
899
|
+
return None
|
|
900
|
+
width = max(len(have), len(want))
|
|
901
|
+
return have + (0,) * (width - len(have)) < want + (0,) * (width - len(want))
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
def _run_peers(args: argparse.Namespace) -> int:
|
|
905
|
+
"""#535: answer "which peers run older than X" from the STORED field —
|
|
906
|
+
never by asking each cell out of band, never by assuming.
|
|
907
|
+
|
|
908
|
+
A registry READ does not require a self identity: an operator asking
|
|
909
|
+
"who is stale" on any box with the shared token is not acting AS a
|
|
910
|
+
cell. --as still works (its peer token outranks the ambient one)."""
|
|
911
|
+
from swarph_cli.tokens import resolve_token
|
|
912
|
+
resolution = resolve_token(args.self_name, args.token_file,
|
|
913
|
+
identity_is_explicit=bool(args.self_name))
|
|
914
|
+
if resolution is None:
|
|
915
|
+
print("swarph mesh peers: no token (set MESH_GATEWAY_TOKEN, pass "
|
|
916
|
+
"--token-file, or --as a peer with a stored token)",
|
|
917
|
+
file=sys.stderr)
|
|
918
|
+
return 2
|
|
919
|
+
token = resolution.token
|
|
920
|
+
gateway = args.gateway.rstrip("/")
|
|
921
|
+
status, payload = _http_get_json(f"{gateway}/peers", token)
|
|
922
|
+
if status < 200 or status >= 300:
|
|
923
|
+
detail = payload.get("detail", "<gateway error>") if isinstance(payload, dict) else "<gateway error>"
|
|
924
|
+
print(f"swarph mesh peers: gateway {status}: {detail}", file=sys.stderr)
|
|
925
|
+
return 1
|
|
926
|
+
peers = payload.get("peers") if isinstance(payload, dict) else None
|
|
927
|
+
if not isinstance(peers, list):
|
|
928
|
+
print("swarph mesh peers: unexpected /peers shape (no peer list)",
|
|
929
|
+
file=sys.stderr)
|
|
930
|
+
return 1
|
|
931
|
+
|
|
932
|
+
cutoff = args.stale_than
|
|
933
|
+
if cutoff is not None and _version_tuple(cutoff) is None:
|
|
934
|
+
print(f"swarph mesh peers: --stale-than {cutoff!r} is not a "
|
|
935
|
+
f"dotted-numeric version", file=sys.stderr)
|
|
936
|
+
return 2
|
|
937
|
+
|
|
938
|
+
stale, unreported, current = [], [], []
|
|
939
|
+
for p in peers:
|
|
940
|
+
if not isinstance(p, dict) or not isinstance(p.get("name"), str):
|
|
941
|
+
continue
|
|
942
|
+
caps = p.get("capabilities")
|
|
943
|
+
version = caps.get("swarph_cli_version") if isinstance(caps, dict) else None
|
|
944
|
+
version_is_valid = _version_tuple(version) is not None
|
|
945
|
+
verdict = _version_is_stale(version, cutoff) if cutoff else None
|
|
946
|
+
if cutoff and verdict is True:
|
|
947
|
+
stale.append((p["name"], version))
|
|
948
|
+
elif not version_is_valid:
|
|
949
|
+
unreported.append(p["name"])
|
|
950
|
+
else:
|
|
951
|
+
current.append((p["name"], version))
|
|
952
|
+
|
|
953
|
+
if cutoff:
|
|
954
|
+
for name, version in stale:
|
|
955
|
+
print(f"STALE {name} reports {version} (< {cutoff})")
|
|
956
|
+
for name in unreported:
|
|
957
|
+
# Named, never silently counted as current: absence of the field
|
|
958
|
+
# is unmeasurability, not health — the falsifier clause.
|
|
959
|
+
print(f"UNREPORTED {name} (no swarph_cli_version — unmeasurable, "
|
|
960
|
+
f"NOT known-current)")
|
|
961
|
+
print(f"{len(stale)} stale, {len(unreported)} unreported, "
|
|
962
|
+
f"{len(current)} current (cutoff {cutoff})")
|
|
963
|
+
else:
|
|
964
|
+
for name, version in current:
|
|
965
|
+
print(f"{name} {version or 'UNREPORTED'}")
|
|
966
|
+
for name in unreported:
|
|
967
|
+
print(f"{name} UNREPORTED")
|
|
968
|
+
return 0
|
|
969
|
+
|
|
970
|
+
|
|
809
971
|
def _default_sidecar_state_dir(self_name: str) -> Path:
|
|
810
972
|
# NOT renamed to .../monitor when `swarph monitor` shipped (card #122).
|
|
811
973
|
# `swarph init` pins this exact path as the cell's cursor_path and the
|
|
@@ -1937,6 +2099,8 @@ def run_mesh(argv: list[str]) -> int:
|
|
|
1937
2099
|
return _run_inbox(args)
|
|
1938
2100
|
if args.command == "register":
|
|
1939
2101
|
return _run_register(args)
|
|
2102
|
+
if args.command == "peers":
|
|
2103
|
+
return _run_peers(args)
|
|
1940
2104
|
if args.command == "sidecar":
|
|
1941
2105
|
return _run_sidecar(args)
|
|
1942
2106
|
parser.error(f"unknown command: {args.command}")
|
|
@@ -427,7 +427,7 @@ def _resolve_token(token_file_arg: Optional[str], *,
|
|
|
427
427
|
" To stand up YOUR OWN instead, no token required:\n"
|
|
428
428
|
" pip install 'swarph-cli[gateway]'\n"
|
|
429
429
|
" swarph gateway serve --port 8788 --db ~/.swarph/mesh.db\n"
|
|
430
|
-
" swarph init <name>
|
|
430
|
+
" swarph init <name> --provider <provider> # scaffold a cell\n"
|
|
431
431
|
)
|
|
432
432
|
|
|
433
433
|
|