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.
Files changed (22) hide show
  1. {swarph_cli-0.1.1/src/swarph_cli.egg-info → swarph_cli-0.2.0}/PKG-INFO +37 -2
  2. {swarph_cli-0.1.1 → swarph_cli-0.2.0}/README.md +36 -1
  3. {swarph_cli-0.1.1 → swarph_cli-0.2.0}/pyproject.toml +1 -1
  4. {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli/__init__.py +1 -1
  5. swarph_cli-0.2.0/src/swarph_cli/commands/__init__.py +15 -0
  6. swarph_cli-0.2.0/src/swarph_cli/commands/import_session.py +232 -0
  7. {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli/main.py +55 -21
  8. swarph_cli-0.2.0/src/swarph_cli/parsers/__init__.py +27 -0
  9. swarph_cli-0.2.0/src/swarph_cli/parsers/claude.py +340 -0
  10. {swarph_cli-0.1.1 → swarph_cli-0.2.0/src/swarph_cli.egg-info}/PKG-INFO +37 -2
  11. {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli.egg-info/SOURCES.txt +6 -0
  12. swarph_cli-0.2.0/tests/test_claude_parser.py +332 -0
  13. swarph_cli-0.2.0/tests/test_import_command.py +288 -0
  14. {swarph_cli-0.1.1 → swarph_cli-0.2.0}/LICENSE +0 -0
  15. {swarph_cli-0.1.1 → swarph_cli-0.2.0}/setup.cfg +0 -0
  16. {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli/caller.py +0 -0
  17. {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  18. {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  19. {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli.egg-info/requires.txt +0 -0
  20. {swarph_cli-0.1.1 → swarph_cli-0.2.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
  21. {swarph_cli-0.1.1 → swarph_cli-0.2.0}/tests/test_main.py +0 -0
  22. {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.1.1
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.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).
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.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).
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.1.1"
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" }
@@ -16,6 +16,6 @@ The architecture splits CLI from substrate so:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.1.1"
19
+ __version__ = "0.2.0"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -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 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.
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 "summarise" --provider gemini --model gemini-2.5-pro
44
+ swarph import ~/.claude/projects/.../X.jsonl --report-only
44
45
 
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.
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"]