An open API service indexing awesome lists of open source software.

https://github.com/anandchowdhary/helios

🌐 Cloud Claude Code API Service: Run Claude Code instances on-demand through a simple REST API.
https://github.com/anandchowdhary/helios

api claude-code

Last synced: 2 days ago
JSON representation

🌐 Cloud Claude Code API Service: Run Claude Code instances on-demand through a simple REST API.

Awesome Lists containing this project

README

          

# Helios

**Cloud Claude Code API Service** — Run Claude Code instances on-demand through a simple REST API.

Helios is a developer API service that provides on-demand Claude Code instances in the cloud. Developers can programmatically spin up isolated containers, clone repositories, execute Claude Code tasks, and retrieve results — all through a simple REST API.

Think of it as **"Claude Code as a Service"** — the same powerful agentic coding assistant, but accessible via API for automation, CI/CD pipelines, and building AI-powered developer tools.

## Use Cases

- **Automated Code Review** — Trigger Claude Code to review PRs on push
- **Bug Fixing Pipelines** — Submit issues and get back code fixes
- **Code Generation** — Generate features from natural language specs
- **Refactoring at Scale** — Batch process repositories for migrations
- **Developer Tools** — Build IDE plugins, Slack bots, or CLI tools powered by Claude Code

## Features

- 🚀 **Simple API** — One endpoint to run a task, one to check status
- 📡 **Streaming Support** — Real-time output via Server-Sent Events (SSE)
- 🔐 **Credential Passthrough** — API keys passed per-request, never stored
- 📦 **Isolated Execution** — Each task runs in its own ephemeral container
- 🌐 **Edge Deployment** — Runs on Cloudflare Workers for global low-latency

## Tech Stack

| Component | Technology |
| ------------- | ------------------------------- |
| Runtime | Cloudflare Workers |
| Containers | Cloudflare Containers |
| Database | Cloudflare KV (task metadata) |
| Storage | Cloudflare R2 (logs, artifacts) |
| Rate Limiting | Cloudflare Rate Limiting API |
| Queue | Cloudflare Queues |
| Framework | Hono |
| Validation | Zod |
| Language | TypeScript |

## Quick Start

### Prerequisites

- Node.js 22+
- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/)
- Cloudflare account

### Installation

```bash
# Clone the repository
git clone https://github.com/AnandChowdhary/helios.git
cd helios

# Install dependencies
npm install

# Start local development server
npm run dev
```

### Configuration

Copy `wrangler.toml.example` to `wrangler.toml` and update with your Cloudflare resource IDs:

```toml
name = "helios"
main = "src/index.ts"
compatibility_date = "2025-01-01"
compatibility_flags = ["nodejs_compat"]

[[kv_namespaces]]
binding = "TASKS"
id = ""

[[kv_namespaces]]
binding = "API_KEYS"
id = ""

[[r2_buckets]]
binding = "ARTIFACTS"
bucket_name = "helios-artifacts"
```

## API Reference

### Authentication

All endpoints under `/v1/*` require authentication via API key in the Authorization header:

```
Authorization: Bearer
```

### Base URL

**Production:**

```
https://helios.getelysium.workers.dev
```

**Staging:**

```
https://helios-staging.getelysium.workers.dev
```

---

### Create Task

Creates a new Claude Code task to execute against a repository.

```http
POST /v1/tasks
```

#### Request Fields

