Files
Arcrun/.agents/specs/arcrun/sdk-and-website/design.md
T
Leo 13b01328c1 docs: add SDD specs + user requirements + tests
- .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>
2026-04-20 17:48:24 +08:00

11 KiB
Raw Blame History

Design Document: arcrun SDK Libraries + Website

Overview

本設計涵蓋 arcrun 的三個新增交付物:

  1. Python SDK libpip install arcrun
  2. JS/TS SDK libnpm 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.devHTTP API
  ├── Python SDKarcrun       → cypher.arcrun.devHTTP API
  └── JS SDKarcrun / @arcrun/sdk → cypher.arcrun.devHTTP API

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

cypher.arcrun.devCloudflare 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 SDKarcrun/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             ← CredentialsClientpush/list/delete
    ├── auth.py              ← AuthClientsetup/bind/get_token/list_services
    └── workflows.py         ← WorkflowClientrun/push/list/delete

API 設計

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.tsencrypt() 函數。 解密只在 server 端發生(cypher-executor 的 credential-injector.tsu6u-core/credentials/getCredentialSecret.ts)。


JS/TS SDKarcrun/js-sdk/

目錄結構

arcrun/js-sdk/
├── package.json            ← name TBDarcrun vs @arcrun/sdk),tsup build
├── tsconfig.json           ← ES2020, NodeNext
└── src/
    ├── index.ts             ← export class Arcrun
    ├── crypto.ts            ← Web Crypto API AES-GCM encryptclient 端)
    ├── creds.ts             ← CredentialsClientpush/list/delete
    ├── auth.ts              ← AuthClientsetup/bind/getToken/listServices
    └── workflows.ts         ← WorkflowClientrun/push/list/delete

API 與 Python SDK 對等

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 APIcrypto.subtle),相容 Node 18+ / browsers / CF Workers / Deno

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/

已完成:

  • / — Hero + Code DemoPython/JS/HTTP tabs
  • /login — Google + GitHub OAuth 按鈕(前端 OK,需設 OAuth secrets
  • /dashboard — API Key 查看/Copy/Rotate/Revoke(依賴 /me API
  • /integrations — 20 個 recipe 靜態卡片
  • /api-docs — Swagger UI CDN 嵌入
  • middleware.ts — 保護 /dashboard(未登入 → /login
  • 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_schemarequired / optional 欄位)
  │   ├── output_schema
  │   ├── credentials_requiredif any
  │   └── config_exampleYAML code block
  └── 分類篩選(邏輯 / API / 控制流)

資料來源:靜態嵌入(從 registry/components/*/component.contract.yaml 在 build 時讀取),不依賴 runtime API。

OAuth 設定(待 richblack 操作)

需要在 Cloudflare Worker 設定以下 secrets

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 1Python SDK 重建 + 測試
  1.1 重建 arcrun/python-sdk/(按本 SDD 的結構)
  1.2 修正上次的 bugrecipe 回應 wrapper、inject key "header" vs "headers"、secret key mapping
  1.3 對 cypher.arcrun.dev live 測試全部 API
  1.4 本地安裝測試(pip install -e .

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

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

Phase 4GitHub README + 發布
  4.1 更新 arcrun/README.md — 三種 Quick Start
  4.2 pip publisharcrun
  4.3 npm publishTBD 套件名)
  4.4 最終驗證:從零開始 pip install / npm install / 打 API