From f8f67524ae2cedb855bc89ee47386e0255c4000e Mon Sep 17 00:00:00 2001 From: Jeffery Date: Mon, 29 Jun 2026 09:48:24 +0000 Subject: [PATCH] =?UTF-8?q?feat(codex=5Faccount):=20=E6=94=B9=E7=94=A8=20s?= =?UTF-8?q?hell=20=E5=8F=96=E5=BE=97=E7=99=BB=E5=85=A5=E5=B8=B3=E8=99=9F?= =?UTF-8?q?=E4=B8=A6=E8=A3=9C=E4=B8=8A=E5=AE=B9=E5=99=A8=E7=92=B0=E5=A2=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 23 +++++++++ action.yml | 17 ++++++- app/codex_account.py | 115 +++++++++++++++++++++---------------------- 3 files changed, 95 insertions(+), 60 deletions(-) create mode 100644 Dockerfile mode change 100644 => 100755 app/codex_account.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..68ac4c2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM ubuntu:latest + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + bash \ + ca-certificates \ + coreutils \ + gawk \ + jq \ + nodejs \ + npm \ + && npm install -g @openai/codex \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /workspace + +COPY app/codex_account.py /usr/local/bin/codex-account +RUN chmod +x /usr/local/bin/codex-account + +CMD ["codex-account"] diff --git a/action.yml b/action.yml index 28e24b3..7843853 100644 --- a/action.yml +++ b/action.yml @@ -62,6 +62,21 @@ runs: exit 1 fi + # 安裝 jq,供 app/codex_account.py 以 shell 解析 JSON-RPC 回應。 + if ! command -v jq >/dev/null 2>&1; then + if command -v apt-get >/dev/null 2>&1; then + apt_get='apt-get' + if command -v sudo >/dev/null 2>&1; then + apt_get='sudo apt-get' + fi + $apt_get update + $apt_get install -y jq + else + echo 'jq is required but apt-get is not available.' >&2 + exit 1 + fi + fi + # 全域安裝 Codex CLI 套件,安裝後可直接使用 codex 指令 npm install -g @openai/codex @@ -97,7 +112,7 @@ runs: # 當 PROMPT 為空時,改走「取得登入帳號 email」分支 if [ -z "$PROMPT" ]; then # 呼叫 codex_account.py 取得目前登入帳號的 email($GITHUB_ACTION_PATH 為 action 根目錄) - email="$(python3 "$GITHUB_ACTION_PATH/app/codex_account.py")" + email="$(bash "$GITHUB_ACTION_PATH/app/codex_account.py")" # 將 email 寫入 text 輸出($GITHUB_OUTPUT 為步驟輸出檔) echo "text=$email" >> "$GITHUB_OUTPUT" # 以 0 正常退出,不再執行 codex exec diff --git a/app/codex_account.py b/app/codex_account.py old mode 100644 new mode 100755 index e73ae55..6087e9f --- a/app/codex_account.py +++ b/app/codex_account.py @@ -1,73 +1,70 @@ -"""透過 codex app-server 的 JSON-RPC account/read 取得目前登入帳號的 email。 +#!/usr/bin/env bash +# 透過 codex app-server 的 JSON-RPC account/read 取得目前登入帳號的 email。 +# +# 這是 TUI `/status` Account 欄位的程式化來源,不解析本地 OAuth token, +# 而是由 codex 自身回報登入帳號。將 email 印到 stdout(取不到時印空字串)。 -這是 TUI `/status` Account 欄位的程式化來源,不解析本地 OAuth token, -而是由 codex 自身回報登入帳號。將 email 印到 stdout(取不到時印空字串)。 -""" +set -uo pipefail -import json -import subprocess -import time +timeout_seconds="${1:-25}" +if ! [[ "$timeout_seconds" =~ ^[0-9]+([.][0-9]+)?$ ]]; then + echo "timeout_seconds must be a number." >&2 + exit 2 +fi +if ! command -v jq >/dev/null 2>&1; then + echo "jq is required." >&2 + exit 2 +fi -def read_account_email(timeout_seconds: float = 25.0) -> str: - """透過 codex app-server 的 JSON-RPC account/read 取得目前登入帳號的 email。 +email="" - 啟動 ``codex app-server`` 子行程,依序送出 initialize / initialized / - account/read 三筆 JSON-RPC 訊息,並讀取其回報的登入帳號 email。 +cleanup() { + if [[ -n "${CODEX_SERVER_PID:-}" ]]; then + kill "$CODEX_SERVER_PID" >/dev/null 2>&1 || true + wait "$CODEX_SERVER_PID" >/dev/null 2>&1 || true + fi +} +trap cleanup EXIT - Args: - timeout_seconds: 等待 app-server 回應的秒數上限,預設 25.0。 +coproc CODEX_SERVER { codex app-server 2>/dev/null; } +exec {codex_server_stdin}>&"${CODEX_SERVER[1]}" +exec {codex_server_stdout}<&"${CODEX_SERVER[0]}" - Returns: - 登入帳號的 email;取不到或逾時時回傳空字串 ""。 +send_json() { + printf '%s\n' "$1" >&"$codex_server_stdin" +} - 使用情境: - 在 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, - ) +send_json '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"clientInfo":{"name":"ci","version":"1.0"}}}' +send_json '{"jsonrpc":"2.0","method":"initialized","params":{}}' +send_json '{"jsonrpc":"2.0","id":2,"method":"account/read","params":{}}' - def send(obj): - """將 dict 物件序列化成 JSON 後寫入 app-server 的 stdin 並 flush。""" - process.stdin.write(json.dumps(obj) + "\n") - process.stdin.flush() +deadline="$( + awk -v now="$(date +%s)" -v timeout="$timeout_seconds" 'BEGIN { printf "%.3f", now + timeout }' +)" - 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": {}}) +while awk -v now="$(date +%s)" -v deadline="$deadline" 'BEGIN { exit !(now < deadline) }'; do + remaining="$( + awk -v now="$(date +%s)" -v deadline="$deadline" 'BEGIN { + remaining = deadline - now + if (remaining < 1) { + remaining = 1 + } + printf "%.0f", remaining + }' + )" - 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() + if ! IFS= read -r -t "$remaining" line <&"$codex_server_stdout"; then + break + fi - return email + message_id="$(jq -r 'try .id catch empty' <<<"$line" 2>/dev/null || true)" + if [[ "$message_id" != "2" ]]; then + continue + fi + email="$(jq -r 'try (.result.account.email // "") catch ""' <<<"$line" 2>/dev/null || true)" + break +done -if __name__ == "__main__": - print(read_account_email()) +printf '%s\n' "$email"