| Field | Type | Required | Default | Description |
| ------------------------------ | -------- | -------- | ------------------------------------------- | ----------------------------------------------------- |
| `prompt` | string | Yes | - | The task prompt (1-100,000 characters) |
| `repository.url` | string | Yes | - | Git repository URL (GitHub, GitLab, or Bitbucket) |
| `repository.branch` | string | No | `main` | Branch to clone |
| `repository.credentials.type` | string | No | - | Authentication type (`token`) |
| `repository.credentials.value` | string | No | - | Authentication token (e.g., `ghp_xxx`) |
| `claude.apiKey` | string | Yes | - | Anthropic API key (must start with `sk-ant-`) |
| `claude.model` | string | No | `claude-sonnet-4-5` | Model to use (`claude-sonnet-4-5` or `claude-opus-4`) |
| `claude.maxTurns` | number | No | `10` | Maximum conversation turns (1-50) |
| `claude.systemPrompt` | string | No | - | Custom system prompt (max 10,000 characters) |
| `options.timeout` | number | No | `300` | Task timeout in seconds (30-600) |
| `options.allowedTools` | string[] | No | `["Read", "Write", "Bash", "Glob", "Grep"]` | Claude Code tools to allow |
| `options.workingDirectory` | string | No | `/workspace` | Working directory in container |
| `options.environment` | object | No | - | Environment variables to set |
| `output.mode` | string | No | `sync` | Output mode (`sync` or `async`) |
| `output.webhook.url` | string | No | - | Webhook URL for async notifications |
| `output.webhook.secret` | string | No | - | Webhook HMAC secret (min 16 characters) |

#### Example: Async Task (curl)

```bash
curl -X POST https://helios.getelysium.workers.dev/v1/tasks \
-H "Authorization: Bearer $HELIOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Fix the failing tests in the auth module",
"repository": {
"url": "https://github.com/user/repo.git",
"branch": "main",
"credentials": {
"type": "token",
"value": "'"$GITHUB_TOKEN"'"
}
},
"claude": {
"apiKey": "'"$ANTHROPIC_API_KEY"'",
"model": "claude-sonnet-4-5",
"maxTurns": 10
},
"output": {
"mode": "async"
}
}'
```

**Response (202 Accepted):**

```json
{
"taskId": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"createdAt": "2025-01-08T10:00:00.000Z",
"statusUrl": "https://helios.getelysium.workers.dev/v1/tasks/550e8400-e29b-41d4-a716-446655440000"
}
```

#### Example: Sync Task with SSE Streaming (curl)

```bash
curl -X POST https://helios.getelysium.workers.dev/v1/tasks \
-H "Authorization: Bearer $HELIOS_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{
"prompt": "List all TypeScript files and count the lines of code",
"repository": {
"url": "https://github.com/user/repo.git"
},
"claude": {
"apiKey": "'"$ANTHROPIC_API_KEY"'",
"maxTurns": 5
},
"output": {
"mode": "sync"
}
}'
```

**Response (200 OK, SSE Stream):**

```
event: status
data: {"status":"starting","taskId":"550e8400-e29b-41d4-a716-446655440000"}

event: status
data: {"status":"running","taskId":"550e8400-e29b-41d4-a716-446655440000"}

event: message
data: {"type":"assistant","content":"I'll search for TypeScript files..."}

event: tool_use
data: {"tool":"Glob","input":{"pattern":"**/*.ts"}}

event: complete
data: {"success":true,"summary":"Found 42 TypeScript files with 3,500 lines of code"}
```

---

### Get Task Status

Retrieve the current status and result of a task.

```http
GET /v1/tasks/:taskId
```

#### Example (curl)

```bash
curl https://helios.getelysium.workers.dev/v1/tasks/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer $HELIOS_API_KEY"
```

**Response (200 OK):**

```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"prompt": "Fix the failing tests",
"repository": {
"url": "https://github.com/user/repo.git",
"branch": "main"
},
"createdAt": "2025-01-08T10:00:00.000Z",
"startedAt": "2025-01-08T10:00:05.000Z",
"completedAt": "2025-01-08T10:02:30.000Z",
"result": {
"success": true,
"summary": "Fixed 3 failing tests in auth module",
"filesChanged": [
{
"path": "src/auth/login.ts",
"additions": 12,
"deletions": 5
}
],
"diff": "diff --git a/src/auth/login.ts..."
}
}
```

---

### Cancel Task

Cancel a pending or running task.

```http
POST /v1/tasks/:taskId/cancel
```

#### Example (curl)

