Files
codecraft/config.example.toml
T
Richard ZinkandClaude Opus 5.5 bfa1f641d7 opencode prices and Go limits, settings out of .env
- Fetch prices for opencode (Zen and Go) incl. limits, image input, reasoning,
  long-context tiers and peak-hour/date factors; costs apply them per request
- Record the cost opencode reports per response; track Go allowance in 5h/7d/month
  windows against a provider plan limit (budget table, status overlay)
- Settings → Config file: move values of ${NAME} references into the config
- Examples, presets and forms keep keys in the config instead of .env

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 10:19:15 +01:00

404 lines
17 KiB
TOML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =============================================================================
# CodeCraft – configuration
# =============================================================================
#
# This file is the single source of truth for the server. Everything here can
# also be edited in the web UI (Settings). The UI edits this file in place and
# keeps your comments and formatting intact.
#
# Location: $CODECRAFT_CONFIG, otherwise <data_dir>/config.toml (Docker: /data/config.toml)
#
# Keys and tokens are entered in the web UI (Settings) and stored here, readable
# for admins and included in backups. Any string value may also reference an
# environment variable as ${NAME}; Settings → Config file moves such values
# into this file.
# =============================================================================
[server]
# Address the HTTP server listens on.
bind = "0.0.0.0:8787"
# Root directory the agents may work in. Sessions pick a project folder below it.
# Docker: mount your projects here (see docker-compose.yml).
workspace = "/workspace"
# Where sessions (SQLite) and runtime state are stored.
data_dir = "/data"
# Default model for new sessions, as "<provider>/<model>".
default_model = "anthropic/claude-sonnet-5-5"
# Cheap/fast model for titles and history compaction. Falls back to default_model.
small_model = "anthropic/claude-haiku-5-5"
# Compact the conversation automatically when the context is this full (0.0–1.0).
auto_compact_threshold = 0.85
# Max. subagents running at the same time (all sessions). Additionally, each
# subagent can be limited with max_parallel, and each provider/model with
# max_concurrency (see below).
max_parallel_subagents = 4
# MCP servers with more tools than this are loaded on demand (see [mcp.*] load).
mcp_direct_limit = 15
# Currency label for the cost statistics (prices below are in this currency).
currency = "USD"
# Largest file upload in MB. Uploads are sent in 8 MB chunks, so the reverse
# proxy's body limit (e.g. Nginx client_max_body_size) does not apply.
max_upload_mb = 500
# Bearer token for Prometheus at GET /metrics (empty = /metrics disabled).
# metrics_token = "a-long-random-string"
# -----------------------------------------------------------------------------
# Providers
# -----------------------------------------------------------------------------
# kind = "anthropic" → Anthropic Messages API (and compatible endpoints)
# kind = "openai" → OpenAI-compatible /chat/completions (Gemini, opencode Go,
# fal, OpenRouter, Ollama, vLLM, llama.cpp, LM Studio, ...)
#
# models: optional list. If empty, the model list is fetched from <base_url>/models.
# Per-model options: context (tokens), max_output (tokens), reasoning (bool),
# vision (bool, can read images), effort ("low" | "medium" | "high" | "xhigh" | "max").
#
# Prices (per 1M tokens, used for the statistics):
# input_price, output_price, cache_read_price, cache_write_price
# Cache prices default to input_price when omitted. Costs are stored per request,
# so changing a price only affects future requests.
#
# Concurrency: max_concurrency limits simultaneous requests – on the provider
# (= the host) and/or per model. Requests beyond the limit wait in a queue,
# e.g. for self-hosted models that can only process one request at a time.
#
# options: extra request body fields sent as-is, e.g.
# options = { temperature = 0.5, reasoning_budget_tokens = 5000 }
#
# Authentication: by default "Authorization: Bearer <api_key>" (openai) or
# "x-api-key: <api_key>" (anthropic). Override with auth_header / auth_prefix.
# Extra static headers: headers = { "X-Foo" = "bar" }
# -----------------------------------------------------------------------------
[providers.anthropic]
kind = "anthropic"
base_url = "https://api.anthropic.com/v1"
api_key = "" # set in Settings
# Current Claude models think adaptively; "effort" controls depth and cost.
# Prices: Anthropic list prices in USD per 1M tokens (Nov 2026) – adjust if needed.
models = [
{ id = "claude-opus-5-5", context = 1000000, max_output = 64000, reasoning = true, vision = true, effort = "high",
input_price = 4.0, output_price = 20.0, cache_read_price = 0.20, cache_write_price = 5.0 },
{ id = "claude-sonnet-5-5", context = 1000000, max_output = 64000, reasoning = true, vision = true, effort = "medium",
input_price = 2.0, output_price = 10.0, cache_read_price = 0.20, cache_write_price = 2.5 },
{ id = "claude-haiku-5-5", context = 1000000, max_output = 32000, reasoning = true, vision = true, effort = "low",
input_price = 0.10, output_price = 0.50, cache_read_price = 0.01, cache_write_price = 0.125 },
]
# Google Gemini through its OpenAI-compatible endpoint.
[providers.gemini]
kind = "openai"
base_url = "https://generativelanguage.googleapis.com/v1beta/openai"
api_key = "" # set in Settings
models = [] # discovered automatically
enabled = false # enable once the key is set
# opencode Go subscription (OpenAI-compatible endpoint for most models).
# Current model list: https://opencode.ai/inference/go/v1/models
[providers.opencode-go]
kind = "openai"
base_url = "https://opencode.ai/inference/go/openai/v1"
api_key = "" # set in Settings
models = []
enabled = false
# fal.ai – OpenRouter-backed LLM router. fal expects "Authorization: Key <key>".
# NOTE: verify base_url against the fal docs for your account.
[providers.fal]
kind = "openai"
base_url = "https://fal.run/openrouter/router/openai/v1"
api_key = "" # set in Settings
auth_prefix = "Key "
models = [
{ id = "google/gemini-2.5-flash", context = 1000000, max_output = 32000, vision = true },
]
enabled = false
# Self-hosted (Ollama shown; vLLM/llama.cpp/LM Studio work the same way).
# From inside Docker, the host is reachable as host.docker.internal.
[providers.local]
kind = "openai"
base_url = "http://host.docker.internal:11434/v1"
api_key = ""
max_concurrency = 1 # one request at a time on this host
models = []
enabled = false
# -----------------------------------------------------------------------------
# MCP servers
# -----------------------------------------------------------------------------
# type = "stdio" → command + args (+ env), started as a child process
# type = "http" → Streamable HTTP endpoint (url + optional headers)
# Tools appear to the model as mcp__<server>__<tool>.
# users = [...]: who may use the server. Default: admins only.
# Use ["*"] for all users or list usernames, e.g. ["anna", "ben"].
# load = "auto" (default) | "always" | "on_demand": servers with many tools
# (more than server.mcp_direct_limit) are not sent with every request; the
# model finds them with mcp_search and calls them with mcp_call. This keeps
# requests small – important for self-hosted models.
# MCP servers may ask you questions (elicitation); they show up in the UI.
# -----------------------------------------------------------------------------
# [mcp.trilium]
# type = "http"
# url = "https://notes.example.org/mcp"
# headers = { Authorization = "Bearer <token>" }
# [mcp.filesystem]
# type = "stdio"
# command = "npx"
# args = ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
# env = {}
# enabled = false
# -----------------------------------------------------------------------------
# Permissions
# -----------------------------------------------------------------------------
# For each tool: "allow" | "ask" | "deny". "ask" forwards the request to the UI.
# MCP tools can be addressed as "mcp__<server>__<tool>" or "mcp__<server>__*".
# Session permission modes in the UI override this:
# default → these rules
# accept_edits → file edits allowed without asking
# bypass → everything allowed (use with care)
# -----------------------------------------------------------------------------
[permissions]
read = "allow"
glob = "allow"
grep = "allow"
list = "allow"
todo_write = "allow"
ask_user = "allow"
task = "allow"
write = "ask"
edit = "ask"
bash = "ask"
bash_output = "allow"
kill_shell = "allow"
web_fetch = "ask"
web_search = "allow"
diagnostics = "allow"
openscad = "allow" # renders .scad files to STL/3MF + previews
remember = "allow" # project memory (.codecraft/memory.md)
browser_snapshot = "allow"
browser = "ask" # interactive browser (clicks, forms); private LAN is blocked
image_compare = "allow"
generate_image = "ask" # costs money per image
"mcp__*" = "ask"
# Shell command patterns (glob, matched against the full command). First match wins.
[permissions.bash_rules]
"git status*" = "allow"
"git diff*" = "allow"
"git log*" = "allow"
"ls*" = "allow"
"pwd" = "allow"
"rm -rf /*" = "deny"
# -----------------------------------------------------------------------------
# Agents (modes)
# -----------------------------------------------------------------------------
# Built-in: build (full access), plan (read-only, proposes a plan),
# craft (study → build → check → ready), and subagents
# general + explore used by the "task" tool.
# Define your own or override built-ins here. mode = "primary" shows up in the
# mode switcher, mode = "subagent" is only available through the task tool.
# -----------------------------------------------------------------------------
# Subagents can run on a different model (and host) than the chat, and in the
# background: the main agent calls task(background = true), keeps working and
# receives the result as a message when the subagent is done.
# [agents.writer]
# mode = "subagent"
# description = "Writes documentation, summaries, guides and translations."
# model = "local/qwen3-4b" # optional, otherwise the session model
# tools = ["read", "glob", "grep"] # optional, default: all tools
# max_parallel = 1 # optional, instances at the same time
# prompt = '''
# You are a technical writer. Write clear, well-structured text.
# '''
# -----------------------------------------------------------------------------
# Image generation (fal.ai)
# -----------------------------------------------------------------------------
# Agents get the tool generate_image; the "Images" page creates images manually.
# Images are saved in the project (output_dir) and listed in a gallery.
# Model ids: https://fal.ai/models – price is per image (for the statistics).
# size_param: "image_size" (FLUX-style presets, default) or "aspect_ratio" ("16:9").
# -----------------------------------------------------------------------------
[images]
enabled = false
base_url = "https://fal.run"
api_key = "" # set in Settings
default_model = "fal-ai/flux/schnell"
output_dir = "generated"
# The agent drafts with a "preview" model, asks which draft to refine, then
# creates the final image with the best "final" model for the purpose
# (good_for: photo, illustration, text, design), passing the draft as reference
# to models with reference_param. The models below have these values built in;
# set stage / good_for / reference_param / price to override or for other models.
models = [
{ id = "fal-ai/flux/schnell", name = "FLUX.1 schnell – fast previews", price = 0.003 },
{ id = "fal-ai/nano-banana-pro/edit", name = "Nano Banana Pro – refine an image", price = 0.15 },
{ id = "fal-ai/nano-banana-pro", name = "Nano Banana Pro", price = 0.15 },
{ id = "fal-ai/flux-2-pro", name = "FLUX.2 pro" },
{ id = "fal-ai/ideogram/v3", name = "Ideogram 3 – text in images", size_param = "aspect_ratio" },
{ id = "fal-ai/recraft/v4/pro/text-to-image", name = "Recraft V4 pro – design" },
]
# -----------------------------------------------------------------------------
# Users & sign-in
# -----------------------------------------------------------------------------
# Users are managed in the web UI (Settings → Users). On first start without
# users, the server prints a one-time setup code to its log; use it in the UI
# to create the first admin – or set CODECRAFT_ADMIN_USER and
# CODECRAFT_ADMIN_PASSWORD in the environment.
#
# Every user works in their own directory <workspace>/<username>; admins can
# use the whole workspace. Note: file tools are confined to that directory,
# but shell commands run in the shared container and are not sandboxed.
# -----------------------------------------------------------------------------
[auth]
# Lifetime of a login in days.
session_days = 30
# External URL of this server – required for single sign-on (OIDC redirects).
public_url = ""
# Secure cookies: "auto" (when served via https), "true" or "false".
cookie_secure = "auto"
# Single sign-on via OpenID Connect (Authentik, Authelia, Keycloak, ...).
# Redirect URI to register at the IdP: <public_url>/api/auth/oidc/callback
[auth.oidc]
enabled = false
issuer = "https://auth.example.org/application/o/codecraft/"
client_id = "codecraft"
client_secret = "" # set in Settings
scopes = ["openid", "profile", "email", "groups"]
username_claim = "preferred_username"
groups_claim = "groups"
# Members of this group are admins; empty = manage roles in CodeCraft.
admin_group = ""
# Only members of this group may sign in; empty = no restriction.
allowed_group = ""
# Create users automatically on their first sign-in.
auto_create = true
# Link a sign-in to an existing local user with the same username.
# Only enable this if you trust the usernames of your IdP.
link_by_username = false
button_label = "Single sign-on"
# -----------------------------------------------------------------------------
# Web search (web_search tool)
# -----------------------------------------------------------------------------
[search]
# "duckduckgo" (no key, may be rate-limited), "searxng" (self-hosted, set url),
# "brave" or "tavily" (set api_key in Settings)
provider = "duckduckgo"
url = ""
api_key = ""
# -----------------------------------------------------------------------------
# Language servers (diagnostics after edits, tool "diagnostics")
# -----------------------------------------------------------------------------
# Built in: rust (rust-analyzer), typescript (typescript-language-server),
# python (pyright-langserver), go (gopls), c (clangd) – used when installed.
# TypeScript and Python servers ship with the image. Servers run in the
# user's sandbox and stop after 15 minutes without use.
# -----------------------------------------------------------------------------
[lsp]
enabled = true
# Add or override a server:
# [lsp.servers.zig]
# command = "zls"
# extensions = ["zig"]
# root_markers = ["build.zig"]
# -----------------------------------------------------------------------------
# Hooks – shell commands on agent events
# -----------------------------------------------------------------------------
# event: pre_tool | post_tool | user_prompt | stop
# matcher: tool name pattern for tool events ("*", "bash", "edit|write", "mcp__*")
# The hook gets the event as JSON on stdin (session_id, cwd, tool, input,
# result, prompt, last_message) and runs in the user's sandbox in the session
# directory. Exit 0: stdout is added as context (user_prompt, post_tool).
# Exit 2: block – pre_tool rejects the call, user_prompt drops the prompt,
# post_tool sends stderr to the agent as feedback, stop makes the agent
# continue with stderr as instruction (max. 3 times per turn).
# -----------------------------------------------------------------------------
# [[hooks]]
# event = "post_tool"
# matcher = "edit|write"
# command = 'f=$(jq -r .input.path); case "$f" in *.py) ruff format -q "$f";; esac; exit 0'
#
# [[hooks]]
# event = "pre_tool"
# matcher = "bash"
# command = 'jq -r .input.command | grep -q "git push" && { echo "No pushes from agents" >&2; exit 2; }; exit 0'
# -----------------------------------------------------------------------------
# Sandbox
# -----------------------------------------------------------------------------
# Shell commands and git of a user run in a bubblewrap sandbox: system
# read-only, only the user's folders writable, own home, own processes, no
# server environment. The network is shared (package managers keep working).
# Needs the security options in docker-compose.yml. Without a working
# sandbox, non-admin users cannot run shell commands.
[sandbox]
# "auto" = non-admin users, "always" = everybody (admins too), "off" = nobody
mode = "auto"
# -----------------------------------------------------------------------------
# Backups
# -----------------------------------------------------------------------------
# A zip with the database and this config, daily into <data_dir>/backups.
# The zip contains API keys: keep the bucket private.
[backup]
enabled = true
hour = 3 # local time
keep = 30 # local backups to keep
# Optional copy to any S3-compatible storage (AWS, MinIO, Garage, B2, …).
[backup.s3]
endpoint = "" # e.g. "https://s3.eu-central-1.amazonaws.com"
region = ""
bucket = ""
prefix = "codecraft/"
access_key = ""
secret_key = "" # "${S3_SECRET_KEY}" keeps the key out of this file
keep = 0 # backups to keep in the bucket (0 = all)
# -----------------------------------------------------------------------------
# Craft mode
# -----------------------------------------------------------------------------
[craft]
# Headless Chromium binary used to study websites and check builds.
chromium = "chromium"
# Screen widths used for screenshots and comparisons.
widths = [390, 768, 1440]
# Clean room: only what a visitor sees (screenshots, visible text, layout).
# Set to false for "deep" mode (page source and scripts may be read).
clean_room = true