Skip to content

Commit aac6b24

Browse files
committed
Sync
1 parent 83de87f commit aac6b24

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+4800
-740
lines changed

.beads/config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,5 @@
5252
# - linear.api-key
5353
# - github.org
5454
# - github.repo
55+
56+
dolt.shared-server: true

.github/workflows/ci.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
check:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: astral-sh/setup-uv@v5
15+
with:
16+
python-version: "3.12"
17+
- run: uv sync --extra dev
18+
- run: uv run ruff check .
19+
- run: uv run mypy libs/py_agent_ctrl
20+
- run: uv run pytest -q

CHANGELOG.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [0.1.0] - 2026-03-23
9+
10+
### Added
11+
12+
- `AgentCtrl` facade with five bridge factories: `claude_code()`, `codex()`, `opencode()`, `pi()`, `gemini()`
13+
- Fluent immutable builder pattern on all action classes with base methods: `with_model`,
14+
`with_system_prompt`, `append_system_prompt`, `with_max_turns`, `in_directory`,
15+
`with_additional_dirs`, `with_timeout`, `resume_session`, `continue_session`
16+
- Agent-specific builder methods — Claude Code: `with_permission_mode`, `with_allowed_tools`;
17+
Codex: `with_sandbox`, `full_auto`, `dangerously_bypass`, `skip_git_repo_check`, `with_images`;
18+
OpenCode: `with_agent`, `with_files`, `with_title`, `share_session`;
19+
Pi: `with_provider`, `with_thinking`, `with_tools`, `with_extensions`, `with_skills`, `ephemeral`, `with_session_dir`;
20+
Gemini: `with_approval_mode`, `plan_mode`, `yolo`, `with_sandbox`, `with_allowed_tools`
21+
- `execute(prompt)` — blocking subprocess call returning `AgentResponse`
22+
- `stream(prompt)` — real-time line-by-line streaming via `subprocess.Popen`, returns `StreamResult`
23+
- `StreamResult` — iterable of `AgentEvent` with `.exit_code` accessible after iterator exhaustion
24+
- Subprocess watchdog timeout: `threading.Timer` kills hung processes regardless of output flow
25+
- `AgentResponse` model with fields: `text`, `exit_code`, `session_id`, `usage` (`TokenUsage`),
26+
`tool_calls`, `cost_usd`, `duration_ms`, `parse_failures`, `parse_failure_samples`
27+
- `TokenUsage` model: `input_tokens`, `output_tokens`, `total_tokens`, `cache_read_tokens`,
28+
`cache_write_tokens`, `reasoning_tokens`
29+
- `AgentTextEvent`, `AgentToolCallEvent`, `AgentResultEvent`, `AgentUnknownEvent` normalized event types
30+
- Gemini streaming: stateful `tool_use`/`tool_result` pairing yields `AgentToolCallEvent` in real time
31+
- `AgentError` and `BinaryNotFoundError` structured exception hierarchy (both subclass `RuntimeError`)
32+
- Session continuity: `resume_session(id)` and `continue_session()` supported across all five bridges
33+
- `ctrlagent` CLI with commands: `execute`, `stream`, `resume`, `continue`,
34+
`agents list`, `agents capabilities`
35+
- 82 unit and feature tests; 10 integration smoke tests (skip automatically without live CLI binaries)
36+
- GitHub Actions CI pipeline: `ruff check`, `mypy --strict`, `pytest` on push and PR to `main`
37+
- PEP 561 `py.typed` marker for IDE type inference from installed package
38+
- Full mypy strict-mode compliance across all 41 source files

README.md

Lines changed: 70 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,90 @@
11
# py-agent-ctrl
22