```bash
curl -X POST https://helios.getelysium.workers.dev/v1/tasks/550e8400-e29b-41d4-a716-446655440000/cancel \
-H "Authorization: Bearer $HELIOS_API_KEY"
```

**Response (200 OK):**

```json
{
"taskId": "550e8400-e29b-41d4-a716-446655440000",
"status": "cancelled",
"cancelledAt": "2025-01-08T10:01:00.000Z"
}
```

---

### Get Task Logs

Retrieve the full execution logs for a completed task.

```http
GET /v1/tasks/:taskId/logs
```

#### Example (curl)

```bash
curl https://helios.getelysium.workers.dev/v1/tasks/550e8400-e29b-41d4-a716-446655440000/logs \
-H "Authorization: Bearer $HELIOS_API_KEY"
```

**Response (200 OK, text/plain):**

```
[2025-01-08T10:00:05.000Z] Starting task...
[2025-01-08T10:00:06.000Z] Cloning repository...
[2025-01-08T10:00:10.000Z] Running Claude Code...
...
```

---

### Get Task Diff

Retrieve the git diff of all changes made by the task.

```http
GET /v1/tasks/:taskId/diff
```

#### Example (curl)

```bash
curl https://helios.getelysium.workers.dev/v1/tasks/550e8400-e29b-41d4-a716-446655440000/diff \
-H "Authorization: Bearer $HELIOS_API_KEY"
```

**Response (200 OK, text/x-diff):**

```diff
diff --git a/src/auth/login.ts b/src/auth/login.ts
index abc123..def456 100644
--- a/src/auth/login.ts
+++ b/src/auth/login.ts
@@ -10,5 +10,7 @@ export function login(user: string, pass: string) {
+ // Validate input before processing
+ if (!user || !pass) throw new Error('Invalid credentials');
return authenticate(user, pass);
}
```

---

### Health Check

Check if the API is healthy (no authentication required).

```http
GET /health
```

#### Example (curl)

```bash
curl https://helios.getelysium.workers.dev/health
```

**Response (200 OK):**

```json
{
"status": "healthy",
"timestamp": "2025-01-08T10:00:00.000Z"
}
```

---

## Task Status Values

| Status | Description |
| ----------- | ----------------------------------- |
| `pending` | Task is queued and waiting to start |
| `running` | Task is currently executing |
| `completed` | Task finished successfully |
| `failed` | Task finished with an error |
| `cancelled` | Task was cancelled by the user |

---

## Error Responses

All error responses follow this format:

```json
{
"error": {
"message": "Description of the error"
}
}
```

### HTTP Status Codes

| Status | Description |
| ------ | ------------------------------------------------------- |
| `200` | Success |
| `202` | Accepted (async task created) |
| `400` | Bad Request - Invalid input or task cannot be cancelled |
| `401` | Unauthorized - Missing or invalid API key |
| `404` | Not Found - Task or resource not found |
| `429` | Too Many Requests - Rate limit exceeded |
| `500` | Internal Server Error |

### Rate Limiting

Rate limits are applied per API key. When rate limited, the response includes these headers:

```
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704708000000
```

---

## SSE Event Types

When using sync mode, the response is a Server-Sent Events stream with these event types:

| Event | Description |
| ------------- | ------------------------------------------- |
| `status` | Task status updates (`starting`, `running`) |
| `message` | Claude's text responses |
| `tool_use` | Tool invocation by Claude |
| `tool_result` | Result from tool execution |
| `complete` | Task completed with final result |
| `error` | Error occurred during execution |

---

## Code Examples

### TypeScript/JavaScript

#### Create an Async Task

```typescript
const response = await fetch("https://helios.getelysium.workers.dev/v1/tasks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HELIOS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "Add unit tests for the auth module",
repository: {
url: "https://github.com/user/repo.git",
branch: "main",
credentials: {
type: "token",
value: process.env.GITHUB_TOKEN,
},
},
claude: {
apiKey: process.env.ANTHROPIC_API_KEY,
model: "claude-sonnet-4-5",
maxTurns: 15,
},
output: {
mode: "async",
},
}),
});

const { taskId, statusUrl } = await response.json();
console.log(`Task created: ${taskId}`);
console.log(`Check status at: ${statusUrl}`);
```

