diff --git a/.env.example b/.env.example index 8372f7a..7fcf279 100644 --- a/.env.example +++ b/.env.example @@ -21,6 +21,12 @@ # The key is passed to Claude Code as ANTHROPIC_AUTH_TOKEN at runtime. # Get your key at: https://platform.deepseek.com/api_keys # DEEPSEEK_API_KEY=sk-... +# +# Z.ai / GLM 5.2 container (--profile zai / `make zai`): +# Runs the Claude Code image pointed at Z.ai's Anthropic-compatible API. +# The key is passed to Claude Code as ANTHROPIC_AUTH_TOKEN at runtime. +# Get your key at: https://z.ai/manage-apikey/apikey-list +# ZAI_API_KEY= # ══════════════════════════════════════════════════════════════════════ # OPTIONAL — Defaults work out of the box diff --git a/DOCKER.md b/DOCKER.md index b39c727..b7c7290 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -197,6 +197,7 @@ Skills are available in the container via two mechanisms: | `portainer` | `portainer/portainer-ce:latest` | `9443` | default | Docker management UI (HTTPS) | | `claude-code` | `crhacky/blhackbox:claude-code` | — | `claude-code` | Claude Code CLI client (Docker) | | `deepseek` | `crhacky/blhackbox:claude-code` | — | `deepseek` | DeepSeek agent — Claude Code image + DeepSeek API (Docker) | +| `zai` | `crhacky/blhackbox:claude-code` | — | `zai` | Z.ai / GLM 5.2 agent — Claude Code image + Z.ai API (Docker) | | `mcp-gateway` | `docker/mcp-gateway:latest` | `8080` | `gateway` | Single MCP entry point (host clients) | | `neo4j` | `neo4j:5` | `7474` / `7687` | `neo4j` | Cross-session knowledge graph | @@ -245,6 +246,7 @@ Requires `--profile gateway` (`make up-gateway`). |:--|:--|:--| | `ANTHROPIC_API_KEY` | — | Required for the Claude Code container (`--profile claude-code`) | | `DEEPSEEK_API_KEY` | — | Required for the DeepSeek container (`--profile deepseek`); passed to Claude Code as `ANTHROPIC_AUTH_TOKEN` | +| `ZAI_API_KEY` | — | Required for the Z.ai / GLM 5.2 container (`--profile zai`); passed to Claude Code as `ANTHROPIC_AUTH_TOKEN` | | `MCP_GATEWAY_PORT` | `8080` | MCP Gateway host port (optional) | | `MSF_TIMEOUT` | `300` | Metasploit command timeout in seconds | | `NEO4J_URI` | `bolt://neo4j:7687` | Neo4j connection URI (optional) | @@ -321,6 +323,20 @@ The Claude Code container includes the full skills system. Skills (`.claude/skil The `deepseek` profile runs the **same Claude Code image** as `--profile claude-code`, only with environment variables that route the model backend to DeepSeek's Anthropic-compatible endpoint. There is no separate image to build, push, or maintain, and the full skills system (slash commands) works exactly as it does with Claude Code. Launch it with `make deepseek` or `docker compose --profile deepseek run --rm deepseek`. +### Z.ai / GLM 5.2 — `zai` profile (reuses `crhacky/blhackbox:claude-code`) + +| | | +|:--|:--| +| **Image** | Same as Claude Code — `crhacky/blhackbox:claude-code` (no separate image) | +| **Agent** | Claude Code CLI, pointed at the Z.ai API | +| **Entrypoint** | `claude-code-entrypoint.sh` (health checks + launch) | +| **MCP config** | Direct Streamable HTTP to each server (no gateway dependency) | +| **Provider** | `ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic` + `ANTHROPIC_AUTH_TOKEN` from `ZAI_API_KEY` env (never written to disk) | +| **Models** | `glm-5.2` (default), `glm-4.5-air` (Haiku tier) — per [Z.ai's Claude Code guide](https://docs.z.ai/devpack/tool/claude) | +| **Requires** | `ZAI_API_KEY` in `.env` | + +The `zai` profile runs the **same Claude Code image** as `--profile claude-code`, only with environment variables that route the model backend to Z.ai's Anthropic-compatible endpoint. There is no separate image to build, push, or maintain, and the full skills system (slash commands) works exactly as it does with Claude Code. Launch it with `make zai` or `docker compose --profile zai run --rm zai`. + --- ## Portainer diff --git a/Makefile b/Makefile index 9c6ccaa..d4a044d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: help setup up up-full up-gateway down logs test test-local lint format clean nuke \ pull status health portainer gateway-logs \ - claude-code deepseek \ + claude-code deepseek zai \ neo4j-browser logs-kali \ logs-wireshark logs-screenshot \ restart-kali \ @@ -27,7 +27,7 @@ up: ## Start default stack (7 containers: Kali, WireMCP, Screenshot, HexStrike A $(COMPOSE) up -d down: ## Stop all services (default + auxiliary profiles) - $(COMPOSE) --profile gateway --profile neo4j --profile claude-code --profile deepseek down + $(COMPOSE) --profile gateway --profile neo4j --profile claude-code --profile deepseek --profile zai down logs: ## Tail logs from all services $(COMPOSE) logs -f @@ -83,7 +83,7 @@ boaz-bridge: ## Run local BOAZ MCP helper server python boaz-mcp/server.py clean: ## Remove containers, volumes, networks, and build artifacts (keeps images) - $(COMPOSE) --profile gateway --profile neo4j --profile claude-code --profile deepseek down -v --remove-orphans + $(COMPOSE) --profile gateway --profile neo4j --profile claude-code --profile deepseek --profile zai down -v --remove-orphans find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true rm -rf dist/ build/ *.egg-info @@ -91,7 +91,7 @@ nuke: ## Full cleanup: containers + volumes + ALL images (frees max disk space) @echo "\033[1;33m WARNING: This will remove ALL blhackbox containers, volumes, AND images.\033[0m" @echo "\033[2m You will need to 'docker compose pull' or 'docker compose build' again.\033[0m" @echo "" - $(COMPOSE) --profile gateway --profile neo4j --profile claude-code --profile deepseek down -v --remove-orphans --rmi all + $(COMPOSE) --profile gateway --profile neo4j --profile claude-code --profile deepseek --profile zai down -v --remove-orphans --rmi all @echo "" @echo "\033[2m Pruning dangling images and build cache...\033[0m" docker image prune -f @@ -131,12 +131,24 @@ deepseek: ## Launch the DeepSeek agent in a Docker container (reuses the Claude @echo "" $(COMPOSE) --profile deepseek run --rm deepseek +# ── Z.ai / GLM 5.2 (Docker — Claude Code pointed at the Z.ai API) ── +zai: ## Launch the Z.ai / GLM 5.2 agent in a Docker container (reuses the Claude Code image) + $(COMPOSE) --profile zai pull zai || $(COMPOSE) --profile claude-code build claude-code + @echo "" + @echo "\033[1m Pre-flight Container Status\033[0m" + @echo "\033[2m ──────────────────────────────────────\033[0m" + @$(COMPOSE) ps --format "table {{.Name}}\t{{.Status}}" 2>/dev/null || $(COMPOSE) ps + @echo "" + @echo "\033[2m Waiting for all dependencies to become healthy...\033[0m" + @echo "" + $(COMPOSE) --profile zai run --rm zai + # ── Health & Status ────────────────────────────────────────────── status: ## Health status of all containers @echo "" @echo "\033[1m blhackbox Container Status\033[0m" @echo "\033[2m ──────────────────────────────────────\033[0m" - @$(COMPOSE) --profile gateway --profile neo4j --profile claude-code --profile deepseek ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || $(COMPOSE) ps + @$(COMPOSE) --profile gateway --profile neo4j --profile claude-code --profile deepseek --profile zai ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || $(COMPOSE) ps @echo "" health: ## Quick health check of all MCP servers diff --git a/README.md b/README.md index e46f2a4..789d3c5 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ |---|---| | **Getting Started** | [How It Works](#how-it-works) · [Architecture](#architecture) · [Prerequisites](#prerequisites) · [Installation](#installation) | | **Skills** | [Skills Overview](#skills--slash-commands) · [Available Skills](#available-skills) · [How Skills Work](#how-skills-work) | -| **Tutorials** | [Claude Code (Docker)](#tutorial-1-claude-code-docker--recommended) · [DeepSeek (Docker)](#tutorial-1b-deepseek-docker) · [Claude Code (Web)](#tutorial-2-claude-code-web) · [Claude Desktop](#tutorial-3-claude-desktop-host--gateway) · [ChatGPT / OpenAI](#tutorial-4-chatgpt--openai-host--gateway) | +| **Tutorials** | [Claude Code (Docker)](#tutorial-1-claude-code-docker--recommended) · [DeepSeek (Docker)](#tutorial-1b-deepseek-docker) · [Z.ai / GLM 5.2 (Docker)](#tutorial-1c-zai--glm-52-docker) · [Claude Code (Web)](#tutorial-2-claude-code-web) · [Claude Desktop](#tutorial-3-claude-desktop-host--gateway) · [ChatGPT / OpenAI](#tutorial-4-chatgpt--openai-host--gateway) | | **Advanced** | [Advanced Usage & FAQ](#advanced-usage--faq) | | **Reference** | [Components](#components) · [Output Files](#output-files) · [CLI Reference](#cli-reference) · [Makefile Shortcuts](#makefile-shortcuts) · [Docker Hub Images](#docker-hub-images) | | **Operations** | [Prompt Flow](#how-prompts-flow-through-the-system) · [MCP Gateway](#do-i-need-the-mcp-gateway) · [Portainer Setup](#portainer-setup) · [Neo4j](#neo4j-optional) · [Troubleshooting](#troubleshooting) | @@ -309,7 +309,7 @@ cd blhackbox The setup wizard walks you through everything interactively: 1. **Check prerequisites** (Docker, Docker Compose, Git, disk space) -2. **Choose your AI client** — Claude (Claude Code in Docker, Claude Code Web, or Claude Desktop), ChatGPT/OpenAI, or DeepSeek. The choice decides which agent/profile starts and which key (if any) you need. +2. **Choose your AI client** — Claude (Claude Code in Docker, Claude Code Web, or Claude Desktop), ChatGPT/OpenAI, DeepSeek, or Z.ai / GLM 5.2. The choice decides which agent/profile starts and which key (if any) you need. 3. **Configure API keys with guidance** — for each key it explains what it's for, where it's used, and the exact steps to obtain it; every key is optional/skippable. 4. **Pick a knowledge graph** — none, local Neo4j (Docker), or Neo4j Aura (cloud). Aura mode walks you through `console.neo4j.io` and stores your connection details without starting a local container. 5. **Optionally add recon API keys** (WPScan, Shodan, VirusTotal, SecurityTrails) — all skippable; tools run without them. @@ -426,6 +426,8 @@ You are now inside an interactive Claude Code session. > **Prefer DeepSeek?** Set `DEEPSEEK_API_KEY` in `.env` and run `make deepseek` (or `docker compose --profile deepseek run --rm deepseek`). This runs the **same Claude Code image** pointed at DeepSeek's Anthropic-compatible API — same MCP wiring, same skills, only the model backend differs. See [Tutorial 1b](#tutorial-1b-deepseek-docker). +> **Prefer GLM 5.2 (Z.ai)?** Set `ZAI_API_KEY` in `.env` and run `make zai` (or `docker compose --profile zai run --rm zai`). This runs the **same Claude Code image** pointed at Z.ai's Anthropic-compatible API — same MCP wiring, same skills, only the model backend differs. See [Tutorial 1c](#tutorial-1c-zai--glm-52-docker). + ### Step 3 — Verify the connection ``` @@ -496,6 +498,32 @@ The entrypoint checks each MCP server, then drops you into an interactive sessio --- +## Tutorial 1c: Z.ai / GLM 5.2 (Docker) + +Prefer Z.ai's GLM 5.2 over Claude? The `zai` profile runs the **same Claude Code image** as Tutorial 1, just pointed at Z.ai's Anthropic-compatible API via `ANTHROPIC_BASE_URL` + `ANTHROPIC_AUTH_TOKEN` (following [Z.ai's Claude Code guide](https://docs.z.ai/devpack/tool/claude)). The MCP wiring, the skills system, and the entrypoint are identical — only the model backend changes. There is no separate image to build or maintain. + +### Step 1 — Set your Z.ai key + +Put `ZAI_API_KEY` in `.env` (subscribe to the GLM Coding Plan and create a key at [z.ai/manage-apikey/apikey-list](https://z.ai/manage-apikey/apikey-list)). `setup.sh` prompts for it, or add it manually. The key is read from the environment at runtime, passed to Claude Code as `ANTHROPIC_AUTH_TOKEN`, and never written into the image. + +### Step 2 — Launch the agent + +```bash +make zai +``` + +Or manually: + +```bash +docker compose --profile zai run --rm zai +``` + +The entrypoint checks each MCP server, then drops you into an interactive session with all the pentest tools available. The default model is **glm-5.2** (background tasks use **glm-4.5-air**); switch the active model in-session with `/model`. For GLM 5.2's full 1M-token context window, uncomment the `glm-5.2[1m]` option in `docker-compose.yml`. + +> **Note:** Because this is Claude Code under the hood, the full skills system works — `/full-pentest`, `/quick-scan`, and the rest of the slash commands behave exactly as in Tutorial 1. + +--- + ## Tutorial 2: Claude Code (Web) Claude Code on [claude.ai/code](https://claude.ai/code) works as a web-based coding agent. When you open this repo in a web session, the MCP server configures itself automatically. @@ -762,6 +790,7 @@ STEP 7 ─ (OPTIONAL) RESULTS STORED IN NEO4J |:--|:--:|:--| | **Claude Code (Docker)** | No | Direct Streamable HTTP to each MCP server over Docker network | | **DeepSeek (Docker)** | No | Claude Code image + DeepSeek API; direct Streamable HTTP to each MCP server over Docker network | +| **Z.ai / GLM 5.2 (Docker)** | No | Claude Code image + Z.ai API; direct Streamable HTTP to each MCP server over Docker network | | **Claude Code (Web)** | No | Stdio MCP server from `.mcp.json` | | **Claude Desktop** | **Yes** | Host GUI app → `localhost:8080/mcp` gateway | | **ChatGPT / OpenAI** | **Yes** | Host GUI/web app → `localhost:8080/mcp` gateway | @@ -883,6 +912,7 @@ blhackbox mcp # Start MCP server | `make down` | Stop all services | | `make claude-code` | Build and launch Claude Code in Docker | | `make deepseek` | Launch the DeepSeek agent in Docker (Claude Code image + DeepSeek API) | +| `make zai` | Launch the Z.ai / GLM 5.2 agent in Docker (Claude Code image + Z.ai API) | | `make status` | Container status table | | `make health` | Quick health check of all services | | `make test` | Run tests | @@ -920,7 +950,7 @@ All custom images are published to `crhacky/blhackbox`: | `crhacky/blhackbox:hexstrike-ai` | HexStrike Gamma API container | | `crhacky/blhackbox:hexstrike-mcp` | Upstream HexStrike Gamma MCP server over Streamable HTTP | | `crhacky/blhackbox:boaz-mcp` | Upstream BOAZ-MCP Gamma server over Streamable HTTP | -| `crhacky/blhackbox:claude-code` | Claude Code CLI client (direct Streamable HTTP to MCP servers); also used by the `deepseek` profile pointed at the DeepSeek API | +| `crhacky/blhackbox:claude-code` | Claude Code CLI client (direct Streamable HTTP to MCP servers); also used by the `deepseek` and `zai` profiles, pointed at the DeepSeek and Z.ai APIs | Official images pulled directly: @@ -985,7 +1015,7 @@ blhackbox/ │ ├── kali-mcp.Dockerfile Kali Linux + Metasploit Framework │ ├── wire-mcp.Dockerfile │ ├── screenshot-mcp.Dockerfile -│ ├── claude-code.Dockerfile MCP client container (also used by the deepseek profile) +│ ├── claude-code.Dockerfile MCP client container (also used by the deepseek and zai profiles) │ └── claude-code-entrypoint.sh Startup script with health checks ├── kali-mcp/ Kali MCP server (70+ tools + Metasploit) ├── wire-mcp/ WireMCP server (tshark, 7 tools) diff --git a/docker-compose.yml b/docker-compose.yml index aea8e9a..31aecf9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,7 @@ # docker compose --profile gateway up -d Start with MCP Gateway # docker compose --profile claude-code up -d Start with Claude Code container # docker compose --profile deepseek run --rm deepseek Run the DeepSeek agent (Claude Code + DeepSeek API) +# docker compose --profile zai run --rm zai Run the Z.ai / GLM 5.2 agent (Claude Code + Z.ai API) # docker compose --profile neo4j up -d Start with Neo4j # docker compose build Build all custom images locally # @@ -432,6 +433,71 @@ services: networks: - blhackbox_net + # -- Z.ai / GLM 5.2 (OPTIONAL) --------------------------------------------- + # Z.ai (GLM) agent running inside Docker -- no host install needed. + # Enable with: docker compose --profile zai run --rm zai + # (or `make zai`) + # + # Reuses the Claude Code image and points it at Z.ai's Anthropic-compatible + # API via ANTHROPIC_BASE_URL + ANTHROPIC_AUTH_TOKEN, following Z.ai's official + # Claude Code guide: + # https://docs.z.ai/devpack/tool/claude + # Same MCP wiring, skills, and entrypoint as the claude-code service — only the + # model backend differs, so there is no separate image to build or maintain. + # Requires ZAI_API_KEY (read from env, never written to disk). + zai: + image: crhacky/blhackbox:claude-code + container_name: blhackbox-zai + profiles: ["zai"] + stdin_open: true + tty: true + environment: + # Route Claude Code at Z.ai's Anthropic-compatible endpoint. + # ANTHROPIC_AUTH_TOKEN is sent as the Authorization: Bearer header. + ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic" + ANTHROPIC_AUTH_TOKEN: "${ZAI_API_KEY:-}" + # Map Claude's model tiers onto GLM models (per Z.ai's Claude Code guide). + # Switch the active model in-session with /model. + ANTHROPIC_MODEL: "glm-5.2" + ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-5.2" + ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-5.2" + ANTHROPIC_DEFAULT_HAIKU_MODEL: "glm-4.5-air" + # GLM reasoning can run long; Z.ai recommends a generous client timeout. + API_TIMEOUT_MS: "3000000" + # Optional: use GLM 5.2's full 1M-token context (uncomment both lines). + # ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-5.2[1m]" + # CLAUDE_CODE_AUTO_COMPACT_WINDOW: "1000000" + # Don't phone home to Anthropic for telemetry/auto-update. + CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" + # Bypass egress proxies (e.g. GitHub Codespaces) for internal Docker traffic + no_proxy: "mcp-gateway,kali-mcp,wire-mcp,screenshot-mcp,hexstrike-ai,hexstrike-bridge-mcp,boaz-mcp,localhost,127.0.0.1" + NO_PROXY: "mcp-gateway,kali-mcp,wire-mcp,screenshot-mcp,hexstrike-ai,hexstrike-bridge-mcp,boaz-mcp,localhost,127.0.0.1" + dns: + - 8.8.8.8 + - 1.1.1.1 + volumes: + - ./output/screenshots:/tmp/screenshots:ro + - ./output/reports:/root/reports + - ./output/sessions:/root/results + # Shared working data from Kali MCP (recon files, tool artifacts) + - shared-output:/root/kali-data:ro + # Mount skills and project instructions so Claude Code discovers them + - ./.claude/skills:/root/.claude/skills:ro + - ./CLAUDE.md:/root/CLAUDE.md:ro + depends_on: + kali-mcp: + condition: service_healthy + wire-mcp: + condition: service_healthy + screenshot-mcp: + condition: service_healthy + hexstrike-bridge-mcp: + condition: service_healthy + boaz-mcp: + condition: service_healthy + networks: + - blhackbox_net + # -- PORTAINER CE ---------------------------------------------------------- # Web UI for container management -- https://localhost:9443 # Source: https://github.com/portainer/portainer diff --git a/setup.sh b/setup.sh index 1b6c732..ab113f0 100755 --- a/setup.sh +++ b/setup.sh @@ -9,6 +9,7 @@ # ./setup.sh # Interactive wizard (recommended) # ./setup.sh --api-key sk-ant-... # Non-interactive: Claude Code container # ./setup.sh --deepseek-key sk-.. # Non-interactive: DeepSeek container +# ./setup.sh --zai-key KEY # Non-interactive: Z.ai / GLM 5.2 container # ./setup.sh --minimal # Core stack only, no prompts # ./setup.sh --with-neo4j # Enable local Neo4j (Docker) # ./setup.sh --with-gateway # Enable MCP Gateway (Desktop/ChatGPT) @@ -50,14 +51,15 @@ ARROW="${CYAN}→${NC}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" API_KEY="" DEEPSEEK_KEY="" +ZAI_KEY="" MINIMAL=false SKIP_PULL=false NEO4J_PASS="" PROFILES="" # Runtime selections (filled in by the wizard). -AI_VARIANT="" # claude-code | claude-web | claude-desktop | chatgpt | deepseek | "" -AGENT_PROFILE="" # claude-code | deepseek | "" (containerized agents only) +AI_VARIANT="" # claude-code | claude-web | claude-desktop | chatgpt | deepseek | zai | "" +AGENT_PROFILE="" # claude-code | deepseek | zai | "" (containerized agents only) NEO4J_MODE="none" # none | local | aura WRITE_ENV=true # whether we may (over)write .env STATUS_LINES=0 # bookkeeping for the live status redraw @@ -168,6 +170,7 @@ usage() { echo "Options:" echo " --api-key KEY Set ANTHROPIC_API_KEY (Claude Code container)" echo " --deepseek-key KEY Set DEEPSEEK_API_KEY (DeepSeek container)" + echo " --zai-key KEY Set ZAI_API_KEY (Z.ai / GLM 5.2 container)" echo " --minimal Core stack only (no prompts, no Neo4j/Gateway)" echo " --with-neo4j Enable local Neo4j knowledge graph (Docker)" echo " --with-gateway Enable MCP Gateway for Claude Desktop/ChatGPT" @@ -306,6 +309,16 @@ print_deepseek_help() { echo -e " ${DIM}4.${NC} Copy it now — DeepSeek shows the key only once" } +print_zai_help() { + echo -e " ${BOLD}What it's for:${NC} authenticates the Z.ai / GLM 5.2 agent (Claude Code + Z.ai API) in Docker." + echo -e " ${BOLD}Where it's used:${NC} the ${CYAN}zai${NC} container (ZAI_API_KEY)." + echo -e " ${BOLD}How to get it:${NC}" + echo -e " ${DIM}1.${NC} Sign in at ${CYAN}https://z.ai${NC} and subscribe to the GLM Coding Plan" + echo -e " ${DIM}2.${NC} Open ${BOLD}API Keys${NC} at ${CYAN}https://z.ai/manage-apikey/apikey-list${NC}" + echo -e " ${DIM}3.${NC} Click ${BOLD}Add New Key${NC} and copy it" + echo -e " ${DIM}4.${NC} Copy it now — Z.ai shows the key only once" +} + print_openai_help() { echo -e " ${BOLD}What it's for:${NC} used by your host-based ChatGPT/OpenAI MCP client" echo -e " ${DIM}(and optional vector-memory embeddings). The Docker stack itself does${NC}" @@ -356,6 +369,10 @@ choose_ai_client() { AI_VARIANT="deepseek"; AGENT_PROFILE="deepseek" [ "$WRITE_ENV" = true ] && set_env "DEEPSEEK_API_KEY" "$DEEPSEEK_KEY" echo -e " ${ARROW} DeepSeek (Docker) — key supplied via --deepseek-key" + elif [ -n "$ZAI_KEY" ]; then + AI_VARIANT="zai"; AGENT_PROFILE="zai" + [ "$WRITE_ENV" = true ] && set_env "ZAI_API_KEY" "$ZAI_KEY" + echo -e " ${ARROW} Z.ai / GLM 5.2 (Docker) — key supplied via --zai-key" else echo -e " ${ARROW} No agent flag given — starting core stack only" fi @@ -367,7 +384,8 @@ choose_ai_client() { echo -e " ${CYAN}1)${NC} Claude ${DIM}(Anthropic)${NC}" echo -e " ${CYAN}2)${NC} ChatGPT ${DIM}(OpenAI)${NC}" echo -e " ${CYAN}3)${NC} DeepSeek" - local provider; provider="$(read_choice 3 1)" + echo -e " ${CYAN}4)${NC} Z.ai ${DIM}(GLM 5.2)${NC}" + local provider; provider="$(read_choice 4 1)" echo "" case "$provider" in @@ -402,6 +420,11 @@ choose_ai_client() { echo -e " ${ARROW} DeepSeek runs containerized in Docker (Claude Code + DeepSeek API)." configure_agent_key "DEEPSEEK_API_KEY" print_deepseek_help ;; + 4) # Z.ai / GLM 5.2 + AI_VARIANT="zai"; AGENT_PROFILE="zai" + echo -e " ${ARROW} Z.ai / GLM 5.2 runs containerized in Docker (Claude Code + Z.ai API)." + configure_agent_key "ZAI_API_KEY" print_zai_help + ;; esac echo "" } @@ -719,6 +742,13 @@ print_launch_hint() { echo -e " ${WARN} ${YELLOW}DEEPSEEK_API_KEY is not set${NC} — the deepseek container won't" echo -e " start until you add it to ${CYAN}.env${NC} (then re-run ${CYAN}make deepseek${NC})." fi ;; + zai) + echo -e " ${BOLD}Launch your agent:${NC}" + echo -e " ${CYAN}make zai${NC} ${DIM}Start the Z.ai / GLM 5.2 agent${NC}" + if ! env_has_value ZAI_API_KEY; then + echo -e " ${WARN} ${YELLOW}ZAI_API_KEY is not set${NC} — the zai container won't" + echo -e " start until you add it to ${CYAN}.env${NC} (then re-run ${CYAN}make zai${NC})." + fi ;; claude-web) echo -e " ${BOLD}Using Claude Code Web:${NC}" echo -e " ${DIM}Open this repository at ${NC}$(link https://claude.ai/code "${CYAN}https://claude.ai/code${NC}")${DIM}.${NC}" @@ -783,6 +813,7 @@ ai_label() { claude-desktop) echo "Claude Desktop (host app via MCP Gateway)" ;; chatgpt) echo "ChatGPT / OpenAI (host app via MCP Gateway)" ;; deepseek) echo "DeepSeek (Docker — Claude Code + DeepSeek API)" ;; + zai) echo "Z.ai / GLM 5.2 (Docker — Claude Code + Z.ai API)" ;; *) echo "None selected (core stack only)" ;; esac } @@ -842,6 +873,10 @@ while [[ $# -gt 0 ]]; do DEEPSEEK_KEY="$2" shift 2 ;; + --zai-key) + ZAI_KEY="$2" + shift 2 + ;; --minimal) MINIMAL=true INTERACTIVE=false diff --git a/tests/test_compose_integrations.py b/tests/test_compose_integrations.py index 996168b..77cb2eb 100644 --- a/tests/test_compose_integrations.py +++ b/tests/test_compose_integrations.py @@ -103,3 +103,23 @@ def test_deepseek_service_is_in_compose_under_profile() -> None: # Routes Claude Code at DeepSeek's Anthropic-compatible endpoint. assert "https://api.deepseek.com/anthropic" in compose assert 'ANTHROPIC_AUTH_TOKEN: "${DEEPSEEK_API_KEY:-}"' in compose + + +def test_zai_reuses_claude_code_image_without_separate_build() -> None: + # The Z.ai / GLM 5.2 profile is just the Claude Code image pointed at the + # Z.ai API — no separate Dockerfile/entrypoint to maintain. + assert not (ROOT / "docker" / "zai.Dockerfile").exists() + assert not (ROOT / "docker" / "zai-entrypoint.sh").exists() + + +def test_zai_service_is_in_compose_under_profile() -> None: + compose = COMPOSE.read_text(encoding="utf-8") + assert "zai:" in compose + # Reuses the Claude Code image rather than building a dedicated one. + assert "image: crhacky/blhackbox:claude-code" in compose + assert "crhacky/blhackbox:zai" not in compose + assert "docker/zai.Dockerfile" not in compose + assert 'profiles: ["zai"]' in compose + # Routes Claude Code at Z.ai's Anthropic-compatible endpoint. + assert "https://api.z.ai/api/anthropic" in compose + assert 'ANTHROPIC_AUTH_TOKEN: "${ZAI_API_KEY:-}"' in compose