# yaml-language-server: $schema=https://schema.zeabur.app/template.json
apiVersion: zeabur.com/v1
kind: Template
metadata:
    name: Claude Code
spec:
    description: |
        Anthropic's AI coding agent CLI running as a persistent developer workstation. Connect via Zeabur Terminal to use Claude Code for code generation, editing, debugging, and git operations.
    coverImage: https://cdn-console.zeabur.com/f/w7XH6/claude-code-coverimage.png
    icon: https://cdn-console.zeabur.com/f/WpysO/claude-color.webp
    variables:
        - key: PUBLIC_DOMAIN
          type: DOMAIN
          name: Domain
          description: The domain for accessing the web terminal (e.g. my-claude-code)
    tags:
        - AI
        - Developer Tools
    readme: |
        # Claude Code

        [Claude Code](https://docs.anthropic.com/en/docs/claude-code) is Anthropic's official AI-powered coding agent CLI. It understands your codebase, generates and edits code, runs commands, and helps with complex development tasks -- all from the command line.

        This template deploys Claude Code as a persistent developer 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. The first boot installs Claude Code and channel plugins automatically — 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** — enter the password from the **Instructions** tab on the Zeabur dashboard.
        4. **Select options** — check the options you want (e.g. skip permissions, Telegram/Discord channel).
        5. **Enter bot token** (if using channels) — paste your bot token into the input field that appears.
        6. **Copy the command** and click **Enter Terminal**.
        7. **Authenticate Claude Code** — paste the command, then follow the login prompts to sign in with your Claude Pro/Max/Team/Enterprise subscription.

        ## Channels (Telegram / Discord)

        Channels let you chat with Claude Code from your phone via Telegram or Discord while it works on your files.

        ### Setup

        1. **Create a bot** — [BotFather](https://t.me/BotFather) for Telegram, or [Discord Developer Portal](https://discord.com/developers/applications) for Discord (enable Message Content Intent, invite bot with Send Messages permission).
        2. **Select the channel** on the landing page and paste your bot token.
        3. **Copy and run the command** in the terminal — the channel starts automatically.
        4. **Pair your account** — DM your bot, it replies with a pairing code. Run `/discord:access pair <code>` or `/telegram:access pair <code>` in Claude Code.
        5. **Lock down access** — run `/discord:access policy allowlist` or `/telegram:access policy allowlist` so only you can send messages.

        After pairing, you can chat with Claude from Telegram/Discord. Your bot token is saved in the browser for next time.

        ### Notes

        - Channels require a Claude Pro/Max/Team/Enterprise subscription (not API key).
        - The bot token is passed as an environment variable in the command (e.g. `DISCORD_BOT_TOKEN=xxx claude ...`) — it is NOT stored on the server.
        - Channels are an [experimental feature](https://code.claude.com/docs/en/channels) in Claude Code.

        ## Using API Key (Optional)

        If you prefer API key authentication instead of a subscription:

        1. Set `ANTHROPIC_API_KEY` to your Anthropic API key from [console.anthropic.com](https://console.anthropic.com/).
        2. Optionally set `ANTHROPIC_BASE_URL` to use [Zeabur AI Hub](https://zeabur.com/ai) (e.g., `https://ai.hub.zeabur.com`).

        ## Common Commands

        | Command | Description |
        |---------|-------------|
        | `claude` | Start interactive Claude Code session |
        | `claude -p "prompt"` | Run a one-shot prompt |
        | `claude login` | Authenticate with Anthropic |
        | `claude --version` | Check installed version |
        | `claude config` | View or modify configuration |

        ## Persistent Storage

        The `/home/node` directory is mounted as a persistent volume. All your projects, Claude Code configuration, and session data stored under `/home/node` will survive service restarts and redeployments.

        **Recommended workflow:**
        - Clone repositories into `/home/node/projects/`
        - Your Claude Code settings are stored in `/home/node/.claude/`

        ## Environment Variables

        | Variable | Required | Description |
        |----------|----------|-------------|
        | `ANTHROPIC_API_KEY` | No | Anthropic API key or Zeabur AI Hub token (only needed if not using subscription login) |
        | `ANTHROPIC_BASE_URL` | No | Custom API endpoint (set this for Zeabur AI Hub) |
        | `WEB_TERMINAL_PASSWORD` | Auto | Password for web terminal login (auto-generated, visible in dashboard) |

        ## Recommended Resources (Official Requirements)
        - **Minimum**: 1 vCPU / 4 GB RAM
        - **Recommended**: 2 vCPU / 4 GB RAM

        Claude Code's official system requirement is 4 GB+ RAM. AI processing happens on the Anthropic API side, so locally it only runs the Node.js CLI and shell tools, keeping CPU requirements low.

        ## Changelog

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

        - ttyd web terminal with password auth
        - Claude Code native installer (auto-installed on first boot)
        - Homebrew and npm global packages persisted in volume
        - Resource requirements and domain binding

        ### 2026-04-02 -- Channel Support & Landing Page Redesign

        - Landing page redesigned with checkbox-based command builder
        - Channel support: Telegram, Discord (via `--channels` flag)
        - Pre-installed channel plugins using `CLAUDE_CODE_PLUGIN_SEED_DIR`
        - Bun runtime pre-installed for channel plugin dependencies
        - Bot token input on landing page (prepended as env var in command)

        ## Links

        - [Claude Code Documentation](https://docs.anthropic.com/en/docs/claude-code)
        - [Anthropic Console](https://console.anthropic.com/)
        - [Zeabur AI Hub](https://zeabur.com/ai)
    resourceRequirement:
        minConfig:
            cpu: 1
            ram: 4
        recommendedConfig:
            cpu: 2
            ram: 4
    services:
        - name: claude-code
          icon: https://cdn-console.zeabur.com/f/WpysO/claude-color.webp
          template: PREBUILT_V2
          spec:
            id: claude-code
            source:
                image: zeabur/claude-code: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}
            env:
                ANTHROPIC_API_KEY:
                    default: ""
                ANTHROPIC_BASE_URL:
                    default: ""
                WEB_TERMINAL_PASSWORD:
                    default: ${PASSWORD}
                    expose: true
            configs:
                - path: /opt/startup.sh
                  template: |
                    #!/bin/sh
                    chown -R node:node /home/node

                    # Homebrew - seed from image layer into volume on first boot (then live in volume so user installs persist)
                    touch /.dockerenv
                    if [ ! -f /home/node/.linuxbrew/bin/brew ] && [ -f /home/linuxbrew/.linuxbrew/bin/brew ] && [ ! -L /home/linuxbrew/.linuxbrew ]; then
                      echo "[startup] Seeding brew from image into volume..."
                      mkdir -p /home/node/.linuxbrew
                      cp -a /home/linuxbrew/.linuxbrew/. /home/node/.linuxbrew/
                      rm -rf /home/linuxbrew/.linuxbrew
                    fi
                    mkdir -p /home/linuxbrew
                    if [ ! -L /home/linuxbrew/.linuxbrew ]; then
                      rm -rf /home/linuxbrew/.linuxbrew 2>/dev/null
                      ln -s /home/node/.linuxbrew /home/linuxbrew/.linuxbrew
                    fi
                    chown -R node:node /home/node/.linuxbrew 2>/dev/null

                    # Claude Code channel plugins - seed from image layer into volume on first boot
                    PLUGINS_DIR=/home/node/.claude/plugins
                    if [ ! -d "$PLUGINS_DIR/marketplaces/claude-plugins-official" ]; then
                      echo "[startup] Seeding plugin marketplace from image..."
                      mkdir -p "$PLUGINS_DIR/marketplaces"
                      cp -a /opt/claude-plugins-official "$PLUGINS_DIR/marketplaces/claude-plugins-official"
                      NOW=$(date -u +%Y-%m-%dT%H:%M:%S.000Z)
                      # Register marketplace
                      if [ ! -f "$PLUGINS_DIR/known_marketplaces.json" ]; then
                        cat > "$PLUGINS_DIR/known_marketplaces.json" << EOJSON
                    {"claude-plugins-official":{"source":{"source":"github","repo":"anthropics/claude-plugins-official"},"installLocation":"$PLUGINS_DIR/marketplaces/claude-plugins-official","lastUpdated":"$NOW"}}
                    EOJSON
                      fi
                      # Initialize installed_plugins.json if not exists
                      if [ ! -f "$PLUGINS_DIR/installed_plugins.json" ]; then
                        echo '{"version":2,"plugins":{}}' > "$PLUGINS_DIR/installed_plugins.json"
                      fi
                      for plugin in telegram discord fakechat; do
                        src="$PLUGINS_DIR/marketplaces/claude-plugins-official/external_plugins/$plugin"
                        if [ -d "$src" ]; then
                          version=$(jq -r '.version // "0.0.0"' "$src/.claude-plugin/plugin.json")
                          cache_dir="$PLUGINS_DIR/cache/claude-plugins-official/$plugin/$version"
                          mkdir -p "$cache_dir"
                          cp -r "$src/." "$cache_dir/"
                          # Register in installed_plugins.json
                          jq --arg k "${plugin}@claude-plugins-official" \
                             --arg p "$cache_dir" \
                             --arg v "$version" \
                             --arg t "$NOW" \
                             '.plugins[$k] = [{"scope":"user","installPath":$p,"version":$v,"installedAt":$t,"lastUpdated":$t}]' \
                             "$PLUGINS_DIR/installed_plugins.json" > "$PLUGINS_DIR/installed_plugins.tmp" \
                          && mv "$PLUGINS_DIR/installed_plugins.tmp" "$PLUGINS_DIR/installed_plugins.json"
                          # Enable in settings.json
                          SETTINGS=/home/node/.claude/settings.json
                          if [ ! -f "$SETTINGS" ]; then echo '{}' > "$SETTINGS"; fi
                          jq --arg k "${plugin}@claude-plugins-official" '.enabledPlugins[$k] = true' \
                             "$SETTINGS" > "$SETTINGS.tmp" && mv "$SETTINGS.tmp" "$SETTINGS"
                        fi
                      done
                      chown -R node:node "$PLUGINS_DIR"
                    fi

                    # Ensure PATH includes claude, npm-global, and brew in login shell
                    for rc in /home/node/.bashrc /home/node/.profile; do
                      grep -qF '.npm-global' "$rc" 2>/dev/null || cat >> "$rc" << 'EOPATH'
                    export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
                    EOPATH
                      grep -qF 'linuxbrew' "$rc" 2>/dev/null || echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> "$rc"
                    done

                    # tmux tips on terminal login
                    grep -qF 'tmux tips' /home/node/.profile 2>/dev/null || cat >> /home/node/.profile << 'EOMOTD'

                    # tmux tips
                    if [ -z "$TMUX" ]; then
                      echo ""
                      echo "  tmux quick reference:"
                      echo "    tmux new -As main     Start or reconnect to session"
                      echo "    tmux attach           Reconnect to last session"
                      echo "    tmux ls               List sessions"
                      echo "    Ctrl+B, D             Detach (session keeps running)"
                      echo ""
                      SESSIONS=$(tmux ls 2>/dev/null)
                      if [ -n "$SESSIONS" ]; then
                        echo "  Active tmux sessions:"
                        echo "$SESSIONS" | sed 's/^/    /'
                        echo ""
                      fi
                    fi
                    EOMOTD

                    # 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', () => {
                        if (!res.headersSent) 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>Claude Code</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; }
                      .btn-secondary { background: #2d2d44; color: #e0e0e0; border: 1px solid #3d3d5c; }
                      .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; }
                      .note {
                        text-align: left; font-size: 0.8rem; color: #bbb;
                        background: #1e1e3a; border: 1px solid #3d3d5c; border-radius: 8px;
                        padding: 0.6rem 0.8rem; margin: 0.75rem 0;
                      }
                      .note strong { color: #d4a574; }
                      .note code { color: #d4a574; font-size: 0.75rem; }
                      .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; }
                      .token-input {
                        width: 100%; padding: 0.5rem 0.8rem; margin: -0.1rem 0 0.4rem 0;
                        background: #12122a; border: 1px solid #3d3d5c; border-radius: 0 0 8px 8px;
                        color: #d4a574; font-family: monospace; font-size: 0.8rem; outline: none;
                      }
                      .token-input:focus { border-color: #d4a574; }
                      .token-hint { font-size: 0.7rem; color: #555; margin: -0.2rem 0 0.4rem 0; text-align: right; }
                      .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/WpysO/claude-color.webp" alt="Claude Code">
                      <h1>Claude Code</h1>
                      <p class="subtitle">AI-powered coding agent workstation</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: Commands + 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-skip-perms" checked onchange="buildCmd()">
                            <span class="option-info">
                              <span class="option-name">Skip permissions</span>
                              <span class="option-desc">Auto-approve all tool calls (--dangerously-skip-permissions)</span>
                            </span>
                          </label>
                          <label class="option">
                            <input type="checkbox" id="opt-ch-telegram" onchange="buildCmd()">
                            <span class="option-info">
                              <span class="option-name">Telegram channel</span>
                              <span class="option-desc">Receive and reply to Telegram messages</span>
                            </span>
                          </label>
                          <input type="text" id="token-telegram" class="token-input hidden" placeholder="Telegram Bot Token (from BotFather)" oninput="buildCmd()" title="Saved in this browser only">
                          <p id="hint-telegram" class="token-hint hidden">Saved in this browser only</p>
                          <label class="option">
                            <input type="checkbox" id="opt-ch-discord" onchange="buildCmd()">
                            <span class="option-info">
                              <span class="option-name">Discord channel</span>
                              <span class="option-desc">Receive and reply to Discord messages</span>
                            </span>
                          </label>
                          <input type="text" id="token-discord" class="token-input hidden" placeholder="Discord Bot Token (from Developer Portal)" oninput="buildCmd()" title="Saved in this browser only">
                          <p id="hint-discord" class="token-hint hidden">Saved in this browser only</p>
                        </div>
                        <p class="step-label">Step 2 — Copy command</p>
                        <div class="cmd-box">
                          <code id="cmd-output">tmux new -As main -- claude --dangerously-skip-permissions</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');
                          document.getElementById('token-telegram').value = localStorage.getItem('tg_token') || '';
                          document.getElementById('token-discord').value = localStorage.getItem('dc_token') || '';
                          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() {
                      const tgOn = document.getElementById('opt-ch-telegram').checked;
                      const dcOn = document.getElementById('opt-ch-discord').checked;
                      document.getElementById('token-telegram').classList.toggle('hidden', !tgOn);
                      document.getElementById('hint-telegram').classList.toggle('hidden', !tgOn);
                      document.getElementById('token-discord').classList.toggle('hidden', !dcOn);
                      document.getElementById('hint-discord').classList.toggle('hidden', !dcOn);
                      let env = '';
                      const tgToken = document.getElementById('token-telegram').value.trim();
                      const dcToken = document.getElementById('token-discord').value.trim();
                      if (tgToken) localStorage.setItem('tg_token', tgToken);
                      if (dcToken) localStorage.setItem('dc_token', dcToken);
                      if (tgOn && tgToken) env += 'TELEGRAM_BOT_TOKEN=' + tgToken + ' ';
                      if (dcOn && dcToken) env += 'DISCORD_BOT_TOKEN=' + dcToken + ' ';
                      let cmd = 'claude';
                      if (document.getElementById('opt-skip-perms').checked) cmd += ' --dangerously-skip-permissions';
                      const channels = [];
                      if (tgOn) channels.push('plugin:telegram@claude-plugins-official');
                      if (dcOn) channels.push('plugin:discord@claude-plugins-official');
                      if (channels.length) cmd += ' --channels ' + channels.join(' ');
                      const useTmux = document.getElementById('opt-tmux').checked;
                      if (useTmux) {
                        document.getElementById('cmd-output').textContent = env + 'tmux new -As main -- ' + cmd;
                      } else {
                        document.getElementById('cmd-output').textContent = env + 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: |
            CLI del agente de programacion IA de Anthropic ejecutandose como estacion de trabajo persistente para desarrolladores. Conectese a traves del Terminal de Zeabur para usar Claude Code para generar, editar, depurar codigo y realizar operaciones Git.
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: Dominio
              description: El dominio para acceder al terminal web (ej. my-claude-code)
        readme: |
            # Claude Code

            [Claude Code](https://docs.anthropic.com/en/docs/claude-code) es el agente de programacion CLI oficial de Anthropic. Comprende su base de codigo, genera y edita codigo, ejecuta comandos y ayuda con tareas de desarrollo complejas, todo desde la linea de comandos.

            Esta plantilla despliega Claude Code como una estacion de trabajo persistente para desarrolladores en Zeabur, con un terminal web (ttyd) accesible desde el navegador.

            ## Primeros pasos

            1. **Despliegue la plantilla** y espere a que el servicio se inicie. La primera vez instala Claude Code y los plugins de canales automaticamente — espere hasta que el health check se ponga verde antes de continuar.
            2. **Abra la URL del servicio** en su navegador (el dominio vinculado al servicio).
            3. **Inicie sesion** — ingrese la contrasena de la pestana **Instructions** en el panel de Zeabur.
            4. **Seleccione opciones** — marque las funciones que necesite (como omitir permisos, canal Telegram/Discord).
            5. **Ingrese el Bot Token** (si usa canales) — peguelo en el campo de entrada que aparece.
            6. **Copie el comando** y haga clic en **Enter Terminal**.
            7. **Autentique Claude Code** — pegue el comando y siga las indicaciones para iniciar sesion con su suscripcion Claude Pro/Max/Team/Enterprise.

            ## Canales (Telegram / Discord)

            Los canales le permiten chatear con Claude Code desde su telefono a traves de Telegram o Discord, mientras opera sus archivos en el servidor.

            ### Pasos de configuracion

            1. **Cree un bot** — Para Telegram use [BotFather](https://t.me/BotFather), para Discord vaya al [Developer Portal](https://discord.com/developers/applications) (habilite Message Content Intent e invite al bot al servidor con permiso de enviar mensajes).
            2. **Seleccione el canal en la pagina de inicio** y pegue el Bot Token.
            3. **Copie y ejecute el comando en la terminal** — el canal se inicia automaticamente.
            4. **Vincule su cuenta** — envie un mensaje directo a su bot, recibira un codigo de vinculacion. Ejecute `/discord:access pair <code>` o `/telegram:access pair <code>` en Claude Code.
            5. **Bloquee el acceso** — ejecute `/discord:access policy allowlist` o `/telegram:access policy allowlist` para asegurar que solo usted pueda enviar mensajes.

            Una vez vinculado, puede chatear con Claude a traves de Telegram/Discord. El Bot Token se guarda en el navegador y no necesita reingresarlo.

            ### Notas

            - Los canales requieren suscripcion Claude Pro/Max/Team/Enterprise (no funcionan con clave API).
            - El Bot Token se pasa como variable de entorno en el comando (ej. `DISCORD_BOT_TOKEN=xxx claude ...`), **no se almacena en el servidor**.
            - Los canales son una [funcion experimental](https://code.claude.com/docs/en/channels) de Claude Code.

            ## Usar clave API (Opcional)

            Si prefiere autenticacion por clave API en lugar de suscripcion:

            1. Configure `ANTHROPIC_API_KEY` con su clave API de Anthropic de [console.anthropic.com](https://console.anthropic.com/).
            2. Opcionalmente configure `ANTHROPIC_BASE_URL` para usar [Zeabur AI Hub](https://zeabur.com/ai) (ej. `https://ai.hub.zeabur.com`).

            ## Comandos comunes

            | Comando | Descripcion |
            |---------|-------------|
            | `claude` | Iniciar sesion interactiva de Claude Code |
            | `claude -p "prompt"` | Ejecutar un prompt unico |
            | `claude login` | Autenticarse con Anthropic |
            | `claude --version` | Verificar version instalada |
            | `claude config` | Ver o modificar configuracion |

            ## Almacenamiento persistente

            El directorio `/home/node` esta montado como un volumen persistente. Todos los proyectos, configuracion de Claude Code y datos de sesion almacenados en `/home/node` se mantendran tras reinicios y redespliegues.

            **Flujo de trabajo recomendado:**
            - Clone repositorios en `/home/node/projects/`
            - La configuracion de Claude Code se almacena en `/home/node/.claude/`

            ## Variables de entorno

            | Variable | Requerida | Descripcion |
            |----------|-----------|-------------|
            | `ANTHROPIC_API_KEY` | No | Clave API de Anthropic o token de Zeabur AI Hub (solo necesaria si no usa login por suscripcion) |
            | `ANTHROPIC_BASE_URL` | No | Endpoint API personalizado (configurar para Zeabur AI Hub) |
            | `WEB_TERMINAL_PASSWORD` | Auto | Contrasena del terminal web (generada automaticamente, visible en el panel) |

            ## Recursos Recomendados (Requisitos Oficiales)
            - **Minimo**: 1 vCPU / 4 GB RAM
            - **Recomendado**: 2 vCPU / 4 GB RAM

            El requisito oficial de Claude Code es 4 GB+ de RAM. El procesamiento de IA se realiza en la API de Anthropic, por lo que localmente solo se ejecuta el CLI de Node.js y herramientas de shell, con requisitos minimos de CPU.

            ## Registro de cambios

            ### 2026-04-02 -- Soporte de canales y rediseno de pagina de inicio

            - Pagina de inicio rediseñada con constructor de comandos basado en checkboxes
            - Soporte de canales: Telegram, Discord (via `--channels`)
            - Plugins de canales preinstalados con `CLAUDE_CODE_PLUGIN_SEED_DIR`
            - Runtime Bun preinstalado (dependencia de plugins de canales)
            - Entrada de Bot Token en la pagina de inicio (como variable de entorno en el comando)

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

            - Terminal web ttyd (autenticacion por contrasena)
            - Instalador nativo de Claude Code (instalacion automatica en primer inicio)
            - Homebrew y paquetes npm globales persistidos en volumen
            - Requisitos de recursos y vinculacion de dominio

            ## Enlaces

            - [Documentacion de Claude Code](https://docs.anthropic.com/en/docs/claude-code)
            - [Anthropic Console](https://console.anthropic.com/)
            - [Zeabur AI Hub](https://zeabur.com/ai)
    id-ID:
        description: |
            CLI agen pemrograman AI dari Anthropic yang berjalan sebagai workstation pengembang persisten. Hubungkan melalui Terminal Zeabur untuk menggunakan Claude Code dalam pembuatan, pengeditan, debugging kode, dan operasi Git.
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: Domain
              description: Domain untuk mengakses terminal web (contoh my-claude-code)
        readme: |
            # Claude Code

            [Claude Code](https://docs.anthropic.com/en/docs/claude-code) adalah CLI agen pemrograman AI resmi dari Anthropic. Ia memahami basis kode Anda, menghasilkan dan mengedit kode, menjalankan perintah, dan membantu tugas pengembangan kompleks -- semuanya dari baris perintah.

            Template ini men-deploy Claude Code sebagai workstation pengembang persisten di Zeabur, dengan terminal web (ttyd) yang dapat diakses dari browser.

            ## Memulai

            1. **Deploy template** dan tunggu layanan dimulai. Boot pertama menginstal Claude Code dan plugin channel secara otomatis — tunggu hingga health check berwarna hijau sebelum melanjutkan.
            2. **Buka URL layanan** di browser Anda (domain yang terikat ke layanan).
            3. **Login** — masukkan password dari tab **Instructions** di dashboard Zeabur.
            4. **Pilih opsi** — centang fitur yang dibutuhkan (seperti lewati izin, channel Telegram/Discord).
            5. **Masukkan Bot Token** (jika menggunakan channel) — tempel di kolom input yang muncul.
            6. **Salin perintah** dan klik **Enter Terminal**.
            7. **Autentikasi Claude Code** — tempel perintah, lalu ikuti petunjuk untuk login dengan langganan Claude Pro/Max/Team/Enterprise Anda.

            ## Channel (Telegram / Discord)

            Fitur channel memungkinkan Anda mengobrol dengan Claude Code dari ponsel melalui Telegram atau Discord, sementara ia mengoperasikan file Anda di server.

            ### Langkah Pengaturan

            1. **Buat bot** — Untuk Telegram gunakan [BotFather](https://t.me/BotFather), untuk Discord buka [Developer Portal](https://discord.com/developers/applications) (aktifkan Message Content Intent, undang bot ke server dengan izin kirim pesan).
            2. **Pilih channel di halaman landing** dan tempel Bot Token.
            3. **Salin dan jalankan perintah di terminal** — channel akan dimulai secara otomatis.
            4. **Pasangkan akun** — kirim pesan langsung ke bot Anda, Anda akan menerima kode pairing. Jalankan `/discord:access pair <code>` atau `/telegram:access pair <code>` di Claude Code.
            5. **Kunci akses** — jalankan `/discord:access policy allowlist` atau `/telegram:access policy allowlist` agar hanya Anda yang bisa mengirim pesan.

            Setelah dipasangkan, Anda dapat mengobrol dengan Claude melalui Telegram/Discord. Bot Token disimpan di browser dan tidak perlu dimasukkan ulang.

            ### Catatan

            - Channel memerlukan langganan Claude Pro/Max/Team/Enterprise (tidak bisa menggunakan API key).
            - Bot Token diteruskan sebagai variabel lingkungan dalam perintah (misalnya `DISCORD_BOT_TOKEN=xxx claude ...`), **tidak disimpan di server**.
            - Channel adalah [fitur eksperimental](https://code.claude.com/docs/en/channels) Claude Code.

            ## Menggunakan API Key (Opsional)

            Jika Anda lebih memilih autentikasi API key daripada langganan:

            1. Atur `ANTHROPIC_API_KEY` ke kunci API Anthropic Anda dari [console.anthropic.com](https://console.anthropic.com/).
            2. Opsional atur `ANTHROPIC_BASE_URL` untuk menggunakan [Zeabur AI Hub](https://zeabur.com/ai) (misalnya `https://ai.hub.zeabur.com`).

            ## Perintah Umum

            | Perintah | Deskripsi |
            |----------|-----------|
            | `claude` | Mulai sesi interaktif Claude Code |
            | `claude -p "prompt"` | Jalankan prompt sekali |
            | `claude login` | Autentikasi dengan Anthropic |
            | `claude --version` | Periksa versi terinstal |
            | `claude config` | Lihat atau ubah konfigurasi |

            ## Penyimpanan Persisten

            Direktori `/home/node` di-mount sebagai volume persisten. Semua proyek, konfigurasi Claude Code, dan data sesi yang disimpan di `/home/node` akan bertahan setelah restart dan redeploy layanan.

            **Alur kerja yang disarankan:**
            - Clone repositori ke `/home/node/projects/`
            - Konfigurasi Claude Code disimpan di `/home/node/.claude/`

            ## Variabel Lingkungan

            | Variabel | Wajib | Deskripsi |
            |----------|-------|-----------|
            | `ANTHROPIC_API_KEY` | Tidak | Kunci API Anthropic atau token Zeabur AI Hub (hanya diperlukan jika tidak menggunakan login langganan) |
            | `ANTHROPIC_BASE_URL` | Tidak | Endpoint API kustom (atur untuk Zeabur AI Hub) |
            | `WEB_TERMINAL_PASSWORD` | Otomatis | Password login terminal web (dibuat otomatis, dapat dilihat di dashboard) |

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

            Persyaratan resmi Claude Code adalah RAM 4 GB+. Pemrosesan AI dilakukan di API Anthropic, sehingga secara lokal hanya menjalankan CLI Node.js dan alat shell, dengan kebutuhan CPU yang minimal.

            ## Catatan Perubahan

            ### 2026-04-02 -- Dukungan Channel & Desain Ulang Halaman Landing

            - Halaman landing didesain ulang dengan pembangun perintah berbasis checkbox
            - Dukungan channel: Telegram, Discord (via `--channels`)
            - Plugin channel terpasang otomatis dengan `CLAUDE_CODE_PLUGIN_SEED_DIR`
            - Runtime Bun terpasang otomatis (dependensi plugin channel)
            - Input Bot Token di halaman landing (sebagai variabel lingkungan dalam perintah)

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

            - Terminal web ttyd (autentikasi password)
            - Installer native Claude Code (instalasi otomatis saat boot pertama)
            - Homebrew dan paket npm global disimpan persisten di volume
            - Persyaratan sumber daya dan binding domain

            ## Tautan

            - [Dokumentasi Claude Code](https://docs.anthropic.com/en/docs/claude-code)
            - [Anthropic Console](https://console.anthropic.com/)
            - [Zeabur AI Hub](https://zeabur.com/ai)
    ja-JP:
        description: |
            Anthropic の AI コーディングエージェント CLI を永続的な開発者ワークステーションとして実行。Zeabur ターミナルから接続し、Claude Code でコード生成、編集、デバッグ、Git 操作を行えます。
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: ドメイン
              description: Web ターミナルにアクセスするためのドメイン（例：my-claude-code）
        readme: |
            # Claude Code

            [Claude Code](https://docs.anthropic.com/en/docs/claude-code) は Anthropic 公式の AI コーディングエージェント CLI です。コードベースを理解し、コードの生成・編集、コマンドの実行、複雑な開発タスクの支援をすべてコマンドラインから行います。

            このテンプレートは Claude Code を Zeabur 上の永続的な開発者ワークステーションとしてデプロイし、ブラウザからアクセス可能な Web ターミナル（ttyd）を提供します。

            ## はじめに

            1. **テンプレートをデプロイ**し、サービスの起動を待ちます。初回起動時に Claude Code とチャンネルプラグインが自動インストールされます。ヘルスチェックが緑になるまでお待ちください。
            2. **ブラウザでサービス URL を開きます**（サービスにバインドされたドメイン）。
            3. **ログイン** — Zeabur ダッシュボードの **Instructions** タブに表示されるパスワードを入力します。
            4. **オプションを選択** — 必要な機能にチェックを入れます（権限スキップ、Telegram/Discord チャンネルなど）。
            5. **Bot トークンを入力**（チャンネル使用時） — 表示された入力欄に Bot トークンを貼り付けます。
            6. **コマンドをコピー**し、**Enter Terminal** をクリックします。
            7. **Claude Code を認証** — コマンドを貼り付け、プロンプトに従って Claude Pro/Max/Team/Enterprise サブスクリプションでログインします。

            ## チャンネル（Telegram / Discord）

            チャンネル機能を使うと、Telegram や Discord からスマートフォンで Claude Code と会話できます。サーバー上でファイルを操作しながらやり取りが可能です。

            ### セットアップ手順

            1. **ボットを作成** — Telegram は [BotFather](https://t.me/BotFather)、Discord は [Developer Portal](https://discord.com/developers/applications) で作成します（Message Content Intent を有効にし、ボットをサーバーに招待してメッセージ送信権限を付与してください）。
            2. **ランディングページでチャンネルを選択**し、Bot トークンを貼り付けます。
            3. **コマンドをコピーしてターミナルで実行** — チャンネルが自動的に起動します。
            4. **アカウントをペアリング** — ボットにダイレクトメッセージを送ると、ペアリングコードが返されます。Claude Code で `/discord:access pair <code>` または `/telegram:access pair <code>` を実行します。
            5. **アクセスをロック** — `/discord:access policy allowlist` または `/telegram:access policy allowlist` を実行して、自分だけがメッセージを送れるようにします。

            ペアリング完了後、Telegram/Discord 経由で Claude と会話できます。Bot トークンはブラウザに保存され、次回再入力は不要です。

            ### 注意事項

            - チャンネルには Claude Pro/Max/Team/Enterprise サブスクリプションが必要です（API キーでは使用できません）。
            - Bot トークンは環境変数としてコマンドに付加されます（例：`DISCORD_BOT_TOKEN=xxx claude ...`）。**サーバーには保存されません**。
            - チャンネルは Claude Code の[実験的機能](https://code.claude.com/docs/en/channels)です。

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

            サブスクリプションの代わりに API キー認証を使用する場合：

            1. `ANTHROPIC_API_KEY` を Anthropic API キーに設定します。[console.anthropic.com](https://console.anthropic.com/) から取得できます。
            2. オプションで `ANTHROPIC_BASE_URL` を設定し、[Zeabur AI Hub](https://zeabur.com/ai) を使用できます（例：`https://ai.hub.zeabur.com`）。

            ## よく使うコマンド

            | コマンド | 説明 |
            |----------|------|
            | `claude` | インタラクティブな Claude Code セッションを起動 |
            | `claude -p "プロンプト"` | ワンショットプロンプトを実行 |
            | `claude login` | Anthropic アカウントで認証 |
            | `claude --version` | インストール済みバージョンを確認 |
            | `claude config` | 設定を表示・変更 |

            ## 永続ストレージ

            `/home/node` ディレクトリは永続ボリュームとしてマウントされています。`/home/node` 配下に保存されたプロジェクト、Claude Code の設定、セッションデータはすべてサービスの再起動や再デプロイ後も保持されます。

            **推奨ワークフロー：**
            - リポジトリを `/home/node/projects/` にクローン
            - Claude Code の設定は `/home/node/.claude/` に保存されます

            ## 環境変数

            | 変数 | 必須 | 説明 |
            |------|------|------|
            | `ANTHROPIC_API_KEY` | いいえ | Anthropic API キーまたは Zeabur AI Hub トークン（サブスクリプションログインを使用しない場合のみ必要） |
            | `ANTHROPIC_BASE_URL` | いいえ | カスタム API エンドポイント（Zeabur AI Hub 使用時に設定） |
            | `WEB_TERMINAL_PASSWORD` | 自動 | Web ターミナルのログインパスワード（自動生成、ダッシュボードで確認可能） |

            ## 推奨リソース（公式要件）
            - **最小構成**：1 vCPU / 4 GB RAM
            - **推奨構成**：2 vCPU / 4 GB RAM

            Claude Code の公式システム要件は 4 GB 以上の RAM です。AI 処理は Anthropic API 側で行われるため、ローカルでは Node.js CLI とシェルツールのみが動作し、CPU 要件は低めです。

            ## 変更履歴

            ### 2026-04-02 — チャンネルサポートとランディングページの再設計

            - ランディングページをチェックボックス式コマンドビルダーに変更
            - チャンネルサポート：Telegram、Discord、iMessage（`--channels` フラグ）
            - `CLAUDE_CODE_PLUGIN_SEED_DIR` でチャンネルプラグインをプリインストール
            - Bun ランタイムをプリインストール（チャンネルプラグインの依存関係）
            - ランディングページで Bot トークンを直接入力可能（環境変数としてコマンドに付加）

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

            - ttyd Web ターミナル（パスワード認証）
            - Claude Code ネイティブインストーラー（初回起動時に自動インストール）
            - Homebrew および npm グローバルパッケージをボリュームに永続化
            - リソース要件およびドメインバインディング

            ## リンク

            - [Claude Code 公式ドキュメント](https://docs.anthropic.com/en/docs/claude-code)
            - [Anthropic Console](https://console.anthropic.com/)
            - [Zeabur AI Hub](https://zeabur.com/ai)
    zh-CN:
        description: |
            Anthropic 的 AI 编程代理 CLI，以持久化开发者工作站形式运行。通过 Zeabur 终端连接使用 Claude Code 进行代码生成、编辑、调试和 Git 操作。
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: 域名
              description: 用来访问 Web 终端的域名（例如 my-claude-code）
        readme: |
            # Claude Code

            [Claude Code](https://docs.anthropic.com/en/docs/claude-code) 是 Anthropic 官方的 AI 编程代理 CLI。它能理解您的代码库、生成和编辑代码、执行命令，并协助完成复杂的开发任务——全部通过命令行完成。

            此模板将 Claude Code 部署为 Zeabur 上的持久化开发者工作站，并提供浏览器可访问的 Web 终端（ttyd）。

            ## 开始使用

            1. **部署模板**并等待服务启动。首次启动会自动安装 Claude Code 及频道插件，请等待健康检查变为绿色后再继续。
            2. **在浏览器中打开服务网址**（绑定到服务的域名）。
            3. **登录** — 输入 Zeabur 仪表板 **Instructions** 页签中的密码。
            4. **选择选项** — 勾选需要的功能（如跳过权限、Telegram/Discord 频道）。
            5. **输入 Bot Token**（使用频道时） — 在出现的输入框中粘贴 Bot Token。
            6. **复制命令**并点击 **Enter Terminal**。
            7. **认证 Claude Code** — 粘贴命令，按照提示登录您的 Claude Pro/Max/Team/Enterprise 订阅账号。

            ## 频道（Telegram / Discord）

            频道功能让您通过 Telegram 或 Discord 在手机上与 Claude Code 对话，同时它在服务器上操作您的文件。

            ### 设置步骤

            1. **创建机器人** — Telegram 使用 [BotFather](https://t.me/BotFather)，Discord 到 [Developer Portal](https://discord.com/developers/applications) 创建（需启用 Message Content Intent，邀请机器人加入服务器并授予发送消息权限）。
            2. **在入口页面勾选频道**并粘贴 Bot Token。
            3. **复制命令并在终端中执行** — 频道会自动启动。
            4. **配对账号** — 私信您的机器人，它会回复配对码。在 Claude Code 中执行 `/discord:access pair <code>` 或 `/telegram:access pair <code>`。
            5. **锁定访问** — 执行 `/discord:access policy allowlist` 或 `/telegram:access policy allowlist`，确保只有您能发送消息。

            配对完成后，即可通过 Telegram/Discord 与 Claude 对话。Bot Token 会保存在浏览器中，下次无需重新输入。

            ### 注意事项

            - 频道需要 Claude Pro/Max/Team/Enterprise 订阅（不支持 API 密钥）。
            - Bot Token 以环境变量方式带入命令（如 `DISCORD_BOT_TOKEN=xxx claude ...`），**不会存储在服务器上**。
            - 频道是 Claude Code 的[实验性功能](https://code.claude.com/docs/en/channels)。

            ## 使用 API 密钥（可选）

            如果您偏好使用 API 密钥而非订阅认证：

            1. 将 `ANTHROPIC_API_KEY` 设置为您的 Anthropic API 密钥，可从 [console.anthropic.com](https://console.anthropic.com/) 获取。
            2. 可选择设置 `ANTHROPIC_BASE_URL` 以使用 [Zeabur AI Hub](https://zeabur.com/ai)（例如 `https://ai.hub.zeabur.com`）。

            ## 常用命令

            | 命令 | 说明 |
            |------|------|
            | `claude` | 启动交互式 Claude Code 会话 |
            | `claude -p "提示"` | 执行单次提示 |
            | `claude login` | 验证 Anthropic 账号 |
            | `claude --version` | 检查已安装版本 |
            | `claude config` | 查看或修改配置 |

            ## 持久化存储

            `/home/node` 目录挂载为持久化卷。所有存储在 `/home/node` 下的项目、Claude Code 配置和会话数据，在服务重启和重新部署后都会保留。

            **建议工作流程：**
            - 将代码库克隆到 `/home/node/projects/`
            - Claude Code 配置存储在 `/home/node/.claude/`

            ## 环境变量

            | 变量 | 必填 | 说明 |
            |------|------|------|
            | `ANTHROPIC_API_KEY` | 否 | Anthropic API 密钥或 Zeabur AI Hub Token（仅在不使用订阅登录时需要） |
            | `ANTHROPIC_BASE_URL` | 否 | 自定义 API 端点（使用 Zeabur AI Hub 时设置） |
            | `WEB_TERMINAL_PASSWORD` | 自动 | Web 终端登录密码（自动生成，可在仪表板查看） |

            ## 建议资源配置（官方要求）
            - **最低配置**：1 vCPU / 4 GB RAM
            - **建议配置**：2 vCPU / 4 GB RAM

            Claude Code 官方系统要求为 4 GB 以上内存。AI 运算在 Anthropic API 端进行，本机仅运行 Node.js CLI 和 shell 工具，CPU 需求较低。

            ## 变更记录

            ### 2026-04-02 — 频道支持与入口页面重新设计

            - 入口页面改为勾选式命令组合器
            - 频道支持：Telegram、Discord、iMessage（通过 `--channels` 参数）
            - 使用 `CLAUDE_CODE_PLUGIN_SEED_DIR` 预装频道插件
            - 预装 Bun 运行时（频道插件依赖）
            - 入口页面可直接输入 Bot Token（以环境变量带入命令）

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

            - ttyd Web 终端（密码验证）
            - Claude Code 原生安装器（首次启动自动安装）
            - Homebrew 及 npm 全局包持久化于卷
            - 资源需求及域名绑定

            ## 链接

            - [Claude Code 官方文档](https://docs.anthropic.com/en/docs/claude-code)
            - [Anthropic Console](https://console.anthropic.com/)
            - [Zeabur AI Hub](https://zeabur.com/ai)
    zh-TW:
        description: |
            Anthropic 的 AI 程式設計代理 CLI，以持久化開發者工作站形式運行。透過 Zeabur 指令連線使用 Claude Code 進行程式碼生成、編輯、除錯和 Git 操作。
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: 網域
              description: 用來存取 Web 終端機的網域（例如 my-claude-code）
        readme: |
            # Claude Code

            [Claude Code](https://docs.anthropic.com/en/docs/claude-code) 是 Anthropic 官方的 AI 程式設計代理 CLI。它能理解您的程式碼庫、生成和編輯程式碼、執行命令，並協助完成複雜的開發任務——全部透過命令列完成。

            此模板將 Claude Code 部署為 Zeabur 上的持久化開發者工作站，並提供瀏覽器可存取的 Web 終端機（ttyd）。

            ## 開始使用

            1. **部署模板**並等待服務啟動。首次啟動會自動安裝 Claude Code 及頻道外掛，請等待健康檢查變為綠色後再繼續。
            2. **在瀏覽器中開啟服務網址**（綁定到服務的網域）。
            3. **登入** — 輸入 Zeabur 儀表板 **Instructions** 頁籤中的密碼。
            4. **選擇選項** — 勾選需要的功能（如跳過權限、Telegram/Discord 頻道）。
            5. **輸入 Bot Token**（使用頻道時） — 在出現的輸入框中貼上 Bot Token。
            6. **複製指令**並點擊 **Enter Terminal**。
            7. **認證 Claude Code** — 貼上指令，依照提示登入您的 Claude Pro/Max/Team/Enterprise 訂閱帳號。

            ## 頻道（Telegram / Discord）

            頻道功能讓您透過 Telegram 或 Discord 在手機上與 Claude Code 對話，同時它在伺服器上操作您的檔案。

            ### 設定步驟

            1. **建立機器人** — Telegram 使用 [BotFather](https://t.me/BotFather)，Discord 則到 [Developer Portal](https://discord.com/developers/applications) 建立（需啟用 Message Content Intent，並邀請機器人加入伺服器且授予傳送訊息權限）。
            2. **在入口頁面勾選頻道**並貼上 Bot Token。
            3. **複製指令並在終端機中執行** — 頻道會自動啟動。
            4. **配對帳號** — 私訊您的機器人，它會回覆配對碼。在 Claude Code 中執行 `/discord:access pair <code>` 或 `/telegram:access pair <code>`。
            5. **鎖定存取** — 執行 `/discord:access policy allowlist` 或 `/telegram:access policy allowlist`，確保只有您能傳送訊息。

            配對完成後，即可透過 Telegram/Discord 與 Claude 對話。Bot Token 會儲存在瀏覽器中，下次無需重新輸入。

            ### 注意事項

            - 頻道需要 Claude Pro/Max/Team/Enterprise 訂閱（不支援 API 金鑰）。
            - Bot Token 以環境變數方式帶入指令（如 `DISCORD_BOT_TOKEN=xxx claude ...`），**不會儲存在伺服器上**。
            - 頻道是 Claude Code 的[實驗性功能](https://code.claude.com/docs/en/channels)。

            ## 使用 API 金鑰（選用）

            如果您偏好使用 API 金鑰而非訂閱認證：

            1. 將 `ANTHROPIC_API_KEY` 設定為您的 Anthropic API 金鑰，可從 [console.anthropic.com](https://console.anthropic.com/) 取得。
            2. 可選擇設定 `ANTHROPIC_BASE_URL` 以使用 [Zeabur AI Hub](https://zeabur.com/ai)（例如 `https://ai.hub.zeabur.com`）。

            ## 常用命令

            | 命令 | 說明 |
            |------|------|
            | `claude` | 啟動互動式 Claude Code 工作階段 |
            | `claude -p "提示"` | 執行單次提示 |
            | `claude login` | 驗證 Anthropic 帳號 |
            | `claude --version` | 檢查已安裝版本 |
            | `claude config` | 檢視或修改設定 |

            ## 持久化儲存

            `/home/node` 目錄掛載為持久化磁碟。所有儲存在 `/home/node` 下的專案、Claude Code 設定和工作階段資料，在服務重啟和重新部署後都會保留。

            **建議工作流程：**
            - 將程式碼庫複製到 `/home/node/projects/`
            - Claude Code 設定儲存在 `/home/node/.claude/`

            ## 環境變數

            | 變數 | 必填 | 說明 |
            |------|------|------|
            | `ANTHROPIC_API_KEY` | 否 | Anthropic API 金鑰或 Zeabur AI Hub Token（僅在不使用訂閱登入時需要） |
            | `ANTHROPIC_BASE_URL` | 否 | 自訂 API 端點（使用 Zeabur AI Hub 時設定） |
            | `WEB_TERMINAL_PASSWORD` | 自動 | Web 終端機登入密碼（自動產生，可在儀表板查看） |

            ## 建議資源配置（官方要求）
            - **最低配置**：1 vCPU / 4 GB RAM
            - **建議配置**：2 vCPU / 4 GB RAM

            Claude Code 官方系統要求為 4 GB 以上記憶體。AI 運算在 Anthropic API 端進行，本機僅運行 Node.js CLI 和 shell 工具，CPU 需求較低。

            ## 變更紀錄

            ### 2026-04-02 — 頻道支援與入口頁面重新設計

            - 入口頁面改為勾選式指令組合器
            - 頻道支援：Telegram、Discord、iMessage（透過 `--channels` 參數）
            - 使用 `CLAUDE_CODE_PLUGIN_SEED_DIR` 預裝頻道外掛
            - 預裝 Bun 執行環境（頻道外掛依賴）
            - 入口頁面可直接輸入 Bot Token（以環境變數帶入指令）

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

            - ttyd Web 終端機（密碼驗證）
            - Claude Code 原生安裝器（首次啟動自動安裝）
            - Homebrew 及 npm 全域套件持久化於磁碟
            - 資源需求及網域綁定

            ## 連結

            - [Claude Code 官方文件](https://docs.anthropic.com/en/docs/claude-code)
            - [Anthropic Console](https://console.anthropic.com/)
            - [Zeabur AI Hub](https://zeabur.com/ai)
