Files
bear-cli/docs/commands.md
T
alex 76a14686c1 feat: 實作 bear CLI 骨架與 PAT 模式共用基礎(issue #6)
- 專案骨架:Elixir + Mix escript(單檔可執行 bear),HTTP 一律 Req
- 設定/憑證檔讀寫:0600、原子寫入(rename)、O_CREAT|O_EXCL 防 symlink
- PAT Bearer 呼叫 /userinfo;--json 輸出;規格化退出碼
- 補齊 main 規格承諾的 BEAR_TOKEN 環境變數(優先於憑證檔、不寫入)
- login 無效 PAT 退出碼 3(依 docs/commands.md §3.1);token --refresh 用法錯誤 2(§3.3)
- 35 個單元測試;mix precommit(compile --warnings-as-errors + format + test)全綠
- 沿用已關閉 PR #3 的實作(feat/pat-mode-mvp 分支),文件改依 main 現行版本更新

apps 管理指令待 #5 規格與 alterminal/bear#28 API 就緒後實作。
2026-09-07 22:44:25 +08:00

11 KiB
Raw Blame History

Bear CLI 指令規劃(Command Spec)

狀態:P2 PAT 模式 MVP 已實作(見第 9 節);Device Flow 待伺服器端 P1。 範圍:本文件為指令介面規格;App 管理指令(bear apps CRUD)另見 #5 規劃。 登入採用的 OIDC Device Authorization Grant(RFC 8628)細節,見 bear 倉庫 docs/cli-feature-plan.md(issue #17)。 另支援以**個人存取權杖(PAT)**登入(bear 已於 profile 頁提供 PAT 建立/列表/撤銷,/userinfo 接受 PAT 作為 Bearer)。


1. 命名與呼叫慣例

  • 可執行檔名:bear(Linux 終端機呼叫 bear <command>)。
  • 指令採「動詞在前」的扁平結構,不設多層子指令(首版維持簡單,後續需要再擴充)。
  • 全部指令支援 --json 輸出機器可讀結果;人類可讀輸出為預設。
  • 輸出語言跟隨系統 Locale,優先繁體中文(zh_TW),英文(en)為後備。

2. 全域選項(Global Options)

選項 型別 預設 說明
--issuer URL string https://alterminal.com Bear(OIDC Provider)位址;可被設定檔覆寫
--config PATH string ~/.config/bear/config.json 設定檔路徑
--json bool false 機器可讀輸出(JSON)
-v, --verbose bool false 顯示詳細日誌(含 HTTP 請求摘要,但不含 token)
-h, --help bool — 顯示說明
--version bool — 顯示版本後離開

優先序:CLI flag > 環境變數(BEAR_ISSUER、BEAR_TOKEN)> 設定檔 > 內建預設。

BEAR_TOKEN 為一次性/CI 用的個人存取權杖(PAT);提供後優先於本機憑證檔,但不寫入憑證檔。


3. 指令詳述

3.1 bear login

登入 Bear,取得並保存憑證。預設走 Device Flow;亦可用 --token 直接以**個人存取權杖(PAT)**登入。

bear login [--scope "openid profile email"] [--client-id ID]
bear login --token <PAT>
選項 預設 說明
--scope openid profile email Device Flow 請求的 scope(空白分隔)
--client-id 設定檔內值 OIDC client id
--token <PAT> — 直接以個人存取權杖登入(跳過 Device Flow)

行為(Device Flow,未給 --token):

  1. 讀取設定 → 向 {issuer}/.well-known/openid-configuration 取 device_authorization_endpoint(動態發現,不寫死網址)。

  2. POST /device_authorization(client_id + scope)。

  3. 終端印出:

    請在瀏覽器開啟:https://alterminal.com/device
    輸入代碼:ABCD-EFGH
    

    若終端支援,同步印出 QR(掃碼直達 verification_uri_complete)。

  4. 以 interval 週期輪詢 POST /token(grant_type=device_code):

    • authorization_pending → 繼續等待;
    • slow_down → 加大輪詢間隔;
    • access_denied → 中止並提示;
    • 成功 → 寫入憑證檔(0600),印出 已登入:<email>。

行為(PAT,給 --token <PAT>):

  1. 以該 PAT 作為 Bearer 呼叫 GET /userinfo 驗證可用性並取得身分。
  2. 成功 → 將 PAT 直接寫入憑證檔(0600),印出 已登入:<email>(無 refresh token)。
  3. 失敗(401/無效或已撤銷)→ 提示 PAT 無效,不寫入任何資料。

退出碼:0 成功;3 PAT 無效或已撤銷;4 等待逾時(expires_in);5 使用者拒絕;6 網路/伺服器錯誤。

--json 輸出範例(成功):

{"ok": true, "email": "alice@example.com", "expires_in": 3600}

PAT 模式下 expires_in 依權杖到期日回傳;無到期日則省略該欄位。


3.2 bear whoami

顯示目前登入身分(GET /userinfo,Bearer access token)。

bear whoami

行為:讀取憑證——Device Flow 讀取快取的 access token(過期則先用 refresh token 輪轉換新);PAT 直接使用該 token——呼叫 /userinfo,印出 claims(name、email、picture 等)。/userinfo 同時接受 OIDC access token 與 PAT 作為 Bearer。

退出碼:0 成功;3 未登入。

輸出範例:

sub        : 0190...uuid
name       : Alice Chen
email      : alice@example.com
picture    : https://...

3.3 bear token [--refresh]

印出 access token 到 stdout,供 pipe 給其他工具(如 curl、腳本)。

bear token [--refresh]
選項 說明
--refresh 強制先以 refresh token 輪轉換新 access token

行為:

  • 若無 --refresh 且快取的 access token 未過期 → 直接印出。
  • 若過期(或指定 --refresh)→ 以 refresh token 向 /token 換新,更新快取後印出。
  • PAT 模式:直接印出該 PAT(無 refresh token;--refresh 為無效選項,報用法錯誤 2)。
  • 輸出只含 token 本體(單行、無前綴),方便 pipe;診斷訊息一律走 stderr。

退出碼:0 成功;3 未登入;1 refresh 失敗(token 已撤銷或過期,需重新 login)。


3.4 bear logout

撤銷 refresh token 並清除本機憑證。

bear logout

行為:

  • Device Flow:呼叫 POST /revoke(撤銷 refresh token),無論伺服器回應如何,皆刪除本機憑證檔。
  • PAT:僅刪除本機憑證檔(PAT 的撤銷需於網頁 /profile/tokens 進行,CLI 不提供撤銷)。

退出碼:0 成功;6 伺服器呼叫失敗(但本機憑證已清除)。


3.5 bear status

顯示登入狀態與 token 剩餘效期(不呼叫網路,純本機判定)。

bear status

行為:讀取本機憑證,印出登入與否、憑證類型(Device Flow/PAT)、access token 剩餘秒數、refresh token 是否存在。

退出碼:0 已登入;3 未登入。

輸出範例(Device Flow):

已登入:alice@example.com
憑證類型:Device Flow
access token 剩餘:3210 秒
refresh token:有
issuer:https://alterminal.com

輸出範例(PAT,無到期日):

已登入:alice@example.com
憑證類型:Personal Access Token
access token 剩餘:無到期
refresh token:無
issuer:https://alterminal.com

3.6 bear apps(P3,暫緩)

列出「公開應用」清單,供 Launch/跳轉參考。

bear apps

前置:bear 伺服器端需新增「公開應用清單」API(列為 P3,待確認)。

退出碼:0 成功;3 未登入;6 API 尚未提供。


4. 退出碼總表

碼 語意
0 成功
1 一般錯誤(含 refresh 失敗、憑證檔讀寫失敗)
2 用法錯誤(未知指令/參數)
3 未登入(需先 bear login)
4 授權等待逾時(authorization_pending 超過 expires_in)
5 授權被拒絕(access_denied)
6 網路/伺服器錯誤
7 設定檔/憑證檔格式損毀

5. 設定檔與憑證檔

檔 路徑 內容 權限
設定 ~/.config/bear/config.json issuer、client_id、scope 0644
憑證 ~/.local/state/bear/credentials.json Device Flow:refresh_token+快取 access_token/expires_at;PAT:access_token(無 refresh) 0600
  • Device Flow 只以 refresh token 為主:access token 短命快取,過期以 refresh token 輪轉換新(沿用 Bear 既有 refresh rotation + 重用偵測)。
  • PAT 直接存 access token:無 refresh token、無輪轉;效期由權杖本身的 expires_at 決定(可無到期日)。
  • 寫入憑證檔務必 0600,且以 O_CREAT|O_EXCL 防止 symlink 攻擊。
  • 未來可擴充系統 keyring(libsecret / Keychain),避免明文檔。

6. 錯誤處理與安全

  • authorization_pending、slow_down、access_denied、invalid_grant 依 RFC 8628 §3.5 顯示對應中文提示。
  • 所有診斷訊息走 stderr;--json 模式將錯誤以 {"ok": false, "error": "...", "code": N} 輸出。
  • token、user_code、device_code 一律不入 log、不入 stdout(除 bear token 之目的外)、不入版本庫。
  • 網路層一律使用 Req,遵循 bear 倉庫 HTTP 客戶端慣例。

7. 開放問題(待確認)

  1. CLI 的 OIDC client 採新 method: "device"(client auth 為 none)或沿用 PKCE+JWK?傾向前者(見 bear cli-feature-plan.md §5.5)。
  2. 發行格式:escript(目標機需 Erlang)或 self-contained release(bakeware/burrito)?
  3. bear apps 所需的「公開應用清單」API 是否納入 P3?
  4. 是否支援 offline_access scope,或沿用現行 refresh 輪轉即可?
  5. 是否在 CLI 提供「建立/撤銷 PAT」?目前 PAT 管理僅限網頁(需 session),傾向 CLI 只「使用」不「管理」。

8. 對應關係

本指令 對應 OIDC/Bear 端點
login /.well-known/openid-configuration → POST /device_authorization → POST /token(device_code)
login --token <PAT> GET /userinfo(Bearer PAT 驗證身分,無 OIDC 端點)
whoami GET /userinfo(接受 OIDC access token 或 PAT)
token --refresh POST /token(refresh_token,僅 Device Flow)
logout POST /revoke(僅 Device Flow;PAT 僅本機刪除,撤銷走網頁 /profile/tokens)
apps(P3) 待新增「公開應用清單」API

9. 現行實作狀態(PAT 模式 MVP,issue #6)

目前實作為 PAT 模式 MVP(Elixir + Req、mix escript),與上方 Device Flow 規格的差異:

指令 原規格(Device Flow) 現行實作(PAT 模式)
login 裝置碼流程 --token <PAT> 驗證 GET /userinfo 後保存(- 可從 stdin 讀);未給 --token 時提示 Device Flow 尚未開放(退出碼 2)
whoami GET /userinfo(refresh 輪轉) GET /userinfo(Bearer PAT,無輪轉);BEAR_TOKEN 提供時優先使用
token refresh token 輪轉換新 直接印出 PAT;--refresh 為用法錯誤(退出碼 2);BEAR_TOKEN 提供時優先印出
logout POST /revoke(撤銷 refresh token) 僅清除本機憑證;PAT 需至網頁 /profile/tokens 撤銷
status 顯示 access token 剩餘秒數 純本機判定:顯示模式(PAT/BEAR_TOKEN 環境變數)與 issuer
apps P3 暫緩 未實作;App 管理指令規格見 #5,伺服器端 API 見 alterminal/bear#28

退出碼差異:login/whoami 的 token 無效(401)依 §3.1 為 3(本節實作一致)。

憑證檔結構(PAT 模式,0600、原子寫入、O_EXCL):

{"issuer": "https://alterminal.com", "access_token": "<PAT>", "email": "..."}

建置:

mix deps.get && mix test && mix escript.build   # 產生單檔可執行 bear