# 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 ```