swarph-cli 0.1.1__py3-none-any.whl

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/__init__.py ADDED
@@ -0,0 +1,21 @@
1
+ """swarph-cli — the ``swarph`` binary.
2
+
3
+ Thin client over the ``swarph-mesh`` substrate. v0.0.1 ships the entry-
4
+ point + a status banner. Live one-shot mode + REPL ship in Phase 2 / 5
5
+ per PLAN.md §13.
6
+
7
+ The architecture splits CLI from substrate so:
8
+
9
+ * ``swarph-mesh`` stays a library importable from ``omega-boss``,
10
+ ``Council`` judges, ``lab-orchestrator``, etc. — no CLI surface or
11
+ console-script entry point required.
12
+ * ``swarph-cli`` is a tiny argparse + REPL layer on top, ~200 LOC at
13
+ ship-out. Console users get the binary; library callers don't pull
14
+ in the CLI surface.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ __version__ = "0.1.1"
20
+
21
+ __all__ = ["__version__"]
swarph_cli/caller.py ADDED
@@ -0,0 +1,47 @@
1
+ """Build a default caller-convention slug for one-shot CLI invocations.
2
+
3
+ Per swarph_shared.caller_convention the slug must match:
4
+
5
+ ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$
6
+
7
+ So we sanitize the OS username (which can have hyphens, capitals,
8
+ digits-first, etc.) into a conformant fragment and prepend a fixed
9
+ ``cli.oneshot.`` prefix.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import getpass
15
+ import os
16
+ import re
17
+
18
+
19
+ _NON_SLUG_CHARS = re.compile(r"[^a-z0-9_]+")
20
+
21
+
22
+ def _sanitize_username(name: str) -> str:
23
+ """Turn an arbitrary username into a caller-convention fragment.
24
+
25
+ - Lowercase
26
+ - Replace any non-alnum/underscore with underscore
27
+ - Collapse runs of underscores
28
+ - Ensure leading char is a letter (prepend ``u_`` if not)
29
+ - Fall back to ``unknown`` if empty
30
+ """
31
+ s = name.lower()
32
+ s = _NON_SLUG_CHARS.sub("_", s)
33
+ s = re.sub(r"_+", "_", s).strip("_")
34
+ if not s:
35
+ return "unknown"
36
+ if not s[0].isalpha():
37
+ s = "u_" + s
38
+ return s
39
+
40
+
41
+ def default_caller() -> str:
42
+ """Return ``cli.oneshot.<sanitized-user>`` for the current OS user."""
43
+ try:
44
+ user = getpass.getuser()
45
+ except Exception:
46
+ user = os.environ.get("USER") or os.environ.get("USERNAME") or "unknown"
47
+ return f"cli.oneshot.{_sanitize_username(user)}"
swarph_cli/main.py ADDED
@@ -0,0 +1,223 @@
1
+ """``swarph`` entry-point — Phase 2 one-shot mode.
2
+
3
+ v0.0.1 was the scaffold (banner-only). v0.1.0 ships the falsifiability
4
+ gate from PLAN.md §13 Phase 2:
5
+
6
+ swarph "explain Hawkes process briefly" --provider gemini --model flash
7
+
8
+ Subsequent phases extend the CLI:
9
+ - Phase 3: --ask <peer> mesh-aware one-shot via MeshClient
10
+ - Phase 5: interactive REPL (``swarph chat``)
11
+ - Phase 5.5: ``swarph onboard`` + ``swarph ratify``
12
+ - Phase 5.7: ``swarph daemon`` foreground drain
13
+ - Phase 2.5: ``swarph import --report-only`` per PLAN.md §17.6 reorder
14
+
15
+ For now the entry-point handles ONE shape: positional prompt argument
16
+ + provider/model flags + JSON-mode toggle. argparse subparsers will
17
+ land in Phase 3 when more verbs need their own surface.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import argparse
23
+ import asyncio
24
+ import json
25
+ import os
26
+ import sys
27
+ from pathlib import Path
28
+ from typing import Optional
29
+
30
+ from swarph_cli import __version__
31
+ from swarph_cli.caller import default_caller
32
+
33
+
34
+ _BANNER = """\
35
+ swarph v{version}
36
+
37
+ Usage:
38
+ swarph "your prompt here" [--provider gemini] [--model gemini-2.5-flash]
39
+
40
+ Examples:
41
+ swarph "explain Hawkes process briefly"
42
+ swarph "list 5 tickers" --json
43
+ swarph "summarise" --provider gemini --model gemini-2.5-pro
44
+
45
+ Status: Phase 2 one-shot mode. REPL (Phase 5), --ask <peer>
46
+ (Phase 3), onboard/ratify (Phase 5.5), daemon (Phase 5.7) and
47
+ import (Phase 2.5) ship in subsequent releases.
48
+
49
+ Spec: https://github.com/darw007d/hedge-fund-mcp/blob/main/research/swarph_cli/PLAN.md
50
+ """
51
+
52
+
53
+ def _build_parser() -> argparse.ArgumentParser:
54
+ p = argparse.ArgumentParser(
55
+ prog="swarph",
56
+ description=(
57
+ "swarph — multi-LLM CLI with mesh-gateway integration. "
58
+ "Phase 2 one-shot mode."
59
+ ),
60
+ )
61
+ p.add_argument(
62
+ "prompt",
63
+ nargs="?",
64
+ default=None,
65
+ help='Prompt to send (one-shot mode). Omit to print a usage banner.',
66
+ )
67
+ p.add_argument(
68
+ "--provider",
69
+ default="gemini",
70
+ help='LLM provider. Phase 1 ships "gemini" only; Phase 4+ adds '
71
+ "deepseek/claude/openai/grok.",
72
+ )
73
+ p.add_argument(
74
+ "--model",
75
+ default=None,
76
+ help="Provider-specific model id. Defaults to the adapter's default_model.",
77
+ )
78
+ p.add_argument(
79
+ "--caller",
80
+ default=None,
81
+ help='Caller-convention slug (dotted lowercase). Defaults to '
82
+ '"cli.oneshot.<user>" for the current OS user.',
83
+ )
84
+ p.add_argument(
85
+ "--system",
86
+ default=None,
87
+ help="System prompt prepended to the conversation.",
88
+ )
89
+ p.add_argument(
90
+ "--temperature",
91
+ type=float,
92
+ default=0.7,
93
+ help="Sampling temperature (default 0.7).",
94
+ )
95
+ p.add_argument(
96
+ "--max-tokens",
97
+ type=int,
98
+ default=None,
99
+ help="Max output tokens (provider default if omitted).",
100
+ )
101
+ p.add_argument(
102
+ "--json",
103
+ action="store_true",
104
+ help="Parse response as JSON (TRIGGER for the swarph-mesh JSON "
105
+ "harness — not strict-validation; a permissive {'type': 'object'} "
106
+ "schema is synthesised when --schema is absent). Malformed-JSON "
107
+ "responses cause exit code 1 with raw text on stdout for caller "
108
+ "recovery — useful for shell scripts gating on "
109
+ "`if swarph 'x' --json; then ...`. Full Pydantic validation lands "
110
+ "in Phase 5+.",
111
+ )
112
+ p.add_argument(
113
+ "--schema",
114
+ default=None,
115
+ help="Path to a JSON Schema file. Implies --json. v0.1.0 uses the "
116
+ "schema only as the harness trigger; full Pydantic validation lands "
117
+ "in Phase 5+.",
118
+ )
119
+ p.add_argument(
120
+ "--quiet",
121
+ "-q",
122
+ action="store_true",
123
+ help="Suppress the per-call attribution footer on stderr.",
124
+ )
125
+ p.add_argument(
126
+ "--version",
127
+ action="version",
128
+ version=f"swarph-cli {__version__}",
129
+ )
130
+ return p
131
+
132
+
133
+ def _print_banner() -> int:
134
+ print(_BANNER.format(version=__version__), file=sys.stderr)
135
+ return 0
136
+
137
+
138
+ def _load_schema(path: Optional[str]) -> Optional[dict]:
139
+ if not path:
140
+ return None
141
+ p = Path(path)
142
+ if not p.exists():
143
+ print(f"swarph: --schema file not found: {path}", file=sys.stderr)
144
+ sys.exit(2)
145
+ try:
146
+ return json.loads(p.read_text(encoding="utf-8"))
147
+ except json.JSONDecodeError as exc:
148
+ print(f"swarph: --schema file is not valid JSON: {exc}", file=sys.stderr)
149
+ sys.exit(2)
150
+
151
+
152
+ async def _run_one_shot(args: argparse.Namespace) -> int:
153
+ # Local import so unit tests of the CLI shape don't drag in the
154
+ # full SwarphCall wiring + Gemini SDK on import.
155
+ from swarph_mesh import ChatMessage, SwarphCall
156
+
157
+ caller = args.caller or default_caller()
158
+ json_schema = _load_schema(args.schema) or ({"type": "object"} if args.json else None)
159
+
160
+ try:
161
+ # SwarphCall construction enforces caller convention via
162
+ # swarph_shared.validate_caller — raises ValueError on
163
+ # invalid slugs. Keep inside try/except so a bad --caller
164
+ # argument exits 1 with a friendly error rather than dumps
165
+ # a traceback.
166
+ sc = SwarphCall(
167
+ provider=args.provider,
168
+ caller=caller,
169
+ model=args.model,
170
+ )
171
+ resp = await sc.chat(
172
+ messages=[ChatMessage(role="user", content=args.prompt)],
173
+ system_prompt=args.system,
174
+ json_schema=json_schema,
175
+ temperature=args.temperature,
176
+ max_tokens=args.max_tokens,
177
+ )
178
+ except Exception as exc:
179
+ print(f"swarph: call failed: {exc}", file=sys.stderr)
180
+ return 1
181
+
182
+ # JSON mode prints parsed dict (pretty) when available; falls back
183
+ # to raw text + error_class footer when the harness couldn't parse.
184
+ if json_schema is not None and resp.parsed is not None:
185
+ print(json.dumps(resp.parsed, indent=2, sort_keys=True))
186
+ else:
187
+ print(resp.text)
188
+
189
+ if not args.quiet:
190
+ # ``$0`` displays the subscription-path / free-tier case
191
+ # (adapter returns exactly 0.0); ``$0.0000`` displays a
192
+ # tiny-but-real API cost. Future adapters that introduce
193
+ # float drift around zero (e.g., 1e-12 due to multiplier
194
+ # rounding) would flip the display spuriously to
195
+ # ``$0.0000`` — audit + tighten the threshold if that bites
196
+ # (drop PR #1 review observation #2, DM #681).
197
+ cost_str = f"${resp.cost_usd:.4f}" if resp.cost_usd > 0 else "$0"
198
+ attribution = (
199
+ f"# {resp.input_tokens}+{resp.output_tokens}t "
200
+ f"{cost_str} {resp.duration_s:.2f}s caller={caller} "
201
+ f"provider={args.provider}"
202
+ )
203
+ if resp.cached:
204
+ attribution += " (cached)"
205
+ if resp.error_class:
206
+ attribution += f" error_class={resp.error_class}"
207
+ print(attribution, file=sys.stderr)
208
+
209
+ return 0 if resp.error_class is None else 1
210
+
211
+
212
+ def main(argv: Optional[list[str]] = None) -> int:
213
+ parser = _build_parser()
214
+ args = parser.parse_args(argv)
215
+
216
+ if args.prompt is None:
217
+ return _print_banner()
218
+
219
+ return asyncio.run(_run_one_shot(args))
220
+
221
+
222
+ if __name__ == "__main__":
223
+ raise SystemExit(main())
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: swarph-cli
3
+ Version: 0.1.1
4
+ Summary: The `swarph` binary — multi-LLM CLI with mesh-gateway integration. Phase 2 one-shot mode shipped (PLAN.md §13).
5
+ Author: Pierre Samson, Claude Opus
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/darw007d/swarph-cli
8
+ Project-URL: Source, https://github.com/darw007d/swarph-cli
9
+ Project-URL: Substrate, https://github.com/darw007d/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.1.0
28
+ Requires-Dist: swarph-shared>=0.2.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # swarph-cli
34
+
35
+ The `swarph` binary — multi-LLM CLI with mesh-gateway integration. Thin client over the [`swarph-mesh`](https://github.com/darw007d/swarph-mesh) substrate.
36
+
37
+ ```bash
38
+ pip install swarph-cli
39
+ swarph --version
40
+ ```
41
+
42
+ This is one of three repos in the v0.3.x architecture:
43
+
44
+ | Repo | Role |
45
+ |---|---|
46
+ | [`swarph-mesh`](https://github.com/darw007d/swarph-mesh) | Substrate Python package — Protocol + adapters + SwarphCall + MeshClient. Pure library, no CLI |
47
+ | [`swarph-cli`](https://github.com/darw007d/swarph-cli) | This repo — the `swarph` binary |
48
+ | [`swarph-meshlm`](https://github.com/darw007d/swarph-meshlm) | Simon Willison `llm` plugin |
49
+
50
+ ## Status
51
+
52
+ **v0.1.0 — Phase 2 one-shot mode.** The `swarph "prompt"` binary works end-to-end against `--provider gemini` per PLAN.md §13 falsifiability gate. Subsequent phases extend the CLI surface (REPL, `--ask <peer>`, onboard/ratify, daemon, import).
53
+
54
+ ```bash
55
+ $ swarph "say pong" --provider gemini
56
+ Pong!
57
+ # 3+26t $0.0000 0.73s caller=cli.oneshot.ubuntu provider=gemini
58
+ ```
59
+
60
+ ### `--json` mode semantics
61
+
62
+ `--json` is a **harness trigger**, not a strict-validation gate. When set, swarph routes the response through the swarph-mesh JSON harness:
63
+
64
+ - A permissive `{"type": "object"}` schema is synthesised when `--schema` is absent (Phase 5+ adds Pydantic validation).
65
+ - The harness retries once with `[USER]`-turn feedback on parse failure.
66
+ - **Malformed-JSON exits with code 1** + raw text on stdout for caller recovery. Useful for shell scripts:
67
+ ```bash
68
+ if swarph "give me a trade" --json; then
69
+ # parsed dict was on stdout
70
+ ...
71
+ fi
72
+ ```
73
+ - Pretty-printed parsed dict on stdout when parse succeeds; `error_class=malformed_json` shows up in the stderr attribution footer when it doesn't.
74
+
75
+ ## Spec
76
+
77
+ → [hedge-fund-mcp / research/swarph_cli/PLAN.md](https://github.com/darw007d/hedge-fund-mcp/blob/main/research/swarph_cli/PLAN.md)
78
+
79
+ ## Phase rollout
80
+
81
+ | Phase | What lands |
82
+ |---|---|
83
+ | **0** (this) | Scaffold — entry-point + status banner |
84
+ | **2** | One-shot mode: `swarph "hello" --provider gemini` |
85
+ | **3** | `--ask <peer>` mesh-aware one-shot via MeshClient |
86
+ | **5** | Interactive REPL — `/inbox`, `/reply`, `/dm`, `/watch` |
87
+ | **5.5** | `swarph onboard <peer-name>` + `swarph ratify <peer-name>` (PLAN.md §15) |
88
+ | **5.7** | `swarph daemon` foreground drain loop + `swarph chat` REPL with drain coroutine (PLAN.md §16) |
89
+ | **6** | PyPI publish |
90
+
91
+ ## Why split CLI from substrate
92
+
93
+ `swarph-mesh` (the library) is imported by `omega-boss`, Council judges, `lab-orchestrator`, and any future swarph peer that wants to write programs against the Protocol. Those callers don't need the CLI surface or the console-script entry point. Keeping the CLI in a separate repo means library users `pip install swarph-mesh` without pulling argparse + REPL plumbing they'll never run.
94
+
95
+ ## Install (dev)
96
+
97
+ ```bash
98
+ git clone https://github.com/darw007d/swarph-cli
99
+ cd swarph-cli
100
+ python -m venv venv && source venv/bin/activate
101
+ pip install -e ".[dev]"
102
+ pytest
103
+ swarph --version
104
+ ```
105
+
106
+ ## License
107
+
108
+ MIT. Pierre Samson + Claude Opus, 2026.
@@ -0,0 +1,9 @@
1
+ swarph_cli/__init__.py,sha256=2exfOKQZEUd0ziPGdrxWSZMG6EBPPhCaZa40hdBmdbU,678
2
+ swarph_cli/caller.py,sha256=NKEbGkoyRf-BCdXzGwYzLsPnr6xgxRldRQbxTEcKHc0,1288
3
+ swarph_cli/main.py,sha256=5CSNu4ENSYpG3aF5dOAW52fApuec1fwHB24dGhchkEo,7255
4
+ swarph_cli-0.1.1.dist-info/licenses/LICENSE,sha256=PFBo9Jfj0JbAYTdIvxVWHbMac93_iM57n27yQs80BJ4,1086
5
+ swarph_cli-0.1.1.dist-info/METADATA,sha256=3u6faTLas5BiT5CHTzHMQ733BkgmnPClBX3U_8YL4_M,4485
6
+ swarph_cli-0.1.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
7
+ swarph_cli-0.1.1.dist-info/entry_points.txt,sha256=61jM-mwvdoN9FWnL5AgXxhmbJ39zhP-GsllJzsmRlF0,48
8
+ swarph_cli-0.1.1.dist-info/top_level.txt,sha256=2sTqSJDjO0wW9xURA1IYlRD0kX_1qI0Sm5_rG8jhkQc,11
9
+ swarph_cli-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ swarph = swarph_cli.main:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pierre Samson and Claude Opus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ swarph_cli