diff --git a/Makefile b/Makefile index d4a044d..f8d1ebf 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help setup up up-full up-gateway down logs test test-local lint format clean nuke \ +.PHONY: help setup add-model up up-full up-gateway down logs test test-local lint format clean nuke \ pull status health portainer gateway-logs \ claude-code deepseek zai \ neo4j-browser logs-kali \ @@ -15,6 +15,9 @@ COMPOSE := docker compose setup: ## Interactive setup wizard (prerequisites, .env, pull, start, health check) @bash setup.sh +add-model: ## Add/switch an agent model API key in .env (Claude Code, DeepSeek, Z.ai) + @bash scripts/add_model.sh + help: ## Show this help @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-26s\033[0m %s\n", $$1, $$2}' diff --git a/README.md b/README.md index 789d3c5..c153061 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ | | | |---|---| -| **Getting Started** | [How It Works](#how-it-works) · [Architecture](#architecture) · [Prerequisites](#prerequisites) · [Installation](#installation) | +| **Getting Started** | [How It Works](#how-it-works) · [Architecture](#architecture) · [Prerequisites](#prerequisites) · [Installation](#installation) · [Configure a Model](#configuring-a-model-api-keys) | | **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) · [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) | @@ -400,6 +400,41 @@ RETURN ip, p, s --- +## Configuring a Model (API Keys) + +Each in-Docker agent — **Claude Code**, **DeepSeek**, and **Z.ai / GLM 5.2** — runs the +**same image** pointed at a different model backend, so each needs its own API key in `.env`. +The keys are independent: you can configure more than one and switch between them by launching +the matching `make` target. Nothing is baked into the image — keys are read at runtime only. + +### The easy way: `make add-model` + +```bash +make add-model +``` + +This interactive helper lists the models, shows which already have a key set, then prompts for +the chosen model's key (input hidden) and writes it to `.env`. Re-run it anytime to add another +model or replace an existing key — it prints the launch command when it's done. If you don't +have a `.env` yet, it creates one from `.env.example` and locks it to owner-only (`chmod 600`). + +### The manual way + +Add the relevant line to `.env` (run `cp .env.example .env` first if you don't have one), then +launch with the matching target: + +| Model | `.env` key | Get a key | Launch | +|:--|:--|:--|:--| +| Claude Code (Anthropic) | `ANTHROPIC_API_KEY=sk-ant-...` | [console.anthropic.com](https://console.anthropic.com/settings/keys) | `make claude-code` | +| DeepSeek | `DEEPSEEK_API_KEY=sk-...` | [platform.deepseek.com/api_keys](https://platform.deepseek.com/api_keys) | `make deepseek` | +| Z.ai / GLM 5.2 | `ZAI_API_KEY=...` | [z.ai/manage-apikey/apikey-list](https://z.ai/manage-apikey/apikey-list) | `make zai` | + +For DeepSeek and Z.ai, the key is passed to Claude Code as `ANTHROPIC_AUTH_TOKEN` against the +provider's Anthropic-compatible endpoint (set in `docker-compose.yml`). `.env` is git-ignored — +keep your keys out of version control. + +--- + ## Tutorial 1: Claude Code (Docker) — Recommended Claude Code runs entirely inside a Docker container on the same network as all blhackbox services. It connects **directly** to each MCP server via Streamable HTTP — no MCP Gateway, no host install, no Node.js. @@ -478,7 +513,7 @@ Prefer a DeepSeek-powered agent over Claude? The `deepseek` profile runs the **s ### Step 1 — Set your DeepSeek key -Put `DEEPSEEK_API_KEY` in `.env` (get one at [platform.deepseek.com/api_keys](https://platform.deepseek.com/api_keys)). `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. +Put `DEEPSEEK_API_KEY` in `.env` (get one at [platform.deepseek.com/api_keys](https://platform.deepseek.com/api_keys)). Run `make add-model` for a guided prompt, or `setup.sh`, 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 @@ -504,7 +539,7 @@ Prefer Z.ai's GLM 5.2 over Claude? The `zai` profile runs the **same Claude Code ### 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. +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)). Run `make add-model` for a guided prompt, or `setup.sh`, 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 @@ -904,6 +939,7 @@ blhackbox mcp # Start MCP server | Target | Description | |:--|:--| | `make setup` | Interactive setup wizard (prereqs, .env, pull, start, health) | +| `make add-model` | Add/switch an agent model API key in `.env` (Claude Code, DeepSeek, Z.ai) | | `make help` | Show all available targets | | `make pull` | Pull all pre-built images from Docker Hub | | `make up` | Start default stack (7 containers) | diff --git a/scripts/add_model.sh b/scripts/add_model.sh new file mode 100755 index 0000000..0b23052 --- /dev/null +++ b/scripts/add_model.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# ── blhackbox — Add / Switch Model API Key ─────────────────────────── +# Guided helper to configure the API key for a containerized agent model +# (Claude Code, DeepSeek, or Z.ai / GLM 5.2) WITHOUT running the full +# setup wizard. Lists the available models, shows which already have a key +# set, then prompts for the chosen model's key and writes it to .env. +# +# Usage: +# make add-model # (recommended) +# bash scripts/add_model.sh +# +# The key is read from the terminal with input hidden, written verbatim to +# .env (git-ignored), and never echoed back or logged. +set -euo pipefail + +# Resolve the repo root from this script's location so .env is found whether +# invoked via `make` (cwd = repo root) or directly from scripts/. +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="$ROOT_DIR/.env" + +# ── Color detection (mirror setup.sh: TTY + NO_COLOR convention) ────── +USE_COLOR=true +if [ ! -t 1 ] || [ -n "${NO_COLOR:-}" ]; then + USE_COLOR=false +fi +if [ "$USE_COLOR" = true ]; then + RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' + CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m' +else + RED=''; GREEN=''; YELLOW=''; CYAN=''; BOLD=''; DIM=''; NC='' +fi +CHECK="${GREEN}✔${NC}"; WARN="${YELLOW}!${NC}"; ARROW="${CYAN}→${NC}" + +# ── Helpers (same safe patterns as setup.sh) ───────────────────────── + +# Read a secret without echoing. Prompt + newline go to stderr; the value is +# the only thing on stdout (safe for command substitution). Never logged. +prompt_secret() { + local prompt="$1" val + read -rsp "$prompt" val &2 + printf '%s' "$val" +} + +# Set KEY=VALUE in .env. The key is a literal we control, so the value is +# NEVER passed through sed (no escaping pitfalls, injection-safe). Removes any +# prior definition (commented placeholder or active line), then appends. +set_env() { + local key="$1" val="$2" + sed -i -E "/^[[:space:]]*#?[[:space:]]*${key}=/d" "$ENV_FILE" + printf '%s=%s\n' "$key" "$val" >> "$ENV_FILE" +} + +# Return 0 if KEY is set to a non-empty value in .env. +env_has_value() { + grep -qE "^$1=.+" "$ENV_FILE" 2>/dev/null +} + +# "[key set]" / "[not set]" status label for the menu. +status_label() { + if env_has_value "$1"; then + printf '%b' "${GREEN}[key set]${NC}" + else + printf '%b' "${DIM}[not set]${NC}" + fi +} + +# Soft, non-blocking key-format check. Returns 0 to save, 1 to skip. Never +# prints the value. Unknown providers (e.g. Z.ai) always pass. +key_format_ok() { + local var="$1" val="$2" ans + case "$var" in + ANTHROPIC_API_KEY) + [[ "$val" == sk-ant-* ]] && return 0 + echo -e " ${WARN} That doesn't look like an Anthropic key (expected prefix 'sk-ant-')." ;; + DEEPSEEK_API_KEY) + [[ "$val" == sk-* ]] && return 0 + echo -e " ${WARN} That doesn't look like a typical API key (expected prefix 'sk-')." ;; + *) return 0 ;; + esac + read -rp " Save it anyway? [Y/n] " ans /dev/null || true + echo -e " ${CHECK} Created .env from the template" + echo "" + fi +} + +# ── Main ───────────────────────────────────────────────────────────── + +main() { + echo "" + echo -e "${CYAN}${BOLD} ╔══════════════════════════════════════════════╗${NC}" + echo -e "${CYAN}${BOLD} ║ blhackbox — Add / Switch Model Key ║${NC}" + echo -e "${CYAN}${BOLD} ╚══════════════════════════════════════════════╝${NC}" + echo "" + + if [ ! -t 0 ]; then + echo -e " ${WARN} This is an interactive helper — run it from a terminal:" + echo -e " ${CYAN}make add-model${NC}" + exit 1 + fi + + ensure_env_file + + echo -e " Configure the API key for a containerized agent model. The key is" + echo -e " saved to ${CYAN}.env${NC} ${DIM}(git-ignored)${NC} and read at runtime — never written to an image." + echo "" + echo -e " ${BOLD}Which model do you want to configure?${NC}" + printf " ${CYAN}1)${NC} %-24s %b ${DIM}→ make claude-code${NC}\n" "Claude Code (Anthropic)" "$(status_label ANTHROPIC_API_KEY)" + printf " ${CYAN}2)${NC} %-24s %b ${DIM}→ make deepseek${NC}\n" "DeepSeek" "$(status_label DEEPSEEK_API_KEY)" + printf " ${CYAN}3)${NC} %-24s %b ${DIM}→ make zai${NC}\n" "Z.ai / GLM 5.2" "$(status_label ZAI_API_KEY)" + echo "" + + local choice + read -rp " Enter choice [1]: " choice /dev/null || true + echo -e " ${CHECK} ${var} saved to .env" + echo "" + echo -e " ${BOLD}Next — launch the agent:${NC}" + echo -e " ${CYAN}${launch}${NC}" + echo "" +} + +main "$@"