swarph-cli 0.16.0__tar.gz → 0.18.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/PKG-INFO +33 -1
  2. swarph_cli-0.16.0/src/swarph_cli.egg-info/PKG-INFO → swarph_cli-0.18.0/README.md +28 -40
  3. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/pyproject.toml +10 -1
  4. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/__init__.py +1 -1
  5. swarph_cli-0.18.0/src/swarph_cli/commands/brain.py +74 -0
  6. swarph_cli-0.18.0/src/swarph_cli/commands/service.py +74 -0
  7. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/main.py +2 -0
  8. swarph_cli-0.18.0/src/swarph_cli/service/__init__.py +0 -0
  9. swarph_cli-0.18.0/src/swarph_cli/service/app.py +53 -0
  10. swarph_cli-0.18.0/src/swarph_cli/service/providers.py +87 -0
  11. swarph_cli-0.16.0/README.md → swarph_cli-0.18.0/src/swarph_cli.egg-info/PKG-INFO +72 -0
  12. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli.egg-info/SOURCES.txt +9 -0
  13. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli.egg-info/requires.txt +5 -0
  14. swarph_cli-0.18.0/tests/test_brain_command.py +50 -0
  15. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_caller_meta_guard.py +8 -6
  16. swarph_cli-0.18.0/tests/test_service_app.py +57 -0
  17. swarph_cli-0.18.0/tests/test_service_command.py +53 -0
  18. swarph_cli-0.18.0/tests/test_service_providers.py +97 -0
  19. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/LICENSE +0 -0
  20. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/setup.cfg +0 -0
  21. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/caller.py +0 -0
  22. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/capture/__init__.py +0 -0
  23. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/capture/harden.py +0 -0
  24. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/capture/lineage.py +0 -0
  25. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/capture/liveness.py +0 -0
  26. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/capture/manifest.py +0 -0
  27. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/capture/paths.py +0 -0
  28. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/capture/verify.py +0 -0
  29. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/cell.py +0 -0
  30. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/__init__.py +0 -0
  31. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/add.py +0 -0
  32. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/brain_ask.py +0 -0
  33. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/cell.py +0 -0
  34. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/chat.py +0 -0
  35. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/compress.py +0 -0
  36. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/daemon.py +0 -0
  37. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/gateway.py +0 -0
  38. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/highlight.py +0 -0
  39. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/hook_output.py +0 -0
  40. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/hooks.py +0 -0
  41. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/import_session.py +0 -0
  42. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/init.py +0 -0
  43. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/install_hook.py +0 -0
  44. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/mcp_server.py +0 -0
  45. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/memory_sync.py +0 -0
  46. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/mesh.py +0 -0
  47. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/onboard.py +0 -0
  48. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
  49. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/ratify.py +0 -0
  50. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/security.py +0 -0
  51. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/spawn.py +0 -0
  52. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/commands/watchdog.py +0 -0
  53. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/compress/__init__.py +0 -0
  54. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/compress/levers.py +0 -0
  55. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/compress/marker.py +0 -0
  56. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/compress/verify.py +0 -0
  57. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/gateway/__init__.py +0 -0
  58. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
  59. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
  60. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/gateway/schema.sql +0 -0
  61. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/gateway/server.py +0 -0
  62. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/gateway/services_control.py +0 -0
  63. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/parsers/__init__.py +0 -0
  64. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/parsers/claude.py +0 -0
  65. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  66. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  67. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  68. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  69. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  70. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
  71. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_add_security_gate.py +0 -0
  72. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_artifact_add.py +0 -0
  73. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_artifact_hash.py +0 -0
  74. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_artifact_lib.py +0 -0
  75. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_artifact_mcp.py +0 -0
  76. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_artifact_skill.py +0 -0
  77. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_artifact_tool.py +0 -0
  78. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_artifact_uri.py +0 -0
  79. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_brain_ask_command.py +0 -0
  80. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_capture_lineage.py +0 -0
  81. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_capture_liveness.py +0 -0
  82. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_capture_manifest.py +0 -0
  83. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_capture_paths.py +0 -0
  84. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_capture_security.py +0 -0
  85. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_cell_command_dispatch.py +0 -0
  86. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_cell_harden.py +0 -0
  87. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_cell_loader.py +0 -0
  88. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_cell_verify.py +0 -0
  89. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_chat_command.py +0 -0
  90. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_claude_parser.py +0 -0
  91. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_claude_tmux_template.py +0 -0
  92. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_compress_caller_convention.py +0 -0
  93. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_compress_command.py +0 -0
  94. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_compress_levers.py +0 -0
  95. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_compress_marker.py +0 -0
  96. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_compress_verify.py +0 -0
  97. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_daemon_command.py +0 -0
  98. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_feature_to_uri.py +0 -0
  99. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_gateway_command.py +0 -0
  100. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_highlight_command.py +0 -0
  101. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_hook_output.py +0 -0
  102. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_hooks_add.py +0 -0
  103. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_hooks_bundle.py +0 -0
  104. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_hooks_init.py +0 -0
  105. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_hooks_lifecycle.py +0 -0
  106. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_hooks_merge.py +0 -0
  107. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_import_command.py +0 -0
  108. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_init_command.py +0 -0
  109. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_install_hook.py +0 -0
  110. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_main.py +0 -0
  111. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_mcp_server.py +0 -0
  112. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_memory_sync.py +0 -0
  113. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_mesh_command.py +0 -0
  114. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_mesh_sidecar.py +0 -0
  115. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_onboard_command.py +0 -0
  116. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_protocol_handler.py +0 -0
  117. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_ratify_command.py +0 -0
  118. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_repl_caller_convention.py +0 -0
  119. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_security.py +0 -0
  120. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_session_path_role_gate.py +0 -0
  121. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_smoke_chat.py +0 -0
  122. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_smoke_one_shot.py +0 -0
  123. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_smoke_phase_5_5.py +0 -0
  124. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_spawn_command.py +0 -0
  125. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_spawn_live_pin.py +0 -0
  126. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_spawn_mitosis_lineage.py +0 -0
  127. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_spawn_tmux_session.py +0 -0
  128. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_spawn_windows_relaunch.py +0 -0
  129. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_version_consistency.py +0 -0
  130. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_watchdog.py +0 -0
  131. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_watchdog_dm_wake.py +0 -0
  132. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  133. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  134. {swarph_cli-0.16.0 → swarph_cli-0.18.0}/tests/test_watchdog_model_rung.py +0 -0
  135. {swarph_cli-0.16.0 → swarph_cli-0.18.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.16.0
3
+ Version: 0.18.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
@@ -36,6 +36,10 @@ Requires-Dist: fastapi>=0.115; extra == "gateway"
36
36
  Requires-Dist: uvicorn[standard]>=0.32; extra == "gateway"
37
37
  Requires-Dist: pydantic>=2.9; extra == "gateway"
38
38
  Requires-Dist: croniter>=6.2; extra == "gateway"
39
+ Provides-Extra: service
40
+ Requires-Dist: fastapi>=0.115; extra == "service"
41
+ Requires-Dist: uvicorn[standard]>=0.32; extra == "service"
42
+ Requires-Dist: pydantic>=2.9; extra == "service"
39
43
  Dynamic: license-file
40
44
 
41
45
  # swarph-cli
@@ -65,7 +69,9 @@ This is one of three repos in the v0.3.x architecture:
65
69
  swarph "prompt" one-shot against any provider (claude/openai/gemini/deepseek/grok)
66
70
  swarph chat interactive REPL — multi-turn, slash commands, live provider switch
67
71
  swarph brain-ask "<q>" search the swarph-brain (gbrain) memory — $0 cited answer or raw chunks
72
+ swarph brain serve run the gbrain HTTP brain server (the $0 semantic memory)
68
73
  swarph gateway serve run the bundled mesh-gateway server (the mesh's coordination/DM hub)
74
+ swarph service serve stand up a $0 subscription-LLM HTTP lane (claude/codex/gemini)
69
75
  swarph highlight "<x>" log a highlight to the shared git-backed swarph timeline
70
76
  swarph spawn <role> launch a long-lived agent session as a named mesh cell
71
77
  swarph daemon foreground inbox-drain loop (the mesh doorbell)
@@ -92,6 +98,18 @@ $ swarph brain-ask --no-synth --limit 3 "cross-vendor fallback governor order"
92
98
 
93
99
  Config is via env, mirroring `swarph mesh`'s token model: `GBRAIN_MCP_URL` or `SWARPH_BRAIN_MCP` (gbrain endpoint; defaults to `http://127.0.0.1:8792/mcp`), and the read token resolved `--token-file` > `GBRAIN_TOKEN` > `SWARPH_BRAIN_TOKEN` > the mesh per-peer token (`~/.config/swarph/<self>.peer_token`) — so the mesh peer token doubles as the gbrain read token. Optional `SWARPH_FACADE` / `SWARPH_FACADE_TOKEN` enable the $0 cited-synthesis pass; without them, brain-ask prints the raw ranked chunks.
94
100
 
101
+ ### `swarph brain serve`
102
+
103
+ `brain-ask` *searches* a running brain; **`brain serve`** *runs* one. gbrain (the sovereign $0 semantic-memory server) is an external binary — this verb is a thin launcher that applies the swarph-blessed defaults and replaces itself with `gbrain serve`:
104
+
105
+ ```bash
106
+ $ swarph brain serve # 127.0.0.1:8792, 1-year token TTL
107
+ $ swarph brain serve --bind 100.x.y.z --port 8792 # expose on a tailnet IP
108
+ $ swarph brain serve --gbrain-bin /opt/gbrain # explicit binary (else GBRAIN_BIN / PATH)
109
+ ```
110
+
111
+ Defaults are loopback bind (expose a tailnet IP explicitly) and a **1-year token TTL** — a short TTL silently 401s long-lived mesh cells, a lesson learned the hard way. If `gbrain` isn't on `PATH` the verb prints an install hint and exits `2`; `brain-ask` still queries any already-running brain.
112
+
95
113
  ### `swarph gateway`
96
114
 
97
115
  The **mesh-gateway** is the coordination/DM server every other verb talks to — the peer registry, the DM inbox/outbox, feature aggregation with allowlist + caps, and lane/service control. It used to be a separate deployment; it's now bundled, so any host can stand one up with a single command. It pairs with the client verbs (`mesh`, `spawn`, `daemon`, `watchdog`) and with `brain-ask`/gbrain to form the mesh's coordination plane.
@@ -106,6 +124,20 @@ $ swarph gateway serve --host 100.x.y.z --port 8788 # expose on a tailnet IP
106
124
 
107
125
  `--token` sets the bearer (`MESH_GATEWAY_TOKEN`) for the served process; omit it and an existing env token is used, or a fresh one is minted and printed once so you can hand it to the mesh cells. `--db PATH` points the gateway at a specific SQLite file (`MESH_DB_PATH`). Run without the extra and the verb prints a `pip install "swarph-cli[gateway]"` hint and exits.
108
126
 
127
+ ### `swarph service`
128
+
129
+ Turn a *subscription* LLM CLI into a **$0 HTTP lane** the mesh can call — `swarph service serve --provider <claude|codex|gemini>` runs a small FastAPI app exposing `POST /delegate` (bearer-authed) + `GET /health`:
130
+
131
+ ```bash
132
+ $ pip install "swarph-cli[service]"
133
+ $ swarph service serve --provider claude # 127.0.0.1:8799, mints+prints a token
134
+ $ swarph service serve --provider gemini --host 100.x.y.z --port 8799 # expose on a tailnet IP
135
+ $ curl -s localhost:8799/delegate -H "Authorization: Bearer $TOK" \
136
+ -d '{"prompt":"summarise this in one line: ..."}' # -> {"text": "...", "cost_usd": 0.0}
137
+ ```
138
+
139
+ The load-bearing piece is **billing-scrub**: the subprocess env has every known provider API-key var (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, …) stripped, so the wrapped CLI can *only* use its $0 subscription auth — it can never silently fall back to metered API billing. Default loopback bind (expose a tailnet IP explicitly); the bearer is `--token` > `SWARPH_SERVICE_TOKEN` > a freshly minted+printed token. Note the `gemini` (agy) lane reads the prompt from argv only and hard-caps its length (~4 KB) — very long prompts are rejected, not truncated. Run without the extra and the verb prints a `pip install "swarph-cli[service]"` hint and exits.
140
+
109
141
  ### `swarph highlight`
110
142
 
111
143
  The swarph **timeline** is an append-only, multi-author `TIMELINE.md` — a git-backed continuous-learning log where every cell's highlights converge into one file. `swarph highlight` is the mechanics; *you* (or your agent) decide what's worth logging and which memory it links to.
@@ -1,43 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: swarph-cli
3
- Version: 0.16.0
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
- Author: Pierre Samson, Claude Opus
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/BrainSurfing-tech/swarph-cli
8
- Project-URL: Source, https://github.com/BrainSurfing-tech/swarph-cli
9
- Project-URL: Substrate, https://github.com/BrainSurfing-tech/swarph-mesh
10
- Project-URL: Spec, https://github.com/darw007d/hedge-fund-mcp/blob/main/research/swarph_cli/PLAN.md
11
- Keywords: swarph,llm,cli,mesh,gemini,claude,deepseek
12
- Classifier: Development Status :: 3 - Alpha
13
- Classifier: Environment :: Console
14
- Classifier: Intended Audience :: Developers
15
- Classifier: License :: OSI Approved :: MIT License
16
- Classifier: Operating System :: POSIX :: Linux
17
- Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.10
19
- Classifier: Programming Language :: Python :: 3.11
20
- Classifier: Programming Language :: Python :: 3.12
21
- Classifier: Programming Language :: Python :: 3.13
22
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
- Classifier: Topic :: Utilities
24
- Requires-Python: >=3.10
25
- Description-Content-Type: text/markdown
26
- License-File: LICENSE
27
- Requires-Dist: swarph-mesh>=0.5.0
28
- Requires-Dist: swarph-shared>=0.3.3
29
- Requires-Dist: PyYAML>=6.0
30
- Provides-Extra: dev
31
- Requires-Dist: pytest>=7.0; extra == "dev"
32
- Provides-Extra: mcp
33
- Requires-Dist: mcp>=1.0; extra == "mcp"
34
- Provides-Extra: gateway
35
- Requires-Dist: fastapi>=0.115; extra == "gateway"
36
- Requires-Dist: uvicorn[standard]>=0.32; extra == "gateway"
37
- Requires-Dist: pydantic>=2.9; extra == "gateway"
38
- Requires-Dist: croniter>=6.2; extra == "gateway"
39
- Dynamic: license-file
40
-
41
1
  # swarph-cli
42
2
 
43
3
  **One CLI for every LLM.** Drive Claude, GPT, Gemini, DeepSeek, and Grok from a single binary — then connect them into a coordinating *mesh* where agents from different vendors talk to each other through a shared protocol, each staying itself.
@@ -65,7 +25,9 @@ This is one of three repos in the v0.3.x architecture:
65
25
  swarph "prompt" one-shot against any provider (claude/openai/gemini/deepseek/grok)
66
26
  swarph chat interactive REPL — multi-turn, slash commands, live provider switch
67
27
  swarph brain-ask "<q>" search the swarph-brain (gbrain) memory — $0 cited answer or raw chunks
28
+ swarph brain serve run the gbrain HTTP brain server (the $0 semantic memory)
68
29
  swarph gateway serve run the bundled mesh-gateway server (the mesh's coordination/DM hub)
30
+ swarph service serve stand up a $0 subscription-LLM HTTP lane (claude/codex/gemini)
69
31
  swarph highlight "<x>" log a highlight to the shared git-backed swarph timeline
70
32
  swarph spawn <role> launch a long-lived agent session as a named mesh cell
71
33
  swarph daemon foreground inbox-drain loop (the mesh doorbell)
@@ -92,6 +54,18 @@ $ swarph brain-ask --no-synth --limit 3 "cross-vendor fallback governor order"
92
54
 
93
55
  Config is via env, mirroring `swarph mesh`'s token model: `GBRAIN_MCP_URL` or `SWARPH_BRAIN_MCP` (gbrain endpoint; defaults to `http://127.0.0.1:8792/mcp`), and the read token resolved `--token-file` > `GBRAIN_TOKEN` > `SWARPH_BRAIN_TOKEN` > the mesh per-peer token (`~/.config/swarph/<self>.peer_token`) — so the mesh peer token doubles as the gbrain read token. Optional `SWARPH_FACADE` / `SWARPH_FACADE_TOKEN` enable the $0 cited-synthesis pass; without them, brain-ask prints the raw ranked chunks.
94
56
 
57
+ ### `swarph brain serve`
58
+
59
+ `brain-ask` *searches* a running brain; **`brain serve`** *runs* one. gbrain (the sovereign $0 semantic-memory server) is an external binary — this verb is a thin launcher that applies the swarph-blessed defaults and replaces itself with `gbrain serve`:
60
+
61
+ ```bash
62
+ $ swarph brain serve # 127.0.0.1:8792, 1-year token TTL
63
+ $ swarph brain serve --bind 100.x.y.z --port 8792 # expose on a tailnet IP
64
+ $ swarph brain serve --gbrain-bin /opt/gbrain # explicit binary (else GBRAIN_BIN / PATH)
65
+ ```
66
+
67
+ Defaults are loopback bind (expose a tailnet IP explicitly) and a **1-year token TTL** — a short TTL silently 401s long-lived mesh cells, a lesson learned the hard way. If `gbrain` isn't on `PATH` the verb prints an install hint and exits `2`; `brain-ask` still queries any already-running brain.
68
+
95
69
  ### `swarph gateway`
96
70
 
97
71
  The **mesh-gateway** is the coordination/DM server every other verb talks to — the peer registry, the DM inbox/outbox, feature aggregation with allowlist + caps, and lane/service control. It used to be a separate deployment; it's now bundled, so any host can stand one up with a single command. It pairs with the client verbs (`mesh`, `spawn`, `daemon`, `watchdog`) and with `brain-ask`/gbrain to form the mesh's coordination plane.
@@ -106,6 +80,20 @@ $ swarph gateway serve --host 100.x.y.z --port 8788 # expose on a tailnet IP
106
80
 
107
81
  `--token` sets the bearer (`MESH_GATEWAY_TOKEN`) for the served process; omit it and an existing env token is used, or a fresh one is minted and printed once so you can hand it to the mesh cells. `--db PATH` points the gateway at a specific SQLite file (`MESH_DB_PATH`). Run without the extra and the verb prints a `pip install "swarph-cli[gateway]"` hint and exits.
108
82
 
83
+ ### `swarph service`
84
+
85
+ Turn a *subscription* LLM CLI into a **$0 HTTP lane** the mesh can call — `swarph service serve --provider <claude|codex|gemini>` runs a small FastAPI app exposing `POST /delegate` (bearer-authed) + `GET /health`:
86
+
87
+ ```bash
88
+ $ pip install "swarph-cli[service]"
89
+ $ swarph service serve --provider claude # 127.0.0.1:8799, mints+prints a token
90
+ $ swarph service serve --provider gemini --host 100.x.y.z --port 8799 # expose on a tailnet IP
91
+ $ curl -s localhost:8799/delegate -H "Authorization: Bearer $TOK" \
92
+ -d '{"prompt":"summarise this in one line: ..."}' # -> {"text": "...", "cost_usd": 0.0}
93
+ ```
94
+
95
+ The load-bearing piece is **billing-scrub**: the subprocess env has every known provider API-key var (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, …) stripped, so the wrapped CLI can *only* use its $0 subscription auth — it can never silently fall back to metered API billing. Default loopback bind (expose a tailnet IP explicitly); the bearer is `--token` > `SWARPH_SERVICE_TOKEN` > a freshly minted+printed token. Note the `gemini` (agy) lane reads the prompt from argv only and hard-caps its length (~4 KB) — very long prompts are rejected, not truncated. Run without the extra and the verb prints a `pip install "swarph-cli[service]"` hint and exits.
96
+
109
97
  ### `swarph highlight`
110
98
 
111
99
  The swarph **timeline** is an append-only, multi-author `TIMELINE.md` — a git-backed continuous-learning log where every cell's highlights converge into one file. `swarph highlight` is the mechanics; *you* (or your agent) decide what's worth logging and which memory it links to.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.16.0"
7
+ version = "0.18.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" }
@@ -70,6 +70,15 @@ gateway = [
70
70
  "pydantic>=2.9",
71
71
  "croniter>=6.2",
72
72
  ]
73
+ # `swarph service serve` stands up a $0 subscription-LLM HTTP lane. Same
74
+ # FastAPI/uvicorn stack as the gateway, behind its own extra so the core
75
+ # client paths stay dependency-light; the verb prints a
76
+ # `pip install swarph-cli[service]` hint when these are absent.
77
+ service = [
78
+ "fastapi>=0.115",
79
+ "uvicorn[standard]>=0.32",
80
+ "pydantic>=2.9",
81
+ ]
73
82
 
74
83
  [project.scripts]
75
84
  swarph = "swarph_cli.main:main"
@@ -16,6 +16,6 @@ The architecture splits CLI from substrate so:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.16.0"
19
+ __version__ = "0.18.0"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -0,0 +1,74 @@
1
+ """``swarph brain`` — run the gbrain HTTP brain server (the $0 semantic memory).
2
+
3
+ ``brain-ask`` *searches* a running brain; ``brain serve`` *runs* one. gbrain
4
+ (the sovereign $0 semantic-memory server) is an external binary — this verb is a
5
+ thin launcher that applies the swarph-blessed defaults: loopback bind (expose a
6
+ tailnet IP explicitly), and a 1-year token TTL (a short TTL silently 401s
7
+ long-lived mesh cells — a lesson learned the hard way). It replaces the current
8
+ process with ``gbrain serve`` via ``os.execvp`` so signals/stdio pass straight
9
+ through.
10
+
11
+ Usage:
12
+ swarph brain serve [--port 8792] [--bind 127.0.0.1]
13
+ [--token-ttl 31536000] [--gbrain-bin PATH]
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import os
20
+ import shutil
21
+ import sys
22
+
23
+ _DEFAULT_PORT = 8792
24
+ _DEFAULT_BIND = "127.0.0.1"
25
+ _DEFAULT_TOKEN_TTL = 31536000 # 1 year — short TTLs silently 401 long-lived cells
26
+
27
+
28
+ def _find_gbrain(explicit) -> str | None:
29
+ """Resolve the gbrain binary: --gbrain-bin, else GBRAIN_BIN, else PATH."""
30
+ if explicit:
31
+ return explicit if (os.path.isfile(explicit) or shutil.which(explicit)) else None
32
+ env = os.environ.get("GBRAIN_BIN")
33
+ if env and (os.path.isfile(env) or shutil.which(env)):
34
+ return env
35
+ return shutil.which("gbrain")
36
+
37
+
38
+ def run_brain(argv: list) -> int:
39
+ p = argparse.ArgumentParser(
40
+ prog="swarph brain",
41
+ description="Run the gbrain HTTP brain server (the $0 semantic memory).")
42
+ sub = p.add_subparsers(dest="cmd")
43
+ serve = sub.add_parser("serve", help="run the gbrain HTTP brain server")
44
+ serve.add_argument("--port", type=int, default=_DEFAULT_PORT,
45
+ help=f"HTTP port (default {_DEFAULT_PORT})")
46
+ serve.add_argument("--bind", default=_DEFAULT_BIND,
47
+ help=f"bind address (default {_DEFAULT_BIND}; pass a tailnet "
48
+ "IP to expose on the mesh)")
49
+ serve.add_argument("--token-ttl", type=int, default=_DEFAULT_TOKEN_TTL,
50
+ help="minted-token lifetime in seconds (default 1 year — "
51
+ "short TTLs silently 401 long-lived mesh cells)")
52
+ serve.add_argument("--gbrain-bin", default=None,
53
+ help="path to the gbrain binary (else GBRAIN_BIN / PATH)")
54
+ args = p.parse_args(argv)
55
+
56
+ if args.cmd != "serve":
57
+ p.print_help()
58
+ return 0
59
+
60
+ gbin = _find_gbrain(args.gbrain_bin)
61
+ if not gbin:
62
+ print("swarph brain serve needs the `gbrain` binary on PATH (the sovereign "
63
+ "$0 semantic-memory server). Install it, or pass --gbrain-bin PATH / "
64
+ "set GBRAIN_BIN. `swarph brain-ask` queries an already-running brain.",
65
+ file=sys.stderr)
66
+ return 2
67
+
68
+ cmd = [gbin, "serve", "--http",
69
+ "--port", str(args.port),
70
+ "--bind", args.bind,
71
+ "--token-ttl", str(args.token_ttl)]
72
+ # Replace this process with gbrain so it owns the terminal / signals.
73
+ os.execvp(gbin, cmd)
74
+ return 0 # unreachable when execvp succeeds; keeps the type-checker happy
@@ -0,0 +1,74 @@
1
+ """``swarph service`` — stand up a $0 subscription-LLM HTTP lane.
2
+
3
+ `swarph service serve --provider <claude|codex|gemini>` runs a small FastAPI app
4
+ that exposes the provider's *subscription* CLI as a $0 HTTP lane (`POST /delegate`,
5
+ `GET /health`, bearer auth). The subprocess env is billing-scrubbed so the lane
6
+ can only use the $0 subscription path, never metered API billing. The server
7
+ stack (fastapi/uvicorn) is the optional ``[service]`` extra; the verb prints an
8
+ install hint and exits 2 when it's absent.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import os
15
+ import secrets
16
+ import sys
17
+
18
+
19
+ def _have_server_deps() -> bool:
20
+ try:
21
+ import fastapi # noqa: F401
22
+ import uvicorn # noqa: F401
23
+ return True
24
+ except ModuleNotFoundError:
25
+ return False
26
+
27
+
28
+ def run_service(argv: list) -> int:
29
+ p = argparse.ArgumentParser(
30
+ prog="swarph service",
31
+ description="Stand up a $0 subscription-LLM HTTP lane.")
32
+ sub = p.add_subparsers(dest="cmd")
33
+ serve = sub.add_parser("serve", help="run a $0 service lane for one provider")
34
+ serve.add_argument("--provider", required=True,
35
+ help="claude | codex | gemini")
36
+ serve.add_argument("--host", default="127.0.0.1",
37
+ help="bind host (default 127.0.0.1; pass a tailnet IP to expose)")
38
+ serve.add_argument("--port", type=int, default=8799, help="HTTP port (default 8799)")
39
+ serve.add_argument("--token", default=None,
40
+ help="bearer token (else SWARPH_SERVICE_TOKEN, else minted+printed)")
41
+ serve.add_argument("--timeout", type=int, default=120,
42
+ help="per-call subprocess timeout in seconds (default 120)")
43
+ args = p.parse_args(argv)
44
+
45
+ if args.cmd != "serve":
46
+ p.print_help()
47
+ return 0
48
+
49
+ from swarph_cli.service import providers as pv
50
+ if args.provider not in pv.SUPPORTED:
51
+ print(f"swarph service: unsupported provider {args.provider!r}; "
52
+ f"supported: {', '.join(pv.SUPPORTED)}", file=sys.stderr)
53
+ return 2
54
+
55
+ if not _have_server_deps():
56
+ print('swarph service serve needs the server extra: '
57
+ 'pip install "swarph-cli[service]"', file=sys.stderr)
58
+ return 2
59
+
60
+ token = args.token or os.environ.get("SWARPH_SERVICE_TOKEN")
61
+ if not token:
62
+ token = secrets.token_urlsafe(32)
63
+ print("swarph service: minted bearer token (set SWARPH_SERVICE_TOKEN to "
64
+ f"reuse it):\n {token}", file=sys.stderr)
65
+
66
+ from swarph_cli.service.app import build_app
67
+ import uvicorn
68
+
69
+ app = build_app(args.provider, token, timeout=args.timeout)
70
+ print(f"swarph service: {args.provider} $0 lane on "
71
+ f"http://{args.host}:{args.port} (POST /delegate, GET /health)",
72
+ file=sys.stderr)
73
+ uvicorn.run(app, host=args.host, port=args.port)
74
+ return 0
@@ -92,7 +92,9 @@ _VERB_HANDLERS: dict[str, str] = {
92
92
  "compress": "swarph_cli.commands.compress.run_compress",
93
93
  "cell": "swarph_cli.commands.cell.run_cell",
94
94
  "brain-ask": "swarph_cli.commands.brain_ask.run_brain_ask",
95
+ "brain": "swarph_cli.commands.brain.run_brain",
95
96
  "gateway": "swarph_cli.commands.gateway.run_gateway",
97
+ "service": "swarph_cli.commands.service.run_service",
96
98
  "highlight": "swarph_cli.commands.highlight.run_highlight",
97
99
  # Future: "list-peers", "list-adapters", etc.
98
100
  }
File without changes
@@ -0,0 +1,53 @@
1
+ """FastAPI $0-service app — a subscription LLM CLI exposed as a $0 HTTP lane.
2
+
3
+ `POST /delegate` (bearer-authed) runs the provider's subscription CLI one-shot
4
+ with a billing-scrubbed env (see ``providers.scrub_billing_env``) and returns the
5
+ completion at ``cost_usd: 0.0``. Behind the optional ``[service]`` extra.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import time
11
+
12
+ from fastapi import FastAPI, Header, HTTPException
13
+ from pydantic import BaseModel
14
+
15
+ from . import providers as pv
16
+
17
+
18
+ class DelegateRequest(BaseModel):
19
+ prompt: str
20
+ system: str | None = None
21
+ timeout: int | None = None
22
+
23
+
24
+ def build_app(provider: str, token: str, timeout: int = pv._DEFAULT_TIMEOUT) -> FastAPI:
25
+ if provider not in pv.SUPPORTED:
26
+ raise ValueError(
27
+ f"unsupported provider {provider!r}; supported: {', '.join(pv.SUPPORTED)}")
28
+
29
+ app = FastAPI(title=f"swarph service ({provider})",
30
+ description="$0 subscription-LLM HTTP lane")
31
+
32
+ def _auth(authorization: str) -> None:
33
+ if authorization != f"Bearer {token}":
34
+ raise HTTPException(status_code=401, detail="bad or missing bearer token")
35
+
36
+ @app.get("/health")
37
+ def health():
38
+ return {"ok": True, "provider": provider, "cost_model": "$0 subscription"}
39
+
40
+ @app.post("/delegate")
41
+ def delegate(req: DelegateRequest, authorization: str = Header(default="")):
42
+ _auth(authorization)
43
+ prompt = req.prompt if not req.system else f"System: {req.system}\n\n{req.prompt}"
44
+ t0 = time.monotonic()
45
+ try:
46
+ text = pv.run_provider(provider, prompt, timeout=req.timeout or timeout)
47
+ except RuntimeError as exc:
48
+ # CLI failed — surface the (already-truncated, env-free) reason, never the env.
49
+ raise HTTPException(status_code=502, detail=str(exc))
50
+ return {"text": text, "provider": provider, "cost_usd": 0.0,
51
+ "duration_s": round(time.monotonic() - t0, 3)}
52
+
53
+ return app
@@ -0,0 +1,87 @@
1
+ """$0-service provider core — billing-scrub + the CLI command map.
2
+
3
+ A swarph **service** wraps a *subscription* LLM CLI as a $0 HTTP lane. The
4
+ load-bearing security property: such a lane must NEVER be able to fall back to
5
+ metered API billing (the €50-Google-alert lesson, made structural). A
6
+ subscription CLI authenticates from its own config files (``~/.claude``,
7
+ ``~/.codex``, ``~/.gemini/antigravity``, …) — *not* from an API-key env var. So
8
+ we strip every known provider API-key var from the subprocess env: if a stray
9
+ ``ANTHROPIC_API_KEY`` were present, the CLI could silently bill it instead of
10
+ the subscription. Scrubbing makes the $0 path the only path.
11
+
12
+ No fastapi here — this module is the pure, importable security logic; the HTTP
13
+ app lives in ``service/app.py`` behind the optional ``[service]`` extra.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import subprocess
19
+
20
+ # Union of metered API-key env vars, stripped for EVERY lane (a claude lane must
21
+ # not be able to bill OpenAI either). These are unambiguously "use a paid API
22
+ # key" — subscription auth never lives here, so stripping them only removes the
23
+ # metered-fallback path.
24
+ _SCRUB_KEYS = frozenset({
25
+ "ANTHROPIC_API_KEY",
26
+ "OPENAI_API_KEY",
27
+ "GEMINI_API_KEY",
28
+ "GOOGLE_API_KEY",
29
+ "XAI_API_KEY",
30
+ "GROK_API_KEY",
31
+ "DEEPSEEK_API_KEY",
32
+ "MISTRAL_API_KEY",
33
+ "GROQ_API_KEY",
34
+ "TOGETHER_API_KEY",
35
+ "PERPLEXITY_API_KEY",
36
+ })
37
+
38
+ # provider -> argv prefix (the prompt is appended as the final arg). The prompt
39
+ # already carries any system context (the app composes it) so these stay simple.
40
+ # NOTE: the gemini lane (agy) reads the prompt ONLY from argv and hard-caps it
41
+ # (MAX_ARG_LEN ~4128) — very long prompts will be rejected by agy, not truncated.
42
+ _COMMANDS = {
43
+ "claude": ["claude", "-p"],
44
+ "codex": ["codex", "exec"],
45
+ "gemini": ["agy", "--print"],
46
+ }
47
+
48
+ SUPPORTED = tuple(_COMMANDS.keys())
49
+
50
+ _DEFAULT_TIMEOUT = 120 # seconds; mirrors CLAUDE_SUBSCRIPTION_TIMEOUT default
51
+
52
+
53
+ def scrub_billing_env(provider: str, base_env: dict) -> dict:
54
+ """Return a COPY of ``base_env`` with every metered API-key var removed.
55
+
56
+ ``provider`` is accepted for symmetry / future per-provider nuance; today the
57
+ scrub is the full union for defense in depth. The input dict is never mutated.
58
+ """
59
+ return {k: v for k, v in base_env.items() if k not in _SCRUB_KEYS}
60
+
61
+
62
+ def provider_command(provider: str, prompt: str) -> list:
63
+ """Build the subprocess argv for ``provider`` running ``prompt`` one-shot."""
64
+ prefix = _COMMANDS.get(provider)
65
+ if prefix is None:
66
+ raise ValueError(
67
+ f"unsupported provider {provider!r}; supported: {', '.join(SUPPORTED)}")
68
+ return [*prefix, prompt]
69
+
70
+
71
+ def run_provider(provider: str, prompt: str, timeout: int = _DEFAULT_TIMEOUT,
72
+ base_env: dict | None = None) -> str:
73
+ """Run the provider's subscription CLI one-shot with a billing-scrubbed env.
74
+
75
+ Returns stdout (stripped). Raises ``ValueError`` for an unknown provider and
76
+ ``RuntimeError`` on a non-zero exit (stderr head only — never echo the env).
77
+ """
78
+ import os
79
+
80
+ argv = provider_command(provider, prompt)
81
+ env = scrub_billing_env(provider, os.environ if base_env is None else base_env)
82
+ proc = subprocess.run(argv, env=env, capture_output=True, text=True,
83
+ timeout=timeout)
84
+ if proc.returncode != 0:
85
+ raise RuntimeError(
86
+ f"{provider} CLI exited {proc.returncode}: {proc.stderr.strip()[:200]}")
87
+ return proc.stdout.strip()
@@ -1,3 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: swarph-cli
3
+ Version: 0.18.0
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
+ Author: Pierre Samson, Claude Opus
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/BrainSurfing-tech/swarph-cli
8
+ Project-URL: Source, https://github.com/BrainSurfing-tech/swarph-cli
9
+ Project-URL: Substrate, https://github.com/BrainSurfing-tech/swarph-mesh
10
+ Project-URL: Spec, https://github.com/darw007d/hedge-fund-mcp/blob/main/research/swarph_cli/PLAN.md
11
+ Keywords: swarph,llm,cli,mesh,gemini,claude,deepseek
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: Utilities
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: swarph-mesh>=0.5.0
28
+ Requires-Dist: swarph-shared>=0.3.3
29
+ Requires-Dist: PyYAML>=6.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7.0; extra == "dev"
32
+ Provides-Extra: mcp
33
+ Requires-Dist: mcp>=1.0; extra == "mcp"
34
+ Provides-Extra: gateway
35
+ Requires-Dist: fastapi>=0.115; extra == "gateway"
36
+ Requires-Dist: uvicorn[standard]>=0.32; extra == "gateway"
37
+ Requires-Dist: pydantic>=2.9; extra == "gateway"
38
+ Requires-Dist: croniter>=6.2; extra == "gateway"
39
+ Provides-Extra: service
40
+ Requires-Dist: fastapi>=0.115; extra == "service"
41
+ Requires-Dist: uvicorn[standard]>=0.32; extra == "service"
42
+ Requires-Dist: pydantic>=2.9; extra == "service"
43
+ Dynamic: license-file
44
+
1
45
  # swarph-cli
2
46
 
3
47
  **One CLI for every LLM.** Drive Claude, GPT, Gemini, DeepSeek, and Grok from a single binary — then connect them into a coordinating *mesh* where agents from different vendors talk to each other through a shared protocol, each staying itself.
@@ -25,7 +69,9 @@ This is one of three repos in the v0.3.x architecture:
25
69
  swarph "prompt" one-shot against any provider (claude/openai/gemini/deepseek/grok)
26
70
  swarph chat interactive REPL — multi-turn, slash commands, live provider switch
27
71
  swarph brain-ask "<q>" search the swarph-brain (gbrain) memory — $0 cited answer or raw chunks
72
+ swarph brain serve run the gbrain HTTP brain server (the $0 semantic memory)
28
73
  swarph gateway serve run the bundled mesh-gateway server (the mesh's coordination/DM hub)
74
+ swarph service serve stand up a $0 subscription-LLM HTTP lane (claude/codex/gemini)
29
75
  swarph highlight "<x>" log a highlight to the shared git-backed swarph timeline
30
76
  swarph spawn <role> launch a long-lived agent session as a named mesh cell
31
77
  swarph daemon foreground inbox-drain loop (the mesh doorbell)
@@ -52,6 +98,18 @@ $ swarph brain-ask --no-synth --limit 3 "cross-vendor fallback governor order"
52
98
 
53
99
  Config is via env, mirroring `swarph mesh`'s token model: `GBRAIN_MCP_URL` or `SWARPH_BRAIN_MCP` (gbrain endpoint; defaults to `http://127.0.0.1:8792/mcp`), and the read token resolved `--token-file` > `GBRAIN_TOKEN` > `SWARPH_BRAIN_TOKEN` > the mesh per-peer token (`~/.config/swarph/<self>.peer_token`) — so the mesh peer token doubles as the gbrain read token. Optional `SWARPH_FACADE` / `SWARPH_FACADE_TOKEN` enable the $0 cited-synthesis pass; without them, brain-ask prints the raw ranked chunks.
54
100
 
101
+ ### `swarph brain serve`
102
+
103
+ `brain-ask` *searches* a running brain; **`brain serve`** *runs* one. gbrain (the sovereign $0 semantic-memory server) is an external binary — this verb is a thin launcher that applies the swarph-blessed defaults and replaces itself with `gbrain serve`:
104
+
105
+ ```bash
106
+ $ swarph brain serve # 127.0.0.1:8792, 1-year token TTL
107
+ $ swarph brain serve --bind 100.x.y.z --port 8792 # expose on a tailnet IP
108
+ $ swarph brain serve --gbrain-bin /opt/gbrain # explicit binary (else GBRAIN_BIN / PATH)
109
+ ```
110
+
111
+ Defaults are loopback bind (expose a tailnet IP explicitly) and a **1-year token TTL** — a short TTL silently 401s long-lived mesh cells, a lesson learned the hard way. If `gbrain` isn't on `PATH` the verb prints an install hint and exits `2`; `brain-ask` still queries any already-running brain.
112
+
55
113
  ### `swarph gateway`
56
114
 
57
115
  The **mesh-gateway** is the coordination/DM server every other verb talks to — the peer registry, the DM inbox/outbox, feature aggregation with allowlist + caps, and lane/service control. It used to be a separate deployment; it's now bundled, so any host can stand one up with a single command. It pairs with the client verbs (`mesh`, `spawn`, `daemon`, `watchdog`) and with `brain-ask`/gbrain to form the mesh's coordination plane.
@@ -66,6 +124,20 @@ $ swarph gateway serve --host 100.x.y.z --port 8788 # expose on a tailnet IP
66
124
 
67
125
  `--token` sets the bearer (`MESH_GATEWAY_TOKEN`) for the served process; omit it and an existing env token is used, or a fresh one is minted and printed once so you can hand it to the mesh cells. `--db PATH` points the gateway at a specific SQLite file (`MESH_DB_PATH`). Run without the extra and the verb prints a `pip install "swarph-cli[gateway]"` hint and exits.
68
126
 
127
+ ### `swarph service`
128
+
129
+ Turn a *subscription* LLM CLI into a **$0 HTTP lane** the mesh can call — `swarph service serve --provider <claude|codex|gemini>` runs a small FastAPI app exposing `POST /delegate` (bearer-authed) + `GET /health`:
130
+
131
+ ```bash
132
+ $ pip install "swarph-cli[service]"
133
+ $ swarph service serve --provider claude # 127.0.0.1:8799, mints+prints a token
134
+ $ swarph service serve --provider gemini --host 100.x.y.z --port 8799 # expose on a tailnet IP
135
+ $ curl -s localhost:8799/delegate -H "Authorization: Bearer $TOK" \
136
+ -d '{"prompt":"summarise this in one line: ..."}' # -> {"text": "...", "cost_usd": 0.0}
137
+ ```
138
+
139
+ The load-bearing piece is **billing-scrub**: the subprocess env has every known provider API-key var (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, …) stripped, so the wrapped CLI can *only* use its $0 subscription auth — it can never silently fall back to metered API billing. Default loopback bind (expose a tailnet IP explicitly); the bearer is `--token` > `SWARPH_SERVICE_TOKEN` > a freshly minted+printed token. Note the `gemini` (agy) lane reads the prompt from argv only and hard-caps its length (~4 KB) — very long prompts are rejected, not truncated. Run without the extra and the verb prints a `pip install "swarph-cli[service]"` hint and exits.
140
+
69
141
  ### `swarph highlight`
70
142
 
71
143
  The swarph **timeline** is an append-only, multi-author `TIMELINE.md` — a git-backed continuous-learning log where every cell's highlights converge into one file. `swarph highlight` is the mechanics; *you* (or your agent) decide what's worth logging and which memory it links to.
@@ -20,6 +20,7 @@ src/swarph_cli/capture/paths.py
20
20
  src/swarph_cli/capture/verify.py
21
21
  src/swarph_cli/commands/__init__.py
22
22
  src/swarph_cli/commands/add.py
23
+ src/swarph_cli/commands/brain.py
23
24
  src/swarph_cli/commands/brain_ask.py
24
25
  src/swarph_cli/commands/cell.py
25
26
  src/swarph_cli/commands/chat.py
@@ -39,6 +40,7 @@ src/swarph_cli/commands/onboard.py
39
40
  src/swarph_cli/commands/protocol_handler.py
40
41
  src/swarph_cli/commands/ratify.py
41
42
  src/swarph_cli/commands/security.py
43
+ src/swarph_cli/commands/service.py
42
44
  src/swarph_cli/commands/spawn.py
43
45
  src/swarph_cli/commands/watchdog.py
44
46
  src/swarph_cli/compress/__init__.py
@@ -53,6 +55,9 @@ src/swarph_cli/gateway/server.py
53
55
  src/swarph_cli/gateway/services_control.py
54
56
  src/swarph_cli/parsers/__init__.py
55
57
  src/swarph_cli/parsers/claude.py
58
+ src/swarph_cli/service/__init__.py
59
+ src/swarph_cli/service/app.py
60
+ src/swarph_cli/service/providers.py
56
61
  src/swarph_cli/systemd/swarph-watchdog.default
57
62
  src/swarph_cli/systemd/swarph-watchdog.service
58
63
  src/swarph_cli/systemd/swarph-watchdog.timer
@@ -65,6 +70,7 @@ tests/test_artifact_skill.py
65
70
  tests/test_artifact_tool.py
66
71
  tests/test_artifact_uri.py
67
72
  tests/test_brain_ask_command.py
73
+ tests/test_brain_command.py
68
74
  tests/test_caller_meta_guard.py
69
75
  tests/test_capture_lineage.py
70
76
  tests/test_capture_liveness.py
@@ -106,6 +112,9 @@ tests/test_protocol_handler.py
106
112
  tests/test_ratify_command.py
107
113
  tests/test_repl_caller_convention.py
108
114
  tests/test_security.py
115
+ tests/test_service_app.py
116
+ tests/test_service_command.py
117
+ tests/test_service_providers.py
109
118
  tests/test_session_path_role_gate.py
110
119
  tests/test_smoke_chat.py
111
120
  tests/test_smoke_one_shot.py
@@ -13,3 +13,8 @@ croniter>=6.2
13
13
 
14
14
  [mcp]
15
15
  mcp>=1.0
16
+
17
+ [service]
18
+ fastapi>=0.115
19
+ uvicorn[standard]>=0.32
20
+ pydantic>=2.9