- 對接 alterminal/bear#41 的 /api/v1/vault JSON API(PAT Bearer) - 客戶端端到端加密,格式與 Web Vault(P2)完全一致: - 加密字串 2.<iv>.<ct>.<mac>(AES-256-CBC+HMAC-SHA-256、PKCS#7、encrypt-then-MAC) - 主金鑰 PBKDF2-SHA512(迭代數取自 /vault/config 與 profile,不寫死;salt=email 小寫) - BIP39 助記詞(12 字、128-bit、英文詞表)+救援路徑 - K_user 僅存記憶體:vault unlock 匯出 BEAR_VAULT_SESSION(base64), 同 Bitwarden CLI BW_SESSION 慣例;lock 提示 unset;不寫入任何檔案 - docs/commands.md 補 §3.11 規格;README 同步 - 測試:fake API 注入+Bear.Vault.Crypto 密文樣本交叉驗證(雙向); BIP39 官方向量;46 個新測試,全套 196 passed
48 KiB
Bear CLI 指令規劃(Command Spec)
狀態:P2 PAT 模式 MVP 已實作(見第 9 節);Device Flow 待伺服器端 P1。 範圍:本文件為指令介面規格;App 管理指令(
bear appsCRUD)另見 #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):
-
讀取設定 → 向
{issuer}/.well-known/openid-configuration取device_authorization_endpoint(動態發現,不寫死網址)。 -
POST /device_authorization(client_id+scope)。 -
終端印出:
請在瀏覽器開啟:https://alterminal.com/device 輸入代碼:ABCD-EFGH若終端支援,同步印出 QR(掃碼直達
verification_uri_complete)。 -
以
interval週期輪詢POST /token(grant_type=device_code):authorization_pending→ 繼續等待;slow_down→ 加大輪詢間隔;access_denied→ 中止並提示;- 成功 → 寫入憑證檔(0600),印出
已登入:<email>。
行為(PAT,給 --token <PAT>):
- 以該 PAT 作為 Bearer 呼叫
GET /userinfo驗證可用性並取得身分。 - 成功 → 將 PAT 直接寫入憑證檔(0600),印出
已登入:<email>(無 refresh token)。 - 失敗(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 已上線(PR alterminal/bear#29),本節指令群已實作(issue #10);--visibility/--status過濾目前由 CLI 全量列舉後本地套用(伺服器 list 端點尚無對應查詢參數)。
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 已上線(PR alterminal/bear#38),本節指令群已實作(issue #11);端點細節以 #38 實作為準。
比照 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
變更密碼。無任何命令列參數(密碼一律互動輸入)。
行為:
- 互動提示(不回顯)依序讀取:
目前密碼、新密碼、確認新密碼。 - 兩次新密碼不一致 → 退出碼
2(本地檢查),不發請求。 POST /api/v1/profile/password(Bearer PAT,body 帶current_password/new_password/password_confirmation;CLI 以兩次輸入一致的新密碼填password_confirmation)。- 成功 → 印出
密碼已變更;現行密碼錯誤 → 退出碼1;新密碼不符伺服器強度規則 →422,退出碼1。
比照網頁流程,變更密碼不自動撤銷其他 session;需要時另用 bear sessions revoke-others。
3.7.4 bear email change --new EMAIL
變更 email(雙向驗證碼流程)。驗證碼一律互動輸入,不接受命令列參數。
| 選項 | 說明 |
|---|---|
--new EMAIL |
(必填)新 email 位址 |
行為:
POST /api/v1/profile/email/request-codes(Bearer PAT,new_email)→ 伺服器向現有與新信箱各寄一組驗證碼。- 終端印出
驗證碼已寄至現有信箱與 <new>,互動提示(不回顧)讀取:現有信箱驗證碼、新信箱驗證碼。 POST /api/v1/profile/email(new_email/current_code/new_code;API 版需再帶一次new_email,CLI 自動帶入)。- 成功 → 印出
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
0192a1b0-… 203.0.113.10 Mozilla/5.0 (X11; Linux…) 2026-09-07 10:00:00Z 2026-09-08 09:00:00Z
(user_agent 截斷至欄寬;--json 輸出完整值。API 回應欄位為 id/ip/user_agent/created_at/last_seen_at,不含 expires_at。)
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 (無到期) (未使用) expired
STATUS:active/expired(CLI 依 expires_at 本地判定)。API 的 list 端點只回未撤銷的 token,故 revoked 不會出現在清單。
bear tokens create --name NAME [--expires-in DAYS|never]:POST /api/v1/profile/tokens(Bearer PAT,bodyname/expires_in;scope 由伺服器給預設openid profile email,API 不接受指定)。成功後一次性顯示:
PAT 已建立:ci-token(30 天後到期)
token(只顯示這一次,請立即保存):
9f3a…(Base64url,43 字元,無前綴)
| 選項 | 預設 | 說明 |
|---|---|---|
--name |
(必填) | 顯示名稱;空白或重複名稱依伺服器規則 422 → 退出碼 1 |
--expires-in |
30 |
天數(正整數)或 never(永不過期) |
--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 已撤銷;若撤銷的是目前憑證用的 PAT,後續指令會得到401(退出碼3),屆時重新bear login(本機憑證檔未存 token id,CLI 無法預先比對,僅能提示此退路)。
3.7.7 bear mfa
TOTP 兩因子管理(WebAuthn/passkey 不在範圍)。
-
bear mfa status:讀GET /api/v1/profile的mfa_enabled欄位,印出MFA(TOTP):已啟用/未啟用。 -
bear mfa setup:兩步互動流程——POST /api/v1/profile/mfa/setup(無 body,Bearer PAT)→ 回傳 Base32secret與otpauth://URI。終端印出 URI、secret,並在本機產生 ASCII QR(掃碼加入驗證器;QR 由 CLI 本地生成,不經伺服器;--json模式此步資訊走 stderr,stdout 留給最終結果)。- 互動提示(不回顧)讀取 6 位碼 →
POST /api/v1/profile/mfa/setup/confirm(code)確認。 - 成功 → 一次性顯示 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": ["…", "…"]}(只出現這一次)。
3.8 bear jwks:簽章金鑰管理指令群(admin,已實作)
依賴:bear 伺服器端 JWK 管理 JSON API(alterminal/bear#36/PR #46,資源前綴
/api/v1/jwks,PAT Bearer 認證、admin 限定)。API 已上線,本節指令群已實作(issue #12)。
比照 apps 的「資源群組」結構。金鑰材質(key_data)永不經 CLI 顯示(API 端即不序列化;公開 JWK 走 /.well-known/jwks.json),本群組僅管理公開中繼資料。
bear jwks list [--json]
bear jwks show <kid> [--json]
bear jwks create --kid KID [--alg ALG] [--json]
bear jwks toggle <kid> [--json]
認證與授權(群組共通):一律使用既有 PAT 憑證(bear login --token 或 BEAR_TOKEN 環境變數)作為 Bearer,僅限 admin 角色。
401(token 無效)→ 退出碼 3,提示重新 bear login;403(非 admin)→ 退出碼 8,提示此操作需 admin 權限,不重試。
識別與退出碼:金鑰一律以 kid(人類可讀)識別;API 路徑參數為 UUID,由 CLI 先以 list 比對解析。退出碼同 §3.6 群組共通表(0/1/2/3/6/8)。
3.8.1 bear jwks list
行為:GET /api/v1/jwks(Bearer PAT),列出所有金鑰(active 與 inactive),最新在前;人類可讀輸出為表格(ID 前 8 碼、KID、KTY、ALG、ACTIVE、CREATED AT)。
ID KID KTY ALG ACTIVE CREATED AT
0192cccc sig-2026-01 RSA RS256 active 2026-09-09T00:00:00Z
0192cccc sig-2025-12 EC ES256 inactive 2025-12-01T00:00:00Z
--json 輸出範例:{"ok": true, "jwks": [{…}]}(僅公開欄位,無 key_data)。
3.8.2 bear jwks show <kid>
行為:GET /api/v1/jwks/{id}(以 kid 解析),印出公開欄位(id、kid、kty、alg、use、active、created_at);找不到 kid → 退出碼 1。
3.8.3 bear jwks create --kid KID [--alg ALG]
| 選項 | 預設 | 說明 |
|---|---|---|
--kid |
(必填) | 金鑰識別碼 |
--alg |
RS256 |
RS256/RS384/RS512/ES256/ES384/ES512 |
行為:POST /api/v1/jwks(body 為 kid 與 alg;金鑰由伺服器產生)。成功印出 JWK 已建立:<kid>(<alg>/active)。alg 非法 → 退出碼 2(本地檢查);kid 重複等 422 → 退出碼 1 逐欄位列錯。
--json 輸出範例:{"ok": true, "jwk": {…}}
3.8.4 bear jwks toggle <kid>
行為:POST /api/v1/jwks/{id}/toggle(以 kid 解析),切換 active ⇄ inactive;成功印出如 sig-2026-01:active → inactive。
--json 輸出範例:{"ok": true, "jwk": {"id": "…", "kid": "…", "active": false}}
3.9 bear accounts:帳號管理指令群(admin,已實作)
依賵:bear 伺服器端 帳號管理 JSON API(alterminal/bear#36/PR #46,資源前綴
/api/v1/accounts,PAT Bearer 認證、admin 限定)。API 已上線,本節指令群已實作(issue #12)。
帳號 JSON 不含機密欄位(hash_password、totp_secret、recovery codes;API 端保證),CLI 亦不顯示。敏感規則與伺服器端一致:不可移除自己的 admin 角色(403)、不可刪除自己(403)。
bear accounts list [--page N] [--per-page N] [--json]
bear accounts show <email> [--json]
bear accounts create --email EMAIL [--role user|admin] [--generate-password] [--json]
bear accounts update <email> --role user|admin [--json]
bear accounts set-password <email> [--generate-password] [--json]
bear accounts delete <email> [--json]
認證與授權(群組共通):同 §3.8(PAT Bearer、僅限 admin;401 → 3;403 → 8)。退出碼同 §3.6 群組共通表。
識別與密碼:帳號一律以 email 識別;API 路徑參數為 UUID,由 CLI 先以 list 分頁比對解析。密碼不進命令列(避免 shell history 與 ps):交互提示輸入(不回顧)或 --generate-password 由 CLI 產生。初始/新密碼只在成功當下一次性輸出。
3.9.1 bear accounts list
行為:GET /api/v1/accounts(Bearer PAT,分頁,最新在前);人類可讀輸出為表格。
EMAIL ROLE TOTP CREATED AT
admin@example.com admin on 2026-01-01T00:00:00Z
alice@example.com user off 2026-02-01T00:00:00Z
--json 輸出範例:{"ok": true, "page": 1, "per_page": 20, "total": 2, "accounts": […]}
3.9.2 bear accounts show <email>
行為:GET /api/v1/accounts/{id}(以 email 解析),印出公開欄位(id、email、role、totp_enabled、created_at、updated_at);找不到 → 退出碼 1。
3.9.3 bear accounts create
| 選項 | 預設 | 說明 |
|---|---|---|
--email |
(必填) | 帳號 email(唯一) |
--role |
user |
user 或 admin |
--generate-password |
互動輸入 | 由 CLI 產生 16 字元隨機密碼(含字母與數字) |
行為:POST /api/v1/accounts(免信箱驗證;密碼以明文放入 hash_password,伺服器端驗強度後雜湏)。成功印出帳號摘要與一次性密碼區塊:
帳號已建立:new@example.com(user)
初始密碼(只顯示這一次,請立即保存並轉交當事人):
xK7mP9qR2sL4wY6z
--json 輸出範例:{"ok": true, "account": {…}, "initial_password": "…"}(密碼只出現這一次)。
3.9.4 bear accounts update <email> --role user|admin
行為:PUT /api/v1/accounts/{id}(body 為 role)。成功印出 alice@example.com:角色 → admin。伺服器拒絕自降 admin(403)→ 退出碼 8。--role 罪給或非法 → 退出碼 2。
3.9.5 bear accounts set-password <email>
行為:PUT /api/v1/accounts/{id}/password(管理員設新密碼,免舊密碼)。密碼來源同 create;非 TTY 且未給 --generate-password → 退出碼 2,提示加 --generate-password。成功後一次性顯示新密碼(同 create 提示)。
3.9.6 bear accounts delete <email>
行為:DELETE /api/v1/accounts/{id}(管理端硬刪除,免驗證碼)。成功印出 已刪除帳號:<email>;伺服器拒絕自刪(403)→ 退出碼 8。
--json 輸出範例:{"ok": true, "email": "alice@example.com", "deleted": true}
3.10 bear audit-logs:稽核日誌指令群(admin,已實作)
依賵:bear 伺服器端 稽核日誌 JSON API(alterminal/bear#36/PR #46,
/api/v1/audit-logs,PAT Bearer 認證、admin 限定)。API 已上線,本節指令群已實作(issue #12)。
bear audit-logs list [--page N] [--per-page N] [--category CATEGORY] [--json]
認證與授權:同 §3.8(PAT Bearer、僅限 admin;401 → 3;403 → 8)。
3.10.1 bear audit-logs list
| 選項 | 預設 | 說明 |
|---|---|---|
--page |
1 |
分頁頁碼 |
--per-page |
50 |
每頁筆數(伺服器上限 200) |
--category |
全部 | 過濾類別(伺服器端支援;如 account、jwk) |
行為:GET /api/v1/audit-logs(Bearer PAT,最新在前);回應附 emails 對照(account/actor id → email),人類可讀輸出以 email 呈現、無對照時顯示 id 前 8 碼:
TIME CATEGORY EVENT ACTOR ACCOUNT
2026-09-09T01:00:00Z account account_created_by_admin admin@example.com alice@example.com
--json 輸出範例:{"ok": true, "page": 1, "per_page": 50, "total": 1, "total_pages": 1, "emails": {…}, "entries": […]}
3.11 bear vault:密碼管理指令群(P3,已實作)
依賴:bear 伺服器端 vault JSON API(alterminal/bear#41,
/api/v1/vault、PAT Bearer、任何有效 PAT 帳號)與 Web Vault(P2,alterminal/bear#48/#49)。兩者皆已上線,本節指令群已實作(issue #17)。
端到端加密(與 Web Vault 完全一致,同一 vault 兩端互相可解):
- 加密字串
"2.<iv_b64>.<ct_b64>.<mac_b64>":AES-256-CBC+PKCS#7、encrypt-then-MAC(HMAC-SHA-256 overiv <> ct)、先驗 MAC 再解密。 - K_user 為隨機 64 byte(前 32 enc_key/後 32 mac_key);K_user 的包裝(wrapped key)加密的是 K_user 的 base64。
- 主金鑰:PBKDF2-SHA512(salt=帳號 email 小寫;迭代數以
GET /api/v1/vault/config公告與 profile 記錄為準,不寫死)。 - 助記詞:BIP39(12 字、128-bit 熵、英文詞表);救援金鑰=種子 hex → PBKDF2-SHA512(salt=email 小寫)。
- 所有加解密全在客戶端(本 CLI)完成;伺服器零解密。
K_user 僅存記憶體(不落盤):vault unlock 解開 K_user 後輸出 session 環境變數設定指令,使用者 export BEAR_VAULT_SESSION=<base64> 匯入後,同一 shell 的後續 vault 指令即可解密(同 Bitwarden CLI 的 BW_SESSION 慣例;環境變數只存在 shell 記憶體,不寫入任何檔案)。vault lock 提示 unset BEAR_VAULT_SESSION 即丟棄。
bear vault status [--json]
bear vault unlock [--json]
bear vault lock
bear vault list [--folder UUID] [--json]
bear vault get <uuid> [--json]
bear vault create --type login|secure_note|card|identity [--folder UUID] [--json]
bear vault edit <uuid> [--json]
bear vault delete <uuid> [--json]
bear vault restore <uuid> [--json]
bear vault purge <uuid> [--json]
bear vault folders list|create|rename <uuid>|delete <uuid> [--json]
bear vault sync [--json]
bear vault password change [--json]
bear vault rescue [--json]
認證與授權(群組共通):
- 使用既有 PAT 憑證(
bear login --token或BEAR_TOKEN)作為 Bearer;任何有效 PAT 帳號皆可。 - KDF salt 需要帳號 email:來源優先序為
--email EMAIL選項>憑證檔記錄的 email>(無法得知 → 用法錯誤2,提示以--email提供)。一般bear login後憑證檔即有 email,無需手動給。 401→3;403→8;404/422→1;網路/伺服器錯誤 →6(沿用 §4 總表)。- 主密碼、助記詞、項目密碼等敏感輸入一律互動提示讀取且不回顧,不接受命令列明文參數;非 TTY →
2。名稱、URI、備註等非敏感欄位互動提示可回顯(直接 Enter=保留現值/略過)。 - vault 尚未初始化(
GET /profile404)→vault status顯示「尚未初始化」並提示至 Web Vault 設定(初始化產生助記詞的流程在 Web Vault P2,CLI 不重作);其餘需要 profile 的指令 → 退出碼1。
3.11.1 bear vault status
顯示初始化狀態、KDF 參數、上次同步與本 shell 解鎖狀態(BEAR_VAULT_SESSION 是否設定)。GET /api/v1/vault/config+GET /api/v1/vault/profile。
--json:{"ok": true, "initialized": true, "kdf": {…}, "unlocked": false, …};未初始化 → {"ok": true, "initialized": false, "unlocked": false}。
3.11.2 bear vault unlock
互動輸入主密碼(不回顧)→ PBKDF2 推導 K_master → 解開 wrapped_user_password 得 K_user → 輸出 export BEAR_VAULT_SESSION=…。主密碼錯誤(MAC 驗證失敗)→ 退出碼 1(訊息「主密碼不正確」)。
--json:{"ok": true, "session": "<base64>", "env": "BEAR_VAULT_SESSION"}。
3.11.3 bear vault lock
K_user 只存在環境變數,本指令提示 unset BEAR_VAULT_SESSION(無法代跨 shell unset)。
3.11.4 bear vault list [--folder UUID]
GET /api/v1/vault/ciphers。未解鎖 → 只顯示 UUID/類型(名稱為密文狀態提示);已解鎖 → 一併解密名稱與所屬資料夾名。--folder 以 data.folder_uuid 過濾(資料夾關聯存在 cipher 的 data 欄位,與 Web Vault 一致)。
UUID NAME TYPE FOLDER UPDATED STATE
c9J2… GitHub login 工作 2026-09-10T05:00:00Z 有效
--json:{"ok": true, "decrypted": true, "ciphers": […含 "plain" 明文欄位(已解鎖時)…]}。
3.11.5 bear vault get <uuid>
GET /api/v1/vault/ciphers/{uuid}。已解鎖 → 解密全部欄位(名稱/帳號/密碼/URI/備註/額外資訊);未解鎖 → 顯示密文與提示。密碼欄位只在 stdout 呈現(供腳本擷取),不提供 --clip(本版)。
3.11.6 bear vault create --type login|secure_note|card|identity [--folder UUID]
互動輸入欄位(名稱必填;login:帳號/密碼(不回顧)/URI(逗號分隔可多個)/備註;secure_note:筆記內容;card/identity:額外資訊,加密存於 data.extra——與 Web Vault 相同的欄位配置)。客戶端加密後 POST /api/v1/vault/ciphers(客戶端產生 UUID)。需要解鎖。
3.11.7 bear vault edit <uuid>
GET 現值 → 解密 → 逐欄提示(直接 Enter=保留現值;密碼欄不回顧)→ 僅重加密有變更的欄位 → PUT /api/v1/vault/ciphers/{uuid}。需要解鎖。
3.11.8 bear vault delete/restore/purge <uuid>
delete → POST …/{uuid}/delete(回收桶);restore → POST …/{uuid}/restore;purge → DELETE …/{uuid}/purge(永久刪除,互動確認 y 才執行,其他輸入取消 → 退出碼 1)。已回收桶再 delete → 409 → 1。
3.11.9 bear vault folders list|create|rename <uuid>|delete <uuid>
資料夾 CRUD(name 客戶端加密):GET/POST(互動輸入名稱,需解鎖)/PUT …/{uuid}(互動輸入新名稱,需解鎖)/DELETE …/{uuid}。刪除資料夾不影響所屬項目(僅失去分類)。
3.11.10 bear vault sync
GET /api/v1/vault/sync(伺服器回 profile+folders+ciphers 並標記 last_synced_at)。離線衝突以伺服器為準(CLI 為無狀態客戶端,不做雙向合併;本機無快取可衝突)。顯示數量統計;--json 回完整 sync payload。
3.11.11 bear vault password change
互動輸入:目前主密碼 → 驗證(解開 wrapped_user_password)→ 新主密碼+確認(≥8 字元,兩次一致)→ 以新 K_master 重新包裝 K_user → PUT /api/v1/vault/profile(只動 wrapped_user_password,ciphers 不需重新加密)。
3.11.12 bear vault rescue
忘記主密碼的救援路徑:互動輸入助記詞(12 字,會做 BIP39 完整驗證:字數/詞表/校驗和)→ 解開 wrapped_user_mnemonic 得 K_user → 設新主密碼(≥8 字元,兩次一致)→ 重新包裝上傳。助記詞錯誤 → 退出碼 1。CLI 不提供助記詞顯示(僅 Web Vault 設定時顯示一次)。
4. 退出碼總表
| 碼 | 語意 |
|---|---|
0 |
成功 |
1 |
一般錯誤(含 refresh 失敗、憑證檔讀寫失敗) |
2 |
用法錯誤(未知指令/參數) |
3 |
未登入(需先 bear login) |
4 |
授權等待逾時(authorization_pending 超過 expires_in) |
5 |
授權被拒絕(access_denied) |
6 |
網路/伺服器錯誤 |
7 |
設定檔/憑證檔格式損毀 |
8 |
權限不足(HTTP 403;管理端操作僅限 admin:apps/jwks/accounts/audit-logs) |
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. 開放問題(待確認)
- CLI 的 OIDC client 採新
method: "device"(client auth 為none)或沿用PKCE+JWK?傾向前者(見 bearcli-feature-plan.md§5.5)。 - 發行格式:escript(目標機需 Erlang)或 self-contained release(bakeware/burrito)?
bear apps list --visibility public未來是否開放非 admin(僅公開/active 清單,同網頁 Launch)?目前規劃為 admin 限定。- 是否支援
offline_accessscope,或沿用現行 refresh 輪轉即可? - 是否在 CLI 提供「建立/撤銷 PAT」?原規劃傾向 CLI 只「使用」不「管理」;#35 的 PAT 端點落地後,
bear tokens已納入 §3.7.6 規格(管理自己帳號的 PAT),此題視為已解。 - App 管理 API 的路徑參數最終採 UUID 或
client_id(alterminal/bear#28 規格為{id})?若為 UUID,CLI 需先以 list 解析client_id→ UUID,或伺服器提供以client_id直取的端點。 bear profile set --phone-verified(自我標記電話已驗證)是否開放?#35 已定案:API 接受該欄位(profile_changeset的profile_fields含phone_number_verified),CLI 保留--phone-verified旗標(僅設定,清除不在本期)。mfa setup的secret/otpauth://屬敏感資料,#35 的 API 回應是否應限制僅 setup 流程期間回傳(未確認前不重送)?#38 實作為 setup 回應一次性回傳,未確認前重呼 setup 會重產 secret;CLI 依此實作(第二步走/mfa/setup/confirm)。
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(取 secret)→ POST /api/v1/profile/mfa/setup/confirm(帶 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 暫緩 | ✅ 已實作(issue #10):list/show/create/update/rotate-secret/toggle,對接 alterminal/bear#28 的 /api/v1/apps JSON API(PAT Bearer、admin 限定)。client_id → UUID 由 CLI 以 list 解析;一次性 client_secret 僅於 create/rotate-secret 成功當下輸出 |
profile/password/email/sessions/tokens/mfa |
P3 暫緩 | ✅ 已實作(issue #11):profile show/set、password change、email change、sessions list/revoke/revoke-others、tokens list/create/revoke、mfa status/setup/disable/recovery-codes,對接 alterminal/bear#38 的 /api/v1/profile* JSON API(PAT Bearer、任何有效帳號)。敏感輸入互動提示;一次性明文(PAT 明文、recovery codes)僅於成功當下輸出;mfa setup 的 ASCII QR 由本機 eqrcode 生成 |
jwks/accounts/audit-logs |
P3 暫緩 | ✅ 已實作(issue #12):對接 alterminal/bear#46 的管理端 JSON API(PAT Bearer、admin 限定);規格見 §3.8–§3.10 |
退出碼差異: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