最近我陸續上架了好幾個專門給 Agent 用的 CLI 工具,讓 Agent 可以直接透過終端機呼叫功能、傳入參數,再讀取結構化的結果。工具做好之後,接下來就是把它們發佈到 npm,讓其他人和 Agent 也能直接安裝使用。我原本想,這件事以前就做過了,而且一月還寫過如何利用全新的 Trusted publishing 方法發佈 npm 套件,這次應該可以把整個流程交給 Agent 處理。
結果還是有幾個地方值得記錄:Agent 會寫程式,不代表它已經登入 npm;Workflow 寫好了,不代表 npm 已經信任它;終端機印出發佈成功,也不代表使用者馬上就能安裝。最後還有一個小插曲,我只是合併一份驗證文件,npm 就多出了一個 0.1.1 版本。回頭看才發現,這其實完全符合我自己下的指令。
這篇就用這次的實作過程,整理如何讓 Agent 從建立 CLI、準備 npm 套件、建立 GitHub 儲存庫,一路做到 Trusted Publisher 與 CI/CD 驗證。重點不只是那份 YAML,而是把要做的事、發佈的時機,以及怎樣才算完成,事先講清楚。

本文整理 2026 年 10 月的操作經驗,開發環境為 macOS 26.6.2、Node.js 24.22.1、npm 12.2.1,CI 另外驗證 Node.js 22 與 24。下文統一用 my-agent-cli 作為示意套件名稱、my-agent 作為命令名稱,your-github-account 與 your-npm-account 則是示意帳號;它們不代表本文作者已發佈的特定作品。套用範例時請換成自己的資料。文中不包含登入連結、OTP、Access Token 或其他一次性認證資訊。
先釐清:Agent、登入帳號與 CI 是三件事
我這裡說的 Agent,是指能讀寫專案檔案、執行終端機命令,必要時還能操作瀏覽器的 AI 工具。它不是只在聊天視窗裡貼一段設定給我,而是真的會去建立檔案、執行測試、查詢遠端狀態,再根據結果繼續處理。
不過,這個能力不等於帳號權限。這次的分工可以整理成下面這張表:
| 工作 | 由誰執行 | 怎樣才算完成 |
| 建立 CLI、測試與 npm 封裝 | Agent 操作本機專案 | 測試通過,封裝內容正確,安裝後能執行 |
| 首次 npm 登入與人工驗證 | Agent 啟動流程,我完成認證 | npm whoami 回傳預期帳號 |
| 建立公開 GitHub 儲存庫 | Agent 使用已授權的 GitHub CLI | 遠端儲存庫存在,而且能取得程式碼 |
| 設定 Trusted Publisher | Agent 操作 npm 網站,我完成必要的 2FA | 設定確實儲存,第一次 OIDC 發佈驗證成功 |
| 後續自動發佈 | GitHub Actions | 測試、OIDC 發佈、registry 驗證都成功 |
Agent 負責操作,帳號擁有者負責完成必要的身分驗證。 兩者可以接力,但不要把「請 AI 幫我發佈」理解成「AI 應該想辦法跳過登入」。
文中的操作提示詞也整理成純文字附件,可以下載後換成自己的套件名稱與 GitHub 資料。
另外,npm 帳號與 GitHub 帳號不一定相同。假設 npm 使用者是 your-npm-account,GitHub 使用者是 your-github-account,Trusted Publisher 裡面就應該填入後者。這裡要核對的是 GitHub 的 owner 與 repository,不是看見 npm 帳號就直接照填。這種看似很小的細節,反而很適合讓 Agent 查完之後列出來,再繼續設定。
步驟一:先把 CLI 的需求與驗收方式交代完整
專門給 Agent 使用的 CLI,我會特別在意輸入與輸出能不能穩定地被程式處理。例如參數要有清楚的說明、支援 STDIN、輸出 JSON、錯誤訊息與結果分流,以及用退出碼表示成功或失敗。這些約定比互動式選單更適合放進 Agent 的工作流程。
如果要重新做一次,我會把提示詞整理成這樣:
請在目前工作目錄建立 my-agent-cli 子資料夾,實作一個給 Agent 使用的 Node.js CLI。
請先閱讀需求及相關 API 的官方文件,列出功能、輸入與輸出契約。
需求:
- npm 套件名稱為 my-agent-cli,命令名稱為 my-agent。
- 支援以 --input 指定輸入檔案,也能從 STDIN 接收內容。
- 支援 --format json,讓 Agent 能穩定解析結果。
- 結果寫入 STDOUT;進度與錯誤寫入 STDERR。
- 以明確的退出碼區分成功、參數錯誤與執行失敗。
- 寫入檔案或呼叫遠端服務的操作,提供不產生副作用的 --dry-run。
- 提供 --help、--version、Makefile 與 README。
- 加入參數、STDIN、錯誤處理及核心功能的自動測試。
請實際執行測試、檢查 npm 封裝,並從封裝安裝後驗證 CLI。
請建立獨立 Git 儲存庫,每個完成階段使用詳細繁體中文 commit 記錄。
涉及外部服務的功能,請區分離線測試與真實服務驗證,不要混為一談。
這一步先完成本機專案,不要先發佈到 npm。
我特別保留 --dry-run,因為這會讓後續驗證簡單很多。Agent 可以先檢查參數解析、STDIN 與預計執行的操作,不必在每次測試時真的修改檔案或呼叫遠端服務。下面以支援檔案與文字輸入的示意 CLI 為例:
# 以旗標指定輸入檔案
node src/cli.js --input ./input.txt --format json --dry-run
# 從 STDIN 輸入內容
printf '%s\n' '待處理的內容' |
node src/cli.js --format json --dry-run
這裡還有一個很實用的驗收點:第二個例子的 STDOUT 應該是乾淨的 JSON,不能混進「正在讀檔」或 npm script 的提示文字。CLI 要能接管線,這些小地方比多做幾個參數更重要。
如果 CLI 要存取外部服務,也要在 README 說明認證方式、可測試的範圍,以及哪些操作真的會產生副作用。不要讓 Agent 把「離線測試通過」寫成「所有外部服務都已驗證」。
步驟二:先確認 npm 裝到的到底是什麼
npm 的套件名稱與使用者在終端機輸入的命令名稱可以不同。以下使用示意名稱:
{
"name": "my-agent-cli",
"version": "0.1.0",
"type": "module",
"bin": {
"my-agent": "src/cli.js"
},
"files": ["src", "README.md", "examples", "LICENSE"],
"engines": {
"node": ">=22"
},
"repository": {
"type": "git",
"url": "git+https://github.com/your-github-account/my-agent-cli.git"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
這是重點欄位節錄,實際套件還有相依套件與 scripts。bin 指到的檔案要有 Node.js 的 shebang,像這樣:
#!/usr/bin/env node
我會要求 Agent 檢查下面幾件事,而不是看到 package.json 就算完成:
請驗證 npm 封裝:
1. 執行 npm pack --dry-run,列出將發佈的檔案。
2. 確認沒有 .env、API 金鑰、node_modules、測試暫存資料。
3. 實際產生 tgz,安裝到獨立暫存目錄。
4. 使用安裝後的 my-agent 執行 --version、--help 與 --dry-run。
5. 確認 --version 讀的是封裝內的 package.json,不是寫死在程式裡。
最後一點在自動升版時很容易出錯。假設 CI 把版本改成 0.1.1,但 CLI 裡仍然寫死 0.1.0,使用者安裝成功後看到的版本就會不一致。這次後來也把版本顯示改成讀取套件本身的 metadata。
repository.url 也要指向實際發佈的 GitHub 儲存庫,不能留著範本專案的網址。後面要驗證 provenance 時,這個來源資訊會派上用場。
步驟三:用最小套件完成第一次發佈
Trusted Publisher 是針對 npm 上已經存在的套件設定。這次我先發佈一個最小版本,把套件建立起來,再接 GitHub Actions。
這是這次選用的初始化方式,不代表每個專案都應該先發佈空套件。 如果你的 CLI 已經準備好,也可以直接手動發佈第一個可用版本;如果這個 npm 套件原本就存在,更不需要再做一次初始化。
我當時已經有完整 CLI,所以沒有把根目錄的程式刪掉,而是另外建立 release/placeholder/:
release/placeholder/
├── package.json
├── README.md
└── LICENSE
裡面的 package.json 只有最基本的資訊:
{
"name": "my-agent-cli",
"version": "0.0.0",
"description": "Initial placeholder for an agent-oriented CLI.",
"license": "MIT",
"files": ["README.md", "LICENSE"],
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
我下給 Agent 的指令是:
請先確認 my-agent-cli 在 npm 的名稱狀態與目前登入帳號。
準備 0.0.0 的最小套件,清楚標示這個版本不含可執行 CLI。
保留根目錄已完成的實作,只發佈 release/placeholder/。
先檢查 tarball 內容,再發佈;需要登入或 2FA 時讓我在瀏覽器完成。
實際會用到這幾個命令:
npm whoami --registry=https://registry.npmjs.org/
npm view my-agent-cli versions --json
npm pack ./release/placeholder --dry-run
npm login --registry=https://registry.npmjs.org/
npm publish ./release/placeholder --access public
第一次的 npm whoami 回傳 401 Unauthorized,因此 Agent 啟動登入流程,讓我在 npm 的網頁完成認證。登入成功後,再次查詢才看到預期的帳號。
這裡不要把查詢套件時的 404 當成「這個名稱一定可以註冊」的保證。它只能說明當下這次查詢沒有取得套件,真正能不能發佈,還是要看 registry 的驗證結果。
步驟四:建立 GitHub 儲存庫,先決定什麼時候發佈
接著我要求 Agent 把專案放到公開 GitHub 儲存庫,並且加入 PR 驗證與 npm Trusted Publishing。這一步建議把 GitHub 帳號、儲存庫名稱、workflow 檔名與發佈規則一起寫清楚:
請將這個專案發佈到我的公開 GitHub 儲存庫:
your-github-account/my-agent-cli
建立以下 CI/CD:
- PR 對 main 執行測試與封裝檢查,不發布套件。
- 測試 Node.js 22 與 24,作業系統包含 Linux、macOS、Windows。
- 所有測試通過後才允許發布。
- npm 發布 workflow 檔名固定為 publish.yml。
- 使用 GitHub-hosted runner 與名為 npm 的 GitHub environment。
- 使用 OIDC Trusted Publishing,不設定 NPM_TOKEN 或 NODE_AUTH_TOKEN。
- workflow_dispatch 可以手動重跑。
這次採用的發布規則:每次 main push 都發布新的穩定版本,
包含文件變更;第一個完整版本是 0.1.0,之後自動遞增 patch。
請不要把 CI 產生的版本更新再 commit 回 main。
相同 commit 已發布時,重跑必須跳過,不能重複發布。
最後那一段,最好在一開始就決定。
我這次最初只說「main 有新的 commits 就發佈」,後來 Agent 合併一份驗證文件,果然就觸發了 0.1.1。當我問「為什麼又發佈一個版本」,回頭看規則才發現,文件 commit 也是 commit。
如果你的預期是「只有調整 package.json 版本才發佈」,就要明確指定這個條件,並要求已存在的版本直接跳過。不要一邊採用每次 push 自動升版,一邊期待文件變更不會產生版本。
這不是要多加一道確認流程,而是避免把兩種不同的發佈策略混在一起。
步驟五:把 PR 測試與發佈權限分開
其中一個專案的 CI 使用六組測試環境:三種作業系統,各跑 Node.js 22 與 24。當時共有 28 項自動測試,涵蓋參數、STDIN、錯誤處理、核心功能與版本選擇。不同工具的測試內容會不同,但都要驗證 Agent 實際依賴的輸入與輸出契約。
ci.yml 的結構如下。下面保留主要設定;實際專案會把 Actions 固定到完整 commit SHA,可以交給 Agent 依專案的測試命令產生對應檔案。
name: CI
on:
pull_request:
branches: [main]
workflow_call:
workflow_dispatch:
permissions:
contents: read
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [22, 24]
steps:
- uses: actions/[email protected]
with:
persist-credentials: false
- uses: actions/[email protected]
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- run: npm run check
- run: npm test
- run: npm pack --dry-run
passed:
name: Tests passed
needs: test
if: always()
runs-on: ubuntu-latest
steps:
- name: Require all test jobs to pass
env:
TEST_RESULT: ${{ needs.test.result }}
run: test "$TEST_RESULT" = success
Tests passed 是一個統一的檢查名稱,可以放進 main 分支的必要檢查。if: always() 是讓前面的測試失敗時,這個檢查仍然有機會執行;它接著會檢查測試結果,並不是把失敗變成成功。
不過,看到 Workflow 有這個 job,還不能直接說「PR 已經不能亂合併」。要再確認 GitHub 的分支保護規則真的把它列為必要檢查,以及管理員是否保留略過規則的權限。這次的設定有保留管理員略過,因此不能宣稱任何人都無法直接推送。
我另外讓 Agent 建立一個小型文件 PR,實際跑完六組 CI,看到必要檢查通過後才合併。這比只檢查 YAML 語法,更能證明 PR 事件與檢查名稱接得起來。
步驟六:在 npm 網站設定 Trusted Publisher
這次我直接要求 Agent 使用瀏覽器操作 npm 網站:
請使用瀏覽器,開啟 my-agent-cli 的 npm Settings。
新增 GitHub Actions Trusted Publisher,設定為:
Organization or user: your-github-account
Repository: my-agent-cli
Workflow filename: publish.yml
Environment name: npm
Allowed actions: 允許 npm publish
請實際完成儲存並核對畫面上的設定。
如果出現登入、2FA 或需要我操作的瀏覽器認證,
停在該畫面交由我完成;完成後請繼續,不要把它當成設定已成功。
這幾個欄位要能對得起來:
| npm 設定 | 對應的 GitHub 設定 |
Organization or user:your-github-account | 儲存庫擁有者 |
Repository:my-agent-cli | 儲存庫名稱 |
Workflow filename:publish.yml | .github/workflows/publish.yml,欄位只填檔名 |
Environment name:npm | 發佈 job 的 environment: npm |
允許 npm publish | Workflow 確實執行 npm publish |
Environment name 不是 npm 的帳號,也不是儲存庫名稱。我另外把 GitHub 的 npm environment 限制成只允許 main 分支部署,讓這個發佈入口有明確範圍。
還有一個與一月文章不同的地方:目前的介面有 Allowed actions。依官方文件,新連線會允許 npm stage publish;如果要像這次一樣直接發佈,還要允許 npm publish,不能只把前面幾個文字欄位填好就結束。
這次我先完成了一次登入認證,送出 Trusted Publisher 表單時,npm 又要求了一次 2FA。Agent 因此停在驗證畫面等我接手。已登入 npm 網站,不代表每一個帳號設定操作都不需要再次驗證。
完成後,畫面先顯示連線已加入,狀態則是 Pending validation。等到 GitHub Actions 真正用 OIDC 發佈成功,才看到 Valid。所以我把「已填表」、「已儲存」與「已驗證」當成三個不同的進度,不再只看 Agent 說了一句完成。
步驟七:讓 GitHub Actions 自己取得發佈身分
先更新一個版本資訊:一月那篇文章的環境說明,不能原封不動沿用到現在。本文查閱時,npm 的 Trusted Publishing 文件列出 npm CLI 至少 11.5.1、Node.js 至少 22.14.0。我這次的發佈 job 使用 Node.js 24,另外明確安裝 npm 12.2.1。
不要只寫「LTS 應該夠新」就算了,請讓 Agent 查目前支援範圍,再把實際測試版本記錄下來。
下面是搭配前面 ci.yml 的發佈流程。為了閱讀方便,Actions 使用版本標籤;實際專案固定完整 SHA。這份範例在發佈 job 關閉 setup-node 快取,CI 測試仍可使用自己的 npm 快取。它會用到下方說明的兩個版本管理腳本,並不是單獨貼一份 YAML 就能完成的範例。
name: Publish npm
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: npm-publish
queue: max
cancel-in-progress: false
jobs:
ci:
uses: ./.github/workflows/ci.yml
publish:
needs: ci
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: npm
permissions:
contents: read
id-token: write
steps:
- uses: actions/[email protected]
with:
persist-credentials: false
- uses: actions/[email protected]
with:
node-version: 24
package-manager-cache: false
- run: npm install --global [email protected]
- run: npm ci
- name: Select an unpublished version
id: release
run: node scripts/prepare-release.mjs
- name: Publish using GitHub OIDC
if: steps.release.outputs.publish == 'true'
run: npm publish --provenance --access public --tag latest
- name: Verify registry propagation
if: steps.release.outputs.publish == 'true'
run: node scripts/verify-release.mjs
這裡有幾個設計是刻意分開的:
- PR 測試只需要
contents: read,沒有 id-token: write。 - 發佈 job 才取得 OIDC 權限,而且必須等
ci 成功。 - 沒有設定
NPM_TOKEN 或 NODE_AUTH_TOKEN 來執行 npm publish。 environment: npm 必須與 npm 網站的設定一致。 - 測試可以重用 workflow,但這次把真正的
npm publish 留在 publish.yml,減少辨識發佈 workflow 時的混淆。
範例保留 --provenance,讓命令的意圖清楚。以本文使用的公開 GitHub 儲存庫與 npm 套件而言,目前 Trusted Publishing 也會自動產生 provenance;它不是要求另外建立一個祕密變數。
這次都是公開相依套件。如果你的 npm ci 必須安裝私有套件,那是另一個需要處理的讀取授權,不能因為發佈已經有 OIDC,就推論私有套件安裝也不用認證。
套用時請讓 Agent 把這兩份 workflow 與專案的 scripts 一起驗證。上面的發佈範例另外需要 prepare-release.mjs 與 verify-release.mjs;下載後放到專案的 scripts/,或由 Agent 依相同規則實作。
步驟八:處理自動升版與重跑,避免下一次發佈撞版本
如果選擇「每次 main push 都發佈」,就不能永遠把 package.json 留在同一個版本,然後期待 npm publish 自動幫你遞增。
範例中的 prepare-release.mjs 會先讀取 registry,再決定這次是否需要發佈:
查詢 registry 的所有版本
如果目前 commit 已經發布過:
輸出 publish=false,跳過
否則:
找出最高的穩定版本
如果專案設定的版本比它高,使用專案版本
否則,把最高穩定版本的 patch 加一
只更新 CI 工作目錄的版本與 gitHead
例如,registry 只有初始化的 0.0.0,專案版本是 0.1.0,第一次就發佈 0.1.0。下一次 main push 再變成 0.1.1。如果我要開始 0.2.0,則可以明確調整專案版本。
不要為了自動升版,把版本更新再推回 main,然後又觸發下一次自動升版。 這次只修改 runner 的工作目錄,不建立版本 commit。
我也要求 Agent 把「同一個 commit 重跑」納入測試。發佈失敗後重試很正常,但若前一次其實已經成功,下一次就應該辨識出來並跳過,而不是憑空多產生一個版本。
另外,這個查版本、再發佈的過程需要排隊,避免兩個工作同時算出同一個下一版。上面的 queue: max 是依本文查閱時的 GitHub concurrency 文件設定;一般 concurrency 預設只保留一個 pending 工作,不能想當然地以為每次 push 都會完整排隊等待。這個設定也不是無限佇列,官方上限是 100 個等待工作。
步驟九:不要只看見成功訊息,就說已經上架
這次最值得補進驗收提示詞的,就是下面這段:
請逐項驗證,不要把不同階段混成一句「已完成」:
1. Trusted Publisher 的設定已儲存,欄位與 GitHub 相符。
2. GitHub Actions 的測試工作全部通過。
3. 發佈 job 使用 OIDC 成功,不需要人工 npm login 或寫入 token。
4. npm registry 能查到指定版本,latest 指向預期版本。
5. registry 的 gitHead 對應本次發佈 commit。
6. tarball 能下載,且可找到 provenance 資訊。
7. 用指定版本安裝後,實際執行 CLI 的 --version 與 --dry-run。
如果 npm 仍在處理,請回報「發布請求已接受,尚未驗證可下載」,
不要重複發布同一版,也不要改用另一個版本掩蓋尚未查明的問題。
我第一次發佈時,終端機印出了成功訊息,但 registry 還查不到指定版本。Agent 一度只確認到名稱已建立,後來才確認版本真的能下載。這兩個時間點應該分開記錄。
完整版本的檢查可以這樣做:
npm view [email protected] version gitHead dist.tarball --json
npm view my-agent-cli dist-tags --json
npm view [email protected] dist.attestations --json
npm exec --yes [email protected] -- my-agent --version
npm exec --yes [email protected] -- \
my-agent --input ./input.txt --format json --dry-run
上面的套件名稱與命令名稱都是示意,請換成自己已審查並發佈的工具,再執行 npm exec。最後一個命令只驗證安裝後的 CLI 與 dry-run 行為;如果工具會存取外部服務,不能拿這個結果宣稱真實服務的整合也已完成。
這次最後確認到:0.1.0 是透過 GitHub Actions 的 OIDC 發佈,registry 有對應的 gitHead 與 provenance 資訊,安裝後的 --version 與 --dry-run 也能正常執行。後續文件合併觸發的 0.1.1,同樣有成功的 workflow 紀錄。
初始化版本用完了,清理也要精準指定
正式版本完成後,我要求 Agent 下架初始化的 0.0.0 與當時出現的 0.0.0-stage,留下可用的 CLI 版本:
npm unpublish [email protected]
npm unpublish [email protected]
這裡要明確指定版本。不要把只想移除兩個測試版本的需求,改寫成移除整個套件。 能否下架仍受 npm unpublish 規則限制,而且同一個套件名稱與版本在下架後不能重新使用。
0.0.0-stage 也不能跟我自己建立的 0.0.0 混為一談。npm 的 staged publishing 文件有說明這個 placeholder 版本;這次確實在 registry 看過它,但當時的 npm stage list 是空的,所以我沒有要求 Agent 憑這個版本名稱就推論還有一個等待人工核准的工作。
實際清理時,npm 對兩次下架分別要求了瀏覽器驗證。完成後,Agent 再查版本清單與兩份 tarball,確認版本已消失、下載回傳 404,同時 0.1.0 與 0.1.1 都還存在。這才算清理完成。
總結
這次從 CLI 開發到 npm 上架,大部分的檔案與網站操作確實都交給 Agent 做了。它可以查文件、寫測試、建立儲存庫、設定 workflow、操作 npm 表單,也可以在失敗後繼續找原因。我需要做的,則是說清楚需求與發佈策略,完成必要的帳號驗證,再看它拿出的證據。
我覺得 Agentic 版本最有價值的地方,是把原本散在終端機、GitHub 與 npm 網站的操作接起來。但接起來之後,更要分清楚哪一步只是已送出、哪一步已經被接受、哪一步真的可以交付給使用者。
下次要交給 Agent 做類似工作,我會先講清楚三件事:發佈什麼、何時發佈、如何驗收。至於「main 有 commit 就發佈」這句話,現在我會連文件變更也一起想清楚,再交出去。
相關連結