---
status: paused
superseded_by: ""
---

# Design Document: arcrun SDK Libraries + Website

## Overview

本設計涵蓋 arcrun 的三個新增交付物：
1. Python SDK lib（`pip install arcrun`）
2. JS/TS SDK lib（`npm install arcrun` 或 `@arcrun/sdk`）
3. arcrun.dev 網站完善（零件列表、recipe 列表、登入管理）

**設計原則：修改不重建。** SDK 是 `cypher.arcrun.dev` HTTP API 的 thin wrapper。不在 client 端重新實作任何 server 端已有的邏輯（workflow 執行、credential 注入、auth recipe 解析）。唯一在 client 做的是 AES-GCM 加密（因為 server 的 POST /credentials 期望收到加密後的 payload）。

---

## Architecture

### 系統關係圖

```
使用者程式碼
  ├── CLI（acr）                → cypher.arcrun.dev（HTTP API）
  ├── Python SDK（arcrun）       → cypher.arcrun.dev（HTTP API）
  └── JS SDK（arcrun / @arcrun/sdk） → cypher.arcrun.dev（HTTP API）

arcrun.dev 網站（Next.js / Cloudflare Pages）
  ├── /login → /auth/google/start, /auth/github/start（cypher.arcrun.dev）
  ├── /dashboard → /me, /me/api-key/rotate（cypher.arcrun.dev）
  ├── /integrations → /auth-recipes（cypher.arcrun.dev）
  └── /components → /recipes + 靜態零件清單（embedded）

cypher.arcrun.dev（Cloudflare Worker — cypher-executor，不改）
  ├── POST /credentials         ← 接收 { name, encrypted, iv }
  ├── GET  /credentials         ← 列出 credential 名稱
  ├── DELETE /credentials/:name ← 刪除 credential
  ├── GET  /auth-recipes        ← 列出 20 個 auth recipe
  ├── GET  /auth-recipes/:service ← 單一 recipe 詳情
  ├── POST /webhooks/named      ← 部署 workflow
  ├── POST /webhooks/named/:name/trigger ← 觸發 workflow
  ├── GET  /webhooks/named      ← 列出 workflow
  ├── POST /register            ← 註冊取得 API Key
  ├── GET  /me                  ← 當前用戶資訊
  └── /auth/*                   ← OAuth 流程
```

---

## Python SDK（`arcrun/python-sdk/`）

### 目錄結構

```
arcrun/python-sdk/
├── pyproject.toml          ← hatchling build, name="arcrun", deps=[httpx>=0.27, cryptography>=42]
├── README.md
└── arcrun/
    ├── __init__.py          ← from .client import Arcrun
    ├── client.py            ← Arcrun class（主入口）
    ├── crypto.py            ← AES-GCM 加密（client 端，用 cryptography 套件）
    ├── creds.py             ← CredentialsClient（push/list/delete）
    ├── auth.py              ← AuthClient（setup/bind/get_token/list_services）
    └── workflows.py         ← WorkflowClient（run/push/list/delete）
```

### API 設計

```python
from arcrun import Arcrun

# 建構 — api_key 從參數 > 環境變數 > ~/.arcrun/config.yaml 自動取得
client = Arcrun()
# 或明確指定
client = Arcrun(api_key="ak_xxx", encryption_key="hexstring")

# Auth：設定並綁定服務
client.auth.setup("openai", api_key="sk-xxx")          # 加密 + 上傳
openai_client = client.auth.bind("openai")              # 取回 pre-auth client
response = openai_client.get("/models")                 # httpx.Client
token = client.auth.get_token("openai")                 # raw token string
services = client.auth.list_services()                  # [{ service, display_name, ... }]

# Credentials：低階操作
client.creds.push("my_token", "value123")
names = client.creds.list()
client.creds.delete("my_token")

# Workflows
result = client.workflows.run("my-flow", {"email": "user@example.com"})
url = client.workflows.push("my-flow", graph_dict)
workflows = client.workflows.list()
```

### Credential 加密流程

