Merge pull request 'feat(codex): 新增 Codex CLI composite action' (#1) from ai-review-resolve/develop-20260629-115924 into develop
Reviewed-on: composite-actions/codex#1
This commit was merged in pull request #1.
This commit is contained in:
@@ -1,19 +1,34 @@
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- master
|
- master
|
||||||
types: [opened, synchronize]
|
types: [opened, synchronize]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ai-code-review:
|
release-tag-version:
|
||||||
name: AI Code Review
|
name: Release Tag Version
|
||||||
runs-on: ubuntu
|
runs-on: ubuntu
|
||||||
permissions:
|
outputs:
|
||||||
contents: write
|
version: ${{ steps.release-tag-version.outputs.version }}
|
||||||
pull-requests: write
|
|
||||||
issues: write
|
|
||||||
steps:
|
steps:
|
||||||
- name: AI 程式碼審查 by OpenCode
|
- name: 計算版本號
|
||||||
uses: https://gitea.jsc.idv.tw/composite-actions/opencode-code-review@${{ vars.ACTION_OPENCODE_CODE_REVIEW_VERSION }}
|
id: release-tag-version
|
||||||
|
uses: https://gitea.jsc.idv.tw/composite-actions/release-tag-version@${{ vars.ACTION_RELEASE_TAG_VERSION }}
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.TOKEN }}
|
is_beta: 'true'
|
||||||
|
|
||||||
|
codex:
|
||||||
|
name: Codex
|
||||||
|
runs-on: ubuntu
|
||||||
|
needs: release-tag-version
|
||||||
|
steps:
|
||||||
|
- name: 測試工具
|
||||||
|
id: codex
|
||||||
|
uses: https://gitea.jsc.idv.tw/composite-actions/codex@v${{ needs.release-tag-version.outputs.version }}
|
||||||
|
with:
|
||||||
|
oauth: ${{ secrets.CODEX_OAUTH }}
|
||||||
|
- name: 檢查輸出
|
||||||
|
if: ${{ steps.codex.outputs.text != vars.CODEX_EMAIL }}
|
||||||
|
run: exit 1
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# Codex CLI Composite Action
|
||||||
|
|
||||||
|
此 repository 提供一個 Gitea/GitHub Actions composite action,用於安裝 Codex CLI、寫入 OAuth token、執行指定提示詞,並將 CLI 輸出寫入 action output。
|
||||||
|
|
||||||
|
## 專案列表
|
||||||
|
|
||||||
|
### 專案描述
|
||||||
|
|
||||||
|
| 專案名稱 | 專案描述 |
|
||||||
|
| --- | --- |
|
||||||
|
| [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) | 無 |
|
||||||
|
|
||||||
|
### NuGet 套件
|
||||||
|
|
||||||
|
| 專案名稱 | NuGet 套件列表 |
|
||||||
|
| --- | --- |
|
||||||
|
| [codex](https://gitea.jsc.idv.tw/composite-actions/codex) | 無 |
|
||||||
|
|
||||||
|
## 功能列表
|
||||||
|
|
||||||
|
此 repository 未包含可列入 README 的 public method、public constructor、public extension method 或 public operator。
|
||||||
|
|
||||||
|
## 使用範例
|
||||||
|
|
||||||
|
### 在 workflow 中呼叫 Codex CLI
|
||||||
|
|
||||||
|
以下範例示範在 workflow step 中呼叫此 composite action,傳入 OAuth token 與提示詞,並在後續步驟讀取 `text` output。模型名稱由 action 直接讀取 `vars.CODEX_MODEL`。
|
||||||
|
|
||||||
|
```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 }}'
|
||||||
|
```
|
||||||
|
|
||||||
|
前置條件:
|
||||||
|
|
||||||
|
- `secrets.CODEX_OAUTH` 必須是 Codex OAuth token 檔案內容的 base64 字串。
|
||||||
|
- `vars.CODEX_MODEL` 必須是 Codex CLI 可用的模型名稱。
|
||||||
|
- runner 必須可透過 npm 安裝 `@openai/codex`。
|
||||||
|
|
||||||
|
預期結果:
|
||||||
|
|
||||||
|
- action 會安裝 `codex` CLI。
|
||||||
|
- action 會將 OAuth token 寫入 `$HOME/.codex/auth.json` 並設定檔案權限為 `600`。
|
||||||
|
- action 會執行 `codex exec --model "$MODEL" "$PROMPT"`,並把 stdout 寫入 `steps.codex.outputs.text`。
|
||||||
+42
-19
@@ -1,33 +1,56 @@
|
|||||||
name: 'Composite Action Template'
|
name: 'Codex CLI'
|
||||||
description: 'Composite Action 範本'
|
description: 'Codex CLI 工具'
|
||||||
author: 'Jeffery'
|
author: 'Jeffery'
|
||||||
inputs:
|
inputs:
|
||||||
text:
|
prompt:
|
||||||
description: '輸入的文字'
|
description: '傳給 Codex CLI 的提示詞'
|
||||||
required: false
|
required: false
|
||||||
default: 'Hello, World!'
|
oauth:
|
||||||
|
description: 'base64 編碼的 Codex OAuth token 檔案內容'
|
||||||
|
required: true
|
||||||
outputs:
|
outputs:
|
||||||
text:
|
text:
|
||||||
description: '輸出的文字'
|
description: '輸出的文字'
|
||||||
value: ${{ steps.change.outputs.text }}
|
value: ${{ steps.codex.outputs.text }}
|
||||||
runs:
|
runs:
|
||||||
using: 'composite'
|
using: 'composite'
|
||||||
steps:
|
steps:
|
||||||
- name: 交換
|
- name: 安裝工具
|
||||||
id: change
|
|
||||||
env:
|
env:
|
||||||
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
OAUTH: ${{ inputs.oauth }}
|
||||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
|
||||||
GITEA_TOKEN: ${{ gitea.token }}
|
|
||||||
TEXT: ${{ inputs.text }}
|
|
||||||
run: |
|
run: |
|
||||||
echo "Gitea Server Url: $GITEA_SERVER_URL"
|
if [ -z "$OAUTH" ]; then
|
||||||
|
echo 'oauth input (secrets.CODEX_OAUTH) is required and must not be empty.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Gitea Repository: $GITEA_REPOSITORY"
|
npm install -g @openai/codex
|
||||||
|
|
||||||
echo "Gitea Token: $GITEA_TOKEN"
|
oauth_file="$HOME/.codex/auth.json"
|
||||||
|
install -d -m 700 "$(dirname "$oauth_file")"
|
||||||
echo "Text: $TEXT"
|
printf '%s' "$OAUTH" | base64 -d > "$oauth_file"
|
||||||
|
chmod 600 "$oauth_file"
|
||||||
echo "text=$TEXT" >> "$GITHUB_OUTPUT"
|
shell: bash
|
||||||
|
- name: 執行工具
|
||||||
|
id: codex
|
||||||
|
env:
|
||||||
|
MODEL: ${{ vars.CODEX_MODEL }}
|
||||||
|
PROMPT: ${{ inputs.prompt }}
|
||||||
|
run: |
|
||||||
|
if [ -z "$MODEL" ]; then
|
||||||
|
echo 'CODEX_MODEL repository variable is required.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "$PROMPT" ]; then
|
||||||
|
email="$(python3 "$GITHUB_ACTION_PATH/app/codex_account.py")"
|
||||||
|
echo "text=$email" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
text="$(codex exec --skip-git-repo-check -s danger-full-access --model "$MODEL" "$PROMPT")"
|
||||||
|
printf '%s\n' "$text"
|
||||||
|
{
|
||||||
|
echo 'text<<CODEX_OUTPUT'
|
||||||
|
printf '%s\n' "$text"
|
||||||
|
echo 'CODEX_OUTPUT'
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
"""透過 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:
|
||||||
|
process = subprocess.Popen(
|
||||||
|
["codex", "app-server"],
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
text=True,
|
||||||
|
bufsize=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
def send(obj):
|
||||||
|
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