# yaml-language-server: $schema=https://schema.zeabur.app/template.json
apiVersion: zeabur.com/v1
kind: Template
metadata:
    name: OpenAB Claude
spec:
    description: |
        Open Agent Broker — a lightweight Rust harness that bridges Discord and Slack to any ACP-compatible coding CLI (Claude Code, Codex, Gemini, Kiro) over stdio JSON-RPC.
        Source: https://github.com/openabdev/openab
    coverImage: https://cdn-console.zeabur.com/f/Xp0H6/openab-cover.webp
    icon: https://cdn-console.zeabur.com/f/0Ewi6/openab-icon.webp
    variables:
        - key: DISCORD_BOT_TOKEN
          type: STRING
          name: Discord Bot Token (optional)
          description: Optional. Token from https://discord.com/developers/applications. Leave empty if using Slack only.
        - key: OPENAB_AUTH_TOKEN
          type: STRING
          name: Auth Token
          description: 'Anthropic API key, Zeabur AI Hub token, or Claude OAuth token (from: claude setup-token)'
        - key: OPENAB_ALLOWED_CHANNELS
          type: STRING
          name: Discord Channel IDs (optional)
          description: 'Optional. Comma-separated Discord channel IDs where the bot listens. Leave empty to allow all channels. Example: 123456789012345678,234567890123456789'
        - key: OPENAB_ALLOWED_USERS
          type: STRING
          name: Discord User IDs (optional)
          description: Optional. Comma-separated Discord user IDs allowed to interact with the bot. Leave empty to allow everyone in the allowed channels.
        - key: SLACK_BOT_TOKEN
          type: STRING
          name: Slack Bot Token (optional)
          description: Optional. Bot User OAuth Token (xoxb-...) from Slack App settings. Leave empty to disable Slack adapter.
        - key: SLACK_APP_TOKEN
          type: STRING
          name: Slack App Token (optional)
          description: Optional. App-Level Token (xapp-...) for Socket Mode. Required when Slack Bot Token is set.
        - key: OPENAB_ALLOW_BOT_MESSAGES
          type: STRING
          name: Allow Bot Messages (optional)
          description: Optional. Set to 'on' to allow messages from bots/webhooks to trigger the agent. Leave empty to disable (default).
        - key: OPENAB_MAX_SESSIONS
          type: STRING
          name: Max Concurrent Sessions (optional)
          description: 'Optional. Maximum number of concurrent agent sessions (default: 10). Oldest idle session is evicted when the limit is reached.'
    tags:
        - AI
        - Developer Tools
        - Discord
        - Slack
    readme: |
        # OpenAB

        [OpenAB](https://github.com/openabdev/openab) is a lightweight, open-source Rust harness that bridges Discord to any [Agent Client Protocol](https://github.com/anthropics/agent-protocol)-compatible coding CLI over stdio JSON-RPC.

        This template deploys the Claude variant (`openab-claude`) which uses `claude-agent-acp` as the agent backend.

        ## Important Notes

        - **Image tag:** This template uses a pinned version (`0.8.0`). To upgrade, change the image tag in Zeabur Dashboard → Service → Settings. Available tags: [GitHub Packages](https://github.com/openabdev/openab/pkgs/container/openab-claude).
        - **Persistent storage:** `/home/node` is mounted as a persistent volume. Plugins, settings, config, and credentials survive restarts.
        - **Config ownership:** The container runs directly as `node` user (uid 1000). If you encounter permission issues on the persistent volume, restart the service.

        ## Setup

        ### 1. Get a Discord Bot Token

        1. Go to https://discord.com/developers/applications and click **New Application**
        2. Navigate to **Bot** tab → click **Reset Token** → copy the token
        3. On the same page, scroll down and enable **Message Content Intent** under Privileged Gateway Intents
        4. Go to **OAuth2 → URL Generator** → check scope `bot` → check permissions: Send Messages, Send Messages in Threads, Create Public Threads, Read Message History, Add Reactions, Manage Messages
        5. Copy the generated URL and open it in your browser to invite the bot to your server

        ### 2. Get an Auth Token (choose one)

        | Method | How to get it |
        |--------|--------------|
        | **Anthropic API Key** | Go to [console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys) → Create Key → copy the `sk-ant-api...` key |
        | **Zeabur AI Hub Token** | Go to [Zeabur AI Hub](https://zeabur.com/ai-hub) → API Keys → Create → copy the token |
        | **Claude OAuth Token** | Run `claude setup-token` in your terminal (requires Claude Pro/Max/Team/Enterprise subscription) → visit the URL it shows → authorize → paste the code back → copy the `sk-ant-oat...` token it outputs |

        ### 3. Get Discord Channel IDs

        1. Open Discord → go to **User Settings** (gear icon) → **Advanced** → enable **Developer Mode**
        2. Right-click the channel where you want the bot to respond → **Copy Channel ID**
        3. For multiple channels, separate IDs with commas: `123456789012345678,234567890123456789`

        ### 4. Deploy

        Fill in the variables and click deploy. The service connects to Discord and/or Slack automatically.

        ## Slack Setup (Optional)

        OpenAB supports Slack via Socket Mode — no public URL needed.

        1. Go to https://api.slack.com/apps → **Create New App** → **From scratch**
        2. **Socket Mode** → Enable → generate an App-Level Token with scope `connections:write` → copy the `xapp-...` token (`SLACK_APP_TOKEN`)
        3. **Event Subscriptions** → Enable Events → add bot events: `app_mention`, `message.channels`, `message.groups`
        4. **OAuth & Permissions** → Bot Token Scopes → add: `app_mentions:read`, `chat:write`, `channels:history`, `groups:history`, `channels:read`, `groups:read`, `reactions:write`, `files:read`, `users:read`
        5. **Install App** → Install to Workspace → copy the `xoxb-...` token (`SLACK_BOT_TOKEN`)
        6. In each Slack channel you want the bot in, run `/invite @YourAppName`

        Set `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN` in the template variables before deploying. Both Discord and Slack can run simultaneously.

        ## Usage

        - **@mention the bot** in an allowed channel or Slack channel to start a conversation
        - OpenAB creates a **thread** for multi-turn conversations — no @mention needed for follow-ups
        - Each thread maps to a persistent Claude session (24h TTL)

        ## GitHub Integration (Optional)

        The agent can use `gh` (GitHub CLI) to review PRs, create issues, comment, merge, etc. Since the container is headless, authentication uses device flow:

        1. Ask the bot in Discord: **"log in to GitHub"**
        2. The bot will reply with a one-time code and a URL (e.g. `https://github.com/login/device`)
        3. Open the URL on your phone or computer, enter the code, and authorize
        4. The bot confirms login — `gh` is now authenticated for this session

        See [gh-auth-device-flow.md](https://github.com/openabdev/openab/blob/main/docs/gh-auth-device-flow.md) for technical details.

        ## Customization

        | File | Description |
        |------|-------------|
        | `/home/node/CLAUDE.md` | Agent instructions — Claude reads this on every session |
        | `/home/node/.config/openab/config.toml` | OpenAB config (sessions, reactions, STT, etc.) |
        | `/etc/claude-code/managed-settings.json` | Claude Code permission mode |

        `config.toml` is created from a built-in template on first boot. After that, edit the file directly — environment variables are only used for initial setup. To reset to defaults, delete the file and restart: `rm /home/node/.config/openab/config.toml`

        For config options, see the [OpenAB documentation](https://github.com/openabdev/openab).

        ## Disabling

        Leave `Discord Bot Token` empty. The service sleeps instead of crashing.

        ## Links

        - [OpenAB GitHub](https://github.com/openabdev/openab)
        - [Agent Client Protocol](https://github.com/anthropics/agent-protocol)
        - [Anthropic Console](https://console.anthropic.com/)
        - [Zeabur AI Hub](https://zeabur.com/ai-hub)
    resourceRequirement:
        minConfig:
            cpu: 2
            ram: 4
        recommendedConfig:
            cpu: 4
            ram: 8
    services:
        - name: openab
          icon: https://cdn-console.zeabur.com/f/0Ewi6/openab-icon.webp
          template: PREBUILT_V2
          spec:
            id: openab
            source:
                image: ghcr.io/openabdev/openab-claude:0.8.0
                command:
                    - /bin/sh
                    - -c
                    - /opt/start-openab.sh
            volumes:
                - id: node-home
                  dir: /home/node
            env:
                DISCORD_BOT_TOKEN:
                    default: ${DISCORD_BOT_TOKEN}
                OPENAB_ALLOW_BOT_MESSAGES:
                    default: ""
                OPENAB_ALLOWED_CHANNELS:
                    default: ""
                OPENAB_ALLOWED_USERS:
                    default: ""
                OPENAB_AUTH_TOKEN:
                    default: ${OPENAB_AUTH_TOKEN}
                OPENAB_MAX_SESSIONS:
                    default: ""
                SLACK_APP_TOKEN:
                    default: ""
                SLACK_BOT_TOKEN:
                    default: ""
            configs:
                - path: /opt/start-openab.sh
                  template: |
                    #!/bin/sh
                    set -e

                    if [ -z "$OPENAB_AUTH_TOKEN" ]; then
                      echo "openab: OPENAB_AUTH_TOKEN not set — sleeping"
                      exec sleep infinity
                    fi

                    if [ -z "$DISCORD_BOT_TOKEN" ] && [ -z "$SLACK_BOT_TOKEN" ]; then
                      echo "openab: neither DISCORD_BOT_TOKEN nor SLACK_BOT_TOKEN set — sleeping"
                      exec sleep infinity
                    fi

                    # Detect auth token type and export appropriate env var
                    case "$OPENAB_AUTH_TOKEN" in
                      sk-ant-oat*)
                        export CLAUDE_CODE_OAUTH_TOKEN="$OPENAB_AUTH_TOKEN"
                        ;;
                      sk-ant-*)
                        export ANTHROPIC_API_KEY="$OPENAB_AUTH_TOKEN"
                        ;;
                      *)
                        export ANTHROPIC_API_KEY="$OPENAB_AUTH_TOKEN"
                        export ANTHROPIC_BASE_URL="https://hnd1.aihub.zeabur.ai"
                        export CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1
                        ;;
                    esac

                    CONFIG_DIR=/home/node/.config/openab
                    CONFIG_FILE=$CONFIG_DIR/config.toml
                    mkdir -p "$CONFIG_DIR"

                    # Generate config.toml on first boot only
                    # To regenerate, delete the file and restart
                    if [ ! -f "$CONFIG_FILE" ]; then
                      cp /opt/config.toml.template "$CONFIG_FILE"

                      # Add [discord] section if token provided
                      if [ -n "$DISCORD_BOT_TOKEN" ]; then
                        printf '\n[discord]\nbot_token = "${DISCORD_BOT_TOKEN}"\n' >> "$CONFIG_FILE"
                        if [ -n "$OPENAB_ALLOW_BOT_MESSAGES" ]; then
                          printf 'allow_bot_messages = "%s"\n' "$OPENAB_ALLOW_BOT_MESSAGES" >> "$CONFIG_FILE"
                        fi
                        if [ -n "$OPENAB_ALLOWED_CHANNELS" ]; then
                          IFS=','; channels=""; for id in $OPENAB_ALLOWED_CHANNELS; do
                            id=$(echo "$id" | tr -d ' '); [ -n "$id" ] && channels="${channels}\"${id}\","; done; unset IFS
                          printf 'allowed_channels = [%s]\n' "$(echo "$channels" | sed 's/,$//')" >> "$CONFIG_FILE"
                        fi
                        if [ -n "$OPENAB_ALLOWED_USERS" ]; then
                          IFS=','; users=""; for id in $OPENAB_ALLOWED_USERS; do
                            id=$(echo "$id" | tr -d ' '); [ -n "$id" ] && users="${users}\"${id}\","; done; unset IFS
                          printf 'allowed_users = [%s]\n' "$(echo "$users" | sed 's/,$//')" >> "$CONFIG_FILE"
                        fi
                      fi

                      # Add [slack] section if tokens provided
                      if [ -n "$SLACK_BOT_TOKEN" ] && [ -n "$SLACK_APP_TOKEN" ]; then
                        printf '\n[slack]\nbot_token = "${SLACK_BOT_TOKEN}"\napp_token = "${SLACK_APP_TOKEN}"\n' >> "$CONFIG_FILE"
                      fi

                      # Add [pool] section if max_sessions provided
                      if [ -n "$OPENAB_MAX_SESSIONS" ]; then
                        printf '\n[pool]\nmax_sessions = %s\n' "$OPENAB_MAX_SESSIONS" >> "$CONFIG_FILE"
                      fi

                      echo "openab: config.toml generated"
                    else
                      echo "openab: using existing config.toml (delete to regenerate)"
                    fi

                    # Run openab as node user (claude-code 2.1+ blocks bypassPermissions as root)
                    exec su -p -s /bin/sh node -c "exec openab run '$CONFIG_FILE'"
                  permission: 493
                  envsubst: null
                - path: /opt/config.toml.template
                  template: |
                    [agent]
                    command = "claude-agent-acp"
                    args = []
                    working_dir = "/home/node"
                  permission: null
                  envsubst: null
                - path: /etc/claude-code/managed-settings.json
                  template: |
                    {"permissions":{"defaultMode":"bypassPermissions"}}
                  permission: null
                  envsubst: null