#### Poll for Task Completion

```typescript
async function waitForTask(taskId: string): Promise {
const maxAttempts = 60;
const pollInterval = 5000; // 5 seconds

for (let i = 0; i < maxAttempts; i++) {
const response = await fetch(
`https://helios.getelysium.workers.dev/v1/tasks/${taskId}`,
{
headers: {
Authorization: `Bearer ${process.env.HELIOS_API_KEY}`,
},
},
);

const task = await response.json();

if (task.status === "completed" || task.status === "failed") {
return task;
}

await new Promise((resolve) => setTimeout(resolve, pollInterval));
}

throw new Error("Task timed out");
}

const result = await waitForTask(taskId);
console.log(`Task ${result.status}:`, result.result?.summary);
```

#### Stream Sync Task with EventSource

```typescript
// Note: EventSource doesn't support custom headers in browsers.
// Use fetch with ReadableStream for full control.

async function streamTask(taskPayload: object) {
const response = await fetch(
"https://helios.getelysium.workers.dev/v1/tasks",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HELIOS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
...taskPayload,
output: { mode: "sync" },
}),
},
);

if (!response.body) {
throw new Error("No response body");
}

const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";

while (true) {
const { done, value } = await reader.read();
if (done) break;

buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";

let currentEvent = "message";
for (const line of lines) {
if (line.startsWith("event: ")) {
currentEvent = line.slice(7);
} else if (line.startsWith("data: ")) {
const data = JSON.parse(line.slice(6));
console.log(`[${currentEvent}]`, data);

if (currentEvent === "complete") {
return data;
}
if (currentEvent === "error") {
throw new Error(data.message);
}
}
}
}
}
```

#### Simple Wrapper Class

```typescript
class HeliosClient {
constructor(
private apiKey: string,
private baseUrl = "https://helios.getelysium.workers.dev",
) {}

async createTask(options: {
prompt: string;
repoUrl: string;
branch?: string;
githubToken?: string;
anthropicApiKey: string;
async?: boolean;
}) {
const response = await fetch(`${this.baseUrl}/v1/tasks`, {
method: "POST",
headers: {
Authorization: `Bearer ${this.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: options.prompt,
repository: {
url: options.repoUrl,
branch: options.branch ?? "main",
credentials: options.githubToken
? { type: "token", value: options.githubToken }
: undefined,
},
claude: {
apiKey: options.anthropicApiKey,
},
output: {
mode: options.async ? "async" : "sync",
},
}),
});

if (!response.ok) {
const error = await response.json();
throw new Error(error.error?.message ?? "Request failed");
}

return response.json();
}

async getTask(taskId: string) {
const response = await fetch(`${this.baseUrl}/v1/tasks/${taskId}`, {
headers: {
Authorization: `Bearer ${this.apiKey}`,
},
});

if (!response.ok) {
const error = await response.json();
throw new Error(error.error?.message ?? "Request failed");
}

return response.json();
}

async cancelTask(taskId: string) {
const response = await fetch(`${this.baseUrl}/v1/tasks/${taskId}/cancel`, {
method: "POST",
headers: {
Authorization: `Bearer ${this.apiKey}`,
},
});

if (!response.ok) {
const error = await response.json();
throw new Error(error.error?.message ?? "Request failed");
}

return response.json();
}
}

// Usage
const helios = new HeliosClient(process.env.HELIOS_API_KEY!);

const { taskId } = await helios.createTask({
prompt: "Refactor the utils module to use TypeScript",
repoUrl: "https://github.com/user/repo.git",
anthropicApiKey: process.env.ANTHROPIC_API_KEY!,
async: true,
});

// Later...
const task = await helios.getTask(taskId);
```

### Python

#### Create an Async Task

```python
import os
import requests

response = requests.post(
"https://helios.getelysium.workers.dev/v1/tasks",
headers={
"Authorization": f"Bearer {os.environ['HELIOS_API_KEY']}",
"Content-Type": "application/json",
},
json={
"prompt": "Add unit tests for the auth module",
"repository": {
"url": "https://github.com/user/repo.git",
"branch": "main",
"credentials": {
"type": "token",
"value": os.environ.get("GITHUB_TOKEN"),
},
},
"claude": {
"apiKey": os.environ["ANTHROPIC_API_KEY"],
"model": "claude-sonnet-4-5",
"maxTurns": 15,
},
"output": {
"mode": "async",
},
},
)

data = response.json()
print(f"Task created: {data['taskId']}")
print(f"Check status at: {data['statusUrl']}")
```

#### Poll for Task Completion

```python
import time
import requests
import os

def wait_for_task(task_id: str, max_attempts: int = 60, poll_interval: int = 5):
"""Wait for a task to complete, polling at regular intervals."""
base_url = "https://helios.getelysium.workers.dev"
headers = {"Authorization": f"Bearer {os.environ['HELIOS_API_KEY']}"}

for _ in range(max_attempts):
response = requests.get(f"{base_url}/v1/tasks/{task_id}", headers=headers)
task = response.json()

if task["status"] in ("completed", "failed"):
return task

time.sleep(poll_interval)

raise TimeoutError("Task timed out")

# Usage
result = wait_for_task(task_id)
print(f"Task {result['status']}: {result.get('result', {}).get('summary', 'No summary')}")
```

#### Stream Sync Task

```python
import os
import json
import requests

def stream_task(payload: dict):
"""Stream a sync task and yield events."""
response = requests.post(
"https://helios.getelysium.workers.dev/v1/tasks",
headers={
"Authorization": f"Bearer {os.environ['HELIOS_API_KEY']}",
"Content-Type": "application/json",
},
json={**payload, "output": {"mode": "sync"}},
stream=True,
)

current_event = "message"
for line in response.iter_lines():
if not line:
continue

line = line.decode("utf-8")
if line.startswith("event: "):
current_event = line[7:]
elif line.startswith("data: "):
data = json.loads(line[6:])
yield current_event, data

if current_event == "complete":
return
if current_event == "error":
raise Exception(data.get("message", "Unknown error"))

# Usage
for event_type, data in stream_task({
"prompt": "List all Python files in the repository",
"repository": {"url": "https://github.com/user/repo.git"},
"claude": {"apiKey": os.environ["ANTHROPIC_API_KEY"]},
}):
print(f"[{event_type}]", data)
```

#### Simple Client Class

```python
import os
import requests
from typing import Optional

class HeliosClient:
def __init__(self, api_key: str, base_url: str = "https://helios.getelysium.workers.dev"):
self.api_key = api_key
self.base_url = base_url

def _headers(self) -> dict:
return {"Authorization": f"Bearer {self.api_key}"}

def create_task(
self,
prompt: str,
repo_url: str,
anthropic_api_key: str,
branch: str = "main",
github_token: Optional[str] = None,
async_mode: bool = False,
) -> dict:
"""Create a new task."""
payload = {
"prompt": prompt,
"repository": {
"url": repo_url,
"branch": branch,
},
"claude": {"apiKey": anthropic_api_key},
"output": {"mode": "async" if async_mode else "sync"},
}

if github_token:
payload["repository"]["credentials"] = {"type": "token", "value": github_token}

response = requests.post(
f"{self.base_url}/v1/tasks",
headers={**self._headers(), "Content-Type": "application/json"},
json=payload,
)
response.raise_for_status()
return response.json()

def get_task(self, task_id: str) -> dict:
"""Get task status and result."""
response = requests.get(f"{self.base_url}/v1/tasks/{task_id}", headers=self._headers())
response.raise_for_status()
return response.json()

def cancel_task(self, task_id: str) -> dict:
"""Cancel a running task."""
response = requests.post(f"{self.base_url}/v1/tasks/{task_id}/cancel", headers=self._headers())
response.raise_for_status()
return response.json()

def get_diff(self, task_id: str) -> str:
"""Get the git diff from a completed task."""
response = requests.get(f"{self.base_url}/v1/tasks/{task_id}/diff", headers=self._headers())
response.raise_for_status()
return response.text

# Usage
helios = HeliosClient(os.environ["HELIOS_API_KEY"])

result = helios.create_task(
prompt="Refactor the utils module to use TypeScript",
repo_url="https://github.com/user/repo.git",
anthropic_api_key=os.environ["ANTHROPIC_API_KEY"],
async_mode=True,
)

print(f"Task ID: {result['taskId']}")
```

---

## Practical Use Cases

### GitHub Actions: Auto-fix Failing Tests

Add this workflow to automatically create a PR when tests fail:

```yaml
# .github/workflows/auto-fix-tests.yml
name: Auto-fix Failing Tests

on:
workflow_run:
workflows: ["CI"]
types: [completed]

jobs:
auto-fix:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'failure' }}

steps:
- uses: actions/checkout@v4

- name: Create fix task
id: helios
run: |
RESPONSE=$(curl -s -X POST https://helios.getelysium.workers.dev/v1/tasks \
-H "Authorization: Bearer ${{ secrets.HELIOS_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{
"prompt": "The CI tests are failing. Please analyze the test output, identify the issues, and fix them. Run the tests after fixing to verify.",
"repository": {
"url": "https://github.com/${{ github.repository }}.git",
"branch": "${{ github.event.workflow_run.head_branch }}",
"credentials": {
"type": "token",
"value": "${{ secrets.GITHUB_TOKEN }}"
}
},
"claude": {
"apiKey": "${{ secrets.ANTHROPIC_API_KEY }}",
"maxTurns": 20
},
"output": {
"mode": "async"
}
}')
echo "task_id=$(echo $RESPONSE | jq -r '.taskId')" >> $GITHUB_OUTPUT

- name: Wait for task
run: |
TASK_ID="${{ steps.helios.outputs.task_id }}"
for i in {1..60}; do
STATUS=$(curl -s https://helios.getelysium.workers.dev/v1/tasks/$TASK_ID \
-H "Authorization: Bearer ${{ secrets.HELIOS_API_KEY }}" | jq -r '.status')
echo "Task status: $STATUS"
if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then
break
fi
sleep 10
done
```

### Slack Bot: Code Review on Command

```typescript
// Slack bot handler for /review command
import { App } from "@slack/bolt";

const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET,
});

app.command("/review", async ({ command, ack, respond }) => {
await ack();

// Parse: /review https://github.com/user/repo/pull/123
const prUrl = command.text.trim();
const match = prUrl.match(/github\.com\/(.+)\/(.+)\/pull\/(\d+)/);

if (!match) {
await respond("Please provide a valid GitHub PR URL");
return;
}

const [, owner, repo, prNumber] = match;

await respond(`Starting code review for PR #${prNumber}...`);

// Create Helios task
const response = await fetch(
"https://helios.getelysium.workers.dev/v1/tasks",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HELIOS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: `Review the code changes in this pull request. Check for:
- Potential bugs or logic errors
- Security vulnerabilities
- Performance issues
- Code style and best practices
- Missing tests

Provide a detailed review with specific line references.`,
repository: {
url: `https://github.com/${owner}/${repo}.git`,
branch: `refs/pull/${prNumber}/head`,
credentials: {
type: "token",
value: process.env.GITHUB_TOKEN,
},
},
claude: {
apiKey: process.env.ANTHROPIC_API_KEY,
model: "claude-sonnet-4-5",
maxTurns: 10,
},
output: { mode: "async" },
}),
},
);

const { taskId } = await response.json();

// Poll and post result (simplified)
const result = await waitForTask(taskId);
await respond({
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `*Code Review Complete*\n\n${result.result?.summary || "Review completed"}`,
},
},
],
});
});
```

### Webhook Handler: Process Task Results

```typescript
// Express webhook handler
import express from "express";
import crypto from "crypto";

