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