# Design 補充:設定分層(env > 專案層 > 全域)+ init 非互動 > 2026-06-04 建立。`sdk-and-website/design.md` 的單檔補充(規則 02 §4.3 允許)。 > 來源:`docs/壓測報告.md` §1.2(方案 C)+ §2.2(init 非互動)= 阻斷項 #7、#8。 > richblack 2026-06-04 授權:「#7#8 是同一問題,明顯發現的問題當然要解決,完成後就要推。」 --- ## 1. 問題(壓測實測) `acr` v1.1.0 把設定**寫死在全域唯一一份** `~/.arcrun/config.yaml`(`config.ts`: `CONFIG_DIR = join(homedir(), '.arcrun')`),且 `init` 只能 readline 互動。後果: - **#8 多帳號**:同一台電腦只能一個 arcrun 身份 → 接案者 / 多公司 / 個人+公司混用做不到。 壓測者克難法是覆寫 `HOME`(需讀原始碼才找得到、不直覺)。 - **#7 非互動**:init 只能 TTY 問答 → AI / CI 無法用 flag / env 完成。 兩者本質同一:**設定來源不該只有「全域單檔 + 強制 TTY」一條路**。 ## 2. 設計(richblack 2026-06-04 拍板:三層全上 + init flag/env) ### 2.1 設定分層優先序(仿 git config / Claude Code MCP) ``` 1. 環境變數 ARCRUN_* / CLOUDFLARE_* ← 最高,解 #7(AI/CI 非互動) 2. 專案層設定 <就近往上找>/.arcrun.yaml ← 解 #8(接案多帳號),有 fallback 3. 全域設定 ~/.arcrun/config.yaml ← 平常自動用(自己的帳號) ``` - **就近往上找**:從 `process.cwd()` 往上層目錄逐層找 `.arcrun.yaml`,找到第一個即用(停在檔案系統根)。 → 自己的專案不放檔 → 自動 fallback 全域;客戶資料夾放檔 → 只在該樹生效,離開自動切回。零心智負擔、不會忘記切換。 - **覆蓋是「欄位級 merge」**:高層只覆蓋它有提供的欄位,未提供的欄位 fallback 到低層。 (例:專案層只放 `cypher_executor_url`,其餘仍用全域。) ### 2.2 env 變數對應(最高層,欄位級覆蓋) | env | 覆蓋 config 欄位 | 用途 | |---|---|---| | `ARCRUN_MODE` | `mode` | local/standard/self-hosted | | `ARCRUN_API_KEY` | `api_key` | standard | | `ARCRUN_CYPHER_EXECUTOR_URL` | `cypher_executor_url` | self-hosted 指向自己的 cypher | | `CLOUDFLARE_ACCOUNT_ID` | `cloudflare_account_id` | self-hosted(沿用 wrangler 慣用名)| | `CLOUDFLARE_API_TOKEN` | `cf_api_token` | self-hosted(沿用 wrangler 慣用名)| > CF 兩個用 `CLOUDFLARE_*` 而非 `ARCRUN_*`:與 wrangler / deploy.ts 既有慣例一致(deploy.ts 跑 wrangler 時就是設這兩個 env),CI 設一次兩邊通用。 ### 2.3 init 非互動(flag > env > 互動問答) `acr init --self-hosted` 取得 account-id / api-token 的順序: 1. **flag**:`--account-id ` / `--api-token ` 2. **env**:`CLOUDFLARE_ACCOUNT_ID` / `CLOUDFLARE_API_TOKEN` 3. **互動**:前兩者缺才 readline 問(保留現有 UX) > mindset §7 判準:帳號設定**不是**「暴露資料 / 建零件」類風險確認,是單純設定值,flag/env 合法、不違反「非 TTY 拒絕代人類確認」。 > 風險確認(exposure_consent 等)仍維持需人類明示,不在本次放寬範圍。 ## 3. 實作(只動 cli/) | 檔案 | 動作 | |---|---| | `cli/src/lib/config.ts` | `loadConfig()` 改三層解析:全域 → merge 專案層 `findProjectConfig()` → merge env(`applyEnvOverrides()`)。新增 `acr config --where` 用的 `resolveConfigSources()` | | `cli/src/commands/init.ts` | `cmdInit` / `initSelfHosted` 收 `accountId`/`apiToken` options,缺才走 env 再 fallback 互動 | | `cli/src/index.ts` | init 加 `--account-id ` / `--api-token ` option | **`loadConfig()` 是唯一設定入口**(12 個指令全走它)→ 改它一處,全指令自動受益分層。 ### 3.1 `acr config --where`(壓測 §1.2 建議 #3,避免用錯帳號) 新增輕量 `acr config` 指令,印出「現在這個資料夾正用哪個帳號 / 設定來自哪一層」: ``` mode: self-hosted(來源:專案層 /path/客戶A/.arcrun.yaml) cloudflare_account_id: abc...(來源:env CLOUDFLARE_ACCOUNT_ID) cypher_executor_url: https://...(來源:全域 ~/.arcrun/config.yaml) ``` > 安全價值:部署前一眼確認「沒用錯帳號」。本次一併做(成本低、直接回應壓測痛點)。 ### 3.2 安全:專案層 .arcrun.yaml 含憑證 → 必須 gitignore 專案層 `.arcrun.yaml` 可能含 `cf_api_token`。`createCredentialsYamlIfMissing()` 既有 gitignore 邏輯 擴充為一併忽略 `.arcrun.yaml`(壓測 §1.2 安全附帶發現:憑證進版控 = 帳號外洩)。 ## 4. 驗收標準(客觀證據,mindset §7) 1. 專案資料夾放 `.arcrun.yaml` → `acr config --where` 顯示來源為該專案層;離開該樹 → 顯示全域。 2. `CLOUDFLARE_ACCOUNT_ID=x CLOUDFLARE_API_TOKEN=y acr init --self-hosted` → 不問互動直接跑(#7)。 3. `acr init --self-hosted --account-id x --api-token y` → 同上(flag 優先於 env)。 4. 三者皆缺 → fallback 互動問答(既有 UX 不破壞)。 5. env > 專案層 > 全域 的欄位級覆蓋:單元測試覆蓋三層 merge。 6. `npx tsc --noEmit` 全綠。 ## 4.1 實作完成記錄(2026-06-04) 全部 task 完成,客觀證據如下(mindset §7): - [x] `config.ts` 三層解析:`loadConfig()` = 全域 → 專案層 → env 欄位級 merge;新增 `findProjectConfig()`(就近往上找)/ `resolveConfigSources()` / `activeProjectConfigPath()` - [x] `init.ts`:`initSelfHosted` 收 flag/env,缺才互動;gitignore 一併排除 `.arcrun.yaml` - [x] `index.ts`:init 加 `--account-id`/`--api-token`;新增 `acr config [--where]` 指令 - [x] 新增 `commands/config.ts`(token 遮罩、來源層標示) - **驗收證據**: - 端對端測試(真實 fs 臨時目錄樹)8/8 通過:深層就近找專案層、未提供欄位 fallback、env 最高層覆蓋、來源層標示、離開專案樹回全域 - `acr init --help` 顯示 flag;`acr config --where` 正確標來源 + token 遮罩 `secret_t…` - `npx tsc --noEmit` 全綠 ## 5. 為何不違反鐵律 - 只動 `cli/`,不碰零件 / cypher-executor 執行路徑 / Service Binding。 - flag/env 是帳號設定值,非風險確認(mindset §7 風險確認仍須人類明示)。 - 不新增頂層 SDD 目錄(本檔是 sdk-and-website 子系統內單檔補充,rule 02 §4.3)。