# yaml-language-server: $schema=https://schema.zeabur.app/template.json
apiVersion: zeabur.com/v1
kind: Template
metadata:
    name: Codex
spec:
    description: |
        OpenAI Codex CLI - an AI coding agent that runs in your terminal. Deploy as a persistent cloud workstation with Zeabur Terminal access.
    icon: https://cdn-console.zeabur.com/f/zD0ib/codex-color.webp
    variables:
        - key: PUBLIC_DOMAIN
          type: DOMAIN
          name: Domain
          description: The domain for accessing the web terminal (e.g. my-codex)
        - key: OPENAI_API_KEY
          type: STRING
          name: OpenAI API Key
          description: Your OpenAI API key for authenticating Codex. You can also use device auth login after deployment.
        - key: OPENAI_BASE_URL
          type: STRING
          name: OpenAI Base URL (Optional)
          description: Optional — leave blank to use OpenAI directly. Set to Zeabur AI Hub endpoint (e.g. https://hub.zeabur.com/v1) to use Zeabur's unified AI gateway.
    tags:
        - AI
        - Developer Tools
    readme: |
        # OpenAI Codex CLI

        [Codex](https://github.com/openai/codex) is OpenAI's open-source AI coding agent that runs in your terminal. It can understand your codebase, propose changes, execute commands, and help you build software faster — all through natural language conversation.

        This template deploys Codex as a persistent cloud workstation on Zeabur with a web-based terminal (ttyd) accessible from your browser.

        ## Getting Started

        1. **Deploy the template** and wait for the service to start. Wait until the health check turns green before proceeding.
        2. **Open the service URL** in your browser (the domain bound to your service).
        3. **Log in** with username `user` and the password from the **Instructions** tab on the Zeabur dashboard.
        4. **Sign in** — run `codex`, then open https://auth.openai.com/codex/device in your browser and enter the one-time code shown in the terminal. See [Codex Auth Docs](https://developers.openai.com/codex/auth#login-on-headless-devices).

        ## Using API Key (Optional)

        Instead of device code sign-in, you can set `OPENAI_API_KEY` in the Zeabur dashboard. Optionally set `OPENAI_BASE_URL` to use [Zeabur AI Hub](https://zeabur.com/ai) (e.g., `https://hub.zeabur.com/v1`).

        ## Persistent Storage

        All data under `/home/node/` is persisted across restarts and redeployments.

        ## Common Commands

        | Command | Description |
        |---------|-------------|
        | `codex` | Start interactive Codex session |
        | `codex "describe task"` | Run Codex with a specific prompt |
        | `codex exec "task"` | Run non-interactively (for automation) |
        | `codex resume` | Resume a previous session |
        | `codex --version` | Check installed Codex version |

        ## Environment Variables

        | Variable | Required | Description |
        |----------|----------|-------------|
        | `OPENAI_API_KEY` | No | OpenAI API key (only needed if not using device auth) |
        | `OPENAI_BASE_URL` | No | Custom API endpoint (for Zeabur AI Hub) |
        | `WEB_TERMINAL_PASSWORD` | Auto | Password for web terminal login (auto-generated) |

        ## Recommended Resources
        - **Minimum**: 1 vCPU / 2 GB RAM
        - **Recommended**: 2 vCPU / 4 GB RAM

        ## Changelog

        ### 2026-03-31 -- Initial Release

        - ttyd web terminal with password auth and device code sign-in
        - Homebrew and npm global packages persisted in volume
        - Resource requirements and domain binding

        ## Links

        - [GitHub](https://github.com/openai/codex)
        - [Codex CLI Documentation](https://developers.openai.com/codex/cli/features)
    resourceRequirement:
        minConfig:
            cpu: 1
            ram: 2
        recommendedConfig:
            cpu: 2
            ram: 4
    services:
        - name: codex
          icon: https://cdn-console.zeabur.com/f/zD0ib/codex-color.webp
          template: PREBUILT
          spec:
            id: codex
            source:
                image: zeabur/codex:latest
                command:
                    - /bin/sh
                    - -c
                    - /opt/startup.sh
            ports:
                - id: web
                  port: 7681
                  type: HTTP
            volumes:
                - id: home
                  dir: /home/node
            instructions:
                - title: Password
                  content: ${WEB_TERMINAL_PASSWORD}
                - title: Sign in (device code)
                  content: codex login --device-auth
                - title: Start Codex (full permissions)
                  content: codex --full-auto
            env:
                OPENAI_API_KEY:
                    default: ${OPENAI_API_KEY}
                OPENAI_BASE_URL:
                    default: ${OPENAI_BASE_URL}
                WEB_TERMINAL_PASSWORD:
                    default: ${PASSWORD}
                    expose: true
            configs:
                - path: /opt/startup.sh
                  template: |
                    #!/bin/sh
                    chown -R node:node /home/node

                    # Unset empty env vars so Codex falls back to defaults
                    [ -z "$OPENAI_API_KEY" ] && unset OPENAI_API_KEY
                    [ -z "$OPENAI_BASE_URL" ] && unset OPENAI_BASE_URL

                    # Homebrew setup (persisted in volume via symlink)
                    mkdir -p /home/node/.linuxbrew /home/linuxbrew
                    ln -sf /home/node/.linuxbrew /home/linuxbrew/.linuxbrew
                    touch /.dockerenv
                    if [ ! -f /home/node/.linuxbrew/bin/brew ]; then
                      NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
                    fi
                    chown -R node:node /home/node/.linuxbrew /home/linuxbrew/.linuxbrew /home/node/.cache 2>/dev/null
                    for rc in /home/node/.bashrc /home/node/.profile; do
                      grep -qF '.npm-global' "$rc" 2>/dev/null || cat >> "$rc" << 'EOPATH'
                    export PATH="$HOME/.npm-global/bin:$PATH"
                    EOPATH
                      grep -qF 'linuxbrew' "$rc" 2>/dev/null || echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> "$rc"
                    done

                    # Start ttyd on internal port with auth
                    ttyd \
                      --port 7682 \
                      --writable \
                      --credential "user:$WEB_TERMINAL_PASSWORD" \
                      runuser -u node -- bash -l &

                    # Start landing page proxy on exposed port
                    exec node /opt/launcher.js
                  permission: 493
                  envsubst: null
                - path: /opt/launcher.js
                  template: |
                    const http = require('http');
                    const crypto = require('crypto');
                    const fs = require('fs');

                    const TTYD = 7682;
                    const PORT = 7681;
                    const PASS = process.env.WEB_TERMINAL_PASSWORD || '';
                    const AUTH = 'Basic ' + Buffer.from('user:' + PASS).toString('base64');
                    const SECRET = crypto.randomBytes(32).toString('hex');

                    const landing = fs.readFileSync('/opt/landing.html', 'utf8');

                    function hmac(val) {
                      return crypto.createHmac('sha256', SECRET).update(val).digest('hex');
                    }
                    function parseCookies(str) {
                      const obj = {};
                      (str || '').split(';').forEach(p => {
                        const [k, ...v] = p.trim().split('=');
                        if (k) obj[k] = v.join('=');
                      });
                      return obj;
                    }
                    function authorized(req) {
                      return parseCookies(req.headers.cookie).session === hmac(PASS);
                    }
                    function stripGo(url) {
                      const u = new URL(url, 'http://x');
                      u.searchParams.delete('go');
                      const qs = u.searchParams.toString();
                      return u.pathname + (qs ? '?' + qs : '');
                    }

                    const server = http.createServer((req, res) => {
                      // POST /auth — validate password, set session cookie
                      if (req.method === 'POST' && req.url === '/auth') {
                        let body = '';
                        req.on('data', c => body += c);
                        req.on('end', () => {
                          try {
                            const { password } = JSON.parse(body);
                            if (password === PASS) {
                              res.writeHead(200, {
                                'Content-Type': 'application/json',
                                'Set-Cookie': 'session=' + hmac(PASS) + '; Path=/; HttpOnly; SameSite=Strict; Max-Age=86400'
                              });
                              res.end(JSON.stringify({ ok: true }));
                            } else {
                              res.writeHead(200, { 'Content-Type': 'application/json' });
                              res.end(JSON.stringify({ ok: false }));
                            }
                          } catch {
                            res.writeHead(400);
                            res.end('Bad request');
                          }
                        });
                        return;
                      }

                      const u = new URL(req.url, 'http://x');

                      // Landing page — show if root path without ?go and not authenticated
                      if (u.pathname === '/' && !u.searchParams.has('go') && !authorized(req)) {
                        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
                        res.end(landing);
                        return;
                      }

                      // Everything else — require auth, proxy to ttyd
                      if (!authorized(req)) {
                        res.writeHead(302, { Location: '/' });
                        res.end();
                        return;
                      }

                      const headers = { ...req.headers, authorization: AUTH };
                      delete headers.host;
                      const proxyReq = http.request({
                        hostname: '127.0.0.1', port: TTYD,
                        path: stripGo(req.url), method: req.method, headers,
                      }, proxyRes => {
                        res.writeHead(proxyRes.statusCode, proxyRes.headers);
                        proxyRes.pipe(res);
                      });
                      proxyReq.on('error', () => { res.writeHead(502); res.end('Service starting...'); });
                      req.pipe(proxyReq);
                    });

                    // WebSocket upgrade proxy
                    server.on('upgrade', (req, socket, head) => {
                      if (!authorized(req)) { socket.destroy(); return; }
                      const headers = { ...req.headers, authorization: AUTH };
                      delete headers.host;
                      const proxyReq = http.request({
                        hostname: '127.0.0.1', port: TTYD,
                        path: stripGo(req.url), headers,
                      });
                      proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => {
                        let resp = 'HTTP/1.1 101 Switching Protocols\r\n';
                        const rh = proxyRes.rawHeaders;
                        for (let i = 0; i < rh.length; i += 2) resp += rh[i] + ': ' + rh[i+1] + '\r\n';
                        resp += '\r\n';
                        socket.write(resp);
                        if (proxyHead.length) socket.write(proxyHead);
                        proxySocket.pipe(socket);
                        socket.pipe(proxySocket);
                        proxySocket.on('error', () => socket.destroy());
                        socket.on('error', () => proxySocket.destroy());
                      });
                      proxyReq.on('error', () => socket.destroy());
                      proxyReq.end();
                    });

                    server.listen(PORT, () => console.log('Launcher ready on port ' + PORT));
                  permission: null
                  envsubst: null
                - path: /opt/landing.html
                  template: |
                    <!DOCTYPE html>
                    <html lang="en">
                    <head>
                    <meta charset="utf-8">
                    <meta name="viewport" content="width=device-width, initial-scale=1">
                    <title>Codex CLI</title>
                    <style>
                      * { margin: 0; padding: 0; box-sizing: border-box; }
                      body {
                        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
                        background: #1a1a2e;
                        color: #e0e0e0;
                        min-height: 100vh;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                      }
                      .container { text-align: center; max-width: 440px; padding: 2rem; }
                      .logo { width: 72px; height: 72px; margin-bottom: 1.5rem; }
                      h1 { font-size: 1.75rem; margin-bottom: 0.25rem; color: #fff; }
                      .subtitle { color: #888; margin-bottom: 2rem; font-size: 0.9rem; }
                      .input {
                        width: 100%; padding: 0.75rem 1rem; margin-bottom: 0.5rem;
                        background: #16162a; border: 1px solid #3d3d5c; border-radius: 8px;
                        color: #fff; font-size: 1rem; outline: none;
                      }
                      .input:focus { border-color: #d4a574; }
                      .error { color: #e74c3c; font-size: 0.85rem; margin-bottom: 0.5rem; }
                      .btn {
                        display: block; width: 100%; padding: 0.85rem; margin: 0.5rem 0;
                        border: none; border-radius: 8px; font-size: 0.95rem; font-weight: 600;
                        cursor: pointer; transition: opacity 0.2s; text-decoration: none;
                      }
                      .btn:hover { opacity: 0.85; }
                      .btn:disabled { opacity: 0.4; cursor: not-allowed; }
                      .btn-primary { background: #d4a574; color: #1a1a2e; }
                      .hidden { display: none; }
                      .cmd-box {
                        display: flex; align-items: center; gap: 0.5rem;
                        background: #16162a; border: 1px solid #3d3d5c; border-radius: 8px;
                        padding: 0.6rem 0.8rem; margin: 0.75rem 0; text-align: left;
                      }
                      .cmd-box code { flex: 1; font-size: 0.85rem; color: #d4a574; word-break: break-all; }
                      .cmd-box button {
                        background: none; border: none; color: #888; cursor: pointer;
                        font-size: 0.8rem; white-space: nowrap; padding: 0.25rem 0.5rem;
                        border-radius: 4px; transition: background 0.2s;
                      }
                      .cmd-box button:hover { background: #2d2d44; color: #fff; }
                      .step-label { color: #888; font-size: 0.8rem; margin-top: 1.25rem; margin-bottom: 0.25rem; }
                      .options { text-align: left; margin: 0.75rem 0; }
                      .option {
                        display: flex; align-items: flex-start; gap: 0.6rem;
                        padding: 0.6rem 0.8rem; margin: 0.4rem 0;
                        background: #16162a; border: 1px solid #3d3d5c; border-radius: 8px;
                        cursor: pointer; transition: border-color 0.2s;
                      }
                      .option:hover { border-color: #d4a574; }
                      .option input[type="checkbox"] {
                        accent-color: #d4a574; width: 16px; height: 16px; margin-top: 2px; flex-shrink: 0;
                      }
                      .option-info { display: flex; flex-direction: column; gap: 0.15rem; }
                      .option-name { font-size: 0.9rem; color: #fff; font-weight: 500; }
                      .option-desc { font-size: 0.75rem; color: #888; }
                      .footer { margin-top: 2rem; font-size: 0.75rem; color: #555; }
                    </style>
                    </head>
                    <body>
                    <div class="container">
                      <img class="logo" src="https://cdn-console.zeabur.com/f/OJmID/openai-white.webp" alt="Codex CLI">
                      <h1>Codex CLI</h1>
                      <p class="subtitle">OpenAI's AI coding agent</p>

                      <!-- Step 1: Password -->
                      <div id="step-login">
                        <input type="password" id="pw" class="input" placeholder="Enter password" autofocus>
                        <p id="err" class="error hidden">Incorrect password</p>
                        <button class="btn btn-primary" onclick="login()">Login</button>
                      </div>

                      <!-- Step 2: Options + Command + Enter Terminal -->
                      <div id="step-ready" class="hidden">
                        <p class="step-label">Step 1 — Select options</p>
                        <div class="options">
                          <label class="option">
                            <input type="checkbox" id="opt-tmux" checked onchange="buildCmd()">
                            <span class="option-info">
                              <span class="option-name">Use tmux</span>
                              <span class="option-desc">Keep session alive when browser closes (reconnect with tmux attach)</span>
                            </span>
                          </label>
                          <label class="option">
                            <input type="checkbox" id="opt-full-auto" checked onchange="buildCmd()">
                            <span class="option-info">
                              <span class="option-name">Full auto mode</span>
                              <span class="option-desc">Approve all changes automatically (--full-auto)</span>
                            </span>
                          </label>
                        </div>
                        <p class="step-label">Step 2 — Copy command</p>
                        <div class="cmd-box">
                          <code id="cmd-output">tmux new -As main -- codex --full-auto</code>
                          <button id="cmd-copy-btn" onclick="copy(this, document.getElementById('cmd-output').textContent)">Copy</button>
                        </div>
                        <p class="step-label">Step 3 — Open terminal and paste</p>
                        <a class="btn btn-primary" href="/?go">Enter Terminal</a>
                      </div>

                      <p class="footer">Powered by Zeabur</p>
                    </div>
                    <script>
                    function login() {
                      const pw = document.getElementById('pw').value;
                      document.querySelector('#step-login .btn').disabled = true;
                      document.getElementById('err').classList.add('hidden');
                      fetch('/auth', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ password: pw })
                      })
                      .then(r => r.json())
                      .then(d => {
                        if (d.ok) {
                          document.getElementById('step-login').classList.add('hidden');
                          document.getElementById('step-ready').classList.remove('hidden');
                          buildCmd();
                        } else {
                          document.getElementById('err').classList.remove('hidden');
                          document.querySelector('#step-login .btn').disabled = false;
                        }
                      })
                      .catch(() => {
                        document.querySelector('#step-login .btn').disabled = false;
                      });
                    }
                    function buildCmd() {
                      let cmd = 'codex';
                      if (document.getElementById('opt-full-auto').checked) cmd += ' --full-auto';
                      const useTmux = document.getElementById('opt-tmux').checked;
                      if (useTmux) {
                        document.getElementById('cmd-output').textContent = 'tmux new -As main -- ' + cmd;
                      } else {
                        document.getElementById('cmd-output').textContent = cmd;
                      }
                    }
                    function copy(btn, text) {
                      navigator.clipboard.writeText(text).then(() => {
                        btn.textContent = 'Copied!';
                        setTimeout(() => btn.textContent = 'Copy', 1500);
                      });
                    }
                    document.getElementById('pw').addEventListener('keydown', e => {
                      if (e.key === 'Enter') login();
                    });
                    </script>
                    </body>
                    </html>
                  permission: null
                  envsubst: null
          domainKey: PUBLIC_DOMAIN
localization:
    es-ES:
        description: |
            OpenAI Codex CLI - agente de codificacion IA. Estacion de trabajo en la nube persistente con terminal web accesible desde el navegador.
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: Dominio
              description: El dominio para acceder al terminal web (ej. my-codex)
        readme: |
            # OpenAI Codex CLI

            [Codex](https://github.com/openai/codex) es el agente de codificacion IA de codigo abierto de OpenAI. Esta plantilla despliega Codex como una estacion de trabajo en la nube persistente en Zeabur, con un terminal web (ttyd) accesible desde el navegador.

            ## Primeros pasos

            1. **Despliega la plantilla** y espera a que el servicio se inicie. Espera hasta que el health check se ponga verde.
            2. **Abre la URL del servicio** en tu navegador (el dominio vinculado al servicio).
            3. **Inicia sesion** con usuario `user` y la contrasena de la pestana **Instructions** en el panel de Zeabur.
            4. **Iniciar sesion** — ejecuta `codex`, luego abre https://auth.openai.com/codex/device en tu navegador e ingresa el codigo mostrado en el terminal. Ver [Codex Auth Docs](https://developers.openai.com/codex/auth#login-on-headless-devices).

            ## Usar clave API (Opcional)

            En lugar del inicio de sesion por codigo de dispositivo, puedes configurar `OPENAI_API_KEY` en el panel de Zeabur. Opcionalmente configura `OPENAI_BASE_URL` para usar [Zeabur AI Hub](https://zeabur.com/ai).

            ## Almacenamiento persistente

            Todos los datos bajo `/home/node/` se conservan entre reinicios y redespliegues.

            ## Comandos comunes

            | Comando | Descripcion |
            |---------|-------------|
            | `codex` | Iniciar sesion interactiva de Codex |
            | `codex "describir tarea"` | Ejecutar Codex con un prompt especifico |
            | `codex exec "tarea"` | Ejecutar sin interaccion (para automatizacion) |
            | `codex resume` | Reanudar una sesion anterior |
            | `codex --version` | Verificar la version instalada de Codex |

            ## Recursos Recomendados
            - **Minimo**: 1 vCPU / 2 GB RAM
            - **Recomendado**: 2 vCPU / 4 GB RAM

            ## Registro de cambios

            ### 2026-03-31 -- Lanzamiento inicial

            - Terminal web ttyd (autenticacion por contrasena e inicio de sesion por codigo de dispositivo)
            - Homebrew y paquetes npm globales persistidos en volumen
            - Requisitos de recursos y vinculacion de dominio

            ## Enlaces

            - [GitHub](https://github.com/openai/codex)
            - [Documentacion de Codex CLI](https://developers.openai.com/codex/cli/features)
    id-ID:
        description: |
            OpenAI Codex CLI - agen coding AI. Workstation cloud persisten dengan terminal web yang dapat diakses dari browser.
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: Domain
              description: Domain untuk mengakses terminal web (contoh my-codex)
        readme: |
            # OpenAI Codex CLI

            [Codex](https://github.com/openai/codex) adalah agen coding AI open-source dari OpenAI. Template ini mendeploy Codex sebagai workstation cloud persisten di Zeabur, dengan terminal web (ttyd) yang dapat diakses dari browser.

            ## Memulai

            1. **Deploy template** dan tunggu layanan dimulai. Tunggu hingga health check berwarna hijau sebelum melanjutkan.
            2. **Buka URL layanan** di browser Anda (domain yang terikat ke layanan).
            3. **Login** dengan username `user` dan password dari tab **Instructions** di dashboard Zeabur.
            4. **Login** — jalankan `codex`, lalu buka https://auth.openai.com/codex/device di browser dan masukkan kode yang ditampilkan di terminal. Lihat [Codex Auth Docs](https://developers.openai.com/codex/auth#login-on-headless-devices).

            ## Menggunakan API Key (Opsional)

            Selain login kode perangkat, Anda juga dapat mengatur `OPENAI_API_KEY` di dashboard Zeabur. Opsional atur `OPENAI_BASE_URL` untuk menggunakan [Zeabur AI Hub](https://zeabur.com/ai).

            ## Penyimpanan Persisten

            Semua data di bawah `/home/node/` dipertahankan setelah restart dan redeployment.

            ## Perintah Umum

            | Perintah | Deskripsi |
            |----------|-----------|
            | `codex` | Mulai sesi Codex interaktif |
            | `codex "deskripsikan tugas"` | Jalankan Codex dengan prompt tertentu |
            | `codex exec "tugas"` | Jalankan non-interaktif (untuk otomatisasi) |
            | `codex resume` | Lanjutkan sesi sebelumnya |
            | `codex --version` | Periksa versi Codex yang terinstal |

            ## Sumber Daya yang Direkomendasikan
            - **Minimum**: 1 vCPU / 2 GB RAM
            - **Disarankan**: 2 vCPU / 4 GB RAM

            ## Catatan Perubahan

            ### 2026-03-31 -- Rilis Pertama

            - Terminal web ttyd (autentikasi password dan login kode perangkat)
            - Homebrew dan paket npm global disimpan persisten di volume
            - Persyaratan sumber daya dan binding domain

            ## Tautan

            - [GitHub](https://github.com/openai/codex)
            - [Dokumentasi Codex CLI](https://developers.openai.com/codex/cli/features)
    ja-JP:
        description: |
            OpenAI Codex CLI - ターミナルで動作するAIコーディングエージェント。ブラウザからアクセス可能なWebターミナル付き永続クラウドワークステーション。
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: ドメイン
              description: Webターミナルにアクセスするためのドメイン（例：my-codex）
        readme: |
            # OpenAI Codex CLI

            [Codex](https://github.com/openai/codex) はOpenAIのオープンソースAIコーディングエージェントです。このテンプレートはCodexをZeabur上の永続クラウドワークステーションとしてデプロイし、ブラウザからアクセス可能なWebターミナル（ttyd）を提供します。

            ## はじめに

            1. **テンプレートをデプロイ**し、サービスの起動を待ちます。ヘルスチェックが緑になるまでお待ちください。
            2. **ブラウザでサービスURLを開きます**（サービスにバインドされたドメイン）。
            3. **ログイン**：ユーザー名は `user`、パスワードはZeaburダッシュボードの**Instructions**タブで確認できます。
            4. **サインイン** — `codex` を実行し、ブラウザで https://auth.openai.com/codex/device を開いてターミナルに表示されるワンタイムコードを入力します。[Codex 認証ドキュメント](https://developers.openai.com/codex/auth#login-on-headless-devices)を参照。

            ## API キーの使用（オプション）

            デバイスコードサインインの代わりに、Zeabur ダッシュボードで `OPENAI_API_KEY` を設定できます。オプションで `OPENAI_BASE_URL` を設定し、[Zeabur AI Hub](https://zeabur.com/ai) を使用できます。

            ## 永続ストレージ

            `/home/node/` 配下のすべてのデータは、再起動や再デプロイ後も保持されます。

            ## よく使うコマンド

            | コマンド | 説明 |
            |---------|------|
            | `codex` | インタラクティブCodexセッションを開始 |
            | `codex "タスクを説明"` | 特定のプロンプトでCodexを実行 |
            | `codex exec "タスク"` | 非インタラクティブ実行（自動化向け） |
            | `codex resume` | 以前のセッションを再開 |
            | `codex --version` | インストール済みCodexバージョンを確認 |

            ## 推奨リソース
            - **最小構成**：1 vCPU / 2 GB RAM
            - **推奨構成**：2 vCPU / 4 GB RAM

            ## 変更履歴

            ### 2026-03-31 — 初回リリース

            - ttyd Web ターミナル（パスワード認証およびデバイスコードサインイン）
            - Homebrew および npm グローバルパッケージをボリュームに永続化
            - リソース要件およびドメインバインディング

            ## リンク

            - [GitHub](https://github.com/openai/codex)
            - [Codex CLIドキュメント](https://developers.openai.com/codex/cli/features)
    zh-CN:
        description: |
            OpenAI Codex CLI - 在终端中运行的 AI 编程代理。部署为持久化云端工作站，通过浏览器访问 Web 终端。
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: 域名
              description: 用来访问 Web 终端的域名（例如 my-codex）
        readme: |
            # OpenAI Codex CLI

            [Codex](https://github.com/openai/codex) 是 OpenAI 的开源 AI 编程代理。此模板将 Codex 部署为 Zeabur 上的持久化云端工作站，并提供浏览器可访问的 Web 终端（ttyd）。

            ## 开始使用

            1. **部署模板**并等待服务启动。请等待健康检查变为绿色后再继续。
            2. **在浏览器中打开服务网址**（绑定到服务的域名）。
            3. **登录**，用户名为 `user`，密码请在 Zeabur 仪表板的 **Instructions** 页签查看。
            4. **登录** — 执行 `codex`，然后在浏览器打开 https://auth.openai.com/codex/device 并输入终端显示的一次性代码。参考 [Codex 认证文档](https://developers.openai.com/codex/auth#login-on-headless-devices)。

            ## 使用 API 密钥（可选）

            除了设备代码登录外，也可以在 Zeabur 仪表板设置 `OPENAI_API_KEY`。可选择设置 `OPENAI_BASE_URL` 以使用 [Zeabur AI Hub](https://zeabur.com/ai)。

            ## 持久化存储

            `/home/node/` 下的所有数据在重启和重新部署后都会保留。

            ## 常用命令

            | 命令 | 说明 |
            |------|------|
            | `codex` | 启动交互式 Codex 会话 |
            | `codex "描述任务"` | 使用特定提示运行 Codex |
            | `codex exec "任务"` | 非交互式运行（用于自动化） |
            | `codex resume` | 恢复之前的会话 |
            | `codex --version` | 检查已安装的 Codex 版本 |

            ## 建议资源配置
            - **最低配置**：1 vCPU / 2 GB RAM
            - **建议配置**：2 vCPU / 4 GB RAM

            ## 变更记录

            ### 2026-03-31 — 首次发布

            - ttyd Web 终端（密码验证及设备代码登录）
            - Homebrew 及 npm 全局包持久化于卷
            - 资源需求及域名绑定

            ## 链接

            - [GitHub](https://github.com/openai/codex)
            - [Codex CLI 文档](https://developers.openai.com/codex/cli/features)
    zh-TW:
        description: |
            OpenAI Codex CLI - 在終端機中運行的 AI 編程代理。部署為持久化雲端工作站，透過瀏覽器存取 Web 終端機。
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: 網域
              description: 用來存取 Web 終端機的網域（例如 my-codex）
        readme: |
            # OpenAI Codex CLI

            [Codex](https://github.com/openai/codex) 是 OpenAI 的開源 AI 編程代理。此模板將 Codex 部署為 Zeabur 上的持久化雲端工作站，並提供瀏覽器可存取的 Web 終端機（ttyd）。

            ## 開始使用

            1. **部署模板**並等待服務啟動。請等待健康檢查變為綠色後再繼續。
            2. **在瀏覽器中開啟服務網址**（綁定到服務的網域）。
            3. **登入**，使用者名稱為 `user`，密碼請在 Zeabur 儀表板的 **Instructions** 頁籤查看。
            4. **登入** — 執行 `codex`，然後在瀏覽器開啟 https://auth.openai.com/codex/device 並輸入終端機顯示的一次性代碼。參考 [Codex 認證文件](https://developers.openai.com/codex/auth#login-on-headless-devices)。

            ## 使用 API 金鑰（選用）

            除了裝置代碼登入外，也可以在 Zeabur 儀表板設定 `OPENAI_API_KEY`。可選擇設定 `OPENAI_BASE_URL` 以使用 [Zeabur AI Hub](https://zeabur.com/ai)。

            ## 持久化儲存

            `/home/node/` 下的所有資料在重啟和重新部署後都會保留。

            ## 常用指令

            | 指令 | 說明 |
            |------|------|
            | `codex` | 啟動互動式 Codex 會話 |
            | `codex "描述任務"` | 使用特定提示執行 Codex |
            | `codex exec "任務"` | 非互動式執行（用於自動化） |
            | `codex resume` | 恢復先前的會話 |
            | `codex --version` | 檢查已安裝的 Codex 版本 |

            ## 建議資源配置
            - **最低配置**：1 vCPU / 2 GB RAM
            - **建議配置**：2 vCPU / 4 GB RAM

            ## 變更紀錄

            ### 2026-03-31 — 首次發佈

            - ttyd Web 終端機（密碼驗證及裝置代碼登入）
            - Homebrew 及 npm 全域套件持久化於磁碟
            - 資源需求及網域綁定

            ## 連結

            - [GitHub](https://github.com/openai/codex)
            - [Codex CLI 文件](https://developers.openai.com/codex/cli/features)