3-
Programmatic wrapper for the **Claude Code CLI** — Python port of [cognesy/agent-ctrl](https://github.com/cognesy/agent-ctrl) (PHP).
3+
Unified Python bridge for CLI coding agents.
44

5-
Zero dependencies beyond the Python standard library.
5+
This repo exposes:
66

7-
## Install
7+
- a Python API under `py_agent_ctrl`
8+
- a unified `ctrlagent` CLI
9+
- direct CLI bridges for `claude`, `codex`, `opencode`, `pi`, and `gemini`
10+
11+
The codebase follows the clean layout described in [docs/dev/architecture.md](docs/dev/architecture.md):
12+
13+
- `apps/` for runnable shells
14+
- `libs/` for importable code
15+
- `resources/` for passive assets
16+
- `docs/` for documentation
17+
- `tests/` for unit, integration, feature, and regression coverage
18+
19+
## Development
20+
21+
Use `uv` only:
822

923
```bash
10-
pip install git+https://github.com/cognesy/py-agent-ctrl.git@main
24+
uv sync --extra dev
25+
uv run pytest tests/unit tests/integration tests/feature tests/regression
26+
uv run ctrlagent agents list
1127
```
1228

13-
## Usage
29+
## Python API
1430

1531
```python
16-
from agent_ctrl import ClaudeCodeClient
32+
from py_agent_ctrl import AgentCtrl
33+
34+
response = (
35+
AgentCtrl.claude_code()
36+
.with_model("claude-sonnet-4-5")
37+
.with_permission_mode("bypassPermissions")
38+
.execute("Summarize this repository.")
39+
)
1740

18-
# Simple sync
19-
response = ClaudeCodeClient().execute("Summarise this repo.")
2041
print(response.text)
2142
print(response.session_id)
43+
```
2244

23-
# With options
24-
response = (
25-
ClaudeCodeClient()
26-
.with_system_prompt("You are a code analysis assistant.")
27-
.with_max_turns(5)
28-
.with_cwd("/path/to/project")
29-
.execute("List all API endpoints.")
30-
)
45+
Other bridges use the same top-level facade:
3146

32-
# Session continuity
33-
r1 = ClaudeCodeClient().execute("Start a multi-turn analysis.")
34-
r2 = ClaudeCodeClient().resume(r1.session_id).execute("Now summarise what you found.")
47+
```python
48+
from py_agent_ctrl import AgentCtrl
3549

36-
# Async streaming with callbacks
37-
import asyncio
50+
AgentCtrl.codex().with_sandbox("workspace-write").execute("Review the tests.")
51+
AgentCtrl.opencode().with_agent("coder").execute("Refactor the payment flow.")
52+
AgentCtrl.pi().with_thinking("high").execute("Create an implementation plan.")
53+
AgentCtrl.gemini().plan_mode().execute("Inspect the architecture.")
54+
```
3855

39-
async def main():
40-
async for event in ClaudeCodeClient().stream("Explain this codebase."):
41-
from agent_ctrl import AssistantEvent
42-
if isinstance(event, AssistantEvent):
43-
print(event.text, end="", flush=True)
56+
## CLI
4457

45-
asyncio.run(main())
58+
```bash
59+
uv run ctrlagent agents list
60+
uv run ctrlagent agents capabilities --agent claude-code
61+
uv run ctrlagent execute --agent claude-code "Summarize this repository."
62+
uv run ctrlagent resume --agent codex --session thread_123 "Continue."
63+
uv run ctrlagent continue --agent gemini "Proceed."
4664
```
4765

48-
## ClaudeCodeClient builder options
49-
50-
| Method | Description |
51-
|--------|-------------|
52-
| `.with_model(model)` | Override the Claude model |
53-
| `.with_system_prompt(text)` | Replace the default system prompt |
54-
| `.append_system_prompt(text)` | Append to the default system prompt |
55-
| `.with_max_turns(n)` | Limit agentic turns |
56-
| `.with_permission_mode(mode)` | `bypassPermissions` (default) / `acceptEdits` / `default` |
57-
| `.with_allowed_tools(*tools)` | Pre-approve specific tools, e.g. `"Read"`, `"Edit"` |
58-
| `.resume(session_id)` | Resume a specific prior session |
59-
| `.continue_session()` | Resume the most recent session |
60-
| `.with_cwd(path)` | Working directory for the subprocess |
61-
| `.with_timeout(seconds)` | Subprocess timeout (default 120s) |
62-
| `.on_text(fn)` | Callback for each text chunk during async streaming |
63-
| `.on_tool_use(fn)` | Callback for each tool use during async streaming |
64-
65-
## ClaudeResponse
66-
67-
| Field | Type | Description |
68-
|-------|------|-------------|
69-
| `text` | `str` | Concatenated assistant text |
70-
| `session_id` | `str \| None` | Session ID for resuming |
71-
| `success` | `bool` | `exit_code == 0 and not is_error` |
72-
| `tool_calls` | `list[ToolUseContent]` | Tools invoked during the run |
73-
| `cost_usd` | `float \| None` | Reported cost (if available) |
74-
| `duration_ms` | `int \| None` | Reported duration (if available) |
75-
| `events` | `list[StreamEvent]` | All raw typed events |
76-
77-
## Requirements
78-
79-
- Python 3.12+
80-
- `claude` CLI installed and on `PATH`: `npm install -g @anthropic-ai/claude-code`
81-
- `ANTHROPIC_API_KEY` set in the environment
66+
## Supported Agents
67+
68+
| Agent | Python Facade | CLI Name |
69+
|-------|---------------|----------|
70+
| Claude Code | `AgentCtrl.claude_code()` | `claude-code` |
71+
| Codex | `AgentCtrl.codex()` | `codex` |
72+
| OpenCode | `AgentCtrl.opencode()` | `opencode` |
73+
| Pi | `AgentCtrl.pi()` | `pi` |
74+
| Gemini | `AgentCtrl.gemini()` | `gemini` |
75+
76+
## Migration
77+
78+
This repo is a clean cut from the old flat Claude-only layout.
79+
80+
- old root package: `agent_ctrl`
81+
- new root package: `py_agent_ctrl`
82+
- old client: `ClaudeCodeClient`
83+
- new entrypoint: `AgentCtrl`
84+
85+
See:
86+
87+
- [docs/user/quickstart.md](docs/user/quickstart.md)
88+
- [docs/user/cli.md](docs/user/cli.md)
89+
- [docs/user/migration.md](docs/user/migration.md)
90+
- [resources/skills/upgrade/SKILL.md](resources/skills/upgrade/SKILL.md)

agent_ctrl/__init__.py

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)