docs(codex): 補齊 function docstring 與指令檔註解並重建 README #4
@@ -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 }}
|
||||
|
||||
@@ -1,19 +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:
|
||||
ai-code-review:
|
||||
name: AI Code Review
|
||||
# 第一個 job:計算 beta 版本號,供後續 codex job 取用。
|
||||
release-tag-version:
|
||||
# job 顯示名稱。
|
||||
name: Release Tag Version
|
||||
# 指定執行的 runner 標籤(ubuntu)。
|
||||
runs-on: ubuntu
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
# 對外輸出值,讓相依此 job 的其他 job 可引用。
|
||||
outputs:
|
||||
# 將 release-tag-version step 算出的 version 輸出,鍵名為 version。
|
||||
version: ${{ steps.release-tag-version.outputs.version }}
|
||||
# 步驟清單。
|
||||
steps:
|
||||
- name: AI 程式碼審查 by OpenCode
|
||||
uses: https://gitea.jsc.idv.tw/composite-actions/opencode-code-review@${{ vars.ACTION_OPENCODE_CODE_REVIEW_VERSION }}
|
||||
# 呼叫 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:
|
||||
token: ${{ secrets.TOKEN }}
|
||||
# 指定產生 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
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# Codex CLI Composite Action
|
||||
|
||||
提供一個 Gitea/GitHub Actions composite action,用於安裝 Codex CLI、寫入 OAuth token、執行指定提示詞,並將 CLI 輸出寫入 action output;另提供透過 `codex app-server` 取得目前登入帳號 email 的工具函式。
|
||||
|
||||
> 更新時間:2026/06/29 14:18:26
|
||||
|
||||
## 專案列表
|
||||
|
||||
### 專案描述
|
||||
|
||||
| 專案名稱 | 專案描述 |
|
||||
| --- | --- |
|
||||
| [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/src/branch/develop/) | 無 |
|
||||
|
||||
### NuGet 套件
|
||||
|
||||
| 專案名稱 | NuGet 套件列表 |
|
||||
| --- | --- |
|
||||
| [codex](https://gitea.jsc.idv.tw/composite-actions/codex/src/branch/develop/) | 無 |
|
||||
|
||||
## 功能列表
|
||||
|
||||
### 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) |
|
||||
|
||||
## 使用範例
|
||||
|
||||
<a id="codex_accountread_account_email"></a>
|
||||
### codex_account.read_account_email
|
||||
|
||||
透過 `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` 終止子行程。
|
||||
|
||||
簽名:
|
||||
|
||||
```python
|
||||
def read_account_email(timeout_seconds: float = 25.0) -> str
|
||||
```
|
||||
|
||||
前置條件:
|
||||
|
||||
- `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("取不到登入帳號(未登入或逾時)")
|
||||
```
|
||||
|
||||
預期結果:
|
||||
|
||||
- 成功時回傳登入帳號的 email 字串。
|
||||
- 未登入、回應缺漏或逾時時回傳空字串 `""`。
|
||||
|
||||
此模組亦可直接以指令執行,會將 email 印到 stdout(取不到時印空字串):
|
||||
|
||||
```bash
|
||||
python3 app/codex_account.py
|
||||
```
|
||||
+110
-19
@@ -1,33 +1,124 @@
|
||||
name: 'Composite Action Template'
|
||||
description: 'Composite Action 範本'
|
||||
# =============================================================================
|
||||
# 檔案用途:提供 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:
|
||||
text:
|
||||
description: '輸入的文字'
|
||||
# prompt:傳給 Codex CLI 的提示詞
|
||||
prompt:
|
||||
# 參數說明
|
||||
description: '傳給 Codex CLI 的提示詞'
|
||||
# 非必填;未提供(為空)時會走「取登入 email」分支
|
||||
required: false
|
||||
default: 'Hello, World!'
|
||||
# oauth:base64 編碼的 Codex OAuth token 檔案內容
|
||||
oauth:
|
||||
# 參數說明
|
||||
description: 'base64 編碼的 Codex OAuth token 檔案內容'
|
||||
# 必填;缺少時安裝步驟會直接報錯退出
|
||||
required: true
|
||||
# 輸出參數定義區塊
|
||||
outputs:
|
||||
# text:將 Codex 執行結果(或登入 email)對外輸出
|
||||
text:
|
||||
# 參數說明
|
||||
description: '輸出的文字'
|
||||
value: ${{ steps.change.outputs.text }}
|
||||
# 取自 id 為 codex 的 step 所寫入的 text 輸出
|
||||
value: ${{ steps.codex.outputs.text }}
|
||||
# 執行定義區塊
|
||||
runs:
|
||||
# 使用 composite(複合)類型,串接多個 shell step
|
||||
using: 'composite'
|
||||
# 步驟清單
|
||||
steps:
|
||||
- name: 交換
|
||||
id: change
|
||||
# 步驟一:安裝 Codex CLI 並寫入 OAuth 憑證
|
||||
- name: 安裝工具
|
||||
# 步驟環境變數
|
||||
env:
|
||||
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||
GITEA_TOKEN: ${{ gitea.token }}
|
||||
TEXT: ${{ inputs.text }}
|
||||
# 將 inputs.oauth 注入為 OAUTH 環境變數(base64 內容)
|
||||
OAUTH: ${{ inputs.oauth }}
|
||||
# 執行的 shell 指令區塊
|
||||
run: |
|
||||
echo "Gitea Server Url: $GITEA_SERVER_URL"
|
||||
# 檢查 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
|
||||
|
||||
echo "Gitea Repository: $GITEA_REPOSITORY"
|
||||
# 全域安裝 Codex CLI 套件,安裝後可直接使用 codex 指令
|
||||
npm install -g @openai/codex
|
||||
|
||||
echo "Gitea Token: $GITEA_TOKEN"
|
||||
|
||||
echo "Text: $TEXT"
|
||||
|
||||
echo "text=$TEXT" >> "$GITHUB_OUTPUT"
|
||||
# 設定 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<<CODEX_OUTPUT'
|
||||
# 寫入實際輸出內容
|
||||
printf '%s\n' "$text"
|
||||
# heredoc 結束標記
|
||||
echo 'CODEX_OUTPUT'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
# 指定以 bash 執行此 run 區塊
|
||||
shell: bash
|
||||
@@ -0,0 +1,73 @@
|
||||
"""透過 codex app-server 的 JSON-RPC account/read 取得目前登入帳號的 email。
|
||||
|
||||
這是 TUI `/status` Account 欄位的程式化來源,不解析本地 OAuth token,
|
||||
而是由 codex 自身回報登入帳號。將 email 印到 stdout(取不到時印空字串)。
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
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,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
)
|
||||
|
||||
def send(obj):
|
||||
"""將 dict 物件序列化成 JSON 後寫入 app-server 的 stdin 並 flush。"""
|
||||
process.stdin.write(json.dumps(obj) + "\n")
|
||||
process.stdin.flush()
|
||||
|
||||
send({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "initialize",
|
||||
"params": {"clientInfo": {"name": "ci", "version": "1.0"}},
|
||||
})
|
||||
send({"jsonrpc": "2.0", "method": "initialized", "params": {}})
|
||||
send({"jsonrpc": "2.0", "id": 2, "method": "account/read", "params": {}})
|
||||
|
||||
email = ""
|
||||
deadline = time.time() + timeout_seconds
|
||||
try:
|
||||
while time.time() < deadline:
|
||||
line = process.stdout.readline()
|
||||
if not line:
|
||||
break
|
||||
try:
|
||||
message = json.loads(line)
|
||||
except ValueError:
|
||||
continue
|
||||
if message.get("id") == 2:
|
||||
account = (message.get("result") or {}).get("account") or {}
|
||||
email = account.get("email") or ""
|
||||
break
|
||||
finally:
|
||||
process.terminate()
|
||||
|
||||
return email
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(read_account_email())
|
||||
Reference in New Issue
Block a user