const app = express();
app.use(express.raw({ type: "application/json" }));

app.post("/webhooks/helios", (req, res) => {
// Verify webhook signature
const signature = req.headers["x-helios-signature"] as string;
const expectedSignature =
"sha256=" +
crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET!)
.update(req.body)
.digest("hex");

if (signature !== expectedSignature) {
return res.status(401).send("Invalid signature");
}

const event = JSON.parse(req.body.toString());

switch (event.event) {
case "task.completed":
console.log(`Task ${event.taskId} completed!`);
console.log(`Summary: ${event.result.summary}`);
console.log(`Files changed: ${event.result.filesChanged.length}`);
// Create PR, notify team, etc.
break;

case "task.failed":
console.error(`Task ${event.taskId} failed: ${event.error}`);
// Alert on-call, retry, etc.
break;
}

res.sendStatus(200);
});

app.listen(3000);
```

### CLI Tool: Quick Code Tasks

```bash
#!/bin/bash
# helios-cli.sh - Simple CLI for Helios

HELIOS_URL="https://helios.getelysium.workers.dev"

helios_task() {
local prompt="$1"
local repo="${2:-$(git remote get-url origin)}"
local branch="${3:-$(git branch --show-current)}"

echo "Creating task..."

RESPONSE=$(curl -s -X POST "$HELIOS_URL/v1/tasks" \
-H "Authorization: Bearer $HELIOS_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"prompt\": \"$prompt\",
\"repository\": {
\"url\": \"$repo\",
\"branch\": \"$branch\"
},
\"claude\": {
\"apiKey\": \"$ANTHROPIC_API_KEY\"
},
\"output\": {\"mode\": \"async\"}
}")

