diff --git a/.gitea/workflows/cd.yaml b/.gitea/workflows/cd.yaml index 30b1952..8730aef 100644 --- a/.gitea/workflows/cd.yaml +++ b/.gitea/workflows/cd.yaml @@ -1,12 +1,35 @@ +# ============================================================================= +# 用途:CD(Continuous Delivery)持續交付 workflow。 +# 當有 commit push 到 master 分支時觸發,呼叫 release-tag-version +# composite action 進行「釋出並標註正式成品版本」(建立 release / 打 tag), +# 將 master 上的成果標記為一個正式可發布的版本。 +# 更新日期:2026/06/29 14:18:26 +# ============================================================================= + +# workflow 顯示名稱,會出現在 Gitea Actions 的 workflow 清單與執行紀錄中,方便辨識。 name: CD +# 觸發條件:監聽 push 事件。 on: push: + # 只在指定分支發生 push 時觸發,避免其他分支誤觸發正式釋出流程。 branches: + # 僅限 master 分支:master 視為正式(穩定)分支,push 進來代表要正式釋出成品版本。 - master +# 此 workflow 包含的 job 清單。 jobs: + # job 識別碼:release-tag-version,負責釋出並標註成品版本。 release-tag-version: + # job 顯示名稱,呈現於 Actions 執行畫面,便於辨識此 job 的職責。 name: Release Tag Version + # 指定執行此 job 的 runner 標籤;使用 ubuntu runner 執行後續步驟。 runs-on: ubuntu + # 此 job 的執行步驟序列。 steps: + # 步驟名稱:釋出並標註成品版本(建立 release / 打版本 tag)。 - name: 釋出並標註成品版本 + # 呼叫外部 composite action「release-tag-version」執行實際的釋出與 tag 標註邏輯。 + # 版本以變數 vars.ACTION_RELEASE_TAG_VERSION 釘選,確保使用指定版本的 action, + # 避免上游 action 變動造成釋出行為不可預期。 + # 副作用:此步驟會在遠端建立 release 或推送版本 tag(正式釋出動作)。 + # 需人工確認:vars.ACTION_RELEASE_TAG_VERSION 變數需在 repo / org 層級正確設定,否則 action 解析會失敗。 uses: https://gitea.jsc.idv.tw/composite-actions/release-tag-version@${{ vars.ACTION_RELEASE_TAG_VERSION }} diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 95aa463..b254a36 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -1,34 +1,77 @@ +# ============================================================================= +# 用途:PR(非 master 分支、opened/synchronize)觸發的 CI。 +# 流程:先用 release-tag-version composite action 算出 beta 版本號,再用該版本號 +# 叫 codex composite action 執行(未帶 prompt,走取登入 email 分支), +# 最後以「檢查輸出」step 驗證輸出的登入帳號 email 是否等於 vars.CODEX_EMAIL。 +# 更新日期:2026/06/29 14:18:26 +# ============================================================================= + +# workflow 顯示名稱,於 Gitea Actions 列表中辨識此工作流程。 name: CI +# 觸發條件設定區塊。 on: + # 針對 pull_request 事件觸發。 pull_request: + # 略過目標分支為 master 的 PR(亦即只在「非 master 分支」的 PR 上跑), + # 避免對 master 的合併 PR 重複執行 CI。 branches-ignore: - master + # 僅在 PR 被開啟(opened)或有新 commit 推入(synchronize)時觸發, + # 其他事件(如 reopened、labeled)不觸發以節省資源。 types: [opened, synchronize] +# 工作(jobs)定義區塊。 jobs: + # 第一個 job:計算 beta 版本號,供後續 codex job 取用。 release-tag-version: + # job 顯示名稱。 name: Release Tag Version + # 指定執行的 runner 標籤(ubuntu)。 runs-on: ubuntu + # 對外輸出值,讓相依此 job 的其他 job 可引用。 outputs: + # 將 release-tag-version step 算出的 version 輸出,鍵名為 version。 version: ${{ steps.release-tag-version.outputs.version }} + # 步驟清單。 steps: + # 呼叫 release-tag-version composite action 計算版本號。 - name: 計算版本號 + # 此 step 的 id,供 outputs 透過 steps.release-tag-version 引用其輸出。 id: release-tag-version + # 使用外部 composite action;版本由 repo 變數 ACTION_RELEASE_TAG_VERSION 決定, + # 便於集中管理所引用 action 的版本。 uses: https://gitea.jsc.idv.tw/composite-actions/release-tag-version@${{ vars.ACTION_RELEASE_TAG_VERSION }} with: + # 指定產生 beta 版本號(而非正式版),用於 CI 測試情境。 is_beta: 'true' + # 第二個 job:用前一 job 算出的版本號叫 codex action,再驗證其輸出。 codex: + # job 顯示名稱。 name: Codex + # 指定執行的 runner 標籤(ubuntu)。 runs-on: ubuntu + # 相依 release-tag-version job,需等其成功後才執行, + # 以取得其輸出的版本號。 needs: release-tag-version + # 步驟清單。 steps: + # 步驟一:呼叫 codex composite action(未帶 prompt,走「取登入 email」分支)。 - name: 測試工具 + # 此 step 的 id,供後續「檢查輸出」step 引用其 outputs.text。 id: codex + # 使用 codex composite action;版本標籤由前一 job 輸出的 version 組成 + # (前綴 v),確保測試所用 codex action 與本次算出的 beta 版本一致。 uses: https://gitea.jsc.idv.tw/composite-actions/codex@v${{ needs.release-tag-version.outputs.version }} with: + # 由 secrets 注入 codex 登入用 OAuth 憑證,避免明文外洩。 + # 注意:未提供 prompt,codex action 會回傳目前登入帳號的 email。 oauth: ${{ secrets.CODEX_OAUTH }} + # 步驟二:驗證登入身分。 - name: 檢查輸出 + # 當 codex 輸出的登入 email 與 repo 變數 CODEX_EMAIL 不符時條件成立, + # 代表登入帳號不是預期帳號。 if: ${{ steps.codex.outputs.text != vars.CODEX_EMAIL }} + # 條件成立即以非零狀態碼退出,使 CI 失敗(擋下非預期登入帳號)。 run: exit 1 diff --git a/README.md b/README.md index 1f59026..43b6816 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Codex CLI Composite Action -此 repository 提供一個 Gitea/GitHub Actions composite action,用於安裝 Codex CLI、寫入 OAuth token、執行指定提示詞,並將 CLI 輸出寫入 action output。 +提供一個 Gitea/GitHub Actions composite action,用於安裝 Codex CLI、寫入 OAuth token、執行指定提示詞,並將 CLI 輸出寫入 action output;另提供透過 `codex app-server` 取得目前登入帳號 email 的工具函式。 + +> 更新時間:2026/06/29 14:18:26 ## 專案列表 @@ -8,50 +10,66 @@ | 專案名稱 | 專案描述 | | --- | --- | -| [codex](https://gitea.jsc.idv.tw/composite-actions/codex) | 提供 Codex CLI composite action,讓 workflow 可透過 `oauth` 與 `prompt` 輸入執行 CLI,並從 repository variable 讀取模型名稱後取得文字輸出。 | +| [codex](https://gitea.jsc.idv.tw/composite-actions/codex/src/branch/develop/) | 提供 Codex CLI composite action(安裝 `@openai/codex`、寫入 OAuth token、執行提示詞並輸出文字),並提供透過 `codex app-server` JSON-RPC `account/read` 取得目前登入帳號 email 的工具函式。 | ### 參考專案 | 專案名稱 | 參考專案列表 | | --- | --- | -| [codex](https://gitea.jsc.idv.tw/composite-actions/codex) | 無 | +| [codex](https://gitea.jsc.idv.tw/composite-actions/codex/src/branch/develop/) | 無 | ### NuGet 套件 | 專案名稱 | NuGet 套件列表 | | --- | --- | -| [codex](https://gitea.jsc.idv.tw/composite-actions/codex) | 無 | +| [codex](https://gitea.jsc.idv.tw/composite-actions/codex/src/branch/develop/) | 無 | ## 功能列表 -此 repository 未包含可列入 README 的 public method、public constructor、public extension method 或 public operator。 +### codex + +| 功能名稱 | 功能描述 | +| --- | --- | +| [codex_account.read_account_email](https://gitea.jsc.idv.tw/composite-actions/codex/src/branch/develop/app/codex_account.py#L12) | [透過 codex app-server 的 JSON-RPC `account/read` 取得目前登入帳號的 email,取不到或逾時回傳空字串。](#codex_accountread_account_email) | ## 使用範例 -### 在 workflow 中呼叫 Codex CLI + +### codex_account.read_account_email -以下範例示範在 workflow step 中呼叫此 composite action,傳入 OAuth token 與提示詞,並在後續步驟讀取 `text` output。模型名稱由 action 直接讀取 `vars.CODEX_MODEL`。 +透過 `codex app-server` 的 JSON-RPC `account/read` 取得目前登入帳號的 email。函式會啟動 `codex app-server` 子行程,依序送出 `initialize` / `initialized` / `account/read` 三筆 JSON-RPC 訊息,讀取回應中 `result.account.email` 作為回傳值;讀到 EOF、JSON 解析失敗或超過 `timeout_seconds` 逾時都會停止,取不到時回傳空字串 `""`,且不論成功或失敗都會在 `finally` 終止子行程。 -```yaml -- name: 執行 Codex CLI - id: codex - uses: https://gitea.jsc.idv.tw/composite-actions/codex@v1 - with: - oauth: ${{ secrets.CODEX_OAUTH }} - prompt: "請自我介紹" +簽名: -- name: 使用輸出文字 - run: printf '%s\n' '${{ steps.codex.outputs.text }}' +```python +def read_account_email(timeout_seconds: float = 25.0) -> str ``` 前置條件: -- `secrets.CODEX_OAUTH` 必須是 Codex OAuth token 檔案內容的 base64 字串。 -- `vars.CODEX_MODEL` 必須是 Codex CLI 可用的模型名稱。 -- runner 必須可透過 npm 安裝 `@openai/codex`。 +- `codex` CLI 已安裝(例如本 action 的安裝步驟已執行)。 +- `$HOME/.codex/auth.json` 已寫入有效的 OAuth token。 + +典型呼叫方式: + +```python +from app.codex_account import read_account_email + +# 取得目前登入帳號 email;可自訂等待 app-server 回應的逾時秒數 +email = read_account_email(timeout_seconds=25.0) +if email: + print(f"目前登入帳號:{email}") +else: + print("取不到登入帳號(未登入或逾時)") +``` 預期結果: -- action 會安裝 `codex` CLI。 -- action 會將 OAuth token 寫入 `$HOME/.codex/auth.json` 並設定檔案權限為 `600`。 -- action 會執行 `codex exec --model "$MODEL" "$PROMPT"`,並把 stdout 寫入 `steps.codex.outputs.text`。 +- 成功時回傳登入帳號的 email 字串。 +- 未登入、回應缺漏或逾時時回傳空字串 `""`。 + +此模組亦可直接以指令執行,會將 email 印到 stdout(取不到時印空字串): + +```bash +python3 app/codex_account.py +``` diff --git a/action.yml b/action.yml index ef029ee..28e24b3 100644 --- a/action.yml +++ b/action.yml @@ -1,56 +1,124 @@ +# ============================================================================= +# 檔案用途:提供 Codex CLI 的 composite action(Gitea/GitHub 複合動作) +# ----------------------------------------------------------------------------- +# 此 action 主要做三件事: +# 1. 安裝 @openai/codex CLI 工具(透過 npm 全域安裝)。 +# 2. 將傳入的 base64 編碼 OAuth token 解碼後寫入 $HOME/.codex/auth.json, +# 讓 codex CLI 取得登入憑證。 +# 3. 執行 inputs.prompt 指定的提示詞,並把 Codex 的輸出寫入 +# steps.codex.outputs.text 供後續 step 使用。 +# - 若未提供 prompt(PROMPT 為空),則改呼叫 app/codex_account.py 取得目前 +# 登入帳號的 email,寫入 text 輸出後結束(用於驗證身分)。 +# 更新日期:2026/06/29 14:18:26 +# ============================================================================= + +# action 名稱(顯示於工作流程記錄中) name: 'Codex CLI' +# action 說明文字 description: 'Codex CLI 工具' +# action 作者 author: 'Jeffery' +# 輸入參數定義區塊 inputs: + # prompt:傳給 Codex CLI 的提示詞 prompt: + # 參數說明 description: '傳給 Codex CLI 的提示詞' + # 非必填;未提供(為空)時會走「取登入 email」分支 required: false + # oauth:base64 編碼的 Codex OAuth token 檔案內容 oauth: + # 參數說明 description: 'base64 編碼的 Codex OAuth token 檔案內容' + # 必填;缺少時安裝步驟會直接報錯退出 required: true +# 輸出參數定義區塊 outputs: + # text:將 Codex 執行結果(或登入 email)對外輸出 text: + # 參數說明 description: '輸出的文字' + # 取自 id 為 codex 的 step 所寫入的 text 輸出 value: ${{ steps.codex.outputs.text }} +# 執行定義區塊 runs: + # 使用 composite(複合)類型,串接多個 shell step using: 'composite' + # 步驟清單 steps: + # 步驟一:安裝 Codex CLI 並寫入 OAuth 憑證 - name: 安裝工具 + # 步驟環境變數 env: + # 將 inputs.oauth 注入為 OAUTH 環境變數(base64 內容) OAUTH: ${{ inputs.oauth }} + # 執行的 shell 指令區塊 run: | + # 檢查 OAUTH 是否為空;為空代表呼叫端未提供 secrets.CODEX_OAUTH if [ -z "$OAUTH" ]; then + # 輸出錯誤訊息至 stderr(>&2) echo 'oauth input (secrets.CODEX_OAUTH) is required and must not be empty.' >&2 + # 以非零狀態碼退出,使工作流程失敗 exit 1 fi + # 全域安裝 Codex CLI 套件,安裝後可直接使用 codex 指令 npm install -g @openai/codex + # 設定 OAuth 憑證檔的路徑(codex CLI 預期讀取此檔) oauth_file="$HOME/.codex/auth.json" + # 建立憑證檔所在目錄;-m 700 限定僅擁有者可進入(保護憑證) install -d -m 700 "$(dirname "$oauth_file")" + # 將 base64 內容解碼(base64 -d)後寫入憑證檔 printf '%s' "$OAUTH" | base64 -d > "$oauth_file" + # 將憑證檔權限設為 600,僅擁有者可讀寫(避免憑證外洩) chmod 600 "$oauth_file" + # 指定以 bash 執行此 run 區塊 shell: bash + # 步驟二:執行 Codex CLI 或取得登入 email - name: 執行工具 + # 步驟識別碼,供 outputs.text 引用其輸出 id: codex + # 步驟環境變數 env: + # MODEL:取自 repository 變數 CODEX_MODEL(指定使用的模型) MODEL: ${{ vars.CODEX_MODEL }} + # PROMPT:取自 inputs.prompt(要執行的提示詞) PROMPT: ${{ inputs.prompt }} + # 執行的 shell 指令區塊 run: | + # 檢查 MODEL 是否為空;未設定 CODEX_MODEL 則無法執行 if [ -z "$MODEL" ]; then + # 輸出錯誤訊息至 stderr echo 'CODEX_MODEL repository variable is required.' >&2 + # 以非零狀態碼退出,使工作流程失敗 exit 1 fi + # 當 PROMPT 為空時,改走「取得登入帳號 email」分支 if [ -z "$PROMPT" ]; then + # 呼叫 codex_account.py 取得目前登入帳號的 email($GITHUB_ACTION_PATH 為 action 根目錄) email="$(python3 "$GITHUB_ACTION_PATH/app/codex_account.py")" + # 將 email 寫入 text 輸出($GITHUB_OUTPUT 為步驟輸出檔) echo "text=$email" >> "$GITHUB_OUTPUT" + # 以 0 正常退出,不再執行 codex exec exit 0 fi + # 執行 Codex CLI: + # --skip-git-repo-check 跳過 git repo 檢查(允許在非 git 目錄執行) + # -s danger-full-access 沙箱模式設為完整存取(具高風險副作用,可讀寫檔案/網路) + # --model "$MODEL" 指定使用的模型 + # "$PROMPT" 為要執行的提示詞;輸出以命令替換存入 text 變數 text="$(codex exec --skip-git-repo-check -s danger-full-access --model "$MODEL" "$PROMPT")" + # 將 Codex 輸出列印到標準輸出(方便在記錄中檢視) printf '%s\n' "$text" + # 以 heredoc 方式將多行輸出寫入 $GITHUB_OUTPUT(避免換行破壞輸出格式) { + # 宣告 text 輸出,使用 CODEX_OUTPUT 作為 heredoc 結束標記 echo 'text<> "$GITHUB_OUTPUT" + # 指定以 bash 執行此 run 區塊 shell: bash diff --git a/app/codex_account.py b/app/codex_account.py index 10f5648..e73ae55 100644 --- a/app/codex_account.py +++ b/app/codex_account.py @@ -10,6 +10,21 @@ import time def read_account_email(timeout_seconds: float = 25.0) -> str: + """透過 codex app-server 的 JSON-RPC account/read 取得目前登入帳號的 email。 + + 啟動 ``codex app-server`` 子行程,依序送出 initialize / initialized / + account/read 三筆 JSON-RPC 訊息,並讀取其回報的登入帳號 email。 + + Args: + timeout_seconds: 等待 app-server 回應的秒數上限,預設 25.0。 + + Returns: + 登入帳號的 email;取不到或逾時時回傳空字串 ""。 + + 使用情境: + 在 CI 中驗證登入身分時呼叫。前置條件為 codex CLI 已安裝, + 且 $HOME/.codex/auth.json 已寫入有效的 OAuth token。 + """ process = subprocess.Popen( ["codex", "app-server"], stdin=subprocess.PIPE, @@ -20,6 +35,7 @@ def read_account_email(timeout_seconds: float = 25.0) -> str: ) def send(obj): + """將 dict 物件序列化成 JSON 後寫入 app-server 的 stdin 並 flush。""" process.stdin.write(json.dumps(obj) + "\n") process.stdin.flush()