#!/bin/bash # crazycoder-setup.sh # Runs via postStartCommand — configures aichat, opencode and Claude Code MCP. # Executed by Zed/VS Code/Cursor after the container starts, as remoteUser (devuser). export USERNAME=${USERNAME:-devuser} export WORKSPACE=${WORKSPACE:-/workspace} export LOCAL_LLM_API=${LOCAL_LLM_API:-http://host.docker.internal:1248/v1} export LLM_MODEL=${LLM_MODEL:-'ollama:qwen/qwen3-coder-next@4bit'} export AICHAT_API_BASE=$(sed -E 's@(https?://[^/]+).*$@\1@' <<< $LOCAL_LLM_API) export MAXTOKEN=${MAXTOKEN:-4096} export NUM_CTX=${NUM_CTX:-32768} export REPEAT_PENALTY=${REPEAT_PENALTY:-1.3} export TEMPERATURE=${TEMPERATURE:-0.5} export TOP_P=${TOP_P:-0.95} export TOP_K=${TOP_K:-40} export STOP=${STOP:-"[\"\", \"\\n\\n\", \"def \", \"class \"]"} # --- Clear Zed external_agents so Zed can re-download without "Directory not empty" --- rm -rf "$HOME/.local/share/zed/external_agents" # --- Initialise .ai_index si nécessaire --- if [ ! -d "$WORKSPACE/.ai_index" ]; then mkdir -p "$WORKSPACE/.ai_index/ast" mkdir -p "$WORKSPACE/.ai_index/embeddings" touch "$WORKSPACE/.ai_index/graph.db" fi # --- Configure PATH dans .bashrc (une seule fois) --- if ! grep -q "LOCAL_LLM_API" "$HOME/.bashrc"; then echo "export LOCAL_LLM_API=$LOCAL_LLM_API" >> "$HOME/.bashrc" echo "export AICHAT_API_BASE=$AICHAT_API_BASE" >> "$HOME/.bashrc" echo "export PATH=\$HOME/.cargo/bin:\$HOME/go/bin:\$HOME/.local/bin:\$PATH" >> "$HOME/.bashrc" fi # --- Configure aichat --- mkdir -p "$HOME/.config/aichat" cat > "$HOME/.config/aichat/config.yaml" </dev/null || true) mkdir -p "$HOME/.config/opencode" jq -n \ --arg workspace "$WORKSPACE" \ --arg gopath "$GOPATH" \ --arg rust_analyzer "$RUST_ANALYZER" \ --arg llm_api "$LOCAL_LLM_API" \ --arg llm_model "${LLM_MODEL#*:}" \ --arg full_model "$LLM_MODEL" \ --arg github_token "${GITHUB_TOKEN:-}" \ --arg gitea_url "${GITEA_URL:-}" \ --arg gitea_token "${GITEA_TOKEN:-}" \ --argjson temperature "$TEMPERATURE" \ --argjson top_p "$TOP_P" \ --argjson top_k "$TOP_K" \ --argjson max_tokens "$MAXTOKEN" \ --argjson num_ctx "$NUM_CTX" \ --argjson repeat_penalty "$REPEAT_PENALTY" \ --argjson stop "$STOP" \ '{ "$schema": "https://opencode.ai/config.json", "mcp": ({ "go-lsp-tools": { "type": "local", "command": ["mcp-language-server", "--workspace", $workspace, "--lsp", "gopls"], "enabled": true, "environment": { "GOPATH": $gopath } }, "python-lsp-tools": { "type": "local", "command": ["mcp-language-server", "--workspace", $workspace, "--lsp", "pyright-langserver", "--", "--stdio"], "enabled": true }, "rust-lsp-tools": { "type": "local", "command": ["mcp-language-server", "--workspace", $workspace, "--lsp", $rust_analyzer], "enabled": true }, "typescript-lsp-tools": { "type": "local", "command": ["mcp-language-server", "--workspace", $workspace, "--lsp", "typescript-language-server", "--", "--stdio"], "enabled": true }, "shell": { "type": "local", "command": ["mcp-server-commands"], "enabled": true }, "github": (if $github_token != "" then { "type": "local", "command": ["github-mcp-server", "stdio"], "enabled": true, "environment": { "GITHUB_PERSONAL_ACCESS_TOKEN": $github_token } } else null end), "gitea": (if ($gitea_token != "" and $gitea_url != "") then { "type": "local", "command": ["gitea-mcp", "-t", "stdio", "--host", $gitea_url, "--token", $gitea_token], "enabled": true } else null end) } | with_entries(select(.value != null))), "provider": { "lmstudio": { "npm": "@ai-sdk/openai-compatible", "name": "LM Studio (local)", "options": { "baseURL": $llm_api, "apiKey": "" }, "models": { "qwen/qwen3-coder-next@4bit": { "name": "Qwen 3 Coder Next 80B — (LLMStudio ; 4-bit)", "limit": { "context": 200000, "output": 65536 }, "settings": { "temperature": $temperature, "top_p": $top_p, "top_k": $top_k, "max_tokens": $max_tokens, "num_ctx": $num_ctx, "repeat_penalty": $repeat_penalty, "stop": $stop } }, "qwen/qwen3-coder-next@6bit": { "name": "Qwen 3 Coder Next 80B — (LLMStudio ; 6-bit)", "limit": { "context": 200000, "output": 65536 }, "settings": { "temperature": $temperature, "top_p": $top_p, "top_k": $top_k, "max_tokens": $max_tokens, "num_ctx": $num_ctx, "repeat_penalty": $repeat_penalty, "stop": $stop } } } } }, "model": $full_model }' > "$HOME/.config/opencode/opencode.json" # --- Configure MCP pour Claude Code --- MCP_SERVERS=$(jq -n \ --arg workspace "$WORKSPACE" \ --arg gopath "$GOPATH" \ --arg rust_analyzer "$RUST_ANALYZER" \ --arg github_token "${GITHUB_TOKEN:-}" \ --arg gitea_url "${GITEA_URL:-}" \ --arg gitea_token "${GITEA_TOKEN:-}" \ '{ "go-lsp-tools": { "type": "stdio", "command": "mcp-language-server", "args": ["--workspace", $workspace, "--lsp", "gopls"], "env": { "GOPATH": $gopath } }, "python-lsp-tools": { "type": "stdio", "command": "mcp-language-server", "args": ["--workspace", $workspace, "--lsp", "pyright-langserver", "--", "--stdio"] }, "rust-lsp-tools": { "type": "stdio", "command": "mcp-language-server", "args": ["--workspace", $workspace, "--lsp", $rust_analyzer] }, "typescript-lsp-tools": { "type": "stdio", "command": "mcp-language-server", "args": ["--workspace", $workspace, "--lsp", "typescript-language-server", "--", "--stdio"] }, "github": (if $github_token != "" then { "type": "stdio", "command": "github-mcp-server", "args": ["stdio"], "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": $github_token } } else null end), "shell": { "type": "stdio", "command": "mcp-server-commands" }, "gitea": (if ($gitea_token != "" and $gitea_url != "") then { "type": "stdio", "command": "gitea-mcp", "args": ["-t", "stdio", "--host", $gitea_url, "--token", $gitea_token] } else null end) } | with_entries(select(.value != null))') CLAUDE_JSON="$HOME/.claude/.claude.json" mkdir -p "$HOME/.claude" if [ -f "$CLAUDE_JSON" ]; then jq --argjson mcp "$MCP_SERVERS" '. + {mcpServers: $mcp}' "$CLAUDE_JSON" > "${CLAUDE_JSON}.tmp" \ && mv "${CLAUDE_JSON}.tmp" "$CLAUDE_JSON" else jq -n --argjson mcp "$MCP_SERVERS" '{mcpServers: $mcp}' > "$CLAUDE_JSON" fi