Metadata-Version: 2.4
Name: raysurfer
Version: 0.6.6
Summary: Drop-in replacement for Claude Agent SDK with automatic code caching - just swap your import
Project-URL: Homepage, https://raysurfer.com
Project-URL: Repository, https://github.com/raymondxu/raysurfer-python
Author: Raymond Xu
License-Expression: MIT
Keywords: agents,ai,anthropic,claude,code-blocks,embeddings,retrieval
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: claude-agent-sdk>=0.1.0
Requires-Dist: httpx>=0.25.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# RaySurfer Python SDK

Drop-in replacement for Claude Agent SDK with automatic code caching.

## Installation

```bash
pip install raysurfer
```

## Setup

Set your API key:

```bash
export RAYSURFER_API_KEY=your_api_key_here
```

Get your key from the [dashboard](https://raysurfer.com/dashboard/api-keys).

## Usage

Swap your client class and method names. Options come directly from `claude_agent_sdk`:

```python
# Before
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

# After
from raysurfer import RaysurferClient
from claude_agent_sdk import ClaudeAgentOptions

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Write", "Bash"],
    system_prompt="You are a helpful assistant.",
)

async with RaysurferClient(options) as client:
    await client.query("Generate quarterly report")
    async for msg in client.response():
        print(msg)
```

## Method Mapping

| Claude SDK | Raysurfer |
|------------|-----------|
| `ClaudeSDKClient(options)` | `RaysurferClient(options)` |
| `await client.query(prompt)` | `await client.query(prompt)` |
| `client.receive_response()` | `client.response()` |

## How It Works

1. **On `query()`**: Retrieves cached code blocks matching your task
2. **Downloads to sandbox**: Files ready for the agent to execute
3. **Injects into prompt**: Agent sees proven code snippets
4. **After success**: New code is cached for next time

Caching is enabled automatically when `RAYSURFER_API_KEY` is set.

## Snippet Retrieval Scope

Control which cached snippets are retrieved using `snips_desired`:

```python
from raysurfer import RaysurferClient
from claude_agent_sdk import ClaudeAgentOptions

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Write", "Bash"],
)

# Include company-level snippets
client = RaysurferClient(
    options,
    snips_desired="company",  # Company-level snippets (Team/Enterprise)
)

# Enterprise: Retrieve client-specific snippets only
client = RaysurferClient(
    options,
    snips_desired="client",   # Client workspace snippets (Enterprise only)
)
```

| Configuration | Required Tier |
|--------------|---------------|
| `snips_desired="company"` | TEAM or ENTERPRISE |
| `snips_desired="client"` | ENTERPRISE only |

## Full Example

```python
import asyncio
import os
from raysurfer import RaysurferClient
from claude_agent_sdk import ClaudeAgentOptions

os.environ["RAYSURFER_API_KEY"] = "your_api_key"

async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Write", "Bash"],
        system_prompt="You are a helpful assistant.",
    )

    async with RaysurferClient(options) as client:
        # First run: generates and caches code
        await client.query("Fetch GitHub trending repos")
        async for msg in client.response():
            print(msg)

        # Second run: retrieves from cache (instant)
        await client.query("Fetch GitHub trending repos")
        async for msg in client.response():
            print(msg)

asyncio.run(main())
```

## Without Caching

If `RAYSURFER_API_KEY` is not set, `RaysurferClient` behaves exactly like `ClaudeSDKClient` — no caching, just a pass-through wrapper.

## Low-Level API

For custom integrations, use the `RaySurfer` client directly with three core methods:

```python
from raysurfer import RaySurfer
from raysurfer.types import FileWritten, LogFile

client = RaySurfer(api_key="your_api_key")

# 1. Get cached code snippets for a task
snips = client.get_code_snips(task="Fetch GitHub trending repos")
for match in snips.code_blocks:
    print(f"{match.code_block.name}: {match.score}")

# Or use the unified search endpoint
search_result = client.search(task="Fetch GitHub trending repos")
for match in search_result.matches:
    print(f"{match.code_block.name}: {match.combined_score}")

# 2. Upload a new code file after execution
file = FileWritten(path="fetch_repos.py", content="def fetch(): ...")
client.upload_new_code_snip(
    task="Fetch GitHub trending repos",
    file_written=file,
    succeeded=True,
)

# 2b. Bulk upload prompts/logs/code for sandboxed grading
logs = [LogFile(path="logs/run.log", content="Task completed", encoding="utf-8")]
client.upload_bulk_code_snips(
    prompts=["Build a CLI tool", "Add CSV support"],
    files_written=[FileWritten(path="cli.py", content="def main(): ...")],
    log_files=logs,
    auto_vote=True,
)

# 3. Vote on whether a cached snippet was useful
client.vote_code_snip(
    task="Fetch GitHub trending repos",
    code_block_id="abc123",
    code_block_name="github_fetcher",
    code_block_description="Fetches trending repos from GitHub",
    succeeded=True,
)
```

### Async Version

```python
from raysurfer import AsyncRaySurfer
from raysurfer.types import FileWritten

async with AsyncRaySurfer(api_key="your_api_key") as client:
    snips = await client.get_code_snips(task="Fetch GitHub trending repos")

    file = FileWritten(path="fetch_repos.py", content="def fetch(): ...")
    await client.upload_new_code_snip(
        task="Fetch GitHub trending repos",
        file_written=file,
        succeeded=True,
    )

    await client.vote_code_snip(
        task="Fetch GitHub trending repos",
        code_block_id=snips.code_blocks[0].code_block.id,
        code_block_name=snips.code_blocks[0].code_block.name,
        code_block_description=snips.code_blocks[0].code_block.description,
        succeeded=True,
    )
```

### Method Reference

| Method | Description |
|--------|-------------|
| `search(task, top_k, min_verdict_score, prefer_complete)` | Unified search for cached code (recommended) |
| `get_code_snips(task, top_k, min_verdict_score)` | Retrieve cached code snippets by semantic search |
| `upload_new_code_snip(task, file_written, succeeded)` | Store a single code file for future reuse |
| `upload_bulk_code_snips(prompts, files_written, log_files, auto_vote)` | Bulk upload prompts/logs/code for sandboxed grading |
| `vote_code_snip(task, code_block_id, name, description, succeeded)` | Vote on snippet usefulness |

## License

MIT
