Metadata-Version: 2.4
Name: claude-sock
Version: 0.1.0
Summary: Sockpuppet for Claude Code — drive the TUI programmatically, stream JSONL like claude -p
Author: Daniel Huynh
License-Expression: MIT
Project-URL: Homepage, https://github.com/dhuynh95/claude-sock
Project-URL: Repository, https://github.com/dhuynh95/claude-sock
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# claude-sock

A sockpuppet for Claude Code. Drive the TUI programmatically via a pty, stream JSONL output like `claude -p`.

## Why

`claude -p` (pipe mode) is great but limited — no MCP servers, no skills, no interactive tool use. `claude-sock` spawns the full Claude Code TUI, injects keystrokes through a pty, and reads structured output from the session JSONL file. Your code gets the full power of interactive Claude Code through a simple async Python API.

## Install

```bash
pip install claude-sock
```

Requires Claude Code CLI (`claude`) to be installed and authenticated.

## Quick start

### Python API

```python
import asyncio
from claude_sock.orchestrator import ClaudeREPL

async def main():
    async with ClaudeREPL(timeout=60) as repl:
        messages = await repl.query("What files are in this directory?")
        for msg in messages:
            print(msg)

asyncio.run(main())
```

### Streaming

```python
async with ClaudeREPL() as repl:
    async for msg in repl.query_stream("Refactor main.py"):
        print(msg)
```

### Resume a session

```python
async with ClaudeREPL(resume="ef4f97fd-d265-474e-9544-3f228e9654c0") as repl:
    messages = await repl.query("Continue where we left off")
```

### Load skills

```python
async with ClaudeREPL() as repl:
    await repl.send_skill("commit")
    messages = await repl.query("Fix the login bug and commit")
```

### MCP servers

```python
# Pick servers by name from your .mcp.json
async with ClaudeREPL(server_names=["my-server"]) as repl:
    messages = await repl.query("Use the my-server tool")
```

### CLI (drop-in `claude -p` replacement)

```bash
echo "Explain this repo" | claude-sock -p
claude-sock "What does main.py do?" --timeout 60
claude-sock "Continue" --resume ef4f97fd-d265-474e-9544-3f228e9654c0
```

Output is JSONL, compatible with anything that reads `claude -p` format.

## How it works

```
Your code
   │
   ▼ keystrokes
┌─────────┐        ┌──────────────┐
│   pty   │───────▶│  Claude Code  │
│ (write) │        │    TUI        │
└─────────┘        └──────┬───────┘
                          │ writes
                          ▼
                   ┌──────────────┐
                   │ session.jsonl │
                   └──────┬───────┘
                          │ reads
                          ▼
                   ┌──────────────┐
                   │  Your code   │
                   │  (parsed)    │
                   └──────────────┘
```

- **Write channel**: pty file descriptor — fire keystrokes and forget
- **Read channel**: `~/.claude/projects/…/{session_id}.jsonl` — poll for new lines by byte offset

## API reference

### `ClaudeREPL(timeout, session_id, workdir, env, server_names, resume)`

| Param | Type | Default | Description |
|---|---|---|---|
| `timeout` | `float` | `120` | Seconds to wait for activity before timing out |
| `session_id` | `str \| None` | auto-generated | Force a specific session ID |
| `workdir` | `Path \| None` | `cwd` | Working directory for Claude |
| `env` | `dict \| None` | `None` | Extra environment variables |
| `server_names` | `list[str] \| None` | `None` | MCP server names from `.mcp.json` |
| `resume` | `str \| None` | `None` | Session ID to resume |

### Methods

| Method | Returns | Description |
|---|---|---|
| `query(text)` | `list[Message]` | Send a message, wait for completion |
| `query_stream(text)` | `AsyncIterator[Message]` | Send a message, yield parsed messages |
| `query_stream_raw(text)` | `AsyncIterator[dict]` | Send a message, yield raw JSONL dicts |
| `send_skill(name)` | `list[Message]` | Load a slash command (e.g. `"commit"`) |

### Message types

- `AssistantMessage` — contains `TextBlock` and `ToolUseBlock`
- `ToolResultMessage` — contains `ToolResultBlock`
- `ResultMessage` — turn summary (duration, cost, session ID)

## License

MIT
