push-oynptmmzrplt #2

Merged
eric merged 10 commits from push-oynptmmzrplt into main 2026-03-11 22:47:25 +01:00
9 changed files with 405 additions and 184 deletions
Showing only changes of commit 6556c84e29 - Show all commits

View File

@@ -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

View File

@@ -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",
}

View File

@@ -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"]

View File

@@ -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

View File

@@ -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):

View File

@@ -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`

191
bin/crazycoder-setup.sh Normal file
View 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

View File

@@ -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:-"[\"</s>\", \"\\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 <<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
chown ${USERNAME}:${USERNAME} ${AICHAT_CONFIG}
mkdir -p /home/${USERNAME}/.config/opencode
CONFIG_FILE=/home/${USERNAME}/.config/opencode/opencode.json
cat > $CONFIG_FILE <<EOF
{
"\$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", "$(find /usr/local/rustup/toolchains -type f -name rust-analyzer)"
],
"enabled": true
},
"typescript-lsp-tools": {
"type": "local",
"command": ["mcp-language-server",
"--workspace", "$WORKSPACE",
"--lsp", "typescript-language-server",
"--", "--stdio"
],
"enabled": true
},
},
"provider": {
"lmstudio": {
"npm": "@ai-sdk/openai-compatible",
"name": "LM Studio (local)",
"options": {
"baseURL": "$LOCAL_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": ${MAXTOKEN},
"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": ${MAXTOKEN},
"num_ctx": ${NUM_CTX},
"repeat_penalty": ${REPEAT_PENALTY},
"stop": ${STOP}
}
}
}
}
},
"model": "${LLM_MODEL}"
}
EOF
chown ${USERNAME}:${USERNAME} ${CONFIG_FILE}
fi
# --- Force Bash comme shell par défaut ---
if [ -z "$1" ]; then
exec /bin/bash
else

View File

@@ -46,6 +46,7 @@ mkdir -p "$DEVCONTAINER_DIR/config/cargo-bin"
mkdir -p "$DEVCONTAINER_DIR/config/go-cache"
mkdir -p "$DEVCONTAINER_DIR/config/go-bin"
mkdir -p "$DEVCONTAINER_DIR/config/python-local"
mkdir -p "$DEVCONTAINER_DIR/config/zed"
mkdir -p "$DEVCONTAINER_DIR/config/npm-cache"
mkdir -p "$DEVCONTAINER_DIR/config/r-libs"
@@ -55,8 +56,13 @@ if [[ -f "$DEVCONTAINER_JSON" ]]; then
warning "devcontainer.json already exists, skipping"
else
fetch ".devcontainer/devcontainer.json" "$DEVCONTAINER_JSON"
# Rename the service name to match the project
sed -i.bak "s/\"name\": \"PMOCrazyCoder\"/\"name\": \"$(basename "$PROJECT_DIR")\"/" "$DEVCONTAINER_JSON"
PROJECT_NAME="$(basename "$PROJECT_DIR")"
SERVICE_NAME="crazycoder-${PROJECT_NAME}"
# Patch display name and service reference
sed -i.bak \
-e "s/\"name\": \"PMOCrazyCoder\"/\"name\": \"${PROJECT_NAME}\"/" \
-e "s/\"service\": \"pmocrazycoder\"/\"service\": \"${SERVICE_NAME}\"/" \
"$DEVCONTAINER_JSON"
rm -f "$DEVCONTAINER_JSON.bak"
info "Created devcontainer.json"
fi
@@ -67,33 +73,97 @@ if [[ -f "$COMPOSE_FILE" ]]; then
warning "docker-compose.yml already exists, skipping"
else
fetch ".devcontainer/docker-compose.yml" "$COMPOSE_FILE"
PROJECT_NAME="$(basename "$PROJECT_DIR")"
SERVICE_NAME="crazycoder-${PROJECT_NAME}"
# Rename the service to avoid collision with project's own Docker services
sed -i.bak "s/^ pmocrazycoder:/ ${SERVICE_NAME}:/" "$COMPOSE_FILE"
# Patch LOCAL_LLM_API if overridden
if [[ "$LOCAL_LLM_API" != "http://host.docker.internal:1248/v1" ]]; then
sed -i.bak "s|http://host.docker.internal:1248/v1|${LOCAL_LLM_API}|g" "$COMPOSE_FILE"
rm -f "$COMPOSE_FILE.bak"
fi
rm -f "$COMPOSE_FILE.bak"
info "Created docker-compose.yml"
fi
# --- .env ---
ENV_FILE="$DEVCONTAINER_DIR/.env"
if [[ -f "$ENV_FILE" ]]; then
warning ".env already exists, skipping"
else
# Détecter la forge depuis l'origin git du projet
DETECTED_GITEA_URL=""
IS_GITHUB=false
if command -v git >/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"