wport CLI v3(Candidate / Personal 線・OAuth)PRD
PRD 代號:pm_47
延續:pm_41(wport CLI v2 企業線,已交付至 @wport/cli 0.5.0)、pm_37(Enterprise REST)、pm_42(應徵紀錄)、pm_43(收藏與追蹤中心)、pm_44(封鎖公司)
CLI 套件:@wport/cli(同一套件擴充,不另發新套件)
產品原則(Eric,沿 pm_41):GUI 能做什麼,CLI 就做什麼;僅法遵級、核彈行為永久排除。
認證裁決(Eric 2026/06/24,pm_41 DR-5):個人線不使用 API 金鑰(wpk_personal_ 廢止);一律 OAuth 2.0 Device Authorization Grant(RFC 8628)——wport login 開瀏覽器授權,token 可隨時於帳號設定撤銷。
本 PRD 無 mockup:CLI 產品無 UI mockup 需求;OAuth 需要的三個極簡 web 觸點(裝置驗證頁 / 授權同意頁 / 已授權裝置清單)以文字規格交付(§9),是否補 mockup 由 FE 排程時再議。
1. 文件資訊
- 文件類型:CLI + OAuth + REST API 擴充需求規格書
- 適用對象:CLI 工程師、後端、前端(web 觸點)、產品、QA、AI Agent 整合(Cursor / Claude)
- 最後更新:2026/07/14
- 版本:1.2.0(2026/07/14:①v1.1.0 Eric 裁決「限量取代禁令」——批次投遞(≤20/次 + 20/day 硬上限)、本人 resumes/applications 匯出、
profile update、站內信 chats 全數改納 v1;核彈清單收斂為四項;apply 新增 --message 必填;新增 DR-9、BR-API-CLI-017。②v1.2.0 Eric 裁決 Agent-first 能力最大化(DR-10,deny-list 制)+ US-1 北極星情境「個人資料 markdown → Agent → 履歷一次做完」——新增 resumes schema/template/validate、整份聚合寫入編排(§6.4.1,後端分節 DTO 為 SoT)、resumes publish/unpublish。③v1.2.1 照片/附件裁決:photo_url/portfolio_links 皆 URL 欄位入聚合格式,v1 無 binary 上傳。④v1.2.2 通道分離:寫入日上限僅計 personal API 通道(計數器掛 namespace guard),GUI 不計入不受限;domain 規則(BR-011、訊息必填)維持通道無關;寫入記 channel 供稽核。⑤v1.2.3 web 面最小化:W-3 裝置清單 GUI 降 v2,session 治理改 CLI wport sessions list/revoke [--all-others](DR-7 改版);v1 web 觸點僅剩 W-1/W-2 兩極簡頁)
- 建立日期:2026/07/14
- 對應 Coda rowId:[新建列,見 §16]
- 對應 Storybook:
https://storybook.wport.me/?path=/docs/mockups-user-clioauth--docs(W-1 裝置驗證頁 /activate + W-2 授權同意頁 wireframe,story id mockups-user-clioauth;story 檔 storybook/src/stories/mockups/user/CliOauth.stories.ts。CLI 本體仍無 mockup,此為 §9 兩個 web 觸點的畫面)
- 代號對照:
pm_41 = 企業 CLI(v2,已交付);本 PRD = pm_47(求職者 CLI,v3);pm_39 = 第三方登入(裝置驗證頁沿用既有 web 登入,含 pm_39 社群登入)
2. 開發進度與設計來源
開發進度
- CLI 現況:
@wport/cli 0.5.0(2026/07/07 發版)——pm_41 企業命令樹已完整覆蓋(jobs / talents / campaigns / company / usage / keys),prod 煙霧測試通過(唯一 major finding F-001 為後端 talents respond 500,屬企業線,不阻塞本 PRD)
- CLI PR #:[TBD](
apps/cli,同 repo 疊加 personal 命令樹)
- 後端 PR #:[TBD](OAuth AS +
/api/v1/personal/* namespace)
- 前端 PR #:[TBD](§9 三個 web 觸點)
設計來源
- pm_41 §3.2 Personal 線規格預留:
wport login|whoami|logout + wport personal resumes ... + wport personal apply(本 PRD 將其具體化並擴大至 GUI parity)
- 競品對標:GitHub CLI(
gh auth login)、Stripe CLI(stripe login)、Vercel CLI、Heroku CLI、npm CLI(npm login --auth-type=web)——業界對「人」的 CLI 登入已收斂到 browser-based / device-code OAuth
- 內部 SoT:
pm_42(應徵紀錄 API 契約)、pm_43(收藏/Saved Filter)、pm_44(封鎖公司)、BR-010/011(履歷上限、投遞冷卻)
3. 功能摘要(Feature Summary)
3.1 功能概述
- 功能名稱:wport CLI v3 — Candidate(Personal)線
- 主要使用者角色:
- 求職者(工程師 / 技術背景求職者):終端機管理個人資料、履歷、投遞、應徵紀錄與 HR 對話
- AI Agent(Claude / Cursor 等,代表求職者本人操作):以使用者已授權的 OAuth session 執行「搜尋 → 逐缺客製訊息 → 投遞 → 追蹤對話」完整求職工作流(DR-9)
- 核心目標:
- 補齊
@wport/cli 個人線:從現況僅公開 jobs search/view 擴展至 與求職者 GUI 等價 的個人資料、履歷、投遞、應徵紀錄、站內信操作
- 建立 wport 第一個 OAuth 2.0 授權伺服器能力(device-code flow),為未來第三方整合(MCP server、合作方 app)鋪路
- 沿用 pm_41 的終端機契約(exit code、
--output json、--fields/--minimal、Idempotency),Agent 零學習成本
旗艦情境 US-1(Eric 2026/07/14,北極星):「個人資料 markdown → AI Agent → 履歷一次做完」
使用者已在個人筆記(如 Obsidian)維護結構化的個人資訊 SSOT(基本資料 / 現職 / 學歷 / 重要經歷 / 專長 / 證照…)。把這份 markdown 貼給 AI Agent,Agent 透過 CLI 一次完成:建立履歷 → 填滿所有節 → 驗證 → (可選)公開。使用者從頭到尾不開 GUI。
- 這個情境對 CLI 的三個硬需求(皆納 v1,見 DR-10):
- Schema 自描述:Agent 要能問 CLI「履歷長什麼樣」——
resumes schema / resumes template
- 整份聚合寫入:後端履歷為分節 DTO(教育/經歷/技能/證照/語言/自傳/求職條件/作品連結),CLI 收單一聚合 JSON 並代為編排多節寫入,逐節回報成敗
- 可迭代的驗證:
resumes validate --file 本地先驗 + server 422 帶欄位級錯誤,Agent 能自我修正
- 責任邊界:筆記中的敏感區塊(如 🔒 付款/電匯資料)與履歷欄位無對映,schema 之外的資料 CLI 一律拒收——Agent 貼錯也不會流出
3.2 範圍界定(三層:✅ v1 會做 / 🕐 不做 / ☢️ 絕對不做)
✅ 包含範圍(pm_47 v1 交付)——逐條命令清單
A. OAuth 認證(頂層命令,不掛在 personal 下)
| # | 命令 | 說明 |
|---|
| V1-1 | wport login(含 --no-browser、--force) | RFC 8628 device flow:CLI 顯示 user code + 驗證 URL(能開瀏覽器就自動開),使用者於 web 登入(含 pm_39 社群登入)並同意授權;CLI 輪詢取得 token |
| V1-2 | wport whoami | 顯示登入身分(display name + email)與 session 資訊 |
| V1-3 | wport logout | server-side revoke refresh token + 清除本機憑證 |
| V1-3a | wport sessions list / revoke <id> [--all-others] | session 治理走 CLI(DR-7,取代 GUI 裝置清單);失竊自救:任一新機 login 後 revoke --all-others |
B. 個人線命令(wport personal,需 OAuth session)
| # | 命令 | 說明 / 規則 |
|---|
| V1-4 | personal profile view | 個人資料摘要 |
| V1-5 | personal profile update --file | Eric 2026/07/14 裁決納入 v1;欄位語義 = GUI 會員中心編輯(含 pm_27 國籍/身分種類,[RD 以既有 DTO 為 SoT]);server 驗證與 GUI 同源 |
| V1-6 | personal resumes list | ≤6 份全列(BR-010),--fields/--minimal |
| V1-7 | personal resumes view <enc_id> | 單筆完整履歷(聚合所有節) |
| V1-8 | personal resumes create --file <full.json> | 整份聚合格式:CLI 代為編排「建履歷殼 + 各節寫入」(後端為分節 DTO,見 §6.4.1);逐節回報成敗;BR-002 → 422;BR-010 滿 6 份 → 422 |
| V1-9 | personal resumes update <enc_id> --file [--section <name>] | 整份或指定節更新;partial success 逐節回報 |
| V1-10 | personal resumes schema [--section <name>] / resumes template | Agent 自描述配套(US-1):印出聚合格式 JSON Schema(欄位說明 + 範例)/ 骨架檔 |
| V1-11 | personal resumes validate --file | 本地依 bundled schema 結構先驗(免打 API);[RD 議定是否加 server dry-run] |
| V1-12 | personal resumes copy <enc_id> [--name] | 複製計入 6 份上限 |
| V1-13 | personal resumes publish|unpublish <enc_id> | 履歷公開/取消公開(企業可否搜到;對應既有 update-published-status);可逆故無需 --confirm |
| V1-14 | personal resumes delete <enc_id> --confirm | 需 --confirm(BR-API-CLI-015) |
| V1-15 | personal resumes export [--out <dir>] | 匯出本人全部履歷完整 JSON(≤6 份);屬「自己資料的讀取便利」,非 BR-020 法遵匯出(見 ☢️ 表註) |
| V1-16 | personal apply <job_enc_id> --resume <enc_id> --message|--message-file --confirm | 單缺投遞;應徵訊息必填(GUI parity:application_message ≤2000 字);BR-011 72h 冷卻;需 --confirm(DR-5) |
| V1-17 | personal apply --file <batch.json> --confirm | 批次投遞(Eric 2026/07/14 裁決納入 v1,推翻原核彈判定);≤20 筆/次、每筆各自必填 message、partial success(比照企業 jobs batch);每筆計入 apply 日上限(DR-6) |
| V1-18 | personal applications list(含 --cooling) | 應徵紀錄 + 冷卻狀態 + next_apply_at(契約對齊 pm_42) |
| V1-19 | personal applications export [--out <file>] | CLI 端自動翻頁把本人應徵紀錄匯成單一 JSON;不需新 server endpoint,受 60/min rate limit 自然節流 |
| V1-20 | personal chats list / view <room_id> / send <room_id> --body|--body-file | 站內信/HR 對話(Eric 2026/07/14 裁決納入 v1);對應既有 chat 模組(web 已有);send 受日上限(§8.3);企業端對口 = pm_41 talents respond |
C. 後端 / Web 交付(v1 必要配套)
| # | 項目 | 說明 |
|---|
| V1-21 | OAuth 2.0 AS 三端點 | device authorization + token + revocation(RFC 8628 / 7009),refresh rotation + reuse detection |
| V1-22 | /api/v1/personal/* namespace | user-scoped Bearer;支撐 V1-4~20(profile / resumes 各節 / apply / applications / chats) |
| V1-23 | Web 觸點 W-1/W-2(僅兩頁) | 裝置驗證頁 /activate + 授權同意頁(device flow 本質成本,極簡、沿既有登入殼);W-3 裝置清單 GUI 降 v2(DR-7) |
v1 邊界原則(DR-4):只包 GUI 後端已存在 的能力(profile / resumes / apply / applications / chats 的 domain service 均為既有,僅需開 personal REST 面)。上表之外的任何個人線命令都不在 v1。
防濫用原則(Eric 2026/07/14):與其禁功能,不如設上限——所有寫入面均有 server 端硬性 quota(§8.3),CLI 不因來源放寬任何 GUI 規則。
🕐 排除範圍(v1 不做;標注去向與閘門)
| 項目 | 去向 | 閘門 / 理由 |
|---|
personal saved(收藏職缺/公司) | v2 | 閘門 = pm_43 後端落地;CLI 為 thin wrapper,不先於 GUI 後端 |
personal filters(Saved Filter 列表/套用) | v2 | 同上(pm_43) |
personal blocks(封鎖/解除封鎖公司,BR-028 上限 50) | v2 | 閘門 = pm_44 後端落地 |
| granular OAuth scope 勾選 | v2 | v1 固定 scope bundle(DR-3);待第三方 client 需求出現再拆 |
| 投遞附加問卷(apply questionnaire) | 另案 | 對齊 pm_42 排除範圍;問卷職缺 CLI 投遞回 422 引導走 GUI(§10.4) |
| 應徵狀態 badge(審閱中/面試邀約/婉拒) | 隨 pm_42 v2 | HR 端流程未落地前,applications list 不出現該欄位 |
CLI 自身 i18n、shell completion、--watch | CLI roadmap 既有項 | 非 pm_47 範圍,見 apps/cli ROADMAP |
☢️ 絕對不做(核彈 / 永久禁止,CLI + Personal API 皆不得實作)
| 行為 | 相關規則 | 說明 |
|---|
| 刪除個人帳號 | BR-012 | 不可逆、GDPR;僅 GUI 多步驟流程(pm_24) |
| GDPR 級帳號個資打包(含系統紀錄、登入軌跡等超出使用者 GUI 可見範圍的法遵資料包) | BR-020 | 法遵流程專屬,不經 CLI/API。注意:V1-12/V1-16 的「本人履歷/應徵紀錄匯出」只是自己 GUI 可見資料的讀取便利,不屬此項 |
個人 API 金鑰(wpk_personal_ 任何形式復活) | pm_41 DR-5 | Eric 2026/06/24 裁決:個人線僅 OAuth |
| 以個人 token 存取他人資料 | BR-017 | personal namespace 一律 user-scoped(BR-API-CLI-016) |
裁決紀錄(Eric 2026/07/14):原 v1.0.0 將「批次投遞」「applications/resumes 全量匯出」列為核彈、「profile update」「站內信」列 v2/另案——均已推翻,改納 v1,以 server 端硬性上限(apply 20/day、chat send 日上限、60/min rate limit)取代功能禁令。核彈清單收斂為上表四項「上限也救不了」的行為(不可逆 / 法遵 / 認證原則 / 資料邊界)。
3.3 成功條件(KPI,個人線上線後 3 個月)
| KPI | 目標 | 備註 |
|---|
| 完成 OAuth 授權的 unique users | ≥ 50 | 求職者 CLI 屬技術族群 niche,對標 pm_41 企業線 30 的同量級 |
| 經 CLI 完成的投遞 | ≥ 100 筆 / 月 | 且 CLI 投遞的 72h 冷卻違規嘗試 100% 被 server 擋下 |
| 經 CLI 的履歷建立/更新 | ≥ 150 次 / 月 | |
| 聚合 create 首次寫入全節成功率 | ≥ 70% | US-1 健康度:衡量 schema/validate 自描述品質;低於此值代表 schema 說明不足,Agent 得靠 422 試錯 |
| device flow 授權完成率 | ≥ 80% | 發出 user code → 成功取 token;低於此值代表 web 觸點流程有摩擦 |
| CLI 個人線 support ticket | < 10 件 / 月 | |
| token 外洩 / 未授權存取事件 | 0 | |
3.4 設計決策依據(Design Rationale)
DR-1:Device Authorization Grant(RFC 8628)為唯一授權流
- 採用:
wport login 走 device flow:POST /oauth/device/code 取 user_code + verification_uri → 使用者瀏覽器登入並同意 → CLI 以 device_code 輪詢 POST /oauth/token(interval 5s,slow_down 時退避)
- 捨棄之替代:
- Authorization Code + PKCE + localhost redirect(Google Cloud CLI 流派):體驗稍順,但 SSH / headless / container 場景會斷;device flow 天然支援「在 A 機器授權 B 機器」
- 密碼直輸 CLI(legacy Heroku 流派):業界已淘汰——CLI 碰密碼即擴大 phishing 面,且與 pm_39 社群登入(Apple 等無密碼帳號)不相容
- SaaS benchmark:
gh auth login 預設 device flow;stripe login、vercel login 均為 browser-based 授權
- wport 情境加分:裝置驗證頁沿用既有 web 登入,pm_39 社群登入使用者(無站內密碼)也能無縫授權 CLI
DR-2:Token 模型 — 短效 access + 輪替 refresh
- 採用:
- Access token:user-scoped Bearer,TTL 1 小時;打
/api/v1/personal/*
- Refresh token:TTL 30 天 sliding(每次 refresh 展期)+ 90 天 absolute 上限;rotation 必做——每次 refresh 換發新 refresh token,舊者立即失效;偵測重用(reuse detection)即撤銷整個 token family 並要求重新
login
- CLI 在 access token 過期時自動 refresh,使用者無感;refresh 失敗才提示重新
login
- 理由:對比企業線
wpk_live_ 強制 30/60/90 天到期(BR-API-CLI-008),個人線的等價風控就是短效 access + 可撤銷 refresh——外洩的 access token 1 小時自然失效,refresh token 被偷用會觸發 reuse detection 全撤
- SaaS benchmark:OAuth 2.0 Security BCP(RFC 9700)明定 public client 的 refresh token 必須 rotation 或 sender-constrained;Auth0 / Okta 預設 rotation + reuse detection
- Token 格式:access token 建議前綴
wpt_(wport token),與 wpk_live_ 在 log / 支援場景可肉眼區分;[RD 決定 JWT vs opaque,建議 opaque + Redis 查驗,與企業 key 30s 快取機制(spec D6)共用基礎]
DR-3:v1 固定 scope bundle,同意頁全揭露
- 採用:first-party CLI client 申請固定 scope 集:
profile:read resumes applications:apply applications:read;授權同意頁逐條列出能力(「讀取你的個人資料」「管理你的履歷」「代你投遞職缺」「查看你的應徵紀錄」),使用者只有同意/拒絕,不可部分勾選
- 理由:勾選式 granular scope 是「第三方 client 不可信」的防線;first-party CLI 拆 scope 只增加同意頁摩擦與 support 成本。同意頁全揭露維持知情權
- SaaS benchmark:
gh auth login 對自家 client 用固定 scope 集(可 --scopes 加購);Stripe CLI 直接授權完整帳號
- v2 預留:token payload 內保留 scope 欄位,未來第三方 client 接入時同一 AS 直接支援勾選
DR-4:GUI parity(求職者版),核彈除外;v1 邊界 = 已上線的 GUI 後端
- 採用:pm_47 v1 只包 GUI 後端已存在 的能力(個人資料、履歷 CRUD、投遞、應徵紀錄、站內信——domain service 均為既有,僅開 personal REST 面);pm_43 / pm_44 的收藏、Saved Filter、封鎖列為 v2,其後端落地即為 v2 開工閘門
- 理由:CLI = thin wrapper over REST(pm_41 DR-1);先做 CLI 端會製造「CLI 打 404」的體驗(0.5.0 changelog 已有此教訓——需標注「需後端已部署」)
resumes delete 允許(--confirm):pm_41 DR-2 已裁決——履歷刪除是使用者自己的資料,非核彈
DR-5:投遞是「對外」動作,CLI 需顯式確認
- 採用:
personal apply 必須 --confirm(或互動確認顯示職缺名 + 使用履歷名);無 --confirm 且非互動 → exit 2、不發請求
- 理由:pm_41 的
--confirm 守則原是給破壞性寫入;投遞不可逆且對外可見(企業端會看到),Agent 代操時尤其需要防「自動投遞失控」。這是 Agent-safety 決策,非對標
- 配套:server 端 BR-011 冷卻不變是最終防線;CLI 在冷卻中投遞回 409 +
next_apply_at(§10.4)
DR-6:批次投遞開放,以硬性上限取代功能禁令(Eric 2026/07/14 裁決,推翻 v1.0.0 原判)
原 v1.0.0 將批次投遞列為核彈(灌檔攻擊面);Eric 裁決:「可以做,設 CLI API 上限就是了」——防濫用手段從「禁功能」改為「限量」。
- 採用:
personal apply --file <batch.json> --confirm——單次 ≤ 20 筆、每筆各自必填 application_message(無共用模板欄位,客製化責任落在呼叫端)、partial success 語義(比照 pm_41 企業 jobs batch:逐筆回 succeeded/failed,單筆失敗不中斷)
- 總量防線(真正的護欄):
- apply 日上限 20/day/user(server 端硬性,單筆與批次共用計數)——批次一次 20 筆 = 當日額度用罄。僅計 personal API 通道(CLI / 第三方 client),GUI 不計入、不受限(Eric 2026/07/14):上限治理的是 API 帶來的自動化量能增量,GUI 投遞有人速天花板 + 既有 bot 防護,設限只會是對現有使用者的倒退;計數器掛 personal namespace guard,一行不動 web 路徑
- BR-011 72h 冷卻逐筆檢查,不因批次放寬——domain 層規則,通道無關(GUI/CLI 同受約束)
- 每筆訊息必填 + ≤2000 字(空訊息海投在 DTO 層即不可能)——同為 domain 層,通道無關
- 套利分析(誠實記錄):通道分離後,理論上可「CLI 20 筆 + GUI 手動再投」——但 GUI 手動投遞本身就是自然限速(逐缺點擊、填表),且 BR-011 讓同缺 72h 只能一次,套利空間對企業端的實際傷害趨近於零;若監控(
channel 欄位)發現異常混打模式,再上調治理,不預先懲罰正常人
- 取捨(誠實記錄):批次讓「模板灌檔」的操作成本更低,這是真實代價;換得的是 Agent 工作流少 N 次 process 啟動與冷卻/失敗的逐筆結構化回報。在 20/day 硬上限下,批次與迴圈的傷害上限相同,禁批次只剩儀式意義——故裁決成立
- SaaS benchmark:上限式治理對標 GitHub API secondary rate limit(不禁自動化、限速率);LinkedIn Easy Apply 之亂的根因是「無訊息 + 無日限」,兩者本 PRD 都堵住了
DR-7:session 治理 — CLI 自管為主,GUI 延後 v2(Eric 2026/07/14 質疑後改版)
原 v1.0.0 規劃帳號設定「已授權裝置」GUI 頁(W-3)為上線必備。Eric 質疑「這代表要做 GUI?」——重新檢視後:治理入口用 CLI 自己做即可,v1 不做 W-3 GUI。
- 採用(v1):session 治理走 CLI 命令
wport sessions list / revoke <session_id> [--all-others]:
list:本帳號全部 active CLI session(裝置名 = CLI 送 hostname、授權時間、最後使用、標示目前 session)
revoke:撤銷任一 session 的 refresh token family;--all-others 一鍵撤銷「除本機外全部」
- 撤銷 → 立即失效,access token 最多殘存快取 TTL(對齊 spec D6 的 30s 級別,[RD 定值])
- 關鍵場景自洽性驗證(為什麼 CLI 足夠):「筆電失竊」場景——受害者在任一新機器
npm i -g @wport/cli && wport login(瀏覽器授權,不依賴舊裝置)→ wport sessions revoke --all-others 完成自救。CLI 的客群定義上就是會用 CLI 的人,不存在「只能用 GUI 自救」的使用者
- 輔助防線:reuse detection 自動全撤 + 必寄警示信(DR-2)、refresh 30d 自然到期、session 上限 ≤ 10 / user(超過汰換最久未使用)
- GUI 版(W-3)→ v2:待帳號設定頁有其他翻新需求時順帶做;屆時讀同一 session API,零後端增量
- SaaS benchmark 重新校準:GitHub / Google 的裝置清單長在 GUI 是因為它們本來就有成熟帳號設定頁;
gh auth status + gh auth logout 證明 CLI 自管 session 是成立的模式。我們沒有現成頁面,為此新開 GUI 工程違反本 PRD「CLI 產品無 mockup / 最小 web 面」的定位
DR-8:單一 credential store,雙線並存
- 採用:
credentials.json(mode 0600、atomic write,沿 0.2.0 既有機制)同時存企業 wpk_live_(enterprise 區)與個人 OAuth token(personal 區);命令 namespace 決定取用哪份憑證,互不干擾
- 理由:同一位工程師常同時是「自家公司 HR 整合維護者」與「求職者」;兩線憑證衝突會逼使用者反覆 login
- 禁止:token 永不 echo 全文(
whoami 不顯示 token);不寫入 shell history 可觸及處;log 遮罩
DR-9:公開線 + 個人線 = 同一 CLI、可 pipeline 串接(旗艦使用場景)
- 採用:個人線不另發套件,與公開線(
jobs search/view,無需登入)同一 @wport/cli binary;命令是否需要認證由 namespace 決定(jobs 公開、personal 需 OAuth)。明確支持「搜尋 N 缺 → 逐缺研究 → 客製化應徵訊息 → 逐一投遞」的 Agent 工作流(§6.5 範例)
- 客製化訊息:GUI 投遞本就必填
application_message(自我介紹,≤2000 字,SoT:apply-job.dto.ts);CLI parity 以 --message / --message-file(含 stdin -)承接——Agent 為每缺產生量身訊息正是 CLI 相對 GUI 的增量價值
- 投遞總量治理(2026/07/14 更新,對齊 DR-6):迴圈逐缺與批次
--file 皆允許;--confirm 對 Agent 只是減速丘不是閘門,真正的總量防線是 apply 日上限 20/day/user(server 端硬性,單筆/批次共用計數,僅計 personal API 通道——GUI 不受限) + BR-011 冷卻(通道無關)+ 每筆訊息必填。20/day 刻意設在「認真求職者一天投得完、灌檔腳本不夠用」的量級
- SaaS benchmark:
gh 公開讀(gh repo view)與登入寫(gh pr create)同一 binary 組合進 script 是 CLI 產品的常態;job board 對標(LinkedIn Easy Apply 濫用史)反面證明「無訊息 + 無日限」會摧毀企業端信任,故訊息必填 + 日上限是對雙邊市場的保護
DR-10:Agent-first 能力最大化——deny-list 制取代 allow-list 制(Eric 2026/07/14)
- 採用:CLI 能力邊界改為 deny-list 制:求職者 GUI 有的能力,CLI 預設就要有;唯二例外是(1)☢️ 核彈四項、(2)配上 server 端硬性上限後仍不可控的行為(目前無)。「CLI 能做到越多越好」(Eric 原話)
- 理由:CLI 的真正使用者是 AI Agent(US-1)。Agent 工作流斷在任何一個「這功能只有 GUI 有」的點上,整條自動化就退化成「做到一半叫人打開瀏覽器」。allow-list 制(逐功能審核納入)在 Agent 時代是負資產
- US-1 的三個配套(本 DR 的直接產物):
resumes schema / template:聚合格式自描述——Agent 讀 schema 就能把任意 markdown 個人資料對映成合法 payload,不需要人類先讀文件
- 整份聚合寫入 + CLI 編排:後端履歷為 9 類分節 DTO(education / work-experience / certificate / language / professional-skills / autobiography / job-condition / portfolio-links / 名稱與公開狀態);若照純 thin-wrapper 出 9 組子命令,Agent 要打 10+ 次 API 自行管理相依與失敗。CLI 收單一聚合 JSON、代為編排、逐節回報 succeeded/failed——thin-wrapper 原則(DR-1)管的是「不繞過 REST 驗證」,不禁止 client 端編排(0.1.2
jobs view --batch 已有先例)
resumes validate --file:本地結構先驗 + server 422 欄位級錯誤透傳,Agent 可在不消耗寫入額度下迭代修正
- SaaS benchmark:
gh api 讓 agent 直達 GitHub 全 API 面;Stripe CLI fixtures 提供 schema 驅動的資料建立;Terraform 的 schema-first provider 模型證明「機器可讀 schema」是自動化生態的地基
- 安全邊界不變:能力最大化 ≠ 防護鬆綁——OAuth user-scoped、日上限(BR-API-CLI-017)、
--confirm、PII 邊界(BR-API-CLI-016)全部照舊;schema 之外欄位一律拒收(unknown-field reject),筆記裡的敏感區塊(付款資料等)結構上不可能流入
3.5 核彈 Deny List(個人線,永久禁止)
完整清單見 §3.2「☢️ 絕對不做」(單一 SoT,不在此重複):刪除個人帳號(BR-012)、GDPR 級帳號個資打包(BR-020,≠ 本人可見資料匯出)、個人 API 金鑰復活、跨 user 資料存取。原列核彈的批次投遞與本人資料匯出已由 Eric 2026/07/14 裁決改納 v1(配硬性上限)。
4. 商業規則對齊(Business Rules Alignment)
4.1 本功能使用到的業務規則
| 規則 ID | 規則摘要 | 是否關鍵 | 在本 PRD 的對應 |
|---|
| BR-001 | email 驗證後才能登入 | ✅ | 裝置驗證頁走既有 web 登入,未驗證者到不了同意頁 |
| BR-002 | 完成個人資料才能新增履歷 | ✅ | resumes create 未完成個資 → 422 profile_incomplete |
| BR-009 | 職缺狀態機 | ✅ | 對已關閉/刪除職缺 apply → 409(終態不可投) |
| BR-010 | 履歷最多 6 份 | ✅ | resumes create / copy 達上限 → 422 resume_limit_reached |
| BR-011 | 72h 投遞冷卻 | ✅ | apply 冷卻中 → 409 + next_apply_at;applications list 顯示冷卻狀態 |
| BR-012 | 帳號刪除限制 | ✅ | 核彈 deny |
| BR-015 | 職缺搜尋顯示條件 | ➖ | 公開線既有 jobs search 不變;apply 目標須為上架職缺 |
| BR-017 | 資料存取控制 | ✅ | personal namespace 僅回本人資料 |
| BR-019 | 資料保留期限 | ➖ | 應徵紀錄顯示沿 pm_42 |
| BR-020 | 資料匯出規則 | ✅ | 核彈 deny |
4.2 新規則提案(沿 pm_41 BR-API-CLI 系列續編)
- BR-API-CLI-011(建議新增):個人線 CLI / API 認證僅限 OAuth(device-code flow);不核發任何個人 API 金鑰。個人 access token(
wpt_ 前綴)user-scoped、TTL 1h;refresh token 30 天 sliding / 90 天 absolute。
- BR-API-CLI-012(建議新增・資安):refresh token 必須 rotation(每次換發、舊者失效);偵測到已失效 refresh token 被重用時,撤銷該 token family 全部憑證並記 audit event。
- BR-API-CLI-013(建議新增;2026/07/14 改版):使用者可檢視並撤銷任一 CLI session——v1 入口為 CLI
wport sessions list/revoke(GUI 裝置清單降 v2,讀同一 session API);撤銷後該 session 下一請求 401(快取殘存 ≤ [RD 定值,建議 30s,對齊 spec D6])。每帳號 active session ≤ 10,超額汰換最久未使用。
- BR-API-CLI-014(改寫,Eric 2026/07/14):CLI 投遞需顯式
--confirm;單筆與批次(--file ≤20 筆/次)皆允許,每筆 application_message 必填(1~2000 字)。經 personal API 通道的投遞計入 20/day/user 硬性日上限(僅計 API 通道,GUI 不受此限——見 BR-API-CLI-017 通道分離);server 端 BR-011 冷卻與 BR-009 職缺狀態為 domain 層檢查,不分通道、逐筆執行,不因批次或 CLI 來源放寬。
- BR-API-CLI-017(建議新增,Eric 2026/07/14「限量取代禁令」原則;2026/07/14 補充通道分離):個人線寫入面一律配 server 端硬性日上限:apply 20/day、chat send [建議 50/day]、profile update [建議 10/day];額度不足時批次請求整批 429(附剩餘額度),不部分吞。計數僅涵蓋
/api/v1/personal/* 通道(CLI / 未來第三方 client),GUI(web session)不計入、不受限——上限治理的是自動化量能,不動既有 GUI 體驗;實作上計數器掛在 personal namespace guard,domain 層規則(BR-011 冷卻、訊息必填等)維持通道無關。每筆寫入記錄 channel(personal_api / web)供稽核與濫用監控。上限值調整屬營運參數,不需改版 PRD。
- BR-API-CLI-015(建議新增):
resumes delete 需 --confirm(或互動輸入履歷名確認);刪除後保留期沿 BR-019 既有規則。
- BR-API-CLI-016(建議新增):
/api/v1/personal/* 所有端點以 token 綁定之 user 為唯一資料邊界;不接受任何跨 user 查詢參數;回傳欄位不得超過該使用者於 GUI 可見範圍。
5. 資料實體與欄位(Data Entities & Fields)
Rule 30:描述需追蹤的資訊與業務目的;JSON 命名由 OpenAPI 決定。
5.1 實體列表
| 實體 | 用途 | 對應模組 |
|---|
| OAuthClient | 註冊的 client(v1 僅 first-party CLI 一筆) | AS |
| DeviceAuthorization | device flow 進行中的授權請求 | wport login |
| OAuthSession(token family) | refresh token family + 裝置資訊;帳號設定可見可撤 | login / logout / 裝置清單 |
| Resume | 履歷 CRUD + export(既有實體) | personal resumes |
| Application | 應徵紀錄(對齊 pm_42) | personal apply / applications |
| UserProfile | 個人資料讀寫(含 pm_27 欄位) | personal profile |
| ChatRoom / ChatMessage | 站內信(既有 chat 模組) | personal chats |
5.2 實體資料需求說明
DeviceAuthorization
為什麼需要:device flow 的短命中介狀態
必須追蹤:device_code(高熵、hash 儲存)、user_code(8 碼、易讀字元集、格式 XXXX-XXXX)、綁定 client、到期(10 分鐘)、輪詢 interval(5s)、狀態(pending / approved / denied / expired)、核准後綁定的 user
OAuthSession(token family)
為什麼需要:session 治理與 reuse detection 的單位(DR-2 / DR-7)
必須追蹤:
- 綁定 user、client、scope bundle 版本
- 裝置顯示名(CLI 送 hostname,帳號設定可改名)、建立時間、最後使用時間
- 現行 refresh token(hash)+ 已輪替的前代 hash(reuse detection 用)
- 狀態:
active / revoked(含撤銷來源:user_revoke / logout / reuse_detected / expired / evicted)
- absolute 到期(建立 + 90 天)
Application(CLI 視角,契約對齊 pm_42)
必須可回傳:公司名、職缺名、職缺狀態(含終態 boundary)、最近應徵時間、apply_count、冷卻狀態與 next_apply_at(BR-011)
禁止:任何他人資料;HR 端狀態欄位(pm_42 v2 範圍)在其落地前不出現
Resume(分節結構,US-1 聚合介面)
結構事實:後端履歷為分節 DTO(src/modules/resumes/dtos/)——education / work-experience / certificate / language / professional-skills / autobiography / job-condition / portfolio-links + 名稱與公開狀態
必須支援:list(6 份內全列 + is_published)、view 聚合全節、create/update 收聚合 JSON 由 CLI 編排分節寫入(§6.4.1)、schema/template/validate 自描述(不打 API)、copy(計入上限)、publish/unpublish、delete、export
格式紀律:聚合 schema 之外欄位一律拒收(unknown-field reject)——US-1 敏感資料邊界
6. CLI 命令面與資料需求(Command Surface & Data Needs)
為 @wport/cli 與 exec-wport-cli skill 的 SoT;全域契約沿 pm_41 §6.2。
6.1 命令樹(pm_47 目標;enterprise / jobs / config / doctor 維持不變)
wport
├── login # OAuth device flow(個人)
├── whoami # 個人身分;未登入時提示 login
├── logout # server-side revoke + 清本機
├── sessions # session 治理(DR-7,取代 GUI 裝置清單)
│ ├── list # 全部 active session(裝置/時間/最後使用/本機標記)
│ └── revoke <session_id> | --all-others
└── personal # 需 OAuth session
├── profile
│ ├── view
│ └── update --file <json> # Eric 2026/07/14 納入 v1
├── resumes
│ ├── list
│ ├── view <enc_resume_id>
│ ├── schema [--section <name>] # 聚合格式 JSON Schema(Agent 自描述,US-1)
│ ├── template # 骨架 full.json(含範例值)
│ ├── validate --file <full.json> # 本地結構先驗,免打 API
│ ├── create --file <full.json> # CLI 編排多節寫入,逐節回報(§6.4.1)
│ ├── update <enc_resume_id> --file <json> [--section <name>]
│ ├── copy <enc_resume_id> [--name <新名稱>]
│ ├── publish | unpublish <enc_resume_id> # 履歷公開狀態(可逆,無需 confirm)
│ ├── delete <enc_resume_id> --confirm
│ └── export [--out <dir>] # 本人 ≤6 份履歷完整 JSON
├── apply <job_enc_id> --resume <enc_resume_id>
│ --message <text> | --message-file <path|-> # 應徵訊息必填(≤2000 字)
│ --confirm
├── apply --file <batch.json> --confirm # 批次 ≤20 筆/次,每筆必含 message(DR-6)
├── applications
│ ├── list
│ └── export [--out <file>] # CLI 端自動翻頁匯出本人紀錄
└── chats # 站內信 / 與 HR 對話(Eric 2026/07/14 納入 v1)
├── list
├── view <room_id> # 訊息分頁讀取
└── send <room_id> --body <text> | --body-file <path|->
命名注意:頂層已有公開線 wport jobs(search/view 無需登入);投遞掛在 personal apply 而非 jobs apply,明確標示「需登入的個人動作」。
6.2 全域契約增補(相對 pm_41 §6.2)
| 項目 | 規範 |
|---|
| 個人認證解析順序 | credential store personal 區(唯一來源;不提供 --token / env var 直帶 token——防 shell history / CI log 洩漏,[若 Agent 場景證明必要再議]) |
| Token 自動續期 | access 過期 → CLI 靜默 refresh 重試一次;refresh 失敗 → exit 3 + 請執行 wport login |
| Exit codes | 沿 pm_41(0 / 2 / 3 / 4 / 5);login_required 屬 3 |
| 寫入 headers | Idempotency-Key 自動產生(apply / resumes 寫入),可 --idempotency-key 覆寫 |
| Agent 友善 | --output json、--fields、--minimal 全命令支援 |
6.3 認證命令
wport login
| 項目 | 說明 |
|---|
| 流程 | 印 user_code + verification_uri;TTY 且可開瀏覽器時自動開(帶 verification_uri_complete 預填 code);輪詢至 approved / denied / expired |
--no-browser | 只印 URL + code,不嘗試開瀏覽器(SSH 場景) |
| 輸出 | 成功後顯示 display name + email;token 寫入 credential store(0600),全程不 echo token |
| 已登入時 | 提示現有身分,--force 才重新授權(舊 session 保留,由使用者自行於裝置清單撤銷) |
wport whoami / wport logout
whoami:display name、email、session 建立時間;--output json 供 Agent 判斷登入態
logout:呼叫 revocation endpoint(RFC 7009)撤銷 refresh token family → 清本機 personal 區;server 不可達時仍清本機並警告
6.4 個人資料與履歷命令
| 命令 | Flag / 護欄 | 說明 |
|---|
profile view | --fields | 個人資料摘要 |
profile update | --file <json> | 欄位語義 = GUI 會員中心編輯(含 pm_27 國籍/身分種類);server 驗證與 GUI 同源,[RD 以既有 DTO 為 SoT] |
resumes list | --fields / --minimal | 預設欄位:enc_id,name,updated_at,is_complete,is_published;≤6 份無分頁 |
resumes view | --fields | 完整履歷 JSON(聚合所有節) |
resumes schema | --section <name> | 印聚合格式 JSON Schema(欄位型別 + 說明 + 範例);來源 = bundled OpenAPI codegen,離線可用 |
resumes template | --out <file> | 產骨架 full.json(各節含範例值),Agent 直接照填 |
resumes validate | --file <json> | 本地結構驗證(unknown-field reject、必填、長度);通過 exit 0、失敗 exit 2 + 欄位級錯誤清單 |
resumes create | --file <full.json> | 聚合寫入(§6.4.1);BR-002 未完成個資 → 422;BR-010 滿 6 份 → 422 |
resumes update | --file <json> / --section <name> | 整份或單節更新;partial update;[RD 決定是否支援 --if-match optimistic lock,建議對齊企業線] |
resumes copy | --name | 計入 6 份上限 |
resumes publish / unpublish | — | 公開/取消公開(企業人才搜尋可見性;對應既有 update-published-status);可逆,無需 confirm |
resumes delete | --confirm 必填 | 無 --confirm → exit 2 不發請求(BR-API-CLI-015) |
resumes export | --out <dir> | 本人 ≤6 份履歷完整 JSON 存檔(每份一檔或單檔,[CLI 實作決定]) |
6.4.1 整份聚合格式與 CLI 編排(US-1 核心機制)
後端履歷為分節 DTO(SoT:src/modules/resumes/dtos/):education / work-experience / certificate / language / professional-skills / autobiography / job-condition / portfolio-links + 履歷名稱與公開狀態。GUI 逐節編輯;CLI 為 Agent 提供單一聚合 JSON 介面。
- 聚合格式:
{"name": "...", "photo_url": "...", "educations": [...], "work_experiences": [...], "certificates": [...], "languages": [...], "professional_skills": [...], "autobiography": {...}, "job_condition": {...}, "portfolio_links": [...]}——各節 payload 語義 = 對應既有 DTO,節名與欄位由 resumes schema 自描述
- 照片與附件 = URL 欄位(Eric 2026/07/14 裁決):
photo_url 與 portfolio_links(作品/附件連結)皆為 URL 字串,聚合格式直接收——v1 不做 binary 上傳流程(Agent 給可公開存取的 URL 即可);[RD 確認 server 是否轉存 URL 圖片至自家 storage(防外鏈失效/竄改),建議轉存]
- 編排語義(
create --file):CLI 依序執行「建履歷殼 → 各節寫入」;逐節回報 {section, ok, error?};任一節失敗不回滾已成功節(履歷殼保留為草稿),Agent 依回報修正該節後以 update --section 補寫——與企業 batch 的 partial success 哲學一致
- 驗證迭代圈:
schema → Agent 產 full.json → validate(本地,免額度)→ create → 若 server 422(欄位級錯誤)→ Agent 修正 → update --section
- 敏感資料邊界:聚合格式 unknown-field reject——schema 未定義的鍵一律 exit 2 拒收;個人筆記中的付款/證件等區塊結構上不可能流入履歷
US-1 端到端示例(Obsidian markdown → 履歷完成):
wport personal resumes schema > schema.json # 1. Agent 讀 schema
# 2. Agent 將「講師資訊.md」等個人 SSOT 對映成 full.json(付款等非履歷區塊自然無處可放)
wport personal resumes validate --file full.json # 3. 本地先驗,欄位錯誤即改
wport personal resumes create --file full.json --output json # 4. 聚合寫入,逐節回報
wport personal resumes publish <enc_id> # 5.(可選)公開供企業搜尋
6.5 投遞與應徵紀錄
wport personal apply <job_enc_id>
| Flag | 說明 |
|---|
--resume <enc_resume_id> | 必填;指定投遞履歷 |
--message <text> / --message-file <path|-> | 二擇一必填(互斥,比照 talents respond 的 --body/--body-file);對應 GUI application_message(自我介紹);1~2000 字 CLI 本地先驗,超長 exit 2 不發請求;- 讀 stdin(Agent 產生客製訊息的主要路徑) |
--confirm | 必填(非互動);互動模式顯示「以履歷《X》投遞《公司 – 職缺》?」確認 |
| 錯誤面 | 冷卻中 → 409 + next_apply_at(exit 3);職缺終態 → 409;有附加問卷職缺 → 422 questionnaire_required 引導 GUI |
典型 pipeline(公開線 + 個人線串接;Agent 求職工作流):
# 1. 公開線搜尋(無需登入),Agent 篩出合適職缺
wport jobs search -k "backend engineer" --output json --minimal > jobs.json
# 2. Agent 逐缺研究(jobs view 拿完整 JD)並產生客製化應徵訊息
wport jobs view <enc_id> --output json
# 3. 個人線逐一投遞:一缺一命令、一訊息、一 confirm
wport personal apply <enc_id> --resume <enc_resume_id> \
--message-file msg-for-this-job.txt --confirm
邊界(DR-6/DR-9,2026/07/14 更新):迴圈逐一投遞與批次 --file 皆支持;總量由 apply 日上限 20/day(server 端硬性,單筆/批次共用計數,僅計 personal API 通道) 與 BR-011 冷卻收斂;GUI 投遞不計入此額度。
wport personal apply --file <batch.json>(批次投遞,DR-6)
| 項目 | 說明 |
|---|
| 格式 | {"applications":[{"enc_job_id","enc_resume_id","application_message"},...]};≤ 20 筆/次(超過 CLI 本地 exit 2) |
| 訊息 | 每筆各自必填(1~2000 字,CLI 逐筆本地先驗);無共用模板欄位 |
--confirm | 必填;互動模式顯示筆數摘要確認 |
| 回應 | partial success:逐筆 {enc_job_id, ok, error?}(比照 pm_41 企業 jobs batch);冷卻中/終態職缺單筆 fail 不中斷 |
| 計數 | 每筆計入 apply 日上限;額度不足時整批先於 server 拒絕(429 + 剩餘額度),不做部分吞 |
wport personal applications list / export
| Flag | 說明 |
|---|
--cooling | 只列冷卻中的紀錄 |
-p / -s | 分頁(預設 20,max 100) |
| 預設欄位 | job_enc_id,job_title,company_name,last_applied_at,apply_count,can_reapply,next_apply_at,job_status |
export [--out <file>] | CLI 端自動翻頁彙整本人全部紀錄為單一 JSON;不需新 server endpoint,受 60/min rate limit 自然節流 |
resumes export 同理:一次讀出本人 ≤6 份履歷完整 JSON 存檔。兩者皆為「自己 GUI 可見資料」的讀取便利,與 BR-020 法遵打包(含系統紀錄)無涉(§3.2 ☢️ 表註)。
6.6 站內信 / HR 對話命令(personal chats,Eric 2026/07/14 納入 v1)
對應既有 chat 模組(web 求職者端已有);企業端對口為 pm_41 talents respond(該線 F-001 500 修復為共同前置,見 §12.2)。
| 命令 | Flag / 護欄 | 說明 |
|---|
chats list | -p / -s、--unread | 本人聊天室列表(對方公司/職缺、最後訊息時間、未讀數) |
chats view <room_id> | -p / -s | 訊息分頁讀取(僅本人參與的房間,非參與者 → 403/404) |
chats send <room_id> | --body / --body-file <path|->(互斥二擇一必填);≤2000 字 [RD 對齊 chat 既有限制] | 發送訊息;受 chat send 日上限(§8.3);自動附 Idempotency-Key |
刻意不提供:跨房間群發、chats send --batch——訊息是一對一對話,無批次語義。
7. 使用者動作與後端需求(User Actions & Backend Needs)
7.1 OAuth / Session
| 動作 ID | 動作 | 類型 | 需要後端 | 涉及實體 |
|---|
| AUTH-1 | device code 發起 | Write | ✅ | DeviceAuthorization |
| AUTH-2 | web 驗證頁輸入 code + 登入 + 同意 | Write | ✅(web 觸點) | DeviceAuthorization, OAuthSession |
| AUTH-3 | CLI 輪詢換 token | Write | ✅ | OAuthSession |
| AUTH-4 | refresh(rotation) | Write | ✅ | OAuthSession |
| AUTH-5 | logout / revoke | Write | ✅ | OAuthSession |
| AUTH-6 | sessions list / revoke(CLI;GUI 版 v2) | Read/Write | ✅(session 管理 API) | OAuthSession |
7.2 Personal CLI
| 動作 ID | 動作 | 類型 | 需要後端 | 備註 |
|---|
| PER-1 | profile view | Read | ✅ | |
| PER-2 | profile update | Write | ✅ | Eric 2026/07/14;驗證與 GUI 同源 |
| PER-3 | resumes list/view | Read | ✅ | 聚合讀取 |
| PER-3a | resumes schema/template/validate | Local | ❌(bundled schema) | US-1 自描述;不打 API |
| PER-4 | resumes create/update(聚合編排多節) | Write | ✅(各節既有 DTO 端點) | BR-002 / BR-010;逐節 partial success |
| PER-5 | resumes copy | Write | ✅ | BR-010 |
| PER-5a | resumes publish/unpublish | Write | ✅(既有 update-published-status) | 可逆 |
| PER-6 | resumes delete | Write | ✅ | --confirm(BR-API-CLI-015) |
| PER-7 | resumes export | Read | ✅(既有讀取端點即可) | 本人資料讀取便利 |
| PER-8 | apply(單筆) | Write | ✅ | BR-011 / BR-009 / --confirm(BR-API-CLI-014) |
| PER-9 | apply batch(≤20 筆) | Write | ✅ | DR-6;partial success;共用日上限 |
| PER-10 | applications list / export | Read | ✅ | 契約對齊 pm_42;export 為 CLI 端翻頁 |
| PER-11 | chats list / view | Read | ✅ | 既有 chat 模組;僅本人房間 |
| PER-12 | chats send | Write | ✅ | 日上限(§8.3);Idempotency |
8. API Hints(提示用,非最終 API 設計)
8.1 OAuth 端點(新建 AS)
- HINT-OAUTH-1:
POST /oauth/device/code → {device_code, user_code, verification_uri, verification_uri_complete, expires_in: 600, interval: 5}
- HINT-OAUTH-2:
POST /oauth/token(grant_type=urn:ietf:params:oauth:grant-type:device_code)→ 標準錯誤碼 authorization_pending / slow_down / expired_token / access_denied
- HINT-OAUTH-3:
POST /oauth/token(grant_type=refresh_token)→ rotation:回新 access + 新 refresh;舊 refresh 重用 → 400 invalid_grant + family 全撤(BR-API-CLI-012)
- HINT-OAUTH-4:
POST /oauth/revoke(RFC 7009)→ logout 用
- HINT-OAUTH-5(web):
GET /activate(裝置驗證頁,可帶 ?user_code=);登入態沿既有 web session(含 pm_39)
- HINT-OAUTH-6:session 管理 API——list / revoke(含 revoke-all-others);v1 供 CLI
sessions 命令使用,v2 GUI 裝置清單讀同一組端點
8.2 Personal namespace(/api/v1/personal/*,Bearer wpt_)
- HINT-GET-PER-1:profile 摘要(PER-1)
- HINT-MUT-PER-0:profile update(PER-2)——復用 GUI 會員中心編輯 DTO(含 pm_27 欄位驗證)
- HINT-GET-PER-2:resumes 列表/單筆聚合讀取(PER-3、PER-7 export 復用)——單筆需一次回傳全節(或 CLI 併行拉節組裝,[RD 決定])
- HINT-MUT-PER-1:resumes 分節寫入(PER-4~6)——復用既有分節 DTO(education / work-experience / certificate / language / professional-skills / autobiography / job-condition / portfolio-links / rename / published-status),personal namespace 開對應端點即可,CLI 端做聚合編排;寫入強制
Idempotency-Key
- HINT-MUT-PER-2:apply 單筆(PER-8)——payload 含
enc_job_id / enc_resume_id / application_message(復用既有 ApplyJobDto,訊息必填 ≤2000 字);冷卻 409 帶 next_apply_at;問卷職缺 422 questionnaire_required
- HINT-MUT-PER-3:apply batch(PER-9)——
{applications:[ApplyJobDto,...]} ≤20;逐筆檢查 BR-011/BR-009;回 succeeded/failed 陣列;日上限額度不足 → 整批 429 + 剩餘額度
- HINT-GET-PER-3:applications 列表(PER-10)——與 pm_42 前端共用同一契約,勿分岔;export 走同端點翻頁,無新 endpoint
- HINT-GET-PER-4:chats 房間列表 / 訊息分頁(PER-11)——復用既有 chat 模組讀取面,participant guard
- HINT-MUT-PER-4:chats send(PER-12)——復用
handleSendMessage 求職者側路徑;錯誤須丟帶 i18n path 的 HttpException(F-001 教訓,勿拋純 Error 變 500)
8.3 Quota / Rate Limit(建議值)
| 維度 | 預設值 | 備註 |
|---|
| 個人 rate limit | 60 calls/min/user | 對齊企業線每 key 60/min;export 翻頁受此自然節流 |
| 個人月 quota | 不設 | 個人讀自己的資料,無爬蟲面;寫入面各有日上限 |
| apply 日上限 | 20/day/user(硬性,server 端) | Eric 2026/07/14:以上限取代功能禁令的核心防線;單筆/批次共用計數;僅計 personal API 通道,GUI 不受限(BR-API-CLI-017 通道分離) |
| apply batch 單次上限 | 20 筆 | = 日上限;一批用罄額度(DR-6) |
| chat send 日上限 | [PM/RD 議定,建議 50/day/user] | 對照企業端 respond 100/company/day(BR-API-CLI-003);個人為單人,取半 |
| profile update 頻率 | [RD 議定,建議 10/day/user] | 低頻操作,防腳本抖動;不影響正常使用 |
| device code TTL | 10 min | RFC 8628 慣例 |
| 輪詢 interval | 5s(slow_down 時 +5s) | |
| Idempotency TTL | 24h | 沿 pm_37 |
9. Web 觸點(無 mockup;文字規格)
CLI 產品無 mockup。v1 僅兩頁(W-1/W-2)——它們是 device flow 的本質成本(沒有瀏覽器頁就無法授權),但均為極簡頁、沿既有 user app 樣式與登入殼。W-3 已降 v2(DR-7:session 治理由 CLI sessions 命令承接)。
| # | 頁面 | 版本 | 需求 |
|---|
| W-1 | 裝置驗證頁 /activate | v1 | 輸入 8 碼 user code(verification_uri_complete 可預填);未登入先導既有登入(含 pm_39 社群登入);code 錯誤/過期給明確提示 |
| W-2 | 授權同意頁 | v1 | 顯示 client 名(wport CLI)、裝置 hostname、scope bundle 逐條能力清單(DR-3);同意 / 拒絕兩鍵;同意後顯示「回到終端機」完成頁 |
| W-3 | 帳號設定 → 已授權裝置 | v2(Eric 2026/07/14 降級) | v1 由 wport sessions list/revoke 承接(DR-7);GUI 版待帳號設定頁翻新時順帶做,讀同一 session API,零後端增量 |
10. Flowcharts
10.1 Device Flow(登入)
sequenceDiagram
participant U as 使用者
participant CLI as @wport/cli
participant AS as wport OAuth AS
participant W as Web(/activate)
U->>CLI: wport login
CLI->>AS: POST /oauth/device/code
AS-->>CLI: user_code + verification_uri (10min)
CLI->>U: 顯示 code(可自動開瀏覽器)
CLI->>AS: POST /oauth/token(每 5s 輪詢)
AS-->>CLI: authorization_pending
U->>W: 開 /activate,登入(含 pm_39)+ 輸入 code
W->>U: 同意頁(scope 逐條)
U->>W: 同意
CLI->>AS: POST /oauth/token
AS-->>CLI: access(1h) + refresh(rotation)
CLI->>CLI: 寫入 credentials.json (0600)
CLI-->>U: 已登入:<name> <email>
10.2 投遞流程(含護欄)
flowchart TD
A[wport personal apply job --resume r] --> C{--confirm?}
C -- 否/非互動 --> X2[exit 2 不發請求]
C -- 是 --> T{token 有效?}
T -- access 過期 --> R[靜默 refresh] --> T
T -- refresh 失效 --> X3[exit 3 請重新 login]
T -- OK --> API[POST personal apply + Idempotency-Key]
API --> S{server 檢查}
S -- BR-011 冷卻中 --> E409[409 + next_apply_at → exit 3]
S -- 職缺終態 BR-009 --> E409b[409 → exit 3]
S -- 問卷職缺 --> E422[422 引導 GUI → exit 3]
S -- OK --> OK[200 投遞成功 exit 0]
10.3 Edge Case Coverage
| 類別 | 情境 | 系統行為 | CLI 回饋 | 可重試 | 備註 |
|---|
| Auth | access token 過期 | CLI 自動 refresh 重試一次 | 無感 | — | DR-2 |
| Auth | refresh token 過期/被撤銷 | 401 | exit 3 + 引導 wport login | 重新授權 | |
| Auth | refresh token 重用(外洩) | family 全撤 + audit | exit 3 | 重新授權 | BR-API-CLI-012 |
| Auth | device code 逾時(>10min 未授權) | AS 回 expired_token | exit 3 + 引導重跑 login | Yes | |
| Auth | 使用者於同意頁拒絕 | AS 回 access_denied | exit 3 | Yes | |
| Auth | 他機 sessions revoke 後本機續用 | 401(快取殘存 ≤30s 級) | exit 3 | 重新授權 | BR-API-CLI-013;失竊自救場景 |
| Data | resumes create 第 7 份 | 422 resume_limit_reached | exit 3 | 刪除後 | BR-010 |
| Data | 聚合 JSON 含 schema 外欄位 | CLI 本地拒絕(unknown-field reject) | exit 2 + 指出欄位 | 修正後 | US-1 敏感資料邊界 |
| Logical | 聚合 create 單節 422 | 該節 fail、其餘續寫;殼保留草稿 | JSON 逐節 succeeded/failed | update --section 補寫 | §6.4.1,不回滾 |
| Data | resumes create 個資未完成 | 422 profile_incomplete | exit 3 + 引導 GUI | 完成後 | BR-002 |
| Data | delete / apply 缺 --confirm | CLI 本地拒絕 | exit 2 | — | 不發請求 |
| Data | apply 訊息缺漏或 >2000 字 | CLI 本地拒絕 | exit 2 | — | 不發請求;--message/--message-file 互斥二擇一 |
| Logical | apply 冷卻中 | 409 + next_apply_at | exit 3 | 冷卻後 | BR-011 |
| Logical | apply 已關閉職缺 | 409 | exit 3 | No | BR-009 |
| Logical | apply 問卷職缺 | 422 | exit 3 + 引導 GUI | GUI | 另案 |
| Logical | apply 日上限用罄 | 429 + 剩餘額度/重置時間 | exit 3 | 次日 | BR-API-CLI-017 |
| Logical | batch 額度不足(剩 5 送 10) | 整批 429,不部分吞 | exit 3 + 顯示剩餘額度 | 減量重送 | DR-6 |
| Logical | batch 內單筆冷卻/終態 | 該筆 fail、其餘續處理 | JSON succeeded/failed | 失敗項冷卻後 | partial success |
| Logical | chats send 非本人房間 | 403/404(i18n HttpException) | exit 3 | No | F-001 教訓 |
| Logical | chat send 日上限用罄 | 429 | exit 3 | 次日 | BR-API-CLI-017 |
| Network | 逾時 / 重複執行寫入 | Idempotency 去重 | exit 4 → 重送同 key | Yes | 沿 pm_37 |
| Interruption | login 輪詢中 Ctrl+C | 放棄授權;device code 自然過期 | — | Yes | 不留殘留 session |
11. Mock Data Schema
- CLI 對接真實 API;本地僅 credential store(
enterprise + personal 雙區,0600)與 config
- 開發期建議:AS 三端點先行 +
/api/v1/personal/* 可用 stub,CLI 端以 contract test 對 OpenAPI 驗證(沿 gen:openapi codegen 流程)
12. 實作備註(Implementation Notes)
12.1 CLI 必做
login 全程不 echo token;credentials.json 寫入沿既有 atomic write + 0600
- access 過期自動 refresh(單次重試,防迴圈);refresh 失敗訊息必須含
wport login 指令字樣(0.4.0 的 401 提示分流經驗)
personal 寫入命令自動附 Idempotency-Key
apply / resumes delete 無 --confirm 一律本地 exit 2
- 更新
exec-wport-cli SKILL:新增 personal 命令樹 + 「login 需人工完成瀏覽器授權」註記(Agent 不可代按同意頁)
doctor 增列個人線登入態與 AS 連通性探測
12.2 後端必做
- OAuth AS:RFC 8628 + RFC 7009 + refresh rotation / reuse detection;
user_code 高熵短碼、限流防暴力猜測(每 IP 嘗試次數限制)
- device_code / refresh token 一律 hash 儲存;audit event 覆蓋 issue / refresh / revoke / reuse_detected
/api/v1/personal/*:復用既有 web domain services(履歷、投遞、應徵紀錄),user-scoped guard(BR-API-CLI-016)
- OpenAPI 擴充 personal namespace(CLI codegen 依賴)
- 教訓回饋(0.5.0 F-001):domain service 拋純
Error 會變 500 generic——personal namespace 一律丟帶 i18n path 的 HttpException
12.3 禁止事項
- ❌ 個人 API 金鑰任何形式復活
- ❌ 繞過 apply / chat send / profile update 日上限(BR-API-CLI-017);batch 額度不足不得部分吞
- ❌ 跨 user 資料存取;chats 非參與者房間
- ❌ token 明文入 log / email / URL query
- ❌ CLI 繞過 BR-002/010/011 任何 server 檢查
12.4 相依性
| 相依 | 說明 | Blocking |
|---|
| OAuth AS 落地 | login 的前提 | ✅ |
/api/v1/personal/* | 全部 personal 命令 | ✅ |
| Web 觸點 W-1/W-2 | 授權流程必要 | ✅ |
| session 管理 API(HINT-OAUTH-6) | wport sessions 治理入口 | ✅(W-3 GUI 已降 v2,DR-7) |
| pm_42 applications API | applications list/export 契約共用 | ✅(僅該命令) |
| 既有 chat 模組 personal REST 面 | chats list/view/send | ✅(僅 chats);F-001(respond 500)修復為企業⇄求職者對話健康的共同前置 |
| GUI 會員中心編輯 DTO | profile update 欄位 SoT | ✅(僅該命令) |
| pm_43 / pm_44 後端 | saved / blocks 命令 | v2 閘門 |
13. 四大一致性表格
13.1 欄位對照表
| 欄位語義 | 用途 | SoT | 版本 |
|---|
wpt_ access token | 個人認證 | pm_47 | v1 |
| user_code(XXXX-XXXX) | device flow | pm_47 / RFC 8628 | v1 |
next_apply_at / can_reapply | 冷卻呈現 | pm_42 | v1 |
application_message(≤2000 字,必填) | apply 應徵訊息 | 既有 ApplyJobDto(apply-job.dto.ts) | v1 |
| 履歷聚合格式(9 節) | resumes create/update/schema | 既有分節 DTO(src/modules/resumes/dtos/) | v1 |
photo_url / portfolio_links(URL 字串) | 履歷照片/附件匯入 | 既有 aggregate VM / portfolio-links DTO | v1 |
is_published | 履歷公開狀態 | 既有 update-published-status DTO | v1 |
| resume payload | create/update | 既有履歷 DTO [RD 確認] | v1 |
| session 裝置名/最後使用 | 裝置清單 | pm_47 DR-7 | v1 |
13.2 常數表
| 常數 | 值 | 來源 | 版本 |
|---|
| access token TTL | 1h | DR-2 | v1 |
| refresh sliding / absolute | 30d / 90d | DR-2 | v1 |
| device code TTL | 10 min | RFC 8628 慣例 | v1 |
| 輪詢 interval | 5s | RFC 8628 | v1 |
| active session 上限 | 10 / user(汰舊) | DR-7 | v1 |
| 履歷上限 | 6 | BR-010 | 既有 |
| 投遞冷卻 | 72h | BR-011 | 既有 |
| apply 日上限 | 20/day/user(硬性) | Eric 2026/07/14 / BR-API-CLI-017 | v1 |
| apply batch 單次上限 | 20 筆 | DR-6 | v1 |
| chat send 日上限 | [建議 50/day/user] | BR-API-CLI-017 | v1 |
| profile update 日上限 | [建議 10/day/user] | BR-API-CLI-017 | v1 |
| 應徵/聊天訊息長度 | ≤2000 字 | ApplyJobDto / chat 既有 | 既有 |
| page_size max | 100 | pm_41 全域 | 既有 |
13.3 事件字典
| 事件名 | 觸發 | 訂閱者 |
|---|
personal_oauth_session_created | 同意頁核准 | Audit;選配安全提醒信(不含 token,對齊 BR-API-CLI-007 精神) |
personal_oauth_session_revoked | logout / sessions revoke / 汰換 | Audit |
personal_oauth_reuse_detected | refresh 重用 | Audit + 安全提醒信(建議必寄) |
personal_apply_submitted | CLI 投遞成功(單筆或批次逐筆) | 既有投遞事件鏈(與 GUI 同源) |
personal_chat_message_sent | chats send 成功 | 既有 chat 通知鏈(對口 pm_41 talent_respond_sent) |
personal_write_quota_exceeded | 任一日上限 429 | Audit(濫用監控訊號,BR-API-CLI-017) |
13.4 術語字典
| 詞 | 定義 |
|---|
| Personal CLI | wport login/whoami/logout + wport personal 命令樹 |
| Device flow | RFC 8628:CLI 顯示 code、瀏覽器授權、CLI 輪詢取 token |
| Token family | 一次授權產生的 refresh 世代鏈;reuse detection 撤銷單位 |
| Web 觸點 | v1 僅 W-1 驗證頁 / W-2 同意頁;W-3 裝置清單 GUI = v2(§9) |
14. 版本凍結表
| 項目 | 版本 | Blocking? | 決策者 | 日期 |
|---|
| 個人線認證 = OAuth device flow,無個人金鑰 | pm_41 v1.2(裁決)/ pm_47 v1(實作) | ✅ | Eric | 2026/06/24 |
| access 1h + refresh rotation 30/90d | pm_47 v1 | ✅ | Eric [待確認建議值] | 2026/07/14 |
| v1 範圍 = profile(view+update) + resumes(全套+export) + apply(單筆+批次) + applications(list+export) + chats | pm_47 v1.1 | ✅ | Eric | 2026/07/14 |
| 批次投遞開放(≤20/次,配 20/day 硬上限)——推翻 v1.0.0 核彈判定 | pm_47 v1.1 | ✅ | Eric | 2026/07/14 |
| 「限量取代禁令」防濫用原則(BR-API-CLI-017) | pm_47 v1.1 | ✅ | Eric | 2026/07/14 |
| Agent-first 能力最大化(deny-list 制)+ US-1 北極星情境(schema/template/validate、聚合寫入編排、publish/unpublish) | pm_47 v1.2 | ✅ | Eric | 2026/07/14 |
履歷照片/附件 = URL 欄位(photo_url/portfolio_links)入聚合格式;v1 無 binary 上傳 | pm_47 v1.2.1 | ✅ | Eric | 2026/07/14 |
| 寫入日上限僅計 personal API 通道,GUI 不計入不受限(通道分離) | pm_47 v1.2.2 | ✅ | Eric | 2026/07/14 |
session 治理走 CLI sessions list/revoke;W-3 裝置清單 GUI 降 v2 | pm_47 v1.2.3 | ✅ | Eric | 2026/07/14 |
apply 需 --confirm + 訊息必填 | pm_47 v1 | ✅ | Eric [待確認] | 2026/07/14 |
| saved / filters / blocks | pm_47 v2 | ❌ 閘門 = pm_43/44 後端 | — | — |
| granular scope 勾選 | v2(第三方 client 時) | ❌ | — | — |
| 帳號刪除 / GDPR 帳號打包 / 個人金鑰復活 / 跨 user 存取 | — | ❌ 永不 | Eric | 2026/07/14 收斂 |
15. Token / API 契約完整性
| 項目 | 規範 |
|---|
| 個人認證 | OAuth only;wpt_ access(1h)+ rotating refresh(30d sliding / 90d absolute) |
| Rotation / reuse | 每次 refresh 換發;重用 → family 全撤 + audit + 提醒信(BR-API-CLI-012) |
| 撤銷 | logout(RFC 7009)與 web 裝置清單雙入口;撤銷後 401(快取殘存 ≤30s 級) |
| Session 上限 | 10 active / user,汰舊 |
| 資料邊界 | personal namespace 一律 user-scoped,無跨 user 參數(BR-API-CLI-016) |
| Token 傳遞 | 永不入 log / email / URL;CLI 不提供 --token 直帶 |
| Idempotency | personal 寫入必填;TTL 24h |
| 寫入日上限 | apply 20/day(硬性,單筆/批次共計);chat send / profile update 依 BR-API-CLI-017;僅計 personal API 通道(GUI 不計),計數器掛 namespace guard;每筆寫入記 channel 供稽核;額度不足批次整批 429 |
| 即時權限重檢 | 每請求驗 token + user 狀態(停權帳號 → 401/403) |
16. Sync-Fix List
| 動作 | Owner | Blocking? |
|---|
新建 Coda 列 wport CLI v3(求職者線 OAuth) → PRD=pm_47,相依性填 pm_41, pm_42, OAuth AS | PM | ✅ |
doc/feature/README.md 索引 + 代號關聯補 pm_47 | PM | ✅(本次一併完成) |
| 後端:OAuth AS + personal namespace OpenAPI | RD | ✅ |
| FE:W-1/W-2 web 觸點(僅兩極簡頁) | FE | ✅ |
CLI:login/whoami/logout + personal 命令樹,發版 @wport/cli 0.6.0 [或依 semver 議定] | CLI | ✅ |
更新 exec-wport-cli SKILL(personal 命令 + Agent 不可代按同意頁) | CLI | ✅ |
| BR-API-CLI-011~016 提案回寫 business-rules | PM | 上線後 |
| pm_42 契約對齊測試(applications list 共用 DTO) | RD + CLI | ✅ |
17. Email Template
沿 pm_41 資安鐵則:信件一律不含 token 或可還原片段。
| 信件 | 時機 | 必要性 |
|---|
| 新裝置授權提醒 | personal_oauth_session_created | 選配(預設可不寄,對齊 pm_41 Eric 裁決「避免多餘信件面」) |
| 安全警示:偵測異常 token 使用 | personal_oauth_reuse_detected | 建議必寄(外洩訊號,使用者需立即撤銷) |
| 投遞成功通知 | 沿既有 GUI 投遞信 | 既有,不新增 |
18. PRD 問題清單
| # | Issue | Suggested fix | Impact |
|---|
| 1 | access/refresh TTL 建議值(1h / 30d / 90d)待 Eric 拍板 | §14 版本凍結表確認 | 低(皆為業界預設區間) |
| 2 | apply 日上限是否設 已拍板(Eric 2026/07/14):20/day 硬性、單筆批次共計、僅計 personal API 通道(GUI 不受限) | — | 已結案 |
| 2b | chat send(建議 50/day)與 profile update(建議 10/day)上限值 | PM/RD 議定;屬營運參數(BR-API-CLI-017),可上線後調 | 低 |
| 2c | chat 訊息長度上限 | RD 對齊既有 chat 模組限制回寫 §6.6 | 低 |
| 2d | 履歷照片/附件上傳是否入 v1 已拍板(Eric 2026/07/14):照片與附件皆為 URL 欄位(photo_url / portfolio_links),入聚合格式;v1 不做 binary 上傳 | 殘留子題:server 是否轉存外部 URL 圖片至自家 storage(建議轉存,防外鏈失效)→ RD | 低 |
| 2e | resumes validate 是否加 server dry-run 模式 | RD 評估;本地驗證先行,dry-run 可 v1.x 補 | 低 |
| 2f | 單筆履歷聚合讀取:server 一次回全節 vs CLI 併行拉節組裝 | RD 決定(HINT-GET-PER-2) | 中 |
| 3 | token 格式 JWT vs opaque | RD 決定;建議 opaque + Redis(與企業 key spec D6 快取同基礎) | 中 |
| 4 | resume create/update payload 欄位語義 | RD 以既有履歷 DTO 為 SoT 回寫 OpenAPI | 中 |
| 5 | pm_42 applications API 尚未落地時的排程 | 建議 pm_47 後端與 pm_42 後端一併排(共用 DTO 一次到位) | 中 |
| 6 | W-3 裝置清單 mockup 已結案:W-3 GUI 降 v2(Eric 2026/07/14),v1 治理走 wport sessions | — | 已結案 |
19. 下一步(Next Steps)
- Eric:確認 §14 三項 [待確認](TTL 值、v1 範圍、apply —confirm)
- RD:OAuth AS 技術選型(自建 vs library,如 node-oidc-provider)+ personal namespace OpenAPI
- FE:W-1/W-2 排程(僅兩極簡頁;W-3 已降 v2)
- CLI:
login + personal 命令樹實作(可先對 AS stub 開發)
- QA:以「求職者 GUI parity 矩陣」+ §10.3 edge case 表驗收
- pm_43 / pm_44 後端落地後啟動 pm_47 v2(saved / filters / blocks)
附錄 A:pm_41(企業線)vs pm_47(個人線)對照
| 維度 | pm_41 企業線 | pm_47 個人線 |
|---|
| 認證 | wpk_live_ API key(M2M) | OAuth device flow(人) |
| 憑證生命週期 | 強制到期 30/60/90d + rotate | access 1h + refresh rotation 30/90d |
| 撤銷入口 | 金鑰自助頁(軌道 A)+ AMS(軌道 B) | logout + 帳號設定裝置清單 |
| scope | 申請時勾選 4 scope | v1 固定 bundle,同意頁揭露 |
| 資料邊界 | company-scoped | user-scoped |
| 破壞護欄 | delete/close/batch 需 --confirm | delete + apply 需 --confirm |
| 批次 | jobs batch ≤100 | apply batch ≤20/次(= 日上限一次用罄) |
| quota | 5000/月/公司 | 無月 quota;寫入面日上限(apply 20 / chat 50 / profile 10) |
| 核彈 deny | 刪公司/成員治理/bulk export | 刪帳號/GDPR 帳號打包/金鑰復活/跨 user 存取 |
文件結束