Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions skills.sh.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
"skills": [
"web-design-guidelines"
]
},
{
"title": "Agent Relay",
"description": "Skills for delivering artifacts to humans via Agent Relay (arelay.app) — inbox API, end-to-end encrypted uploads, and Railway self-hosting.",
"skills": [
"agent-relay-api",
"agent-relay-e2ee",
"agent-relay-railway"
]
}
]
}
90 changes: 90 additions & 0 deletions skills/agent-relay-api/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
name: agent-relay-api
description: Deliver files and reports to a human via the Agent Relay HTTP API (arelay.app). Use when sending deliverables outside chat, creating inbox sessions, uploading Markdown/HTML/images/PDFs, or when the user mentions Agent Relay, arelay, or agent inbox delivery.
license: MIT
metadata:
author: mmmikael
version: "1.0.0"
---

# Agent Relay API

Deliver artifacts to the human's private inbox instead of email or chat attachments. Each delivery is a **session** (one thread); files are **artifacts**.

## When to use

- HTML pages, Markdown reports, images, PDFs, or multiple related files from one task
- Human should preview/download in the web portal

Do **not** use for short chat replies or secrets the human did not ask you to store.

## Configuration

Set in the agent environment (never commit):

| Variable | Value |
| --- | --- |
| `AGENT_RELAY_URL` | `https://arelay.app` or self-hosted base URL (no trailing slash). For Railway, use exact `RAILWAY_PUBLIC_DOMAIN` — wrong host returns 404. |
| `AGENT_API_TOKEN` | Bearer token from the human's Agent Relay account → Agent tokens |

Every request:

```http
Authorization: Bearer <AGENT_API_TOKEN>
```

## Workflow

1. `POST /api/agent/sessions` with `title` and optional `summary`
2. `POST /api/agent/sessions/<session_id>/artifacts` for each file
3. Optionally `PATCH /api/agent/sessions/<session_id>` to update summary
4. Tell the human: *"Sent to Agent Relay — session: \<title\>"* (no UUID needed)

One session per logical delivery. Re-use the same `session_id` for all files in that delivery.

## Upload methods

| Content | Method | Content-Type |
| --- | --- | --- |
| Markdown, HTML, text, JSON | JSON body with `content` | `application/json` |
| Images, PDFs, binaries | `multipart/form-data` field `file` | `multipart/form-data` |

JSON artifact fields: `filename`, `content_type`, `content` (required).

Common `content_type`: `text/markdown`, `text/html`, `text/plain`, `application/json`, `image/png`, `application/pdf`.

## Storage limits

- **25 MB** per artifact → `413`
- **500 MB** per account → `507`

## Errors

| Status | Meaning |
| --- | --- |
| `401` | Invalid or revoked token |
| `404` | Unknown session or E2EE not configured |
| `413` | File too large |
| `507` | Account quota full |
| `503` | S3 not configured on server |

Read `{ "error": "..." }` and report to the human.

## Checklist before finishing

- [ ] `title` is plain language; `summary` says what to open first
- [ ] Sensible `filename` extensions (`.md`, `.html`, `.png`, …)
- [ ] Text/HTML via JSON; binaries via multipart
- [ ] Human notified (portal refreshes in ~5 seconds)

## Examples

See [references/examples.md](references/examples.md) for curl and Python.

## Encrypted deliveries

If content is sensitive, load the **agent-relay-e2ee** skill and check `GET /api/agent/e2ee/config` first.

## Full reference

Endpoint details: [references/api-reference.md](references/api-reference.md)
59 changes: 59 additions & 0 deletions skills/agent-relay-api/references/api-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Agent Relay API reference

Base: `{AGENT_RELAY_URL}`

## Create session

```http
POST /api/agent/sessions
Content-Type: application/json

{
"title": "Short human-readable title",
"summary": "Optional one-line description"
}
```

**201:** `{ "session": { "id", "title", "summary", "created_at", "updated_at" } }`

## Upload artifact (JSON)

```http
POST /api/agent/sessions/<session_id>/artifacts
Content-Type: application/json

{
"filename": "report.md",
"content_type": "text/markdown",
"content": "# Title\n\nBody..."
}
```

**201:** `{ "artifact": { "id", "session_id", "filename", "content_type", "size_bytes", "created_at" } }`

## Upload artifact (multipart)

```http
POST /api/agent/sessions/<session_id>/artifacts
Content-Type: multipart/form-data

file=<binary>
filename=optional-override.png
```

## Update session

```http
PATCH /api/agent/sessions/<session_id>
Content-Type: application/json

{ "title": "...", "summary": "..." }
```

## List sessions

```http
GET /api/agent/sessions
```

Returns `{ "sessions": [ ... ] }` by `updated_at` descending.
67 changes: 67 additions & 0 deletions skills/agent-relay-api/references/examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Agent Relay API examples

Set `AGENT_RELAY_URL` and `AGENT_API_TOKEN` in the agent environment before running these examples.