```
setup("openai", api_key="sk-xxx")
  1. GET /auth-recipes/openai → recipe（含 required_secrets, inject）
  2. 對應 required_secrets[0].key = "openai_api_key"
  3. crypto.py 用 encryption_key AES-GCM 加密 "sk-xxx"
  4. POST /credentials → { name: "openai_api_key", encrypted, iv }
  5. 本地 _cred_cache["openai_api_key"] = "sk-xxx"（供 bind() 用）

bind("openai")
  1. GET /auth-recipes/openai → recipe.inject.header = { Authorization: "Bearer {{secret.openai_api_key}}" }
  2. 用 _cred_cache["openai_api_key"] 替換 template → "Bearer sk-xxx"
  3. 回傳 AuthenticatedClient(base_url="https://api.openai.com/v1", headers={"Authorization": "Bearer sk-xxx"})
```

**注意**：`bind()` 依賴 `setup()` 在同一 session 建立的 `_cred_cache`。跨 session 使用時（credential 已上傳但 cache 不存在），`bind()` 無法解析 template — 此時 `get_token()` 也無法返回值。**這是已知限制，封測期間先接受。** 長期解法是 server 提供 `/credentials/:name/secret` 解密端點（u6u-core/credentials 已有）。

### 關鍵差異：crypto.py 的定位

`crypto.py` 只做 **加密**（encrypt），不做解密。
功能等同 `u6u-core/credentials/src/actions/crypto.ts` 的 `encrypt()` 函數。
解密只在 server 端發生（cypher-executor 的 `credential-injector.ts` 或 `u6u-core/credentials/getCredentialSecret.ts`）。

---

## JS/TS SDK（`arcrun/js-sdk/`）

### 目錄結構

```
arcrun/js-sdk/
├── package.json            ← name TBD（arcrun vs @arcrun/sdk），tsup build
├── tsconfig.json           ← ES2020, NodeNext
└── src/
    ├── index.ts             ← export class Arcrun
    ├── crypto.ts            ← Web Crypto API AES-GCM encrypt（client 端）
    ├── creds.ts             ← CredentialsClient（push/list/delete）
    ├── auth.ts              ← AuthClient（setup/bind/getToken/listServices）
    └── workflows.ts         ← WorkflowClient（run/push/list/delete）
```

### API 與 Python SDK 對等

```typescript
import { Arcrun } from 'arcrun' // or '@arcrun/sdk'

const client = new Arcrun() // reads ARCRUN_API_KEY from env

await client.auth.setup('openai', { api_key: 'sk-xxx' })
const oai = await client.auth.bind('openai')
const models = await (await oai.get('/models')).json()

const token = await client.auth.getToken('openai')
const services = await client.auth.listServices()

await client.creds.push('my_token', 'value')
const names = await client.creds.list()

const result = await client.workflows.run('my-flow', { email: 'user@example.com' })
```

### Build 產物

```
dist/
├── index.js     ← ESM
├── index.cjs    ← CJS
├── index.d.ts   ← TypeScript 型別
└── index.d.cts
```

### Crypto 實作

使用 Web Crypto API（`crypto.subtle`），相容 Node 18+ / browsers / CF Workers / Deno：

```typescript
async function encrypt(plaintext: string, hexKey: string): Promise<{ encrypted: string; iv: string }> {
  const key = await crypto.subtle.importKey('raw', hexToBytes(hexKey), { name: 'AES-GCM' }, false, ['encrypt']);
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, new TextEncoder().encode(plaintext));
  return { encrypted: toBase64(ciphertext), iv: toBase64(iv.buffer) };
}
```

---

## arcrun.dev 網站

### 現有狀態（`arcrun/landing/`）

已完成：
- [x] `/` — Hero + Code Demo（Python/JS/HTTP tabs）
- [x] `/login` — Google + GitHub OAuth 按鈕（前端 OK，需設 OAuth secrets）
- [x] `/dashboard` — API Key 查看/Copy/Rotate/Revoke（依賴 `/me` API）
- [x] `/integrations` — 20 個 recipe 靜態卡片
- [x] `/api-docs` — Swagger UI CDN 嵌入
- [x] `middleware.ts` — 保護 `/dashboard`（未登入 → `/login`）
- [x] Cloudflare Pages 部署

