swarph-cli 0.1.1__tar.gz → 0.2.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.1.1/src/swarph_cli.egg-info → swarph_cli-0.2.0}/PKG-INFO +37 -2
- {swarph_cli-0.1.1 → swarph_cli-0.2.0}/README.md +36 -1
- {swarph_cli-0.1.1 → swarph_cli-0.2.0}/pyproject.toml +1 -1
- {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli/__init__.py +1 -1
- swarph_cli-0.2.0/src/swarph_cli/commands/__init__.py +15 -0
- swarph_cli-0.2.0/src/swarph_cli/commands/import_session.py +232 -0
- {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli/main.py +55 -21
- swarph_cli-0.2.0/src/swarph_cli/parsers/__init__.py +27 -0
- swarph_cli-0.2.0/src/swarph_cli/parsers/claude.py +340 -0
- {swarph_cli-0.1.1 → swarph_cli-0.2.0/src/swarph_cli.egg-info}/PKG-INFO +37 -2
- {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli.egg-info/SOURCES.txt +6 -0
- swarph_cli-0.2.0/tests/test_claude_parser.py +332 -0
- swarph_cli-0.2.0/tests/test_import_command.py +288 -0
- {swarph_cli-0.1.1 → swarph_cli-0.2.0}/LICENSE +0 -0
- {swarph_cli-0.1.1 → swarph_cli-0.2.0}/setup.cfg +0 -0
- {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.1.1 → swarph_cli-0.2.0}/tests/test_main.py +0 -0
- {swarph_cli-0.1.1 → swarph_cli-0.2.0}/tests/test_smoke_one_shot.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: The `swarph` binary — multi-LLM CLI with mesh-gateway integration. Phase 2 one-shot mode shipped (PLAN.md §13).
|
|
5
5
|
Author: Pierre Samson, Claude Opus
|
|
6
6
|
License: MIT
|
|
@@ -49,7 +49,42 @@ This is one of three repos in the v0.3.x architecture:
|
|
|
49
49
|
|
|
50
50
|
## Status
|
|
51
51
|
|
|
52
|
-
**v0.
|
|
52
|
+
**v0.2.0 — Phase 2 one-shot + Phase 2.5 import.** Two verbs ship:
|
|
53
|
+
|
|
54
|
+
1. `swarph "prompt"` — Phase 2 one-shot mode (against `--provider gemini`)
|
|
55
|
+
2. `swarph import <path>` — Phase 2.5 session import (Claude JSONL → swarph-native, with `--report-only` for honest pre-commit inspection)
|
|
56
|
+
|
|
57
|
+
Subsequent phases extend the CLI surface (REPL, `--ask <peer>`, onboard/ratify, daemon, additional source formats).
|
|
58
|
+
|
|
59
|
+
### `swarph import`
|
|
60
|
+
|
|
61
|
+
Per PLAN.md §17, session import is the **knowledge half of onboarding** — gives a memory-carrying peer (or human migrating CLIs) the substantive context they're bringing into the swarph, paired with §15's contract half (handshake DM acknowledging the four invariants).
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Inspect what would be imported (lossy → honest framing)
|
|
65
|
+
$ swarph import ~/.claude/projects/.../X.jsonl --report-only
|
|
66
|
+
|
|
67
|
+
# Commit — writes ~/.swarph/sessions/<session-id>.jsonl
|
|
68
|
+
$ swarph import ~/.claude/projects/.../X.jsonl
|
|
69
|
+
|
|
70
|
+
# Refuse-with-error if target exists (protects continuation turns)
|
|
71
|
+
$ swarph import same-source.jsonl
|
|
72
|
+
swarph import: target /home/.../X.jsonl already exists (...)
|
|
73
|
+
To proceed:
|
|
74
|
+
--force overwrite (destroys continuation turns)
|
|
75
|
+
--target-session NAME write to a different file
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**What ports cleanly:** plain user/assistant/system text, role tags, conversation order.
|
|
79
|
+
|
|
80
|
+
**What's lossy** (counted in report, kept as visible text where possible):
|
|
81
|
+
- `thinking` blocks (Anthropic-specific reasoning trace)
|
|
82
|
+
- `tool_use` blocks (call shape doesn't port across providers)
|
|
83
|
+
- `tool_result` blocks (companion drop with `tool_use`)
|
|
84
|
+
|
|
85
|
+
**What's dropped:** attachments (would need re-upload), provider-side KV cache, conversation IDs, `cache_control` annotations.
|
|
86
|
+
|
|
87
|
+
Honest framing per PLAN.md §17.3: **teleport is "import + continue", not "freeze and resume"** — the first turn after import on a new provider pays cold-cache cost. Phase 5+ adds `--continue` for live REPL integration.
|
|
53
88
|
|
|
54
89
|
```bash
|
|
55
90
|
$ swarph "say pong" --provider gemini
|
|
@@ -17,7 +17,42 @@ This is one of three repos in the v0.3.x architecture:
|
|
|
17
17
|
|
|
18
18
|
## Status
|
|
19
19
|
|
|
20
|
-
**v0.
|
|
20
|
+
**v0.2.0 — Phase 2 one-shot + Phase 2.5 import.** Two verbs ship:
|
|
21
|
+
|
|
22
|
+
1. `swarph "prompt"` — Phase 2 one-shot mode (against `--provider gemini`)
|
|
23
|
+
2. `swarph import <path>` — Phase 2.5 session import (Claude JSONL → swarph-native, with `--report-only` for honest pre-commit inspection)
|
|
24
|
+
|
|
25
|
+
Subsequent phases extend the CLI surface (REPL, `--ask <peer>`, onboard/ratify, daemon, additional source formats).
|
|
26
|
+
|
|
27
|
+
### `swarph import`
|
|
28
|
+
|
|
29
|
+
Per PLAN.md §17, session import is the **knowledge half of onboarding** — gives a memory-carrying peer (or human migrating CLIs) the substantive context they're bringing into the swarph, paired with §15's contract half (handshake DM acknowledging the four invariants).
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Inspect what would be imported (lossy → honest framing)
|
|
33
|
+
$ swarph import ~/.claude/projects/.../X.jsonl --report-only
|
|
34
|
+
|
|
35
|
+
# Commit — writes ~/.swarph/sessions/<session-id>.jsonl
|
|
36
|
+
$ swarph import ~/.claude/projects/.../X.jsonl
|
|
37
|
+
|
|
38
|
+
# Refuse-with-error if target exists (protects continuation turns)
|
|
39
|
+
$ swarph import same-source.jsonl
|
|
40
|
+
swarph import: target /home/.../X.jsonl already exists (...)
|
|
41
|
+
To proceed:
|
|
42
|
+
--force overwrite (destroys continuation turns)
|
|
43
|
+
--target-session NAME write to a different file
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**What ports cleanly:** plain user/assistant/system text, role tags, conversation order.
|
|
47
|
+
|
|
48
|
+
**What's lossy** (counted in report, kept as visible text where possible):
|
|
49
|
+
- `thinking` blocks (Anthropic-specific reasoning trace)
|
|
50
|
+
- `tool_use` blocks (call shape doesn't port across providers)
|
|
51
|
+
- `tool_result` blocks (companion drop with `tool_use`)
|
|
52
|
+
|
|
53
|
+
**What's dropped:** attachments (would need re-upload), provider-side KV cache, conversation IDs, `cache_control` annotations.
|
|
54
|
+
|
|
55
|
+
Honest framing per PLAN.md §17.3: **teleport is "import + continue", not "freeze and resume"** — the first turn after import on a new provider pays cold-cache cost. Phase 5+ adds `--continue` for live REPL integration.
|
|
21
56
|
|
|
22
57
|
```bash
|
|
23
58
|
$ swarph "say pong" --provider gemini
|
|
@@ -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.2.0"
|
|
8
8
|
description = "The `swarph` binary — multi-LLM CLI with mesh-gateway integration. Phase 2 one-shot mode shipped (PLAN.md §13)."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Subcommand handlers for ``swarph``.
|
|
2
|
+
|
|
3
|
+
Phase 2.5 ships ``import``. Phase 3+ adds ``--ask`` / ``list-peers``;
|
|
4
|
+
Phase 5+ adds ``chat`` REPL; Phase 5.5 adds ``onboard`` / ``ratify``;
|
|
5
|
+
Phase 5.7 adds ``daemon``.
|
|
6
|
+
|
|
7
|
+
Each handler is a function that takes a list of argv-style argument
|
|
8
|
+
strings (the verb stripped off) and returns an int exit code.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from swarph_cli.commands.import_session import run_import
|
|
14
|
+
|
|
15
|
+
__all__ = ["run_import"]
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""``swarph import`` — Phase 2.5 implementation.
|
|
2
|
+
|
|
3
|
+
Per PLAN.md §17.6 forward-reorder, Phase 2.5 ships:
|
|
4
|
+
- Parser + converter for Claude session JSONL
|
|
5
|
+
- Import-report writer
|
|
6
|
+
- ``--report-only`` mode (no writes, no continuation)
|
|
7
|
+
|
|
8
|
+
Phase 3.5+ adds gemini + llm source formats. Phase 5+ adds
|
|
9
|
+
``--continue`` for live REPL integration.
|
|
10
|
+
|
|
11
|
+
Per PLAN.md §17.4 the subcommand spec:
|
|
12
|
+
|
|
13
|
+
swarph import <source-path>
|
|
14
|
+
[--source-format claude|gemini|llm|chatgpt-export]
|
|
15
|
+
[--report-only] # v0.2.0 — this release
|
|
16
|
+
[--target-session NAME] # v0.2.0 (write path)
|
|
17
|
+
[--force] # v0.2.0 (write path)
|
|
18
|
+
[--continue] # Phase 5+
|
|
19
|
+
[--provider gemini|...] # Phase 5+
|
|
20
|
+
[--last N] # Phase 5+
|
|
21
|
+
|
|
22
|
+
v0.2.0 implements `--report-only` as the primary path. Write +
|
|
23
|
+
target-session + force land in the same release because they
|
|
24
|
+
share the cursor.json output codepath. `--continue` defers to
|
|
25
|
+
Phase 5+ (gates on REPL).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
import sys
|
|
34
|
+
from datetime import datetime, timezone
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import Optional
|
|
37
|
+
|
|
38
|
+
from swarph_cli.parsers import ClaudeParser, ImportResult
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
SUPPORTED_FORMATS = {"claude"} # gemini + llm + chatgpt-export land in 3.5+/6+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
45
|
+
p = argparse.ArgumentParser(
|
|
46
|
+
prog="swarph import",
|
|
47
|
+
description=(
|
|
48
|
+
"Import a session from another CLI into swarph-native format. "
|
|
49
|
+
"v0.2.0 ships --report-only + write paths for Claude JSONL "
|
|
50
|
+
"sessions; teleport (--continue) lands in Phase 5+."
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
p.add_argument(
|
|
54
|
+
"source_path",
|
|
55
|
+
help="Path to source session file (e.g. ~/.claude/projects/.../X.jsonl).",
|
|
56
|
+
)
|
|
57
|
+
p.add_argument(
|
|
58
|
+
"--source-format",
|
|
59
|
+
default=None,
|
|
60
|
+
choices=sorted(SUPPORTED_FORMATS),
|
|
61
|
+
help="Override source-format detection (currently only 'claude' supported).",
|
|
62
|
+
)
|
|
63
|
+
p.add_argument(
|
|
64
|
+
"--report-only",
|
|
65
|
+
action="store_true",
|
|
66
|
+
help="Print the import report and exit; do NOT write a swarph-native "
|
|
67
|
+
"session. Use this to inspect what would be imported (and what would "
|
|
68
|
+
"be lost) before committing.",
|
|
69
|
+
)
|
|
70
|
+
p.add_argument(
|
|
71
|
+
"--target-session",
|
|
72
|
+
default=None,
|
|
73
|
+
help="Custom name for the swarph-native session file (default: source "
|
|
74
|
+
"session-id or filename stem). Stored at ~/.swarph/sessions/<NAME>.jsonl.",
|
|
75
|
+
)
|
|
76
|
+
p.add_argument(
|
|
77
|
+
"--force",
|
|
78
|
+
action="store_true",
|
|
79
|
+
help="Overwrite the target session file if it already exists. Without "
|
|
80
|
+
"--force, swarph import refuses-with-error to prevent silently "
|
|
81
|
+
"destroying continuation turns added since a prior import (drop PR "
|
|
82
|
+
"#138 review concern (g)).",
|
|
83
|
+
)
|
|
84
|
+
p.add_argument(
|
|
85
|
+
"--json-report",
|
|
86
|
+
action="store_true",
|
|
87
|
+
help="Emit the import report as a JSON object on stdout instead of "
|
|
88
|
+
"human-readable text. Useful for downstream tooling.",
|
|
89
|
+
)
|
|
90
|
+
return p
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _detect_format(path: Path) -> Optional[str]:
|
|
94
|
+
"""Heuristic source-format detection. Phase 2.5 only knows 'claude'."""
|
|
95
|
+
pstr = str(path).lower()
|
|
96
|
+
if ".claude/projects/" in pstr or path.suffix == ".jsonl":
|
|
97
|
+
# Sniff first line for Claude record-type discriminator
|
|
98
|
+
try:
|
|
99
|
+
with path.open(encoding="utf-8") as f:
|
|
100
|
+
first = f.readline().strip()
|
|
101
|
+
if first:
|
|
102
|
+
rec = json.loads(first)
|
|
103
|
+
if isinstance(rec, dict) and rec.get("type") in {
|
|
104
|
+
"user", "assistant", "system", "permission-mode",
|
|
105
|
+
"file-history-snapshot", "attachment", "queue-operation",
|
|
106
|
+
}:
|
|
107
|
+
return "claude"
|
|
108
|
+
except (json.JSONDecodeError, OSError):
|
|
109
|
+
pass
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _swarph_native_path(target_session: Optional[str], result: ImportResult) -> Path:
|
|
114
|
+
"""Resolve the target swarph-native session file path."""
|
|
115
|
+
base = Path.home() / ".swarph" / "sessions"
|
|
116
|
+
base.mkdir(parents=True, exist_ok=True)
|
|
117
|
+
if target_session:
|
|
118
|
+
name = target_session
|
|
119
|
+
else:
|
|
120
|
+
name = (
|
|
121
|
+
result.metadata.get("session_id")
|
|
122
|
+
or Path(result.report.source_path).stem
|
|
123
|
+
)
|
|
124
|
+
if not name.endswith(".jsonl"):
|
|
125
|
+
name = f"{name}.jsonl"
|
|
126
|
+
return base / name
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _write_swarph_native_session(
|
|
130
|
+
out_path: Path,
|
|
131
|
+
result: ImportResult,
|
|
132
|
+
) -> None:
|
|
133
|
+
"""Write the swarph-native JSONL per PLAN.md §17.5.
|
|
134
|
+
|
|
135
|
+
First line is the metadata header; subsequent lines are
|
|
136
|
+
role+content turns with `_meta.source = "imported"` per drop's
|
|
137
|
+
PR #138 review carry-forward (f).
|
|
138
|
+
"""
|
|
139
|
+
header = {
|
|
140
|
+
"_meta": {
|
|
141
|
+
"version": "v1",
|
|
142
|
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
143
|
+
"imported_from": {
|
|
144
|
+
"format": result.report.source_format,
|
|
145
|
+
"path": result.report.source_path,
|
|
146
|
+
"session_id": result.report.session_id,
|
|
147
|
+
},
|
|
148
|
+
"import_report": result.report.to_dict(),
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
with out_path.open("w", encoding="utf-8") as f:
|
|
152
|
+
f.write(json.dumps(header, separators=(",", ":")) + "\n")
|
|
153
|
+
for m in result.messages:
|
|
154
|
+
row = {
|
|
155
|
+
"role": m.role,
|
|
156
|
+
"content": m.content,
|
|
157
|
+
"_meta": {"source": "imported"},
|
|
158
|
+
}
|
|
159
|
+
f.write(json.dumps(row, separators=(",", ":")) + "\n")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def run_import(argv: list[str]) -> int:
|
|
163
|
+
"""Entry point for the ``swarph import`` verb."""
|
|
164
|
+
parser = _build_parser()
|
|
165
|
+
args = parser.parse_args(argv)
|
|
166
|
+
|
|
167
|
+
src_path = Path(args.source_path).expanduser()
|
|
168
|
+
if not src_path.exists():
|
|
169
|
+
print(f"swarph import: source file not found: {src_path}", file=sys.stderr)
|
|
170
|
+
return 2
|
|
171
|
+
|
|
172
|
+
fmt = args.source_format or _detect_format(src_path)
|
|
173
|
+
if fmt is None:
|
|
174
|
+
print(
|
|
175
|
+
f"swarph import: could not detect source format for {src_path}; "
|
|
176
|
+
f"specify with --source-format. Supported: {sorted(SUPPORTED_FORMATS)}",
|
|
177
|
+
file=sys.stderr,
|
|
178
|
+
)
|
|
179
|
+
return 2
|
|
180
|
+
if fmt not in SUPPORTED_FORMATS:
|
|
181
|
+
print(
|
|
182
|
+
f"swarph import: source-format '{fmt}' not supported in v0.2.0; "
|
|
183
|
+
f"Phase 3.5+ adds gemini + llm; Phase 6+ adds chatgpt-export.",
|
|
184
|
+
file=sys.stderr,
|
|
185
|
+
)
|
|
186
|
+
return 2
|
|
187
|
+
|
|
188
|
+
# Parse
|
|
189
|
+
try:
|
|
190
|
+
if fmt == "claude":
|
|
191
|
+
result = ClaudeParser().parse(src_path)
|
|
192
|
+
else: # pragma: no cover — guarded above
|
|
193
|
+
raise NotImplementedError(fmt)
|
|
194
|
+
except (FileNotFoundError, ValueError) as exc:
|
|
195
|
+
print(f"swarph import: parse failed: {exc}", file=sys.stderr)
|
|
196
|
+
return 2
|
|
197
|
+
|
|
198
|
+
# Print report
|
|
199
|
+
if args.json_report:
|
|
200
|
+
print(json.dumps(result.report.to_dict(), indent=2, sort_keys=True))
|
|
201
|
+
else:
|
|
202
|
+
print(result.report.render_human())
|
|
203
|
+
|
|
204
|
+
if args.report_only:
|
|
205
|
+
return 0
|
|
206
|
+
|
|
207
|
+
# Write path — refuse-with-error default per drop's PR #138 review (g)
|
|
208
|
+
out_path = _swarph_native_path(args.target_session, result)
|
|
209
|
+
if out_path.exists() and not args.force:
|
|
210
|
+
# Show diff-summary so the operator knows what they'd be
|
|
211
|
+
# destroying — same-shape as `git commit --amend` requiring
|
|
212
|
+
# explicit intent on already-pushed commits.
|
|
213
|
+
try:
|
|
214
|
+
existing_lines = sum(1 for _ in out_path.open(encoding="utf-8"))
|
|
215
|
+
except OSError:
|
|
216
|
+
existing_lines = -1
|
|
217
|
+
new_total_lines = 1 + len(result.messages) # header + turns
|
|
218
|
+
print(
|
|
219
|
+
f"swarph import: target {out_path} already exists "
|
|
220
|
+
f"({existing_lines} existing lines vs {new_total_lines} would-be lines).\n"
|
|
221
|
+
f"Refuse-with-error default protects swarph-continuation turns "
|
|
222
|
+
f"added since the prior import.\n"
|
|
223
|
+
f"To proceed:\n"
|
|
224
|
+
f" --force overwrite (destroys continuation turns)\n"
|
|
225
|
+
f" --target-session NAME write to a different file",
|
|
226
|
+
file=sys.stderr,
|
|
227
|
+
)
|
|
228
|
+
return 3
|
|
229
|
+
|
|
230
|
+
_write_swarph_native_session(out_path, result)
|
|
231
|
+
print(f"\nwrote swarph-native session: {out_path}", file=sys.stderr)
|
|
232
|
+
return 0
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
"""``swarph`` entry-point — Phase 2 one-shot
|
|
2
|
-
|
|
3
|
-
v0.0.1 was the scaffold (banner-only). v0.1.0
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- Phase 5
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
"""``swarph`` entry-point — Phase 2 one-shot + Phase 2.5 import.
|
|
2
|
+
|
|
3
|
+
v0.0.1 was the scaffold (banner-only). v0.1.0 shipped the Phase 2
|
|
4
|
+
one-shot falsifiability gate. v0.2.0 adds the Phase 2.5 ``import``
|
|
5
|
+
verb per PLAN.md §17.6 forward-reorder.
|
|
6
|
+
|
|
7
|
+
Verb dispatch shape: if the first arg matches a known verb keyword
|
|
8
|
+
(currently ``import``), the rest of argv is passed to that verb's
|
|
9
|
+
handler. Otherwise argv is treated as the one-shot path (positional
|
|
10
|
+
prompt + flags). Phase 3+ adds ``--ask``, ``list-peers``,
|
|
11
|
+
``list-adapters``; Phase 5 adds ``chat`` REPL; Phase 5.5 adds
|
|
12
|
+
``onboard``/``ratify``; Phase 5.7 adds ``daemon``.
|
|
13
|
+
|
|
14
|
+
Disambiguation note: a literal one-shot prompt that starts with the
|
|
15
|
+
word "import" (e.g. ``swarph "import this report"``) collides with
|
|
16
|
+
the verb. Workaround: rephrase (``swarph "please import this
|
|
17
|
+
report"``) — the collision is rare and the verb takes precedence.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
@@ -36,19 +36,28 @@ swarph v{version}
|
|
|
36
36
|
|
|
37
37
|
Usage:
|
|
38
38
|
swarph "your prompt here" [--provider gemini] [--model gemini-2.5-flash]
|
|
39
|
+
swarph import <path-to-source-session> [--report-only] [--target-session NAME]
|
|
39
40
|
|
|
40
41
|
Examples:
|
|
41
42
|
swarph "explain Hawkes process briefly"
|
|
42
43
|
swarph "list 5 tickers" --json
|
|
43
|
-
swarph
|
|
44
|
+
swarph import ~/.claude/projects/.../X.jsonl --report-only
|
|
44
45
|
|
|
45
|
-
Status: Phase 2 one-shot
|
|
46
|
-
(Phase 3), onboard/ratify (Phase 5.5), daemon (Phase 5.7)
|
|
47
|
-
|
|
46
|
+
Status: Phase 2 one-shot + Phase 2.5 import ready. REPL (Phase 5),
|
|
47
|
+
--ask <peer> (Phase 3), onboard/ratify (Phase 5.5), daemon (Phase 5.7)
|
|
48
|
+
ship in subsequent releases.
|
|
48
49
|
|
|
49
50
|
Spec: https://github.com/darw007d/hedge-fund-mcp/blob/main/research/swarph_cli/PLAN.md
|
|
50
51
|
"""
|
|
51
52
|
|
|
53
|
+
# Known verb keywords that route to their own handler. Order matters
|
|
54
|
+
# only for disambiguation against one-shot prompts (rare).
|
|
55
|
+
_VERB_HANDLERS: dict[str, str] = {
|
|
56
|
+
# verb keyword: dotted-path to handler function (lazy-imported)
|
|
57
|
+
"import": "swarph_cli.commands.import_session.run_import",
|
|
58
|
+
# Future: "chat", "daemon", "onboard", "ratify", "list-peers", etc.
|
|
59
|
+
}
|
|
60
|
+
|
|
52
61
|
|
|
53
62
|
def _build_parser() -> argparse.ArgumentParser:
|
|
54
63
|
p = argparse.ArgumentParser(
|
|
@@ -209,7 +218,32 @@ async def _run_one_shot(args: argparse.Namespace) -> int:
|
|
|
209
218
|
return 0 if resp.error_class is None else 1
|
|
210
219
|
|
|
211
220
|
|
|
221
|
+
def _dispatch_verb(verb: str, verb_argv: list[str]) -> int:
|
|
222
|
+
"""Lazy-import the verb's handler and run it.
|
|
223
|
+
|
|
224
|
+
Lazy import keeps the one-shot path's import surface minimal —
|
|
225
|
+
callers who never use ``swarph import`` don't pay the parser
|
|
226
|
+
package's import cost.
|
|
227
|
+
"""
|
|
228
|
+
handler_path = _VERB_HANDLERS[verb]
|
|
229
|
+
module_path, func_name = handler_path.rsplit(".", 1)
|
|
230
|
+
import importlib
|
|
231
|
+
|
|
232
|
+
mod = importlib.import_module(module_path)
|
|
233
|
+
handler = getattr(mod, func_name)
|
|
234
|
+
return handler(verb_argv)
|
|
235
|
+
|
|
236
|
+
|
|
212
237
|
def main(argv: Optional[list[str]] = None) -> int:
|
|
238
|
+
if argv is None:
|
|
239
|
+
argv = sys.argv[1:]
|
|
240
|
+
|
|
241
|
+
# Verb dispatch — first positional arg may be a known verb keyword.
|
|
242
|
+
# If so, route the rest to that verb's handler. Otherwise fall
|
|
243
|
+
# through to the one-shot path.
|
|
244
|
+
if argv and argv[0] in _VERB_HANDLERS:
|
|
245
|
+
return _dispatch_verb(argv[0], argv[1:])
|
|
246
|
+
|
|
213
247
|
parser = _build_parser()
|
|
214
248
|
args = parser.parse_args(argv)
|
|
215
249
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Source-format parsers for ``swarph import``.
|
|
2
|
+
|
|
3
|
+
v0.1.x ships only the Claude parser (Phase 2.5 build target per
|
|
4
|
+
PLAN.md §17.6). Phase 3.5+ adds gemini-cli + Simon Willison ``llm``;
|
|
5
|
+
Phase 6+ adds chatgpt-export.
|
|
6
|
+
|
|
7
|
+
Each parser accepts a path and returns a normalized
|
|
8
|
+
:class:`ImportResult` containing:
|
|
9
|
+
|
|
10
|
+
- ``messages``: list[swarph_mesh.ChatMessage] — portable turns
|
|
11
|
+
- ``report``: ImportReport — what was kept, what was dropped, why
|
|
12
|
+
- ``metadata``: dict — source format, original session id, model, etc.
|
|
13
|
+
|
|
14
|
+
Parsers are pure (no side effects beyond filesystem read). The
|
|
15
|
+
write step (swarph-native JSONL emission) lives separately so
|
|
16
|
+
``--report-only`` skips it cleanly.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from swarph_cli.parsers.claude import (
|
|
22
|
+
ClaudeParser,
|
|
23
|
+
ImportReport,
|
|
24
|
+
ImportResult,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = ["ClaudeParser", "ImportReport", "ImportResult"]
|