Refactor devcontainer setup with MCP servers and dynamic config
Ajout d'une configuration DevContainer complète avec MCP Servers (LSP multi-langages, GitHub/Gitea), scripts de setup automatisés (crazycoder-setup.sh, setup-devcontainer.sh), et optimisation de l’image Docker (rustup/cargo-binstall, nettoyage des artefacts). Mise à jour du README avec documentation MCP et gestion dynamique des variables d’environnement (.env généré automatiquement selon la forge détectée). Simplification des entrypoints et postStartCommand pour une initialisation plus fiable.
This commit is contained in:
191
bin/crazycoder-setup.sh
Normal file
191
bin/crazycoder-setup.sh
Normal file
@@ -0,0 +1,191 @@
|
||||
#!/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:-"[\"</s>\", \"\\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" <<EOF
|
||||
model: lmstudio:${LLM_MODEL#*:}
|
||||
clients:
|
||||
- type: openai-compatible
|
||||
name: lmstudio
|
||||
api_base: ${LOCAL_LLM_API}
|
||||
api_key: placeholder
|
||||
models:
|
||||
- name: ${LLM_MODEL#*:}
|
||||
max_input_tokens: ${NUM_CTX}
|
||||
temperature: ${TEMPERATURE}
|
||||
top_p: ${TOP_P}
|
||||
EOF
|
||||
|
||||
# --- Configure opencode ---
|
||||
RUST_ANALYZER=$(rustup which --toolchain nightly rust-analyzer 2>/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
|
||||
Reference in New Issue
Block a user