待完成：
- [ ] OAuth secrets 設定（`GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` / `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`）
- [ ] `/components` 頁面（零件列表 — 21 個 WASM 零件的 input/output/config_example）
- [ ] 首頁 code demo 更新為三種使用方式（CLI / Python / JS）
- [ ] 登入流程真實驗證

### 新增頁面：`/components`

```
/components
  ├── 零件卡片（21 個）
  │   ├── canonical_id
  │   ├── display_name
  │   ├── description
  │   ├── input_schema（required / optional 欄位）
  │   ├── output_schema
  │   ├── credentials_required（if any）
  │   └── config_example（YAML code block）
  └── 分類篩選（邏輯 / API / 控制流）
```

資料來源：靜態嵌入（從 `registry/components/*/component.contract.yaml` 在 build 時讀取），不依賴 runtime API。

### OAuth 設定（待 richblack 操作）

需要在 Cloudflare Worker 設定以下 secrets：

```bash
wrangler secret put GOOGLE_CLIENT_ID --name arcrun-cypher-executor
wrangler secret put GOOGLE_CLIENT_SECRET --name arcrun-cypher-executor
wrangler secret put GITHUB_CLIENT_ID --name arcrun-cypher-executor
wrangler secret put GITHUB_CLIENT_SECRET --name arcrun-cypher-executor
wrangler secret put SESSION_SIGNING_SECRET --name arcrun-cypher-executor
```

---

## server 端需要的修改

### cypher-executor 修改（最小化）

目前 `POST /credentials` 端點（`routes/credentials.ts`）接收 `{ name, encrypted, iv }` 後直接存 KV。

SDK 需要的改動：

1. **`GET /auth-recipes` 回應格式**：目前 list 端點回 `{ recipes: [...] }` 但 recipe 的 `service` 欄位是 key — SDK 已在 list_services() 正確處理 ✅

2. **`GET /auth-recipes/:service` 回應格式**：目前回 `{ success: true, recipe: {...} }` — SDK 需讀 `body.recipe` 而非 body 本身 ✅

3. **`POST /credentials` 不需改動** — SDK 自己做 AES-GCM 加密後送 `{ name, encrypted, iv }` ✅

4. **未來**：新增 `GET /credentials/:name/secret` 端點（解密返回 plaintext），讓跨 session 的 `bind()` 能工作。但此端點在 `u6u-core/credentials/src/actions/getCredentialSecret.ts` 已有實作 — 需要在 cypher-executor 整合或 Service Binding 到 u6u-credentials Worker。**封測後再做。**

---

## 不做的事（明確排除）

- ❌ 不在 SDK 裡做 workflow 解析或 YAML 處理 — 那是 CLI 的職責
- ❌ 不在 SDK 裡做 server-side 解密 — 解密只在 server 端
- ❌ 不建新的 credentials Worker — 用現有的
- ❌ 不建新的 KV namespace — 用現有的 CREDENTIALS_KV
- ❌ 不改 cypher-executor 的 credential-injector.ts — 那已經完成且測試通過

---

## 實作順序

```
Phase 1：Python SDK 重建 + 測試
  1.1 重建 arcrun/python-sdk/（按本 SDD 的結構）
  1.2 修正上次的 bug：recipe 回應 wrapper、inject key "header" vs "headers"、secret key mapping
  1.3 對 cypher.arcrun.dev live 測試全部 API
  1.4 本地安裝測試（pip install -e .）

Phase 2：JS SDK 重建 + 測試
  2.1 重建 arcrun/js-sdk/（按本 SDD 的結構）
  2.2 同步修正 Python SDK 發現的所有 recipe 格式問題
  2.3 build（tsup）+ 本地測試

Phase 3：arcrun.dev 網站補完
  3.1 新增 /components 頁面
  3.2 更新首頁 code demo（三種使用方式）
  3.3 OAuth secrets 設定（需 richblack 操作 GCP / GitHub）
  3.4 登入流程驗證

Phase 4：GitHub README + 發布
  4.1 更新 arcrun/README.md — 三種 Quick Start
  4.2 pip publish（arcrun）
  4.3 npm publish（TBD 套件名）
  4.4 最終驗證：從零開始 pip install / npm install / 打 API
```
