ccx-native 0.0.1 → 0.2.0
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.
- package/LICENSE +21 -0
- package/README.md +240 -4
- package/bin/ccx +55 -2
- package/ccx/__init__.py +167 -0
- package/ccx/__main__.py +28 -0
- package/ccx/claude/__init__.py +0 -0
- package/ccx/claude/analytics.py +466 -0
- package/ccx/claude/attach.py +807 -0
- package/ccx/claude/context.py +465 -0
- package/ccx/claude/delivery.py +806 -0
- package/ccx/claude/external.py +253 -0
- package/ccx/claude/host/__init__.py +0 -0
- package/ccx/claude/host/events.py +297 -0
- package/ccx/claude/host/hooks.py +438 -0
- package/ccx/claude/host/pty.py +269 -0
- package/ccx/claude/host/session.py +5244 -0
- package/ccx/claude/host/subagents.py +169 -0
- package/ccx/claude/host/successor.py +267 -0
- package/ccx/claude/managed_turns.py +240 -0
- package/ccx/claude/oauth.py +176 -0
- package/ccx/claude/settings.py +339 -0
- package/ccx/claude/spawn.py +557 -0
- package/ccx/claude/tasks.py +72 -0
- package/ccx/claude/telemetry.py +282 -0
- package/ccx/claude/transcript.py +2535 -0
- package/ccx/claude/usage.py +796 -0
- package/ccx/claude/usage_control.py +204 -0
- package/ccx/cli/__init__.py +0 -0
- package/ccx/cli/codex.py +1009 -0
- package/ccx/cli/commands/__init__.py +0 -0
- package/ccx/cli/commands/_common.py +407 -0
- package/ccx/cli/commands/control.py +161 -0
- package/ccx/cli/commands/create.py +669 -0
- package/ccx/cli/commands/models.py +53 -0
- package/ccx/cli/commands/read.py +1104 -0
- package/ccx/cli/commands/turns.py +1081 -0
- package/ccx/cli/controller.py +757 -0
- package/ccx/cli/main.py +302 -0
- package/ccx/cli/parser.py +1439 -0
- package/ccx/cli/skills.py +215 -0
- package/ccx/cli/statusline.py +160 -0
- package/ccx/client.py +1904 -0
- package/ccx/codex/__init__.py +0 -0
- package/ccx/codex/attach_tty.py +267 -0
- package/ccx/codex/client.py +738 -0
- package/ccx/codex/completion.py +121 -0
- package/ccx/codex/control.py +288 -0
- package/ccx/codex/daemon/__init__.py +0 -0
- package/ccx/codex/daemon/__main__.py +4 -0
- package/ccx/codex/daemon/daemon.py +1558 -0
- package/ccx/codex/daemon/models.py +140 -0
- package/ccx/codex/daemon/native.py +1590 -0
- package/ccx/codex/daemon/release.py +367 -0
- package/ccx/codex/daemon/worker.py +2424 -0
- package/ccx/codex/delivery.py +758 -0
- package/ccx/codex/delivery_recovery.py +232 -0
- package/ccx/codex/evidence.py +827 -0
- package/ccx/codex/history.py +158 -0
- package/ccx/codex/history_store.py +131 -0
- package/ccx/codex/notify_history.py +140 -0
- package/ccx/codex/ops.py +666 -0
- package/ccx/codex/paths.py +70 -0
- package/ccx/codex/protocol.py +805 -0
- package/ccx/codex/provenance.py +105 -0
- package/ccx/codex/read.py +29 -0
- package/ccx/codex/records.py +51 -0
- package/ccx/codex/reports.py +177 -0
- package/ccx/codex/runtime.py +1454 -0
- package/ccx/codex/settle.py +363 -0
- package/ccx/codex/telemetry.py +792 -0
- package/ccx/codex/types.py +659 -0
- package/ccx/codex/worker_environment.py +25 -0
- package/ccx/constants.py +288 -0
- package/ccx/controllers/__init__.py +0 -0
- package/ccx/controllers/controller.py +1449 -0
- package/ccx/controllers/live_vars.py +1706 -0
- package/ccx/controllers/live_vars_runner.py +216 -0
- package/ccx/controllers/notify.py +213 -0
- package/ccx/controllers/orchestrator.py +193 -0
- package/ccx/controllers/policy.py +57 -0
- package/ccx/controllers/store.py +681 -0
- package/ccx/errors.py +634 -0
- package/ccx/models.py +225 -0
- package/ccx/notify/__init__.py +0 -0
- package/ccx/notify/__main__.py +5 -0
- package/ccx/notify/client.py +116 -0
- package/ccx/notify/daemon.py +480 -0
- package/ccx/notify/deliver.py +304 -0
- package/ccx/notify/paths.py +51 -0
- package/ccx/notify/registry.py +649 -0
- package/ccx/notify/select.py +750 -0
- package/ccx/notify/settle.py +684 -0
- package/ccx/ops/__init__.py +0 -0
- package/ccx/ops/background.py +115 -0
- package/ccx/ops/cancel.py +296 -0
- package/ccx/ops/codex_import.py +96 -0
- package/ccx/ops/common.py +397 -0
- package/ccx/ops/compact.py +337 -0
- package/ccx/ops/compact_ops.py +550 -0
- package/ccx/ops/context_events.py +48 -0
- package/ccx/ops/conversation.py +1593 -0
- package/ccx/ops/conversation_paging.py +1339 -0
- package/ccx/ops/create.py +56 -0
- package/ccx/ops/creation.py +952 -0
- package/ccx/ops/creation_steps.py +575 -0
- package/ccx/ops/event_feed.py +710 -0
- package/ccx/ops/forking.py +711 -0
- package/ccx/ops/history.py +1041 -0
- package/ccx/ops/input.py +556 -0
- package/ccx/ops/maintenance.py +295 -0
- package/ccx/ops/report.py +773 -0
- package/ccx/ops/semantics.py +490 -0
- package/ccx/ops/send.py +961 -0
- package/ccx/ops/steer_ops.py +867 -0
- package/ccx/ops/terminate.py +383 -0
- package/ccx/ops/turn_reports.py +348 -0
- package/ccx/ops/wait.py +762 -0
- package/ccx/ops/worker_events.py +640 -0
- package/ccx/rpc/__init__.py +0 -0
- package/ccx/rpc/local_rpc.py +1602 -0
- package/ccx/rpc/rpc.py +559 -0
- package/ccx/rpc/serve.py +374 -0
- package/ccx/rpc/transport.py +409 -0
- package/ccx/rpc/view.py +125 -0
- package/ccx/store/__init__.py +0 -0
- package/ccx/store/discovery.py +170 -0
- package/ccx/store/identity.py +105 -0
- package/ccx/store/journal_io.py +79 -0
- package/ccx/store/liveness.py +194 -0
- package/ccx/store/paths.py +250 -0
- package/ccx/store/record_cache.py +40 -0
- package/ccx/store/records.py +309 -0
- package/ccx/store/root.py +789 -0
- package/ccx/store/runtime_presence.py +137 -0
- package/ccx/store/store.py +2332 -0
- package/ccx/store/worker_state.py +86 -0
- package/ccx/system/__init__.py +0 -0
- package/ccx/system/bounded.py +296 -0
- package/ccx/system/cwd_paths.py +25 -0
- package/ccx/system/engine_binary.py +59 -0
- package/ccx/system/executable.py +50 -0
- package/ccx/system/oslayer.py +667 -0
- package/ccx/system/pipes.py +46 -0
- package/ccx/system/private_json.py +54 -0
- package/ccx/system/sanitize.py +43 -0
- package/ccx/types.py +809 -0
- package/ccx/version.py +29 -0
- package/ccx/workers.py +2093 -0
- package/ccx.py +9 -0
- package/package.json +39 -7
- package/pyproject.toml +87 -0
- package/skills/ccx/SKILL.md +166 -0
- package/skills/ccx/references/controllers.md +244 -0
- package/skills/ccx/references/history.md +50 -0
- package/skills/ccx/references/troubleshooting.md +94 -0
- package/skills/ccx/references/variables.md +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fernando
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,5 +1,241 @@
|
|
|
1
|
-
# ccx
|
|
1
|
+
# ccx
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
**Run native Claude Code and Codex as persistent shared workers.**
|
|
4
|
+
|
|
5
|
+
You, your agents and your scripts can all start, message, steer and check on the
|
|
6
|
+
same workers. They run on your own Claude and ChatGPT subscriptions with your
|
|
7
|
+
existing setup, and you can open any of them live with `ccx attach`.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g ccx-native
|
|
11
|
+
ccx skills install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
[▶ Examples](#examples) · [Docs](docs/getting-started.md) · Unofficial: read
|
|
15
|
+
[Risks and system changes](#risks-and-system-changes).
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Most work needs five commands, the same for Claude Code and Codex:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
ccx new --engine claude "Implement the plan in PLAN.md" # prints claude:1a2b3c4d-...
|
|
23
|
+
ccx wait 1a2b3c4d
|
|
24
|
+
ccx report 1a2b3c4d
|
|
25
|
+
ccx send 1a2b3c4d "Also update the changelog"
|
|
26
|
+
ccx attach 1a2b3c4d
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Use `--engine codex` for a Codex worker. Replace `1a2b3c4d` with a unique prefix
|
|
30
|
+
of at least 8 characters from the id `ccx new` prints. `ccx help` lists the other
|
|
31
|
+
commands.
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- **Let Codex run Claude Code.** Codex starts Claude Code workers, is woken when
|
|
36
|
+
they finish, reviews their work and sends fixes back. Claude Code can drive
|
|
37
|
+
Codex workers the same way.
|
|
38
|
+
- **Runs the official Claude Code and Codex.** ccx drives the unmodified
|
|
39
|
+
`claude` program through a terminal (PTY), as you would, and `codex` through
|
|
40
|
+
its app-server. Workers are signed in as you and run on your Claude or ChatGPT
|
|
41
|
+
subscription, with no API keys.
|
|
42
|
+
- **Keep your setup.** Your config, `CLAUDE.md`/`AGENTS.md`, hooks, skills and
|
|
43
|
+
MCP servers all apply. Start a worker in any folder, which a subagent can't do.
|
|
44
|
+
- **Join any session live.** `ccx attach` opens a worker in the engine's own
|
|
45
|
+
interface while agents keep driving it. `ccx import` brings in a session you
|
|
46
|
+
started yourself.
|
|
47
|
+
- **Find and resume workers.** Workers keep their history across turns and
|
|
48
|
+
restarts, stop when idle and resume on the next message. `ccx list` finds
|
|
49
|
+
them; `ccx peek` shows what one is doing.
|
|
50
|
+
- **Keep long work going.** Steer a running turn, compact a worker's context
|
|
51
|
+
between steps, and stop work at a usage cap.
|
|
52
|
+
- **Build on it.** A [Python API](docs/python/README.md),
|
|
53
|
+
[controllers](docs/controllers.md) and a local [JSON protocol](docs/rpc/README.md)
|
|
54
|
+
put workers inside your own tools, dashboards and services.
|
|
55
|
+
|
|
56
|
+
## Examples
|
|
57
|
+
|
|
58
|
+
### Codex drives Claude Code and Codex workers
|
|
59
|
+
|
|
60
|
+
https://github.com/user-attachments/assets/661f5247-7614-40e8-866b-b8e9742b158b
|
|
61
|
+
|
|
62
|
+
I asked Codex for a Claude worker to implement a design, then for a Codex worker
|
|
63
|
+
to check the proxy setup in parallel. Left: the Codex app. Right: its terminal,
|
|
64
|
+
where I attach to the workers and open the dashboard.
|
|
65
|
+
|
|
66
|
+
Watch for: 0:33 Claude finishes and Codex wakes up · 1:29 Codex tells Claude about
|
|
67
|
+
the Codex worker · 1:45 Codex compacts Claude before the next step.
|
|
68
|
+
|
|
69
|
+
### Native Claude Code and Codex, one Python API
|
|
70
|
+
|
|
71
|
+
https://github.com/user-attachments/assets/849efe10-4f8c-457d-a08e-eea7bf17af17
|
|
72
|
+
|
|
73
|
+
Right: a web chat built on the ccx Python API. Left: the same Claude Code session,
|
|
74
|
+
open with `ccx attach`. Only the chat is used: Claude runs the slow tests in the
|
|
75
|
+
background and has a Codex worker review its changes.
|
|
76
|
+
|
|
77
|
+
## Controllers
|
|
78
|
+
|
|
79
|
+
A controller is a Python script that runs a multi-step flow across workers. It
|
|
80
|
+
can stop at a checkpoint for the owner's decision, then resume without redoing
|
|
81
|
+
finished steps.
|
|
82
|
+
|
|
83
|
+
```mermaid
|
|
84
|
+
%%{init: {"sequence": {"mirrorActors": false}}}%%
|
|
85
|
+
sequenceDiagram
|
|
86
|
+
participant O as Owner<br/>(you or your agent)
|
|
87
|
+
participant C as Controller
|
|
88
|
+
participant CL as Claude worker
|
|
89
|
+
participant CX as Codex worker
|
|
90
|
+
O->>C: ccx controller run deploy.py --notify-parent
|
|
91
|
+
rect rgba(201, 100, 66, 0.12)
|
|
92
|
+
C->>CL: design template.yaml
|
|
93
|
+
C->>CX: security review
|
|
94
|
+
CX-->>C: findings
|
|
95
|
+
C->>CL: fix the findings
|
|
96
|
+
C->>CL: write the runbook, create the change set
|
|
97
|
+
end
|
|
98
|
+
C-->>O: report: change set ready (wakes the owner)
|
|
99
|
+
Note over C: The controller stops here
|
|
100
|
+
O->>C: ccx controller resume deploy --note "Go"
|
|
101
|
+
Note over C: Finished steps are skipped
|
|
102
|
+
rect rgba(59, 111, 182, 0.12)
|
|
103
|
+
C->>CL: deploy the change set, with the owner's note
|
|
104
|
+
C->>CX: verify the live stack
|
|
105
|
+
C->>CL: write the ops notes
|
|
106
|
+
end
|
|
107
|
+
C-->>O: final report (wakes the owner)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from ccx import WorkerClient, controller, current, task
|
|
112
|
+
|
|
113
|
+
workers = WorkerClient()
|
|
114
|
+
|
|
115
|
+
@task
|
|
116
|
+
def security_review(): # a fresh Codex worker returns its findings
|
|
117
|
+
found = {"issues": None}
|
|
118
|
+
workers.create(engine="codex", model="gpt-6.1-sol", cwd=str(current.workspace),
|
|
119
|
+
prompt=REVIEW_PROMPT, variables=found, wait=True)
|
|
120
|
+
return found["issues"]
|
|
121
|
+
|
|
122
|
+
# design, fix, prepare, deploy, verify and handover are tasks like this one.
|
|
123
|
+
|
|
124
|
+
@controller(name="deploy")
|
|
125
|
+
def main():
|
|
126
|
+
design() # Claude writes template.yaml
|
|
127
|
+
issues = security_review()
|
|
128
|
+
if issues:
|
|
129
|
+
fix(issues) # the same Claude worker fixes them
|
|
130
|
+
change_set = prepare() # Claude: runbook + change set
|
|
131
|
+
if not current.inputs: # checkpoint: stop until the owner resumes
|
|
132
|
+
return current.report(status="awaiting-approval", checkpoint="deploy",
|
|
133
|
+
summary="Change set ready. Resume with a note to deploy.",
|
|
134
|
+
data={"change_set": change_set})
|
|
135
|
+
deploy(change_set, current.inputs[0].comment) # Claude follows the runbook
|
|
136
|
+
verify() # Codex smoke-tests the live stack
|
|
137
|
+
handover() # Claude writes the ops notes
|
|
138
|
+
current.report(status="completed", summary="Deployed, verified and documented.")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
[Controllers](docs/controllers.md) explains resume and has runnable examples.
|
|
142
|
+
|
|
143
|
+
## ccx and the Agent SDK
|
|
144
|
+
|
|
145
|
+
The Claude Agent SDK runs the Claude Code binary from Python or TypeScript.
|
|
146
|
+
The program that starts an SDK session drives it while it is live; other
|
|
147
|
+
processes have no built-in way to inspect or drive that live session. You can
|
|
148
|
+
resume it later with `claude --resume <session-id>`.
|
|
149
|
+
|
|
150
|
+
ccx provides a shared worker lifecycle for Claude Code and Codex:
|
|
151
|
+
|
|
152
|
+
- **Shared, live workers.** A worker has a durable id independent of whoever
|
|
153
|
+
started it. Any agent, shell, Python process or dashboard can inspect,
|
|
154
|
+
message or steer it, or open it live with `ccx attach`.
|
|
155
|
+
- **Every operation is a command.** Any agent that can run a shell command
|
|
156
|
+
can drive workers. Python and RPC interfaces are available too.
|
|
157
|
+
- **Built-in lifecycle.** Send a new message or steer a running turn. Questions
|
|
158
|
+
and approvals go to whoever is driving. Idle workers stop and revive on the
|
|
159
|
+
next message. A Codex parent is notified when its Claude worker finishes.
|
|
160
|
+
The same lifecycle covers Claude Code and Codex.
|
|
161
|
+
|
|
162
|
+
The SDK and ccx serve different layers and can complement each other in the
|
|
163
|
+
same workflow.
|
|
164
|
+
|
|
165
|
+
## Install
|
|
166
|
+
|
|
167
|
+
You need:
|
|
168
|
+
|
|
169
|
+
- Linux, macOS or WSL2, with Python 3.11 or newer
|
|
170
|
+
- [Claude Code](https://docs.claude.com/en/docs/claude-code),
|
|
171
|
+
[Codex](https://github.com/openai/codex) or both, installed and signed in
|
|
172
|
+
|
|
173
|
+
Install ccx with whichever tool you like:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
npm install -g ccx-native # needs Python 3.11+ on your PATH
|
|
177
|
+
uv tool install ccx-native
|
|
178
|
+
pipx install ccx-native
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The package is `ccx-native`; the command is `ccx`.
|
|
182
|
+
|
|
183
|
+
Optionally, teach your agents to use it:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
ccx skills install
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
This adds the ccx skill to Claude Code and Codex (`~/.claude/skills`,
|
|
190
|
+
`~/.agents/skills`) and keeps it current when you upgrade. Or use
|
|
191
|
+
[skills.sh](https://skills.sh):
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
npx skills add ccx-native/ccx -g
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Tested with Claude Code 2.1.316 and Codex 0.166 on WSL2, macOS 12 (Intel) and
|
|
198
|
+
macOS 27 (Apple silicon). If `ccx` isn't found after installing, see
|
|
199
|
+
[Installing](docs/troubleshooting.md#installing).
|
|
200
|
+
|
|
201
|
+
## Risks and system changes
|
|
202
|
+
|
|
203
|
+
> [!IMPORTANT]
|
|
204
|
+
> - **Unofficial.** ccx is an independent project, not affiliated with or
|
|
205
|
+
> endorsed by Anthropic or OpenAI. Using Claude Code and Codex through it is
|
|
206
|
+
> still subject to their terms.
|
|
207
|
+
> - **It can break on updates.** ccx drives Claude Code's interactive interface
|
|
208
|
+
> and hooks, and Codex's app-server protocol. Neither is a stable public API,
|
|
209
|
+
> so a new release of either can break ccx until it adapts.
|
|
210
|
+
> - **It spends your quota.** Workers run unattended on your Claude and Codex
|
|
211
|
+
> plans. Usage caps (`--cap-5h`, `--cap-7d`) help, but they're not exact.
|
|
212
|
+
> - **Workers have your full user privileges by default**: they can read, write
|
|
213
|
+
> and run anything you can, not only inside their folder. Use
|
|
214
|
+
> `--permissions restricted` for permission prompts and Codex sandboxing.
|
|
215
|
+
> - **It changes a few things on your machine:** it marks worker folders as
|
|
216
|
+
> trusted in `~/.claude.json` (`ccx untrust` undoes it), leaves background
|
|
217
|
+
> processes running (`ccx shutdown` stops them), and adds `python`/`python3`
|
|
218
|
+
> shell functions inside worker shells for [variables](docs/variables/README.md).
|
|
219
|
+
> [What ccx changes on your machine](docs/machine-changes.md) has the full list.
|
|
220
|
+
|
|
221
|
+
## Docs
|
|
222
|
+
|
|
223
|
+
Start with [Getting started](docs/getting-started.md) and
|
|
224
|
+
[Concepts](docs/concepts.md). The rest:
|
|
225
|
+
[Permissions](docs/permissions.md) ·
|
|
226
|
+
[Usage and caps](docs/usage.md) ·
|
|
227
|
+
[Codex workers](docs/codex.md) ·
|
|
228
|
+
[Controllers](docs/controllers.md) ·
|
|
229
|
+
[Variables](docs/variables/README.md) ·
|
|
230
|
+
[Troubleshooting](docs/troubleshooting.md) ·
|
|
231
|
+
[What ccx changes on your machine](docs/machine-changes.md) ·
|
|
232
|
+
[Python API](docs/python/README.md) ·
|
|
233
|
+
[RPC](docs/rpc/README.md) ·
|
|
234
|
+
[Architecture](docs/architecture.md).
|
|
235
|
+
`ccx help COMMAND` has the details for each command.
|
|
236
|
+
|
|
237
|
+
## Contributing, security, license
|
|
238
|
+
|
|
239
|
+
Small fixes are welcome as pull requests; for new features, open an issue
|
|
240
|
+
instead. See [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md).
|
|
241
|
+
Released under the [MIT License](LICENSE).
|
package/bin/ccx
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
// Plain old syntax on purpose: an outdated distro Node should still reach the
|
|
3
|
+
// Python check below instead of failing to parse this file.
|
|
4
|
+
const { spawnSync } = require("child_process");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const script = path.resolve(__dirname, "..", "ccx.py");
|
|
8
|
+
// Homebrew's versioned formulas and uv-installed Pythons often only provide
|
|
9
|
+
// a versioned name, so try those after python3.
|
|
10
|
+
const candidates = process.env.PYTHON ? [process.env.PYTHON] : [
|
|
11
|
+
"python3", "python3.15", "python3.14", "python3.13", "python3.12", "python3.11", "python"
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
// ccx needs 3.11+; an older interpreter would fail with a SyntaxError or a
|
|
15
|
+
// missing tomllib instead of saying so.
|
|
16
|
+
const versionProbe = "import sys; print('%d.%d' % sys.version_info[:2]); sys.exit(sys.version_info < (3, 11))";
|
|
17
|
+
|
|
18
|
+
let lastError = null;
|
|
19
|
+
const tooOld = [];
|
|
20
|
+
for (const python of candidates) {
|
|
21
|
+
const probe = spawnSync(python, ["-c", versionProbe], { encoding: "utf8" });
|
|
22
|
+
if (probe.error && probe.error.code === "ENOENT") {
|
|
23
|
+
lastError = probe.error;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (!probe.error && probe.status !== 0) {
|
|
27
|
+
tooOld.push(`${python} is Python ${(probe.stdout || "").trim() || "unknown"}`);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const result = spawnSync(python, [script, ...process.argv.slice(2)], {
|
|
31
|
+
stdio: "inherit"
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (result.error) {
|
|
35
|
+
if (result.error.code === "ENOENT") {
|
|
36
|
+
lastError = result.error;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
console.error(`ccx: failed to run ${python}: ${result.error.message}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (result.signal) {
|
|
44
|
+
process.kill(process.pid, result.signal);
|
|
45
|
+
}
|
|
46
|
+
process.exit(result.status === null ? 0 : result.status);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.error(
|
|
50
|
+
"ccx: Python 3.11+ is required" + (tooOld.length ? ` (${tooOld.join("; ")})` : "") +
|
|
51
|
+
". Set PYTHON=/path/to/python if needed."
|
|
52
|
+
);
|
|
53
|
+
if (lastError && process.env.CCX_DEBUG) {
|
|
54
|
+
console.error(lastError.message);
|
|
55
|
+
}
|
|
56
|
+
process.exit(127);
|
package/ccx/__init__.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"ccx package.\n\nPublic Python API:\n\n from ccx import CcxClient\n cx = CcxClient()\n\n from ccx import CodexClient\n codex = CodexClient()\n\n from ccx import WorkerClient\n workers = WorkerClient() # one lifecycle for either engine\n\n from ccx import controller, current, task\n # durable task-specific Python orchestration, launched by `ccx controller`\n\nSee :mod:`ccx.client` and :mod:`ccx.codex.client` for the clients,\n:mod:`ccx.types` and :mod:`ccx.codex.types` for returned dataclasses, and\n:mod:`ccx.errors` for the shared typed exception hierarchy. Each client drives\nits engine's same on-disk sessions; durable controllers reference those real\nworkers without creating another worker type.\n"
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .client import CcxClient
|
|
6
|
+
from ccx.codex.client import CodexClient
|
|
7
|
+
from ccx.codex.types import (
|
|
8
|
+
CodexApprovalResult,
|
|
9
|
+
CodexCompactResult,
|
|
10
|
+
CodexContext,
|
|
11
|
+
CodexControlResult,
|
|
12
|
+
CodexReport,
|
|
13
|
+
CodexReconcileResult,
|
|
14
|
+
CodexResumeResult,
|
|
15
|
+
CodexSessionInfo,
|
|
16
|
+
CodexTelemetryPage,
|
|
17
|
+
CodexTurnResult,
|
|
18
|
+
CodexUsage,
|
|
19
|
+
CodexWaitResult,
|
|
20
|
+
)
|
|
21
|
+
from .errors import (
|
|
22
|
+
BudgetStopped,
|
|
23
|
+
CapabilityUnsupported,
|
|
24
|
+
CompactFailed,
|
|
25
|
+
CreatePartialFailure,
|
|
26
|
+
CompactIndeterminate,
|
|
27
|
+
CcxError,
|
|
28
|
+
CursorInvalidated,
|
|
29
|
+
DeliveryRejected,
|
|
30
|
+
InvalidRequest,
|
|
31
|
+
AmbiguousWorker,
|
|
32
|
+
OperationConflict,
|
|
33
|
+
OperationTimeout,
|
|
34
|
+
ProviderProtocolError,
|
|
35
|
+
PayloadTooLarge,
|
|
36
|
+
SessionNotFound,
|
|
37
|
+
SessionUnavailable,
|
|
38
|
+
SessionWaiting,
|
|
39
|
+
SpawnFailed,
|
|
40
|
+
SteerIndeterminate,
|
|
41
|
+
SendIndeterminate,
|
|
42
|
+
TurnIncomplete,
|
|
43
|
+
WorkerApiError,
|
|
44
|
+
WorkerApiPermanent,
|
|
45
|
+
WorkerApiTransient,
|
|
46
|
+
WorkerRateLimited,
|
|
47
|
+
ControllerError,
|
|
48
|
+
ControllerLaunchRequired,
|
|
49
|
+
ControllerOperationIndeterminate,
|
|
50
|
+
ControllerReplayMismatch,
|
|
51
|
+
ControllerLiveStateLost,
|
|
52
|
+
)
|
|
53
|
+
from .types import (
|
|
54
|
+
ClaudeContext,
|
|
55
|
+
ClaudeTask,
|
|
56
|
+
CompactOperationResult,
|
|
57
|
+
CompactResult,
|
|
58
|
+
Event,
|
|
59
|
+
ModelCatalog,
|
|
60
|
+
ModelInfo,
|
|
61
|
+
PendingInput,
|
|
62
|
+
Question,
|
|
63
|
+
Report,
|
|
64
|
+
SemanticDeliveryResult,
|
|
65
|
+
SendResult,
|
|
66
|
+
SessionInfo,
|
|
67
|
+
SessionStatus,
|
|
68
|
+
SteerResult,
|
|
69
|
+
Usage,
|
|
70
|
+
WaitResult,
|
|
71
|
+
)
|
|
72
|
+
from ccx.ops.steer_ops import SteerJournalBusy
|
|
73
|
+
from .workers import WorkerClient, evaluate_quota
|
|
74
|
+
from ccx.ops.creation import new_worker_id
|
|
75
|
+
from ccx.store.discovery import resolve_worker
|
|
76
|
+
from ccx.notify.client import ParentNotificationClient
|
|
77
|
+
from ccx.controllers.controller import (
|
|
78
|
+
ControllerInput,
|
|
79
|
+
Task,
|
|
80
|
+
TaskFuture,
|
|
81
|
+
TaskOutput,
|
|
82
|
+
controller,
|
|
83
|
+
current,
|
|
84
|
+
task,
|
|
85
|
+
task_output,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
__all__ = [
|
|
89
|
+
"CcxClient",
|
|
90
|
+
"CodexClient",
|
|
91
|
+
"WorkerClient",
|
|
92
|
+
"new_worker_id",
|
|
93
|
+
"resolve_worker",
|
|
94
|
+
"CreatePartialFailure",
|
|
95
|
+
"ParentNotificationClient",
|
|
96
|
+
"evaluate_quota",
|
|
97
|
+
"controller",
|
|
98
|
+
"task",
|
|
99
|
+
"current",
|
|
100
|
+
"task_output",
|
|
101
|
+
"Task",
|
|
102
|
+
"TaskFuture",
|
|
103
|
+
"TaskOutput",
|
|
104
|
+
"ControllerInput",
|
|
105
|
+
# errors
|
|
106
|
+
"CcxError",
|
|
107
|
+
"CursorInvalidated",
|
|
108
|
+
"InvalidRequest",
|
|
109
|
+
"AmbiguousWorker",
|
|
110
|
+
"OperationConflict",
|
|
111
|
+
"OperationTimeout",
|
|
112
|
+
"ProviderProtocolError",
|
|
113
|
+
"CapabilityUnsupported",
|
|
114
|
+
"CompactIndeterminate",
|
|
115
|
+
"SessionNotFound",
|
|
116
|
+
"PayloadTooLarge",
|
|
117
|
+
"SessionUnavailable",
|
|
118
|
+
"SpawnFailed",
|
|
119
|
+
"SessionWaiting",
|
|
120
|
+
"DeliveryRejected",
|
|
121
|
+
"SteerIndeterminate",
|
|
122
|
+
"SteerJournalBusy",
|
|
123
|
+
"SendIndeterminate",
|
|
124
|
+
"TurnIncomplete",
|
|
125
|
+
"CompactFailed",
|
|
126
|
+
"BudgetStopped",
|
|
127
|
+
"WorkerApiError",
|
|
128
|
+
"WorkerApiPermanent",
|
|
129
|
+
"WorkerApiTransient",
|
|
130
|
+
"WorkerRateLimited",
|
|
131
|
+
"ControllerError",
|
|
132
|
+
"ControllerLaunchRequired",
|
|
133
|
+
"ControllerOperationIndeterminate",
|
|
134
|
+
"ControllerReplayMismatch",
|
|
135
|
+
"ControllerLiveStateLost",
|
|
136
|
+
# types
|
|
137
|
+
"SessionInfo",
|
|
138
|
+
"ClaudeContext",
|
|
139
|
+
"ClaudeTask",
|
|
140
|
+
"SessionStatus",
|
|
141
|
+
"PendingInput",
|
|
142
|
+
"Question",
|
|
143
|
+
"Report",
|
|
144
|
+
"SemanticDeliveryResult",
|
|
145
|
+
"SendResult",
|
|
146
|
+
"SteerResult",
|
|
147
|
+
"CompactResult",
|
|
148
|
+
"CompactOperationResult",
|
|
149
|
+
"WaitResult",
|
|
150
|
+
"Event",
|
|
151
|
+
"ModelCatalog",
|
|
152
|
+
"ModelInfo",
|
|
153
|
+
"Usage",
|
|
154
|
+
# Codex types
|
|
155
|
+
"CodexSessionInfo",
|
|
156
|
+
"CodexResumeResult",
|
|
157
|
+
"CodexTelemetryPage",
|
|
158
|
+
"CodexTurnResult",
|
|
159
|
+
"CodexReconcileResult",
|
|
160
|
+
"CodexControlResult",
|
|
161
|
+
"CodexWaitResult",
|
|
162
|
+
"CodexReport",
|
|
163
|
+
"CodexApprovalResult",
|
|
164
|
+
"CodexCompactResult",
|
|
165
|
+
"CodexContext",
|
|
166
|
+
"CodexUsage",
|
|
167
|
+
]
|
package/ccx/__main__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
if __package__:
|
|
4
|
+
from ccx.cli.main import main
|
|
5
|
+
else:
|
|
6
|
+
# Hosts and hooks may re-exec this file directly after the parent command
|
|
7
|
+
# was invoked as `python -m ccx`. In direct-script mode Python does not know
|
|
8
|
+
# the package parent, so add it before importing ccx.cli absolutely.
|
|
9
|
+
#
|
|
10
|
+
# Do not import pathlib here before fixing sys.path: when this file is
|
|
11
|
+
# executed directly, sys.path[0] is the package directory, so stdlib imports
|
|
12
|
+
# of `types` can accidentally resolve to ccx/types.py.
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
normalized = __file__.replace("\\", "/")
|
|
16
|
+
package_dir = normalized.rsplit("/", 1)[0]
|
|
17
|
+
root = normalized.rsplit("/", 2)[0]
|
|
18
|
+
# Direct script execution sets sys.path[0] to the package directory. That
|
|
19
|
+
# makes top-level stdlib imports like `types` resolve to ccx/types.py. Drop
|
|
20
|
+
# that entry and import ccx as a package from its parent instead.
|
|
21
|
+
sys.path = [p for p in sys.path if p.replace("\\", "/") != package_dir]
|
|
22
|
+
if root not in sys.path:
|
|
23
|
+
sys.path.insert(0, root)
|
|
24
|
+
from ccx.cli.main import main
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
main()
|
|
File without changes
|