diff --git a/.devcontainer/.env.example b/.devcontainer/.env.example new file mode 100644 index 0000000..7ee6092 --- /dev/null +++ b/.devcontainer/.env.example @@ -0,0 +1,9 @@ +# Copier ce fichier en .env et renseigner les valeurs +# Le fichier .env est ignoré par git + +# GitHub +GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx + +# Gitea +GITEA_URL=https://gitea.example.com +GITEA_TOKEN=xxxxxxxxxxxxxxxxxxxx diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f560a7a..1a31bca 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,7 +1,7 @@ { "name": "PMOCrazyCoder", "dockerComposeFile": "docker-compose.yml", - "service": "pmocrazy-coder", + "service": "pmocrazycoder", "workspaceFolder": "/workspace", "remoteUser": "devuser", "settings": { @@ -9,5 +9,6 @@ }, "features": {}, "postCreateCommand": "npm install -g @anthropic-ai/claude-code", + "postStartCommand": "/usr/local/bin/crazycoder-setup.sh", "shutdownAction": "stopCompose", } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 639528e..48f4d9c 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,5 +1,5 @@ services: - pmocrazy-coder: + pmocrazycoder: image: niepce.petite-maison-orange.fr/public/pmocrazycoder:latest volumes: - ..:/workspace @@ -13,12 +13,16 @@ services: - ./config/go-cache:/home/devuser/.cache/go - ./config/go-bin:/home/devuser/go/bin - ./config/python-local:/home/devuser/.local + - ./config/zed:/home/devuser/.local/share/zed - ./config/npm-cache:/home/devuser/.npm - ./config/r-libs:/home/devuser/R working_dir: /workspace environment: - LOCAL_LLM_API=http://host.docker.internal:1248/v1 - CLAUDE_CONFIG_DIR=/home/devuser/.claude + - GITHUB_TOKEN=${GITHUB_TOKEN:-} + - GITEA_URL=${GITEA_URL:-} + - GITEA_TOKEN=${GITEA_TOKEN:-} user: devuser entrypoint: ["/usr/local/bin/entrypoint.sh"] command: ["bash", "-c", "tail -f /dev/null"] diff --git a/Dockerfile b/Dockerfile index af655ac..33051cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,60 +1,75 @@ FROM debian:bookworm-slim -RUN apt-get update && apt-get install -y \ - clang clangd llvm-dev libclang-dev pkg-config build-essential git make curl nodejs npm \ - pylint black flake8 mypy isort logrotate sudo cmake libicu-dev zlib1g-dev \ - libcurl4-openssl-dev libssl-dev ruby-dev jq python3 python3-pip golang-go rustc cargo \ - r-base sqlite3 wget bash zsh libgit2-dev \ +# Base system packages — no golang-go/rustc/cargo from apt (replaced by rustup + install_latest_go.sh) +RUN apt-get update && apt-get install -y --no-install-recommends \ + clang clangd pkg-config build-essential git make curl \ + nodejs npm \ + python3 python3-pip pylint black flake8 mypy isort \ + logrotate sudo cmake zlib1g-dev \ + libcurl4-openssl-dev libssl-dev \ + ruby-dev libicu-dev \ + jq \ + r-base \ + sqlite3 wget bash zsh \ && apt-get clean && rm -rf /var/lib/apt/lists/* -RUN echo "deb http://deb.debian.org/debian testing main" > /etc/apt/sources.list.d/testing.list - +# Go — latest via script only (no apt golang-go) COPY tools/install_latest_go.sh /tmp/install_latest_go.sh -RUN bash /tmp/install_latest_go.sh && \ - echo "PATH=/usr/local/go/bin:$PATH" >> /root/.bashrc +RUN bash /tmp/install_latest_go.sh && rm /tmp/install_latest_go.sh ENV PATH=/usr/local/go/bin:$PATH +# Rust — rustup only (no apt rustc/cargo), stable only, no rust-docs ENV RUSTUP_HOME=/usr/local/rustup ENV CARGO_HOME=/usr/local/cargo ENV PATH=/usr/local/cargo/bin:$PATH -RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable && \ - rustup install nightly && \ - rustup component add rustfmt clippy rust-src rust-docs && \ - cargo install cargo-edit cargo-watch cargo-outdated cargo-make cargo-binstall +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable --no-modify-path \ + && rustup component add rustfmt clippy rust-src rust-analyzer \ + && rustup install nightly --no-self-update \ + && rustup component add rust-analyzer --toolchain nightly \ + # Install cargo tools via binstall (prebuilt binaries, much faster & smaller) + && cargo install cargo-binstall \ + && cargo binstall --no-confirm \ + cargo-watch cargo-outdated cargo-make \ + jj-cli aichat \ + && cargo install cargo-edit \ + && cargo install --git https://github.com/nilskch/jj-lsp.git \ + # Clean build cache (keep installed binaries, drop registry & build artifacts) + && rm -rf $CARGO_HOME/registry $CARGO_HOME/git /tmp/cargo-* -RUN npm install -g typescript tsc @types/node eslint prettier pyright typescript-language-server +# Node tools +RUN npm install -g typescript @types/node eslint prettier pyright typescript-language-server \ + mcp-server-commands \ + && npm cache clean --force -RUN cargo binstall --strategies crate-meta-data jj-cli aichat \ - && cargo install --git https://github.com/nilskch/jj-lsp.git - -RUN gem install github-linguist +# Ruby tools +RUN gem install github-linguist \ + && gem cleanup \ + && rm -rf /var/cache/gem /root/.gem/specs +# Python tools RUN pip install --no-cache-dir --break-system-packages ruff fastmcp ipython tasklin -RUN curl -fsSL https://opencode.ai/install | bash && \ - mkdir -p /root/.config/opencode && \ - mv /root/.opencode/bin/opencode /usr/local/bin/ - +# Go tools ENV GOPATH=/usr/local/go ENV GOBIN=/usr/local/go/bin RUN go install golang.org/x/tools/gopls@latest \ - && go install github.com/isaacphi/mcp-language-server@latest + && go install github.com/isaacphi/mcp-language-server@latest \ + && go install github.com/github/github-mcp-server/cmd/github-mcp-server@latest \ + && go install gitea.com/gitea/gitea-mcp@latest \ + && go clean -cache -modcache +# Shell completions RUN mkdir -p /etc/bash_completion.d \ - && curl https://raw.githubusercontent.com/sigoden/aichat/main/scripts/completions/aichat.bash \ + && curl -fsSL https://raw.githubusercontent.com/sigoden/aichat/main/scripts/completions/aichat.bash \ > /etc/bash_completion.d/aichat \ && jj util completion bash > /etc/bash_completion.d/jj -RUN rustup component add rust-analyzer --toolchain nightly - +# Non-root user ARG USERNAME=devuser ARG USER_UID=1000 ARG USER_GID=1000 RUN groupadd --gid $USER_GID $USERNAME && \ - useradd --uid $USER_UID \ - --gid $USER_GID \ - -m $USERNAME \ - --shell /bin/bash && \ + useradd --uid $USER_UID --gid $USER_GID -m $USERNAME --shell /bin/bash && \ echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/$USERNAME && \ chmod 440 /etc/sudoers.d/$USERNAME @@ -64,7 +79,9 @@ RUN chmod +x /usr/local/bin/entrypoint.sh COPY bin/* /usr/local/bin/ RUN chmod +x /usr/local/bin/* RUN mkdir -p /home/${USERNAME}/.config \ - && chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.config + && mkdir -p /home/${USERNAME}/.cache \ + && chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.config \ + && chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.cache COPY --chown=${USERNAME}:${USERNAME} opencode /home/${USERNAME}/.config/opencode USER $USERNAME diff --git a/Makefile b/Makefile index 3ee4106..939d938 100644 --- a/Makefile +++ b/Makefile @@ -94,13 +94,14 @@ build-multiplatform: .builder-ready || docker buildx create --name $(BUILDER) --driver docker-container --bootstrap touch .builder-ready -## push : Push the OCI archive to the registry using skopeo +## push : Push the OCI archive to the registry using skopeo, then pull locally push: $(OCI_ARCHIVE) skopeo copy \ --dest-tls-verify=true \ --multi-arch all \ oci-archive:$(OCI_ARCHIVE) \ docker://$(FULL_IMAGE) + docker pull $(FULL_IMAGE) # Ensure the OCI archive exists before pushing $(OCI_ARCHIVE): diff --git a/README.md b/README.md index 5b5f1b3..e4363aa 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,58 @@ Environnement de développement conteneurisé avec assistance IA intégrée — - `sqlite3`, `cmake`, `make`, `git` - `aichat` (client LLM CLI) +### MCP Servers installés + +Le conteneur inclut plusieurs MCP servers pour l'intégration avec les outils de développement : + +| MCP Server | Utilisation | +|---|---| +| `mcp-language-server` | LSP pour Go (gopls), Python (pyright-langserver), Rust (rust-analyzer), TypeScript | +| `github-mcp-server` | Intégration GitHub (repos, issues, PR) via `GITHUB_TOKEN` | +| `gitea-mcp` | Intégration Gitea/Forgejo via `GITEA_URL` et `GITEA_TOKEN` | +| `mcp-server-commands` | Exécution de commandes shell depuis l'assistant IA | + ### Assistants IA - **Claude Code** (installé au `postCreateCommand`) - **opencode** (configuré via l'entrypoint) - **aichat** (configuré via l'entrypoint, connecté au LLM local) +### Configuration MCP dans les assistants + +Les MCP servers sont automatiquement configurés dans opencode et Claude Code via `crazycoder-setup.sh` (exécuté en `postStartCommand`) : + +**opencode** (`~/.config/opencode/opencode.json`) : +- LSP pour Go, Python, Rust, TypeScript via `mcp-language-server` +- Commandes shell via `mcp-server-commands` +- GitHub MCP (si `GITHUB_TOKEN` défini) +- Gitea MCP (si `GITEA_URL` et `GITEA_TOKEN` définis) + +**Claude Code** (`~/.claude/.claude.json`) : +- Même configuration MCP qu'opencode + +Les configurations sont générées dynamiquement au démarrage du conteneur avec les variables d'environnement disponibles. + +## Variables d'environnement (.env) + +Le fichier `.env` (non versionné, ignoré par git) contient les credentials pour les services externes : + +| Variable | Description | Exemple | +|---|---|---| +| `GITHUB_TOKEN` | Personal Access Token GitHub pour l'accès API | `ghp_xxxxxxxxxxxxxxxxxxxx` | +| `GITEA_URL` | URL de votre serveur Gitea/Forgejo | `https://gitea.example.com` | +| `GITEA_TOKEN` | Token d'accès API Gitea | `xxxxxxxxxxxxxxxxxxxx` | + +Copiez `.devcontainer/.env.example` vers `.env` et renseignez vos tokens : + +```bash +cp .devcontainer/.env.example .env +# Éditer .env avec vos credentials +``` + +Ces variables sont utilisées par : +- Le MCP Server GitHub (pour les opérations repo/issues/PR) +- Le MCP Server Gitea (intégration avec votre instance) + ## Utilisation rapide ### Ajouter l'environnement à un projet existant @@ -89,6 +136,22 @@ pip install --user monpaquet R -e 'install.packages("tidyverse")' ``` +## Configuration MCP détaillée + +### opencode + +Le fichier `~/.config/opencode/opencode.json` est généré à chaque démarrage avec : + +- **LSP servers** : gopls, pyright-langserver, rust-analyzer, typescript-language-server +- **Shell commands** : `mcp-server-commands` +- **GitHub MCP** : activé si `GITHUB_TOKEN` est défini +- **Gitea MCP** : activé si `GITEA_URL` et `GITEA_TOKEN` sont définis +- **Provider LM Studio** : endpoint local configurable + +### Claude Code + +Le fichier `~/.claude/.claude.json` est mis à jour avec la section `mcpServers` contenant les mêmes serveurs que opencode. + ## LLM local L'entrypoint configure automatiquement opencode et aichat pour pointer sur `LOCAL_LLM_API` diff --git a/bin/crazycoder-setup.sh b/bin/crazycoder-setup.sh new file mode 100644 index 0000000..2ccdfce --- /dev/null +++ b/bin/crazycoder-setup.sh @@ -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:-"[\"\", \"\\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 diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 2f698a0..90e63df 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,150 +1,15 @@ #!/bin/bash -# docker-entrypoint.sh +# docker-entrypoint.sh — minimal, just sets up PATH and execs the command -export USERNAME=${USERNAME:-devuser} +export PATH=/usr/local/cargo/bin:/usr/local/go/bin:$HOME/.local/bin:$PATH -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 \"]"} +# Set npm prefix to user directory so global installs don't require root +npm config set prefix "$HOME/.local" -# --- 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 +# Clear Zed external_agents on each start so Zed can re-download without +# hitting "Directory not empty" on atomic rename. +rm -rf "$HOME/.local/share/zed/external_agents" -# --- Configure LOCAL_LLM_API pour Bash --- -if [ -n "$LOCAL_LLM_API" ]; then - if ! grep -q "LOCAL_LLM_API" /home/${USERNAME}/.bashrc; then - echo "export LOCAL_LLM_API=$LOCAL_LLM_API" >> /home/${USERNAME}/.bashrc - echo "export AICHAT_API_BASE=$AICHAT_API_BASE" >> /home/${USERNAME}/.bashrc - echo "export PATH=\$HOME/.cargo/bin:\$HOME/go/bin:\$HOME/.local/bin:\$PATH" >> /home/${USERNAME}/.bashrc - fi - - # --- Configure aichat --- - mkdir -p /home/${USERNAME}/.config/aichat - AICHAT_CONFIG=/home/${USERNAME}/.config/aichat/config.yaml - cat > $AICHAT_CONFIG < $CONFIG_FILE </dev/null && git -C "$PROJECT_DIR" rev-parse --git-dir &>/dev/null; then + ORIGIN_URL=$(git -C "$PROJECT_DIR" remote get-url origin 2>/dev/null || true) + if [[ -n "$ORIGIN_URL" ]]; then + # Extraire scheme + host (supporte https://host/... et git@host:...) + if [[ "$ORIGIN_URL" =~ ^(https?://[^/]+) ]]; then + ORIGIN_HOST="${BASH_REMATCH[1]}" + elif [[ "$ORIGIN_URL" =~ ^git@([^:]+): ]]; then + ORIGIN_HOST="https://${BASH_REMATCH[1]}" + fi + + if [[ "${ORIGIN_HOST:-}" == *"github.com"* ]]; then + IS_GITHUB=true + info "Forge détectée : GitHub" + elif [[ -n "${ORIGIN_HOST:-}" ]]; then + # Tester si c'est un Gitea/Forgejo via son API + if curl -fsSL --max-time 5 "${ORIGIN_HOST}/api/v1/version" 2>/dev/null | grep -q '"version"'; then + DETECTED_GITEA_URL="$ORIGIN_HOST" + info "Forge détectée : Gitea/Forgejo ($DETECTED_GITEA_URL)" + else + warning "Forge non reconnue pour $ORIGIN_HOST (ni GitHub ni Gitea/Forgejo)" + fi + fi + fi + fi + + { + echo "# Secrets du devcontainer — ne pas committer" + echo "" + if $IS_GITHUB; then + echo "# GitHub" + echo "GITHUB_TOKEN=" + else + echo "# GitHub (non détecté pour ce projet)" + echo "# GITHUB_TOKEN=" + fi + echo "" + if [[ -n "$DETECTED_GITEA_URL" ]]; then + echo "# Gitea" + echo "GITEA_URL=${DETECTED_GITEA_URL}" + echo "GITEA_TOKEN=" + else + echo "# Gitea (non détecté pour ce projet)" + echo "# GITEA_URL=" + echo "# GITEA_TOKEN=" + fi + } > "$ENV_FILE" + + info "Created .env" +fi + # --- .gitignore --- GITIGNORE="$PROJECT_DIR/.gitignore" if [[ -f "$GITIGNORE" ]]; then if grep -q "\.devcontainer/config/" "$GITIGNORE"; then warning ".gitignore already contains .devcontainer/config/, skipping" else - printf '\n# PMOCrazyCoder devcontainer runtime config\n.devcontainer/config/\n' >> "$GITIGNORE" + printf '\n# PMOCrazyCoder devcontainer runtime config\n.devcontainer/config/\n.devcontainer/.env\n' >> "$GITIGNORE" info "Updated .gitignore" fi else - printf '# PMOCrazyCoder devcontainer runtime config\n.devcontainer/config/\n' > "$GITIGNORE" + printf '# PMOCrazyCoder devcontainer runtime config\n.devcontainer/config/\n.devcontainer/.env\n' > "$GITIGNORE" info "Created .gitignore" fi echo info "Done. Next steps:" -echo " 1. Open the project in VS Code / Zed / Cursor" -echo " 2. Reopen in container (the image will be pulled from the registry)" -echo " 3. Run /login in the Claude Agent panel to authenticate" +echo " 1. Renseigner les tokens dans .devcontainer/.env" +echo " 2. Open the project in VS Code / Zed / Cursor" +echo " 3. Reopen in container (the image will be pulled from the registry)" +echo " 4. Run /login in the Claude Agent panel to authenticate" echo info "To use a different local LLM API endpoint:" echo " LOCAL_LLM_API=http://myserver:1248/v1 bash setup-devcontainer.sh"