Files
bear-cli/docs/commands.md
T

32 KiB
Raw Blame History

Bear CLI 指令規劃(Command Spec)

狀態:P2 PAT 模式 MVP 已實作(見第 9 節);Device Flow 待伺服器端 P1。 範圍:本文件為指令介面規格;App 管理指令(bear apps CRUD)另見 #5 規劃、個人自助指令(bear profile 等)另見 #11 規劃。 登入採用的 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:App 管理指令群(P3,規劃中)

依賴:bear 伺服器端 App 管理 JSON API(alterminal/bear#28,資源前綴 /api/v1/apps,PAT Bearer 認證、管理操作限 admin)。API 落地前本節僅為規格。

bear apps 是「資源群組」指令:bear apps <動詞>。這是第 1 節「扁平結構」原則的首個例外——App 的管理操作(列表、檢視、建立、更新、輪轉 secret、啟停用)若攤平命名會造成指令名爆炸(bear apps-create…),故收斂為單一群組;未來同類資源管理指令比照。

bear apps list [選項]
bear apps show <client-id>
bear apps create [選項]
bear apps update <client-id> [選項]
bear apps rotate-secret <client-id>
bear apps toggle <client-id>

認證與授權(群組共通):

  • 一律使用既有 PAT 憑證(bear login --token 登入,或 BEAR_TOKEN 環境變數)作為 Bearer 呼叫 API;不提供其他登入方式。
  • 管理操作僅限 admin 角色帳號。401(token 無效/已撤銷)→ 退出碼 3,提示重新 bear login;403(非 admin)→ 退出碼 8,明確提示「此操作需 admin 權限」,不重試。
  • client_secret 只在 create/rotate-secret 成功當下一次性顯示於 stdout,不寫入憑證檔、log、--verbose 輸出或版本庫;list/show/update/toggle 的回應永遠不含 secret(API 端保證)。
  • App 識別一律以 client_id(人類可讀、唯一),不暴露資料庫 UUID。若 API 路徑參數僅接受 UUID,由 CLI 先以 list 比對解析(見第 7 節開放問題 6)。

群組共通退出碼:

碼 語意
0 成功
1 目標不存在或驗證失敗(HTTP 404/422;含欄位格式錯誤、client_id 重複)
2 用法錯誤(缺必選參數、列舉值非法、PKCE 未綁 --jwk-id 等,由 CLI 本地檢查)
3 未登入或憑證無效(HTTP 401)
6 網路/伺服器錯誤
8 權限不足(HTTP 403,管理操作僅限 admin)

3.6.1 bear apps list

列出 App 清單。

bear apps list [--visibility public|internal] [--status active|inactive]
               [--page N] [--per-page N] [--json]
選項 預設 說明
--visibility 全部 過濾 public 或 internal
--status 全部 過濾 active 或 inactive
--page 1 分頁頁碼(對應 API page)
--per-page 20 每頁筆數(對應 API per_page,上限同伺服器)

行為:GET /api/v1/apps(Bearer PAT),套用過濾與分頁;人類可讀輸出為表格,依 client_id 排序。

CLIENT ID       TITLE             VISIBILITY   STATUS    METHOD          SCOPES
my-app          My App            public       active    client_secret   openid, profile
internal-tool   Internal Tool     internal     inactive  PKCE            openid

--json 輸出範例:

{"ok": true, "page": 1, "per_page": 20, "apps": [{"client_id": "my-app", "title": "My App", "url": "https://example.com", "method": "client_secret", "status": "active", "visibility": "public", "scopes": ["openid", "profile"], "redirect_urls": ["https://example.com/callback"], "post_logout_redirect_uris": [], "sub": "id", "jwk_id": null, "created_at": "2026-09-07T00:00:00Z", "updated_at": "2026-09-07T00:00:00Z"}]}

3.6.2 bear apps show <client-id>

顯示單一 App 完整資訊。

行為:GET /api/v1/apps/{id}(以 client-id 解析);印出全部欄位(同 create 選項集合,另含 status、created_at/updated_at、jwk_id);client_secret 永不回傳。

輸出範例:

client_id     : my-app
title         : My App
url           : https://example.com
method        : client_secret
visibility    : public
status        : active
scopes        : openid, profile
redirect_urls : https://example.com/callback
post_logout_redirect_uris : (無)
sub           : id
jwk_id        : (無)
created_at    : 2026-09-07T00:00:00Z
updated_at    : 2026-09-07T00:00:00Z

--json 輸出範例:{"ok": true, "app": {…同 list 之單一物件…}}

3.6.3 bear apps create

建立 App。method=client_secret 且未給 --secret 時,由伺服器自動產生並一次性回傳明文。

bear apps create --client-id ID --url URL --title TITLE
                 [--method client_secret|PKCE] [--visibility public|internal]
                 [--redirect-url URL]… [--post-logout-redirect-uri URI]…
                 [--scope SCOPE]… [--sub FIELD] [--jwk-id ID] [--secret SECRET] [--json]
選項 預設 說明
--client-id (必填) 唯一識別碼;英數字與連字號/底線
--url (必填) 絕對 URI(http/https 或自訂 scheme 如 myapp://)
--title (必填) 顯示名稱
--method client_secret client_secret 或 PKCE;PKCE 必須給 --jwk-id
--visibility internal public 或 internal
--redirect-url [] 可重複給予,附加為允許的 redirect URL 清單
--post-logout-redirect-uri [] 可重複給予,登出後跳轉 URI 清單
--scope ["openid"] 可重複給予,允許的 scope 清單
--sub id 作為 sub claim 的欄位名
--jwk-id — 綁定的 JWK id(method=PKCE 時必填)
--secret 自動產生 自備 client secret;省略則由伺服器產生並一次性回傳

行為:POST /api/v1/apps(Bearer PAT)。成功後印出 App 摘要與一次性 secret 區塊:

App 已建立:my-app(public/active)
client_secret(只顯示這一次,請立即保存):
  4f9c1d2e-…

--json 輸出範例:{"ok": true, "app": {…}, "client_secret": "4f9c1d2e-…"}(secret 同樣只出現這一次。)

3.6.4 bear apps update <client-id>

更新既有 App。只更新有給的欄位(部分更新);未給的欄位保持不變。client_id 不可變更(伺服器 update 不允許);status 不在此改(用 toggle)。

bear apps update <client-id> [--url URL] [--title TITLE]
                 [--method client_secret|PKCE] [--visibility public|internal]
                 [--redirect-url URL]… [--post-logout-redirect-uri URI]…
                 [--scope SCOPE]… [--sub FIELD] [--jwk-id ID] [--json]

行為:PUT /api/v1/apps/{id}(Bearer PAT),只送有給的欄位。清單類欄位(--redirect-url、--post-logout-redirect-uri、--scope)為整組覆寫語意:有給即取代整份清單。成功後印出更新後的 App 摘要(同 show 的精簡版)。422 驗證失敗時逐欄位列出錯誤。

3.6.5 bear apps rotate-secret <client-id>

輪轉 client secret。舊 secret 立即失效。

行為:POST /api/v1/apps/{id}/rotate-secret(Bearer PAT)。成功後一次性顯示新 secret(提示同 create)。method=PKCE 的 App 不使用 client secret → 退出碼 1,提示該 App 無 secret 可輪轉。

--json 輸出範例:{"ok": true, "client_secret": "9a7b3c…"}(secret 只出現這一次)

3.6.6 bear apps toggle <client-id>

切換 App 狀態:active ↔ inactive。

行為:POST /api/v1/apps/{id}/toggle(Bearer PAT)。成功後印出新狀態,如 my-app:active → inactive。

--json 輸出範例:{"ok": true, "app": {"client_id": "my-app", "status": "inactive"}}


3.7 bear profile:個人自助指令群(P3,規劃中)

依賴:bear 伺服器端個人自助 JSON API(alterminal/bear#35,資源前綴 /api/v1/profile,PAT Bearer 認證、任何有效 PAT 帳號可用,不限 admin)。API 落地前本節僅為規格;端點細節以 #35 定案為準。

比照 bear apps 的「資源群組」例外:個人自助操作若攤平命名同樣會造成指令名爆炸(bear password-change…),收斂為 bear <資源> <動詞> 的六個子群組。頭像(picture)檔案上傳需要 multipart 與本機圖片,終端體驗差,不在本期範圍(#35 有對應端點,未來有需要再評估);如需更換頭像,可自行上傳圖片後以 profile set --picture URL 設定。

bear profile show [--json]
bear profile set [選項] [--json]
bear password change
bear email change --new EMAIL [--json]
bear sessions list|revoke <id>|revoke-others [--json]
bear tokens list|create|revoke <id> [選項] [--json]
bear mfa status|setup|disable|recovery-codes [--json]

認證與授權(群組共通):

  • 一律使用既有 PAT 憑證(bear login --token 登入,或 BEAR_TOKEN 環境變數)作為 Bearer 呼叫 API;不提供其他登入方式。
  • 與 apps 群組不同,個人自助操作任何有效 PAT 帳號皆可(非 admin 限定)。401(token 無效/已撤銷)→ 退出碼 3,提示重新 bear login;403 → 退出碼 8(正常情況不應出現,保留對應);404/422 → 退出碼 1;網路/伺服器錯誤 → 6。退出碼沿用 §4 總表,群組共通部分同 §3.6 表。
  • 敏感輸入一律互動提示讀取且不回顯:目前密碼、新密碼、email 驗證碼、MFA 驗證碼。不接受命令列明文參數(不進 shell history、不出現在 ps)。非 TTY 環境執行需要互動輸入的指令 → 退出碼 2,提示需在終端機執行。
  • 一次性明文(tokens create 的 PAT、mfa setup/recovery-codes 產生的 recovery codes)只於成功當下輸出於 stdout,不寫入憑證檔、log、--verbose 輸出或版本庫(同 apps create 的 client_secret 處理)。
  • 密碼與 email 變更不因 CLI 放寬:沿用伺服器端現行密碼驗證與 email 雙向驗證碼流程,CLI 只是通道。

3.7.1 bear profile show

顯示完整個人資料。

行為:GET /api/v1/profile(Bearer PAT),印出全部 OIDC profile 欄位(name、given_name、family_name、nickname、preferred_username、picture、profile、website、gender、birthdate、zoneinfo、locale、phone_number、address_*、email、email_verified、totp_enabled 等),格式同 whoami 的 key : value。

輸出範例:

sub           : 0190...uuid
email         : alice@example.com
email_verified: true
name          : Alice Chen
given_name    : Alice
family_name   : Chen
nickname      : ali
locale        : zh_TW
zoneinfo      : Asia/Taipei
mfa_enabled   : true

--json 輸出範例:{"ok": true, "profile": {…同上欄位…}}

3.7.2 bear profile set [選項]

更新個人資料。只更新有給的欄位(部分更新,同 apps update 語意);email 不可由此改(走 bear email change),sub、email_verified、totp_enabled 唯讀。

bear profile set [--name NAME] [--given-name NAME] [--family-name NAME]
                 [--nickname NAME] [--preferred-username NAME]
                 [--profile URL] [--picture URL] [--website URL]
                 [--gender male|female|other] [--birthdate YYYY-MM-DD]
                 [--zoneinfo TZ] [--locale LOC] [--phone-number E164]
                 [--phone-verified] [--address-formatted TEXT]
                 [--address-street-address TEXT] [--address-locality TEXT]
                 [--address-region TEXT] [--address-postal-code TEXT]
                 [--address-country TEXT] [--json]
選項 說明
--name 等 20 個欄位選項 對應網頁 /profile/edit 表單的同一欄位集合(profile_changeset 的 profile_fields)
--gender 列舉 male/female/other(非法值 → 退出碼 2)
--birthdate YYYY-MM-DD
--phone-verified 布林旗標(設定 phone_number_verified;清除不在本期,避免自我標記已驗證的濫用——待 #35 確認是否開放)
URL 類(--profile/--picture/--website) 需 http(s):// 開頭,否則 422 → 退出碼 1

行為:PUT /api/v1/profile(Bearer PAT),只送有給的欄位。成功後印出更新後摘要;422 驗證失敗時逐欄位列出錯誤(退出碼 1)。preferred_username 與他人重複 → 422,退出碼 1。

--json 輸出範例:{"ok": true, "profile": {…更新後完整物件…}}

3.7.3 bear password change

變更密碼。無任何命令列參數(密碼一律互動輸入)。

行為:

  1. 互動提示(不回顯)依序讀取:目前密碼、新密碼、確認新密碼。
  2. 兩次新密碼不一致 → 退出碼 2(本地檢查),不發請求。
  3. POST /api/v1/profile/password(Bearer PAT,body 帶 current_password/new_password)。
  4. 成功 → 印出 密碼已變更;現行密碼錯誤 → 退出碼 1;新密碼不符伺服器強度規則 → 422,退出碼 1。

比照網頁流程,變更密碼不自動撤銷其他 session;需要時另用 bear sessions revoke-others。

3.7.4 bear email change --new EMAIL

變更 email(雙向驗證碼流程)。驗證碼一律互動輸入,不接受命令列參數。

選項 說明
--new EMAIL (必填)新 email 位址

���為:

  1. POST /api/v1/profile/email/request-codes(Bearer PAT,new_email)→ 伺服器向現有與新信箱各寄一組驗證碼。
  2. 終端印出 驗證碼已寄至 alice@example.com 與 new@example.com,互動提示(不回顯)讀取:現有信箱驗證碼、新信箱驗證碼。
  3. POST /api/v1/profile/email(current_code/new_code)。
  4. 成功 → 印出 email 已更新:new@example.com,並提醒 bear login 用的 PAT 不受影響(PAT 不綁 email)。

錯誤:same_email/email_taken/invalid_email → 退出碼 1;驗證碼錯誤或逾期 → 退出碼 1,提示重新執行(會重寄驗證碼);伺服器限流(429)→ 退出碼 1,提示稍後再試。

--json 輸出範例(成功):{"ok": true, "email": "new@example.com"}

3.7.5 bear sessions

管理網頁 session(裝置登入狀態);與 PAT 無關——撤銷 session 不影響 PAT,PAT 的撤銷用 bear tokens revoke。CLI 以 PAT 呼叫 API,本身沒有「目前 session」,清單不標記目前項。

  • bear sessions list:GET /api/v1/profile/sessions(Bearer PAT),表格輸出,新到舊排序:
ID                                    IP              USER AGENT                  CREATED               LAST SEEN             EXPIRES
0192a1b0-…                            203.0.113.10    Mozilla/5.0 (X11; Linux…)  2026-09-07 10:00:00Z  2026-09-08 09:00:00Z  2026-09-21 10:00:00Z

(user_agent 截斷至欄寬;--json 輸出完整值。)

  • bear sessions revoke <id>:POST /api/v1/profile/sessions/{id}/revoke。成功印出 session 已撤銷;不存在或非本人 → 404,退出碼 1。
  • bear sessions revoke-others:POST /api/v1/profile/sessions/revoke-others。成功印出 已撤銷 N 個其他 session。

<id> 為 session UUID(取自 list 輸出;不接受前綴比對)。

--json 輸出範例:{"ok": true, "sessions": […]}/{"ok": true, "revoked": 3}

3.7.6 bear tokens

管理 PAT。token 明文絕不出現在 list(伺服器只存 SHA-256 雜湊)。

  • bear tokens list:GET /api/v1/profile/tokens(Bearer PAT),新到舊排序:
ID                                    NAME       SCOPE                 CREATED               EXPIRES               LAST USED             STATUS
0192a1c0-…                            ci-token   openid, profile       2026-09-01 00:00:00Z  2026-10-01 00:00:00Z  2026-09-08 08:00:00Z  active
0192a1b0-…                            old-laptop openid, profile      2026-08-01 00:00:00Z  (無到期)              (未使用)              revoked

STATUS:active/expired/revoked(CLI 依 expires_at/revoked_at 本地判定)。

  • bear tokens create --name NAME [--expires-in DAYS|never] [--scope SCOPES]:POST /api/v1/profile/tokens(Bearer PAT)。成功後一次性顯示:
PAT 已建立:ci-token(30 天後到期)
token(只顯示這一次,請立即保存):
  9f3a…(Base64url,43 字元,無前綴)
選項 預設 說明
--name (必填) 顯示名稱;空白或重複名稱依伺服器規則 422 → 退出碼 1
--expires-in 30 天數(正整數)或 never(永不過期)
--scope openid profile email 空白分隔的 scope 清單(同伺服器預設)

--json 輸出範例:{"ok": true, "token": {…list 之單一物件…}, "raw": "9f3a…"}(raw 只出現這一次。) 提醒:新 PAT 要使用須另以 bear login --token 登入(或設 BEAR_TOKEN)。

  • bear tokens revoke <id>:POST /api/v1/profile/tokens/{id}/revoke(<id> 為 UUID)。成功印出 PAT 已撤銷:<name>;若撤銷的是目前憑證用的 PAT,後續指令會得到 401(退出碼 3),屆時重新 bear login;CLI 在撤銷成功後偵測到 id 對應本機憑證時,主動提示執行 bear logout 清除本機憑證。

3.7.7 bear mfa

TOTP 兩因子管理(WebAuthn/passkey 不在範圍)。

  • bear mfa status:讀 GET /api/v1/profile 的 MFA 欄位,印出 MFA(TOTP):已啟用/未啟用。

  • bear mfa setup:兩步互動流程——

    1. POST /api/v1/profile/mfa/setup(無 code,Bearer PAT)→ 回傳 Base32 secret 與 otpauth:// URI。終端印出 URI、secret,並在本機產生 ASCII QR(掃碼加入驗證器;QR 由 CLI 本地生成,不經伺服器)。
    2. 互動提示(不回顯)讀取 6 位碼 → POST /api/v1/profile/mfa/setup(帶 code)確認。
    3. 成功 → 一次性顯示 recovery codes(提示同 PAT:只顯示這一次)。

    已啟用 MFA 再執行 → 退出碼 1,提示已啟用。驗證碼錯誤 → 退出碼 1,可重試(不重產 secret)。

  • bear mfa disable:互動提示(不回顯)讀取目前的 TOTP 碼或 recovery code → POST /api/v1/profile/mfa/disable。成功印出 MFA 已停用;碼錯誤 → 退出碼 1,未停用。

  • bear mfa recovery-codes:互動提示(不回顯)讀取目前的 TOTP 碼或 recovery code → POST /api/v1/profile/mfa/recovery-codes。成功後一次性顯示整組新的 recovery codes(舊組全部失效)。

--json 輸出範例:{"ok": true, "totp_enabled": true};setup 確認成功:{"ok": true, "recovery_codes": ["…", "…"]}(只出現這一次)。


4. 退出碼總表

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

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 之目的外)、不入版本庫。
  • client_secret(App 管理)僅於 apps create/apps rotate-secret 成功當下一次性輸出;403 顯示「此操作需 admin 權限」並以退出碼 8 結束,不重試。
  • 個人自助的敏感輸入(密碼、email/MFA 驗證碼)一律互動提示、不回顧、不接受命令列參數;一次性明文(tokens create 的 PAT、mfa 的 recovery codes)僅於成功當下輸出(見 §3.7)。
  • 網路層一律使用 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 list --visibility public 未來是否開放非 admin(僅公開/active 清單,同網頁 Launch)?目前規劃為 admin 限定。
  4. 是否支援 offline_access scope,或沿用現行 refresh 輪轉即可?
  5. 是否在 CLI 提供「建立/撤銷 PAT」?原規劃傾向 CLI 只「使用」不「管理」;#35 的 PAT 端點落地後,bear tokens 已納入 §3.7.6 規格(管理自己帳號的 PAT),此題視為已解。
  6. App 管理 API 的路徑參數最終採 UUID 或 client_id(alterminal/bear#28 規格為 {id})?若為 UUID,CLI 需先以 list 解析 client_id → UUID,或伺服器提供以 client_id 直取的端點。
  7. bear profile set --phone-verified(自我標記電話已驗證)是否開放?#35 需決定 API 是否接受該欄位;不開放則 CLI 移除此選項。
  8. mfa setup 的 secret/otpauth:// 屬敏感資料,#35 的 API 回應是否應限制僅 setup 流程期間回傳(未確認前不重送)?

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 list GET /api/v1/apps(Bearer PAT;分頁 page/per_page)
apps show GET /api/v1/apps/{id}
apps create POST /api/v1/apps
apps update PUT /api/v1/apps/{id}
apps rotate-secret POST /api/v1/apps/{id}/rotate-secret
apps toggle POST /api/v1/apps/{id}/toggle
profile show GET /api/v1/profile
profile set PUT /api/v1/profile
password change POST /api/v1/profile/password
email change POST /api/v1/profile/email/request-codes → POST /api/v1/profile/email
sessions list GET /api/v1/profile/sessions
sessions revoke <id> POST /api/v1/profile/sessions/{id}/revoke
sessions revoke-others POST /api/v1/profile/sessions/revoke-others
tokens list GET /api/v1/profile/tokens
tokens create POST /api/v1/profile/tokens
tokens revoke <id> POST /api/v1/profile/tokens/{id}/revoke
mfa status GET /api/v1/profile(讀 MFA 欄位)
mfa setup POST /api/v1/profile/mfa/setup(先無 code 取 secret,再帶 code 確認)
mfa disable POST /api/v1/profile/mfa/disable
mfa recovery-codes POST /api/v1/profile/mfa/recovery-codes

apps 群組依賴 alterminal/bear#28 的 App 管理 JSON API(PAT Bearer、admin 限定);profile 系列依賴 alterminal/bear#35 的個人自助 JSON API(PAT Bearer、任何有效帳號)。


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
profile/password/email/sessions/tokens/mfa P3 暫緩 未實作;個人自助指令規格見 #11(§3.7),伺服器端 API 見 alterminal/bear#35

退出碼差異: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