13b01328c1
- .agents/specs/: spec-driven-dev docs for arcrun MVP, auth-recipe, credential-primitives-wasm (active refactor), landing-page, sdk-and-website, u6u-core-mvp, u6u-platform-evolution. - .agents/steerings/tech.md: detailed tech stack rationale. - docs/user_requirements/: long-form requirements incl. credential primitives, pages spec, py strategy analysis. - tests/: end-to-end harness scaffolding. These are the durable context backing CLAUDE.md's SDD protocol. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
282 lines
11 KiB
Markdown
282 lines
11 KiB
Markdown
# 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
|
||
```
|