TASK_ID=$(echo "$RESPONSE" | jq -r '.taskId')
echo "Task ID: $TASK_ID"

# Poll for completion
while true; do
STATUS=$(curl -s "$HELIOS_URL/v1/tasks/$TASK_ID" \
-H "Authorization: Bearer $HELIOS_API_KEY" | jq -r '.status')

echo "Status: $STATUS"

if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then
# Get full result
curl -s "$HELIOS_URL/v1/tasks/$TASK_ID" \
-H "Authorization: Bearer $HELIOS_API_KEY" | jq '.result'
break
fi

sleep 5
done
}

# Usage: ./helios-cli.sh "Add error handling to the API routes"
helios_task "$1" "$2" "$3"
```

---

## Development

```bash
# Start local development server
npm run dev

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

# Lint code
npm run lint

# Type check
npm run typecheck
```

## Deployment

```bash
# Deploy to staging
npm run deploy:staging

# Deploy to production
npm run deploy:prod
```

### Setting Secrets

```bash
wrangler secret put WEBHOOK_SIGNING_KEY
```

## Project Structure

```
helios/
├── src/
│ ├── index.ts # Worker entry point
│ ├── routes/
│ │ └── tasks.ts # Task API routes
│ ├── middleware/
│ │ ├── auth.ts # API key authentication
│ │ ├── rateLimit.ts # Rate limiting
│ │ └── validate.ts # Request validation
│ ├── schemas/
│ │ └── task.ts # Zod schemas
│ ├── types/
│ │ └── index.ts # TypeScript types
│ └── utils/
│ └── errors.ts # Error handling
├── test/
│ ├── unit/ # Unit tests
│ └── integration/ # Integration tests
├── wrangler.toml # Cloudflare configuration
├── vitest.config.ts # Test configuration
└── package.json
```

## Security

- **Credentials are never stored** — API keys passed per-request, used once, discarded
- **Ephemeral containers** — Each task gets a fresh container, destroyed after completion
- **Network isolation** — Containers can only access allowed endpoints
- **Input validation** — All requests validated with Zod schemas
- **Rate limiting** — Per-API-key rate limits prevent abuse

## License

MIT © [Anand Chowdhary](https://anandchowdhary.com)