## curl — Markdown + image

```bash
BASE="$AGENT_RELAY_URL"
TOKEN="$AGENT_API_TOKEN"

SESSION=$(curl -s -X POST "$BASE/api/agent/sessions" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Weekly report","summary":"Metrics and chart"}' \
| jq -r '.session.id')

curl -s -X POST "$BASE/api/agent/sessions/$SESSION/artifacts" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"filename":"report.md","content_type":"text/markdown","content":"# Weekly report\n\nAll good."}'

curl -s -X POST "$BASE/api/agent/sessions/$SESSION/artifacts" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@./chart.png"
```

## Python

Use the same two environment variables. Example with explicit values (substitute from your host env):

```python
import requests

relay_url = "https://arelay.app" # AGENT_RELAY_URL
api_token = "ar_your_token_here" # AGENT_API_TOKEN

BASE = relay_url.rstrip("/")
HEADERS = {"Authorization": f"Bearer {api_token}"}

r = requests.post(
f"{BASE}/api/agent/sessions",
headers={**HEADERS, "Content-Type": "application/json"},
json={"title": "API design draft", "summary": "OpenAPI + notes"},
timeout=60,
)
r.raise_for_status()
session_id = r.json()["session"]["id"]

requests.post(
f"{BASE}/api/agent/sessions/{session_id}/artifacts",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"filename": "design.md",
"content_type": "text/markdown",
"content": "# API design\n\n...",
},
timeout=60,
).raise_for_status()

with open("diagram.png", "rb") as f:
requests.post(
f"{BASE}/api/agent/sessions/{session_id}/artifacts",
headers=HEADERS,
files={"file": ("diagram.png", f, "image/png")},
timeout=120,
).raise_for_status()
```
100 changes: 100 additions & 0 deletions skills/agent-relay-e2ee/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
name: agent-relay-e2ee
description: Upload end-to-end encrypted sessions and artifacts to Agent Relay using P-256 ECDH and AES-256-GCM. Use when deliverables are sensitive, when GET /api/agent/e2ee/config returns configured, or when the human requires encrypted agent delivery to arelay.app.
license: MIT
metadata:
author: mmmikael
version: "1.0.0"
---

# Agent Relay E2EE uploads

The server stores only ciphertext. Decryption happens in the human's browser after they unlock their encryption key.

## Prerequisites

1. Human has set up encryption in the Agent Relay portal (passkey + recovery key).
2. Agent has `AGENT_RELAY_URL` and `AGENT_API_TOKEN` (same as plaintext API).

## Check encryption status

```http
GET /api/agent/e2ee/config
Authorization: Bearer <AGENT_API_TOKEN>
```

- **200** `{ "configured": true, "publicKeyJwk": { ... } }` → encrypt locally before upload
- **404** → ask human to enable encryption before sending sensitive content

## Envelope format

Each encrypted string or file uses:

```json
{
"v": 1,
"alg": "P-256-ECDH-A256GCM",
"epk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." },
"iv": "base64url-no-padding",
"ciphertext": "base64url-no-padding"
}
```

Use P-256 ECDH with the relay `publicKeyJwk`, derive AES-256-GCM key, fresh ephemeral key + IV per field/file.

**Important:** The browser uses Web Crypto `deriveKey(ECDH → AES-GCM)`. Hand-rolled Python `cryptography` ECDH/HKDF often produces incompatible ciphertext. Prefer the bundled reference script:

```bash
AGENT_RELAY_URL=https://arelay.app AGENT_API_TOKEN=ar_... \
node scripts/e2ee-upload.mjs "Delivery title" "report.md" "# Report body"
```

(`scripts/e2ee-upload.mjs` ships with this skill.)

## Create encrypted session

```http
POST /api/agent/sessions
Content-Type: application/json

{
"encrypted": true,
"encrypted_title": { "...": "title envelope" },
"encrypted_summary": { "...": "optional summary envelope" }
}
```

## Upload encrypted artifact

```http
POST /api/agent/sessions/<session_id>/artifacts
Content-Type: application/json

{
"encrypted": true,
"encrypted_filename": { "...": "filename envelope" },
"encrypted_content_type": { "...": "content-type envelope" },
"encrypted_payload": {
"v": 1,
"alg": "P-256-ECDH-A256GCM",
"epk": { "...": "ephemeral public JWK" },
"iv": "base64url-no-padding"
},
"ciphertext_base64": "base64url-no-padding",
"size_bytes": 12345
}
```

`encrypted_payload` is the file envelope **without** `ciphertext`; file bytes go in `ciphertext_base64`.

## Storage limits

Same as plaintext: **25 MB** per artifact, **500 MB** per account.

## Plaintext fallback

If E2EE is not configured and content is not sensitive, use **agent-relay-api** for standard uploads.

## Reference

API endpoints: [agent-relay-api/references/api-reference.md](../agent-relay-api/references/api-reference.md)
Loading