codex2claude 0.1.2__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 (35) hide show
  1. codex2claude-0.1.2/LICENSE +21 -0
  2. codex2claude-0.1.2/PKG-INFO +300 -0
  3. codex2claude-0.1.2/README.md +271 -0
  4. codex2claude-0.1.2/bridge/codex2claude/__init__.py +3 -0
  5. codex2claude-0.1.2/bridge/codex2claude/__main__.py +5 -0
  6. codex2claude-0.1.2/bridge/codex2claude/claude_cli.py +96 -0
  7. codex2claude-0.1.2/bridge/codex2claude/cli.py +239 -0
  8. codex2claude-0.1.2/bridge/codex2claude/errors.py +30 -0
  9. codex2claude-0.1.2/bridge/codex2claude/locking.py +22 -0
  10. codex2claude-0.1.2/bridge/codex2claude/logging_utils.py +19 -0
  11. codex2claude-0.1.2/bridge/codex2claude/models.py +43 -0
  12. codex2claude-0.1.2/bridge/codex2claude/paths.py +35 -0
  13. codex2claude-0.1.2/bridge/codex2claude/state.py +32 -0
  14. codex2claude-0.1.2/bridge/codex2claude/threading.py +14 -0
  15. codex2claude-0.1.2/bridge/codex2claude/version.py +1 -0
  16. codex2claude-0.1.2/bridge/codex2claude.egg-info/PKG-INFO +300 -0
  17. codex2claude-0.1.2/bridge/codex2claude.egg-info/SOURCES.txt +33 -0
  18. codex2claude-0.1.2/bridge/codex2claude.egg-info/dependency_links.txt +1 -0
  19. codex2claude-0.1.2/bridge/codex2claude.egg-info/entry_points.txt +2 -0
  20. codex2claude-0.1.2/bridge/codex2claude.egg-info/top_level.txt +1 -0
  21. codex2claude-0.1.2/pyproject.toml +44 -0
  22. codex2claude-0.1.2/setup.cfg +4 -0
  23. codex2claude-0.1.2/tests/test_admin_commands.py +135 -0
  24. codex2claude-0.1.2/tests/test_ask_flow_mock.py +286 -0
  25. codex2claude-0.1.2/tests/test_claude_cli_mock.py +68 -0
  26. codex2claude-0.1.2/tests/test_cli_smoke.py +13 -0
  27. codex2claude-0.1.2/tests/test_concurrency.py +63 -0
  28. codex2claude-0.1.2/tests/test_locking.py +19 -0
  29. codex2claude-0.1.2/tests/test_package_metadata.py +11 -0
  30. codex2claude-0.1.2/tests/test_paths.py +26 -0
  31. codex2claude-0.1.2/tests/test_real_claude_cli.py +52 -0
  32. codex2claude-0.1.2/tests/test_recovery.py +64 -0
  33. codex2claude-0.1.2/tests/test_skill_docs.py +11 -0
  34. codex2claude-0.1.2/tests/test_state.py +71 -0
  35. codex2claude-0.1.2/tests/test_threading.py +14 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
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,300 @@
1
+ Metadata-Version: 2.4
2
+ Name: codex2claude
3
+ Version: 0.1.2
4
+ Summary: Local Codex-to-Claude bridge CLI
5
+ Author: OpenAI Codex
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Housetan218/codex2claude
8
+ Project-URL: Repository, https://github.com/Housetan218/codex2claude
9
+ Project-URL: Issues, https://github.com/Housetan218/codex2claude/issues
10
+ Project-URL: Releases, https://github.com/Housetan218/codex2claude/releases
11
+ Keywords: codex,claude,cli,bridge,agent
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: POSIX
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Software Development :: Build Tools
24
+ Classifier: Topic :: Utilities
25
+ Requires-Python: >=3.11
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Dynamic: license-file
29
+
30
+ # codex2claude
31
+
32
+ `codex2claude` is a local one-way bridge from Codex to Claude.
33
+
34
+ It provides:
35
+
36
+ - a reusable Python CLI bridge
37
+ - Claude session persistence via native `session_id`
38
+ - deterministic per-thread locking
39
+ - a thin Codex skill wrapper surface
40
+ - no non-stdlib Python runtime dependency inside the bridge
41
+
42
+ Current implementation target:
43
+
44
+ - macOS / POSIX environments with Python 3 and local Claude CLI access
45
+ - not designed for Windows in its current `fcntl`-based form
46
+
47
+ ## Current Status
48
+
49
+ Implemented and locally verified on this machine:
50
+
51
+ - `ask`
52
+ - `status`
53
+ - `forget`
54
+ - `gc`
55
+ - automatic resume via stored Claude `session_id`
56
+ - same-thread lock conflict handling
57
+
58
+ Fresh verification completed during implementation:
59
+
60
+ - `PYTHONPATH=bridge python3 -m unittest discover -s tests -p 'test_*.py' -v`
61
+ - real Claude new-session smoke
62
+ - real Claude resume smoke
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ python3 -m venv .venv
68
+ source .venv/bin/activate
69
+ python3 -m pip install -e .
70
+ ```
71
+
72
+ When a PyPI release is available, install with:
73
+
74
+ ```bash
75
+ python3 -m pip install codex2claude
76
+ ```
77
+
78
+ If you do not want to install it yet, you can run it directly:
79
+
80
+ ```bash
81
+ PYTHONPATH=bridge python3 -m codex2claude ask --prompt "Reply with ok only"
82
+ ```
83
+
84
+ After editable install, both of these work:
85
+
86
+ ```bash
87
+ python -m codex2claude --help
88
+ codex2claude --help
89
+ ```
90
+
91
+ ## Usage
92
+
93
+ Ask Claude in the current workspace thread:
94
+
95
+ ```bash
96
+ codex2claude ask --prompt "Review this design" --workspace "$PWD"
97
+ ```
98
+
99
+ Use a named thread:
100
+
101
+ ```bash
102
+ codex2claude ask --prompt "Continue the design review" --workspace "$PWD" --thread design
103
+ ```
104
+
105
+ Force a fresh Claude session:
106
+
107
+ ```bash
108
+ codex2claude ask --prompt "Start over" --workspace "$PWD" --new
109
+ ```
110
+
111
+ Inspect stored state:
112
+
113
+ ```bash
114
+ codex2claude status --workspace "$PWD"
115
+ ```
116
+
117
+ Forget the current thread:
118
+
119
+ ```bash
120
+ codex2claude forget --workspace "$PWD"
121
+ ```
122
+
123
+ Remove stale thread files:
124
+
125
+ ```bash
126
+ codex2claude gc --max-age-days 7
127
+ ```
128
+
129
+ ## Codex Skill
130
+
131
+ The Codex-facing wrapper lives at:
132
+
133
+ ```text
134
+ skills/codex-to-claude/SKILL.md
135
+ ```
136
+
137
+ The skill should stay thin. It should only:
138
+
139
+ - collect the user prompt
140
+ - choose default thread, named thread, or `--new`
141
+ - invoke `codex2claude`
142
+ - return stdout or surface stderr
143
+
144
+ It should not own Claude JSON parsing, session files, or retries.
145
+
146
+ Common trigger phrases that should explicitly steer Codex toward this skill:
147
+
148
+ - `Use the codex-to-claude skill`
149
+ - `ask Claude about this`
150
+ - `send this to Claude`
151
+ - `let Claude review this`
152
+ - `ask cc`
153
+ - `ask cc about this`
154
+ - `cc review this`
155
+ - `cc 怎么看`
156
+ - `cc 觉得呢`
157
+ - `cc 能帮忙看下吗`
158
+ - `问问cc`
159
+ - `问下Claude`
160
+ - `Claude 怎么看`
161
+ - `Claude 能看下吗`
162
+ - `让cc帮忙看看`
163
+ - `cc review下`
164
+ - `cc check一下`
165
+ - `give this to cc`
166
+ - `给 Claude 看看`
167
+ - `让 Claude 看一下`
168
+ - `发给 Claude`
169
+ - `给 cc 看看`
170
+ - `让 cc review 一下`
171
+ - `发给 cc`
172
+
173
+ For stability, prefer phrases where `cc` appears with an action like ask, review, check, or look. Avoid relying on bare `cc` by itself.
174
+
175
+ ## Triggering
176
+
177
+ Recommended everyday trigger phrases:
178
+
179
+ - `给cc看看这个`
180
+ - `问问cc`
181
+ - `cc怎么看`
182
+ - `让cc review一下`
183
+ - `给Claude看看`
184
+
185
+ More explicit variants:
186
+
187
+ - `Use the codex-to-claude skill`
188
+ - `ask Claude about this`
189
+ - `ask cc`
190
+ - `cc review this`
191
+ - `给 cc 看看`
192
+ - `问下Claude`
193
+
194
+ ## Activation Scope
195
+
196
+ These paths are confirmed to work:
197
+
198
+ - new Codex sessions started after the skill was installed
199
+ - `codex exec` runs started after the skill was installed
200
+ - direct `codex2claude` CLI usage
201
+
202
+ Do not assume already-open Codex sessions will hot-reload newly installed or updated skills.
203
+
204
+ If you changed trigger phrases or installed the skill during an existing session, restart Codex or open a fresh session before testing.
205
+
206
+ ## Troubleshooting
207
+
208
+ If a trigger phrase does not route to Claude:
209
+
210
+ 1. Start a new Codex session.
211
+ 2. Test with a high-signal phrase such as `问问cc:请只回复 ok`.
212
+ 3. If needed, use the most explicit form: `Use the codex-to-claude skill. Ask Claude: ...`
213
+ 4. Verify the bridge directly with `codex2claude ask --prompt "Reply with ok only" --workspace "$PWD"`.
214
+
215
+ If direct CLI usage works but a natural-language trigger does not, the issue is skill discovery in that session, not the bridge itself.
216
+
217
+ ## Threading Model
218
+
219
+ By default, one workspace maps to one Claude thread.
220
+
221
+ Use `--thread <name>` to split conversations inside the same repo:
222
+
223
+ ```bash
224
+ codex2claude ask --prompt "Review API design" --workspace "$PWD" --thread api
225
+ codex2claude ask --prompt "Review docs tone" --workspace "$PWD" --thread docs
226
+ ```
227
+
228
+ Use `--new` when you want a fresh Claude conversation for the selected thread key.
229
+
230
+ ## Environment
231
+
232
+ - `CODEX2CLAUDE_CLAUDE_BIN`: override the Claude executable path
233
+ - `CODEX2CLAUDE_HOME`: override the bridge state root without changing your real shell `HOME`
234
+ - `CODEX2CLAUDE_RUN_REAL=1`: enable opt-in real Claude integration tests
235
+
236
+ ## Test
237
+
238
+ Run the main test suite:
239
+
240
+ ```bash
241
+ PYTHONPATH=bridge python3 -m unittest discover -s tests -p 'test_*.py' -v
242
+ ```
243
+
244
+ Run the opt-in real Claude smoke test:
245
+
246
+ ```bash
247
+ PYTHONPATH=bridge CODEX2CLAUDE_RUN_REAL=1 python3 -m unittest tests.test_real_claude_cli -v
248
+ ```
249
+
250
+ The real-Claude test is opt-in because it spends actual Claude usage and requires local auth.
251
+
252
+ ## Exit Codes
253
+
254
+ - `0`: success
255
+ - `1`: Claude invocation failure or generic bridge failure
256
+ - `2`: Claude timeout
257
+ - `3`: same-thread lock conflict
258
+ - `4`: invalid arguments
259
+ - `5`: corrupted state or persistence failure
260
+
261
+ ## State Layout
262
+
263
+ ```text
264
+ ~/.codex/codex2claude/
265
+ threads/
266
+ runs/
267
+ logs/
268
+ ```
269
+
270
+ Important files:
271
+
272
+ - `threads/<thread_key>.json`: current thread state and stored Claude `session_id`
273
+ - `runs/<thread_key>/...json`: per-run artifacts
274
+ - `logs/bridge.log`: append-only bridge events
275
+
276
+ ## Current Scope
277
+
278
+ This version is intentionally one-way only:
279
+
280
+ - Codex initiates
281
+ - Claude replies
282
+ - bridge stores Claude `session_id`
283
+ - follow-up turns use native `claude --resume`
284
+
285
+ Bidirectional agent protocols are out of scope for v1.
286
+
287
+ ## References
288
+
289
+ - Design: `docs/superpowers/specs/2026-03-28-codex-to-claude-design.md`
290
+ - Plan: `docs/superpowers/plans/2026-03-28-codex-to-claude-v1.md`
291
+
292
+ ## Contributing
293
+
294
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development, verification, and release steps.
295
+
296
+ ## CI And Releases
297
+
298
+ GitHub Actions runs unit tests and packaging checks on pushes and pull requests.
299
+
300
+ PyPI publishing is wired through `.github/workflows/release.yml` and is intended to use PyPI Trusted Publishing from GitHub Actions. Repository maintainers still need to configure the matching trusted publisher entry on PyPI before tag-based publishing can succeed.
@@ -0,0 +1,271 @@
1
+ # codex2claude
2
+
3
+ `codex2claude` is a local one-way bridge from Codex to Claude.
4
+
5
+ It provides:
6
+
7
+ - a reusable Python CLI bridge
8
+ - Claude session persistence via native `session_id`
9
+ - deterministic per-thread locking
10
+ - a thin Codex skill wrapper surface
11
+ - no non-stdlib Python runtime dependency inside the bridge
12
+
13
+ Current implementation target:
14
+
15
+ - macOS / POSIX environments with Python 3 and local Claude CLI access
16
+ - not designed for Windows in its current `fcntl`-based form
17
+
18
+ ## Current Status
19
+
20
+ Implemented and locally verified on this machine:
21
+
22
+ - `ask`
23
+ - `status`
24
+ - `forget`
25
+ - `gc`
26
+ - automatic resume via stored Claude `session_id`
27
+ - same-thread lock conflict handling
28
+
29
+ Fresh verification completed during implementation:
30
+
31
+ - `PYTHONPATH=bridge python3 -m unittest discover -s tests -p 'test_*.py' -v`
32
+ - real Claude new-session smoke
33
+ - real Claude resume smoke
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ python3 -m venv .venv
39
+ source .venv/bin/activate
40
+ python3 -m pip install -e .
41
+ ```
42
+
43
+ When a PyPI release is available, install with:
44
+
45
+ ```bash
46
+ python3 -m pip install codex2claude
47
+ ```
48
+
49
+ If you do not want to install it yet, you can run it directly:
50
+
51
+ ```bash
52
+ PYTHONPATH=bridge python3 -m codex2claude ask --prompt "Reply with ok only"
53
+ ```
54
+
55
+ After editable install, both of these work:
56
+
57
+ ```bash
58
+ python -m codex2claude --help
59
+ codex2claude --help
60
+ ```
61
+
62
+ ## Usage
63
+
64
+ Ask Claude in the current workspace thread:
65
+
66
+ ```bash
67
+ codex2claude ask --prompt "Review this design" --workspace "$PWD"
68
+ ```
69
+
70
+ Use a named thread:
71
+
72
+ ```bash
73
+ codex2claude ask --prompt "Continue the design review" --workspace "$PWD" --thread design
74
+ ```
75
+
76
+ Force a fresh Claude session:
77
+
78
+ ```bash
79
+ codex2claude ask --prompt "Start over" --workspace "$PWD" --new
80
+ ```
81
+
82
+ Inspect stored state:
83
+
84
+ ```bash
85
+ codex2claude status --workspace "$PWD"
86
+ ```
87
+
88
+ Forget the current thread:
89
+
90
+ ```bash
91
+ codex2claude forget --workspace "$PWD"
92
+ ```
93
+
94
+ Remove stale thread files:
95
+
96
+ ```bash
97
+ codex2claude gc --max-age-days 7
98
+ ```
99
+
100
+ ## Codex Skill
101
+
102
+ The Codex-facing wrapper lives at:
103
+
104
+ ```text
105
+ skills/codex-to-claude/SKILL.md
106
+ ```
107
+
108
+ The skill should stay thin. It should only:
109
+
110
+ - collect the user prompt
111
+ - choose default thread, named thread, or `--new`
112
+ - invoke `codex2claude`
113
+ - return stdout or surface stderr
114
+
115
+ It should not own Claude JSON parsing, session files, or retries.
116
+
117
+ Common trigger phrases that should explicitly steer Codex toward this skill:
118
+
119
+ - `Use the codex-to-claude skill`
120
+ - `ask Claude about this`
121
+ - `send this to Claude`
122
+ - `let Claude review this`
123
+ - `ask cc`
124
+ - `ask cc about this`
125
+ - `cc review this`
126
+ - `cc 怎么看`
127
+ - `cc 觉得呢`
128
+ - `cc 能帮忙看下吗`
129
+ - `问问cc`
130
+ - `问下Claude`
131
+ - `Claude 怎么看`
132
+ - `Claude 能看下吗`
133
+ - `让cc帮忙看看`
134
+ - `cc review下`
135
+ - `cc check一下`
136
+ - `give this to cc`
137
+ - `给 Claude 看看`
138
+ - `让 Claude 看一下`
139
+ - `发给 Claude`
140
+ - `给 cc 看看`
141
+ - `让 cc review 一下`
142
+ - `发给 cc`
143
+
144
+ For stability, prefer phrases where `cc` appears with an action like ask, review, check, or look. Avoid relying on bare `cc` by itself.
145
+
146
+ ## Triggering
147
+
148
+ Recommended everyday trigger phrases:
149
+
150
+ - `给cc看看这个`
151
+ - `问问cc`
152
+ - `cc怎么看`
153
+ - `让cc review一下`
154
+ - `给Claude看看`
155
+
156
+ More explicit variants:
157
+
158
+ - `Use the codex-to-claude skill`
159
+ - `ask Claude about this`
160
+ - `ask cc`
161
+ - `cc review this`
162
+ - `给 cc 看看`
163
+ - `问下Claude`
164
+
165
+ ## Activation Scope
166
+
167
+ These paths are confirmed to work:
168
+
169
+ - new Codex sessions started after the skill was installed
170
+ - `codex exec` runs started after the skill was installed
171
+ - direct `codex2claude` CLI usage
172
+
173
+ Do not assume already-open Codex sessions will hot-reload newly installed or updated skills.
174
+
175
+ If you changed trigger phrases or installed the skill during an existing session, restart Codex or open a fresh session before testing.
176
+
177
+ ## Troubleshooting
178
+
179
+ If a trigger phrase does not route to Claude:
180
+
181
+ 1. Start a new Codex session.
182
+ 2. Test with a high-signal phrase such as `问问cc:请只回复 ok`.
183
+ 3. If needed, use the most explicit form: `Use the codex-to-claude skill. Ask Claude: ...`
184
+ 4. Verify the bridge directly with `codex2claude ask --prompt "Reply with ok only" --workspace "$PWD"`.
185
+
186
+ If direct CLI usage works but a natural-language trigger does not, the issue is skill discovery in that session, not the bridge itself.
187
+
188
+ ## Threading Model
189
+
190
+ By default, one workspace maps to one Claude thread.
191
+
192
+ Use `--thread <name>` to split conversations inside the same repo:
193
+
194
+ ```bash
195
+ codex2claude ask --prompt "Review API design" --workspace "$PWD" --thread api
196
+ codex2claude ask --prompt "Review docs tone" --workspace "$PWD" --thread docs
197
+ ```
198
+
199
+ Use `--new` when you want a fresh Claude conversation for the selected thread key.
200
+
201
+ ## Environment
202
+
203
+ - `CODEX2CLAUDE_CLAUDE_BIN`: override the Claude executable path
204
+ - `CODEX2CLAUDE_HOME`: override the bridge state root without changing your real shell `HOME`
205
+ - `CODEX2CLAUDE_RUN_REAL=1`: enable opt-in real Claude integration tests
206
+
207
+ ## Test
208
+
209
+ Run the main test suite:
210
+
211
+ ```bash
212
+ PYTHONPATH=bridge python3 -m unittest discover -s tests -p 'test_*.py' -v
213
+ ```
214
+
215
+ Run the opt-in real Claude smoke test:
216
+
217
+ ```bash
218
+ PYTHONPATH=bridge CODEX2CLAUDE_RUN_REAL=1 python3 -m unittest tests.test_real_claude_cli -v
219
+ ```
220
+
221
+ The real-Claude test is opt-in because it spends actual Claude usage and requires local auth.
222
+
223
+ ## Exit Codes
224
+
225
+ - `0`: success
226
+ - `1`: Claude invocation failure or generic bridge failure
227
+ - `2`: Claude timeout
228
+ - `3`: same-thread lock conflict
229
+ - `4`: invalid arguments
230
+ - `5`: corrupted state or persistence failure
231
+
232
+ ## State Layout
233
+
234
+ ```text
235
+ ~/.codex/codex2claude/
236
+ threads/
237
+ runs/
238
+ logs/
239
+ ```
240
+
241
+ Important files:
242
+
243
+ - `threads/<thread_key>.json`: current thread state and stored Claude `session_id`
244
+ - `runs/<thread_key>/...json`: per-run artifacts
245
+ - `logs/bridge.log`: append-only bridge events
246
+
247
+ ## Current Scope
248
+
249
+ This version is intentionally one-way only:
250
+
251
+ - Codex initiates
252
+ - Claude replies
253
+ - bridge stores Claude `session_id`
254
+ - follow-up turns use native `claude --resume`
255
+
256
+ Bidirectional agent protocols are out of scope for v1.
257
+
258
+ ## References
259
+
260
+ - Design: `docs/superpowers/specs/2026-03-28-codex-to-claude-design.md`
261
+ - Plan: `docs/superpowers/plans/2026-03-28-codex-to-claude-v1.md`
262
+
263
+ ## Contributing
264
+
265
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development, verification, and release steps.
266
+
267
+ ## CI And Releases
268
+
269
+ GitHub Actions runs unit tests and packaging checks on pushes and pull requests.
270
+
271
+ PyPI publishing is wired through `.github/workflows/release.yml` and is intended to use PyPI Trusted Publishing from GitHub Actions. Repository maintainers still need to configure the matching trusted publisher entry on PyPI before tag-based publishing can succeed.
@@ -0,0 +1,3 @@
1
+ from .version import __version__
2
+
3
+ __all__ = ["__version__"]
@@ -0,0 +1,5 @@
1
+ from .cli import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import subprocess
5
+ from dataclasses import dataclass
6
+
7
+ from .errors import ClaudeInvocationError, ClaudeTimeoutError, StateCorruptionError
8
+
9
+
10
+ @dataclass(slots=True)
11
+ class ClaudeResult:
12
+ session_id: str | None
13
+ result_text: str
14
+ raw_payload: dict[str, object]
15
+ stderr_text: str
16
+
17
+
18
+ def build_claude_command(prompt: str, session_id: str | None, claude_bin: str = "claude") -> list[str]:
19
+ command = [claude_bin]
20
+ if session_id:
21
+ command.extend(["--resume", session_id])
22
+ command.extend(["-p", prompt, "--output-format", "json"])
23
+ return command
24
+
25
+
26
+ def parse_claude_response(stdout: str, stderr: str = "") -> ClaudeResult:
27
+ try:
28
+ payload = json.loads(stdout)
29
+ except json.JSONDecodeError as exc:
30
+ raise StateCorruptionError("Claude returned malformed JSON") from exc
31
+
32
+ result_text = payload.get("result")
33
+ if not isinstance(result_text, str):
34
+ raise StateCorruptionError("Claude JSON missing string result")
35
+
36
+ if payload.get("is_error") is True:
37
+ raise ClaudeInvocationError(result_text)
38
+
39
+ session_id = payload.get("session_id")
40
+ if session_id is not None and not isinstance(session_id, str):
41
+ raise StateCorruptionError("Claude JSON returned invalid session_id")
42
+ if isinstance(session_id, str) and not session_id:
43
+ raise StateCorruptionError("Claude JSON returned empty session_id")
44
+
45
+ return ClaudeResult(
46
+ session_id=session_id,
47
+ result_text=result_text,
48
+ raw_payload=payload,
49
+ stderr_text=stderr,
50
+ )
51
+
52
+
53
+ def invoke_claude(
54
+ prompt: str,
55
+ session_id: str | None,
56
+ timeout_seconds: int,
57
+ cwd: str | None = None,
58
+ claude_bin: str = "claude",
59
+ ) -> ClaudeResult:
60
+ command = build_claude_command(prompt=prompt, session_id=session_id, claude_bin=claude_bin)
61
+ try:
62
+ completed = subprocess.run(
63
+ command,
64
+ check=False,
65
+ capture_output=True,
66
+ text=True,
67
+ timeout=timeout_seconds,
68
+ cwd=cwd,
69
+ )
70
+ except FileNotFoundError as exc:
71
+ raise ClaudeInvocationError(f"Claude CLI not found: {claude_bin}") from exc
72
+ except subprocess.TimeoutExpired as exc:
73
+ raise ClaudeTimeoutError(f"Claude timed out after {timeout_seconds}s") from exc
74
+
75
+ if completed.returncode != 0:
76
+ stderr = completed.stderr.strip() or completed.stdout.strip() or "unknown Claude failure"
77
+ raise ClaudeInvocationError(stderr)
78
+
79
+ return parse_claude_response(completed.stdout, completed.stderr)
80
+
81
+
82
+ def read_claude_version(claude_bin: str = "claude") -> str | None:
83
+ try:
84
+ completed = subprocess.run(
85
+ [claude_bin, "--version"],
86
+ check=False,
87
+ capture_output=True,
88
+ text=True,
89
+ timeout=20,
90
+ )
91
+ except (FileNotFoundError, subprocess.TimeoutExpired):
92
+ return None
93
+ if completed.returncode != 0:
94
+ return None
95
+ version = completed.stdout.strip() or completed.stderr.strip()
96
+ return version or None