fix: move more into install.sh

model config, downloads, llama-server service and a README.md to explain --host
This commit is contained in:
Brydon DeWitt 2026-06-08 20:38:24 -04:00
parent d237fcfcd3
commit 2a00366204
8 changed files with 493 additions and 25 deletions

39
README.md Normal file
View File

@ -0,0 +1,39 @@
# dotfiles
Personal dotfiles and AI-agent infrastructure for VS Code Copilot and OpenCode.
## Quick Start
For host machines, install dotfiles plus llama-server config and systemd services via the `--host` flag:
```bash
git clone https://github.com/username/dotfiles ~/dotfiles
~/dotfiles/install.sh --host
```
If using devcontainers, drop the `--host` flag in the Dockerfile or just rely on vscode settings or, possibly better, a devcontainer "features" config such as:
```json
"features": {
"ghcr.io/willfantom/features/dotfiles:1": {
"repository": "git@git.bcdewitt.ddns.net:bcdewitt/dotfiles.git",
"targetPath": "~/dotfiles",
"installCommand": "install.sh"
}
}
```
## What Gets Installed
**Basic install** (`install.sh`):
- Agent hooks wired into VS Code Copilot and OpenCode (the `.agents/` infrastructure)
- OpenCode config symlinked to `~/.config/opencode/opencode.json`
**Host install** (`install.sh --host`):
- Everything in basic install, plus:
- llama-server presets, startup script, and systemd units from `config/llama-server/`
## Idempotent
The install script is idempotent — safe to re-run at any time. It skips steps that
are already in place and only changes what needs updating.

View File

@ -0,0 +1,8 @@
[Unit]
Description=Restart llama-server when presets.ini changes
[Path]
PathModified=/home/dev/models/presets.ini
[Install]
WantedBy=default.target

View File

@ -0,0 +1,6 @@
[Unit]
Description=Restart llama-server (triggered by presets.ini change)
[Service]
Type=oneshot
ExecStart=/bin/systemctl restart llama-server

View File

@ -0,0 +1,15 @@
[Unit]
Description=llama-server
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/opt/llama-server/start.sh
User=ollama
Group=ollama
Restart=always
RestartSec=3
Environment="PATH=/home/dev/.nvm/versions/node/v24.15.0/bin:/home/dev/.opencode/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/lib/wsl/lib:/mnt/c/Program Files (x86)/NVIDIA Corporation/PhysX/Common:/mnt/c/Python312/Scripts/:/mnt/c/Python312/:/mnt/c/Program Files/Microsoft/jdk-17.0.8.7-hotspot/bin:/mnt/c/Program Files/Oculus/Support/oculus-runtime:/mnt/c/Windows/system32:/mnt/c/Windows:/mnt/c/Windows/System32/Wbem:/mnt/c/Windows/System32/WindowsPowerShell/v1.0/:/mnt/c/Windows/System32/OpenSSH/:/mnt/c/Program Files/dotnet/:/mnt/c/Program Files/Microsoft VS Code/bin:/mnt/c/WINDOWS/system32:/mnt/c/WINDOWS:/mnt/c/WINDOWS/System32/Wbem:/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/:/mnt/c/WINDOWS/System32/OpenSSH/:/mnt/c/Program Files/nodejs/:/mnt/c/ProgramData/chocolatey/bin:/mnt/c/Users/Dev/AppData/Local/Programs/cursor/resources/app/bin:/mnt/c/WINDOWS/system32:/mnt/c/WINDOWS:/mnt/c/WINDOWS/System32/Wbem:/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/:/mnt/c/WINDOWS/System32/OpenSSH/:/mnt/c/Program Files/Docker/Docker/resources/bin:/mnt/c/Users/Dev/AppData/Local/Microsoft/WindowsApps:/mnt/c/Users/Dev/AppData/Roaming/npm:/mnt/c/Users/Dev/.dotnet/tools:/mnt/c/Users/Dev/AppData/Local/Microsoft/WinGet/Packages/albertony.npiperelay_Microsoft.Winget.Source_8wekyb3d8bbwe:/mnt/c/Users/Dev/.lmstudio/bin:/snap/bin"
[Install]
WantedBy=default.target

View File

@ -0,0 +1,147 @@
version = 1
; ─── Global ──────────────────────────────────────────────────────────────────
; Settings in [*] are inherited by every model loaded by the router.
; Per-model sections below override individual keys.
[*]
; Number of model layers to offload to GPU.
; 99 means "offload everything" — llama.cpp loads as many as fit and falls back
; to CPU automatically for any overflow. Using an explicit value avoids the
; occasional conservative auto-estimate.
; Default: auto
; n-gpu-layers = 99
; Flash Attention: reduces KV-cache VRAM usage and speeds up long-context
; inference by computing attention without materializing the full NxN matrix.
; "on" forces it; "auto" (default) enables it when CUDA is detected — same
; effect in practice, but explicit is clearer here.
; Default: auto
flash-attn = on
; Number of CPU threads used for non-GPU work: tokenization, sampling, and any
; layers that overflow VRAM during hybrid inference. ~2/3 of physical cores is
; the rule of thumb; going higher causes contention on the same cores the GPU
; DMA uses. (Machine has 12 logical cores → 8 threads.)
; Default: -1 (use all cores)
threads = 8
; Number of inference slots (parallel sequences). 1 = single-user server with
; no batching overhead. Increase only if you need concurrent requests; each
; extra slot consumes a proportional share of KV-cache VRAM.
; Default: -1 (auto, usually 1)
parallel = 1
; Jinja2 chat templating — required for models with complex chat templates
; (e.g. Qwen3, which uses raise_exception() guards). Without this, llama.cpp
; falls back to a static PEG auto-parser that can't handle those templates.
jinja = on
; Token budget for chain-of-thought reasoning.
; -1 = unrestricted (model decides when to stop thinking)
; 0 = disable thinking entirely
; N = hard cap at N tokens, then force the model to answer
; Commented out: matches the default (-1 = unrestricted).
; reasoning-budget = -1
ctx-size = 32768
n-predict = 4096
; ─── Qwen3-14B ───────────────────────────────────────────────────────────────
; ~8.5 GB GGUF — fits fully in 12 GB VRAM. Fast (~1218 tok/s). Good daily
; driver for interactive coding and Q&A.
[Qwen_Qwen3-14B-Q4_K_M]
; Full 32 K context is safe: 14B fits in VRAM with plenty of headroom for the
; KV cache. At 32 K × 2 bytes × 2 (K+V) × 40 layers ≈ ~5 GB worst-case KV.
; Default: 0 (read from model metadata, typically the training context limit)
ctx-size = 32768
; Cap generation at 4096 tokens. Prevents runaway responses; raise if you need
; longer output (documentation, large refactors). Default: -1 (unlimited)
n-predict = 4096
; ─── OmniCoder-2-9B ──────────────────────────────────────────────────────────
; ~9.4 GB GGUF — fits fully in 12 GB VRAM. Fast generation. Vision-capable
; (multimodal projector at OmniCoder-2-9B.Q8_0/mmproj-Q8_0.gguf — auto-detected
; from subdirectory layout by the router).
[OmniCoder-2-9B.Q8_0]
; Full 32 K context fits comfortably alongside 9B weights.
; Default: 0 (read from model metadata)
ctx-size = 32768
; Cap generation at 4096 tokens. Default: -1 (unlimited)
n-predict = 4096
; ─── Qwen3.6-35B-A3B (MoE + MTP) ────────────────────────────────────────────
; 13.6 GB GGUF — ~12 GB on GPU, ~1.6 GB CPU offload on a 12 GB card.
; MoE model: only ~3B parameters active per forward pass despite 35B total.
; MTP (multi-token prediction) heads baked in — uses draft-mtp speculative
; decoding to roughly double throughput vs non-speculative. Requires b9279+.
[Qwen3.6-35B-A3B-IQ3_S-3.06bpw]
; KV cache is small (~31 MiB/1K tokens) due to GQA — 32K context only needs
; ~1 GB KV cache, which pages to CPU gracefully without major throughput loss.
ctx-size = 32768
; Cap generation at 4096 tokens. Default: -1 (unlimited)
n-predict = 4096
; Multi-token prediction speculative decoding.
; spec-type = draft-mtp uses MTP heads built into the model weights.
spec-type = draft-mtp
; Minimum acceptance probability for a speculated draft token (01).
; 0.75 = accept tokens the model is 75%+ confident in. Lower = more aggressive
; speculation (faster but slightly more divergence risk).
spec-draft-p-min = 0.75
; Max tokens to speculate per step. 3 is the sweet spot for Qwen3.6 MTP.
spec-draft-n-max = 3
; ─── Qwen3.6-27B ─────────────────────────────────────────────────────────────
; 17 GB GGUF — ~12 GB on GPU, ~5 GB CPU offload on a 12 GB card.
; Slower (~48 tok/s) due to CPU↔GPU transfers; best for deep analysis tasks.
[Qwen_Qwen3.6-27B-Q4_K_M]
; Smaller context than 14B to keep the KV cache on-GPU. At 16 K the KV cache
; is roughly half the size, which reduces how much spills to CPU on each
; forward pass — meaningful when every byte of VRAM is already spoken for.
; Default: 0 (read from model metadata)
ctx-size = 16384
; Cap generation at 4096 tokens. Default: -1 (unlimited)
n-predict = 4096
[Qwopus3.6-27B-v2-MTP-Q4_K_M]
ctx-size = 32768
n-predict = 4096
spec-type = draft-mtp
spec-draft-p-min = 0.75
spec-draft-n-max = 3
[Qwopus3.6-35B-A3B-v1-MTP-Q4_K_M]
ctx-size = 32768
n-predict = 4096
spec-type = draft-mtp
spec-draft-p-min = 0.75
spec-draft-n-max = 3
[Qwopus3.5-9B-Coder-MTP-Q8_0]
ctx-size = 65536
n-predict = 4096
spec-type = draft-mtp
spec-draft-p-min = 0.75
spec-draft-n-max = 3
[agentica-org_DeepCoder-14B-Preview-Q5_K_M]
ctx-size = 32768
n-predict = 4096

9
config/llama-server/start.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
export LD_LIBRARY_PATH=/opt/llama-server${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
cd /opt/llama-server
exec /opt/llama-server/llama-server \
--models-dir /home/dev/models \
--models-max 1 \
--models-preset /home/dev/models/presets.ini \
--host 127.0.0.1 \
--port 8080

View File

@ -0,0 +1,172 @@
{
"$schema": "https://opencode.ai/config.json",
"default_agent": "orchestrator",
"compaction": {
"reserved": 3000
},
"agent": {
"orchestrator": {
"mode": "all",
"model": "llama-server/Qwopus3.6-27B-v2-MTP-Q4_K_M",
"permission": {
"edit": "deny",
"bash": {
"*": "deny",
"* /tmp/.last-user-prompt.txt": "allow",
"* /tmp/.last-user-prompt.txt << *": "allow"
}
}
},
"build": {
"mode": "subagent",
"permission": {
"webfetch": "deny",
"websearch": "deny",
"question": "deny",
"todowrite": "deny",
"skill": "deny"
}
},
"research": {
"mode": "all",
"permission": {
"*": "allow"
}
}
},
"permission": {
"external_directory": {
"/tmp/**": "allow",
"~/dotfiles/**": "allow",
"~/.config/opencode/**": "allow",
"~/.local/share/opencode/log/**": "allow",
"~/.copilot/**": "allow",
"~/code/**": "allow"
},
"websearch": "allow"
},
"share": "disabled",
"lsp": true,
"provider": {
"llama-server": {
"npm": "@ai-sdk/openai-compatible",
"name": "llama-server",
"options": {
"baseURL": "http://127.0.0.1:8080/v1"
},
"models": {
"OmniCoder-2-9B.Q8_0": {
"name": "OmniCoder 2 9B Q8 (llama-server)",
"tools": true,
"agent": {
"plan": {
"temperature": 0.1
},
"build": {
"temperature": 0.3
},
"brainstorm": {
"temperature": 0.7
}
},
"limit": {
"context": 32768,
"output": 4096
}
},
"Qwopus3.5-9B-Coder-MTP-Q8_0": {
"name": "Qwopus3.5 9B Coder MTP Q8 (llama-server)",
"tools": true,
"agent": {
"plan": {
"temperature": 0.1
},
"build": {
"temperature": 0.3
},
"brainstorm": {
"temperature": 0.7
}
},
"limit": {
"context": 32768,
"output": 4096
}
},
"Qwopus3.6-27B-v2-MTP-Q4_K_M": {
"name": "Qwopus3.6 27B MTP Q4 (llama-server)",
"tools": true,
"agent": {
"plan": {
"temperature": 0.1
},
"orchestrator": {
"temperature": 0.2
},
"build": {
"temperature": 0.3
},
"brainstorm": {
"temperature": 0.7
}
},
"limit": {
"context": 32768,
"output": 4096
}
},
"Qwopus3.6-35B-A3B-v1-MTP-Q4_K_M": {
"name": "Qwopus3.6 35B A3B MTP Q4 (llama-server)",
"tools": true,
"agent": {
"plan": {
"temperature": 0.1
},
"orchestrator": {
"temperature": 0.2
},
"build": {
"temperature": 0.3
},
"brainstorm": {
"temperature": 0.7
}
},
"limit": {
"context": 32768,
"output": 4096
}
},
"agentica-org_DeepCoder-14B-Preview-Q5_K_M": {
"name": "DeepCoder 14B Q5 (llama-server)",
"tools": true,
"agent": {
"plan": {
"temperature": 0.1
},
"build": {
"temperature": 0.3
},
"brainstorm": {
"temperature": 0.7
}
},
"limit": {
"context": 32768,
"output": 4096
}
}
}
}
},
"mcp": {
"all-agents": {
"type": "local",
"command": [
"node",
"--experimental-strip-types",
"/home/dev/dotfiles/.agents/mcp/index.ts"
]
}
}
}

View File

@ -1,10 +1,15 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# install.sh — Wire ~/dotfiles/.agents/ into global tool configs. (Script lives at repo root.) # install.sh — Wire .agents/ into global tool configs.
# Run with --host to also install llama-server config and systemd services.
# Idempotent: safe to re-run. Creates dirs, symlinks, and config entries. # Idempotent: safe to re-run. Creates dirs, symlinks, and config entries.
# Run once per machine after cloning dotfiles. # Run once per machine after cloning dotfiles.
set -euo pipefail set -euo pipefail
INSTALL_HOST=false
for arg in "$@"; do case "$arg" in --host) INSTALL_HOST=true ;; esac; done
DOTFILES_AGENTS="$(cd "$(dirname "$0")" && pwd)/.agents" DOTFILES_AGENTS="$(cd "$(dirname "$0")" && pwd)/.agents"
DOTFILES_CONFIG="$(cd "$(dirname "$0")" && pwd)/config"
log() { printf '\033[0;32m✓\033[0m %s\n' "$1"; } log() { printf '\033[0;32m✓\033[0m %s\n' "$1"; }
warn() { printf '\033[0;33m⚠\033[0m %s\n' "$1"; } warn() { printf '\033[0;33m⚠\033[0m %s\n' "$1"; }
@ -86,32 +91,98 @@ else
log "OpenCode AGENTS.md symlink: $OC_AGENTS_LINK$OC_AGENTS_TARGET" log "OpenCode AGENTS.md symlink: $OC_AGENTS_LINK$OC_AGENTS_TARGET"
fi fi
# ── 4. OpenCode global MCP entry ──────────────────────────────────────────── # ── 4. OpenCode global config (opencode.json) ────────────────────────────────
OC_CONFIG="$HOME/.config/opencode/opencode.json" OC_CONFIG_SOURCE="$DOTFILES_CONFIG/opencode/opencode.json"
MCP_KEY="all-agents" OC_CONFIG_LINK="$HOME/.config/opencode/opencode.json"
MCP_CMD="[\"node\", \"--experimental-strip-types\", \"$DOTFILES_AGENTS/mcp/index.ts\"]"
if [[ ! -f "$OC_CONFIG" ]]; then mkdir -p "$(dirname "$OC_CONFIG_LINK")"
warn "No OpenCode config at $OC_CONFIG — creating minimal config with MCP entry." if [[ -L "$OC_CONFIG_LINK" && "$(readlink "$OC_CONFIG_LINK")" == "$OC_CONFIG_SOURCE" ]]; then
printf '{\n "$schema": "https://opencode.ai/config.json",\n "mcp": {\n "%s": {\n "type": "local",\n "command": %s\n }\n }\n}\n' "$MCP_KEY" "$MCP_CMD" > "$OC_CONFIG" skip "OpenCode config symlink already set: $OC_CONFIG_LINK"
log "Created $OC_CONFIG with all-agents MCP entry"
elif node -e "const c=JSON.parse(require('fs').readFileSync('$OC_CONFIG','utf8')); process.exit(c.mcp && c.mcp['$MCP_KEY'] ? 0 : 1)" 2>/dev/null; then
skip "OpenCode MCP entry '$MCP_KEY' already present in $OC_CONFIG"
else else
# Merge the MCP entry using node — jq may not be available everywhere ln -sf "$OC_CONFIG_SOURCE" "$OC_CONFIG_LINK"
node -e " log "OpenCode config symlink: $OC_CONFIG_LINK$OC_CONFIG_SOURCE"
const fs = require('fs');
const path = '$OC_CONFIG';
const config = JSON.parse(fs.readFileSync(path, 'utf8'));
config.mcp = config.mcp || {};
config.mcp['$MCP_KEY'] = { type: 'local', command: $MCP_CMD };
fs.writeFileSync(path, JSON.stringify(config, null, 2) + '\n');
console.log('Merged all-agents MCP entry into ' + path);
"
log "OpenCode MCP entry merged: $OC_CONFIG"
fi fi
# ── 5. VS Code global MCP ──────────────────────────────────────────────────── # ── 5. Llama-server host config (requires --host) ───────────────────────────
if [[ "$INSTALL_HOST" != "true" ]]; then
skip "Llama-server host config skipped (use --host to install)"
else
# ── 5a. Model downloads (requires --host) ──────────────────────────────────
if ! command -v huggingface-cli >/dev/null 2>&1; then
warn "huggingface-cli not found — skipping model downloads (install via 'pip install huggingface_hub')"
else
_hf_download() {
local repo="$1" file="$2" dir="$3"
local dest="$dir/$file"
if [[ -f "$dest" ]]; then
skip "Model already present: $dest"
else
mkdir -p "$dir"
huggingface-cli download "$repo" "$file" --local-dir "$dir" >/dev/null
log "Downloaded model: $repo/$file$dest"
fi
}
_hf_download "Jackrong/Qwopus3.6-27B-v2-MTP-GGUF" "Qwopus3.6-27B-v2-MTP-Q4_K_M.gguf" "$HOME/models"
_hf_download "Jackrong/Qwopus3.5-9B-Coder-MTP-GGUF" "Qwopus3.5-9B-Coder-MTP-Q8_0.gguf" "$HOME/models"
_hf_download "bartowski/agentica-org_DeepCoder-14B-Preview-GGUF" "agentica-org_DeepCoder-14B-Preview-Q5_K_M.gguf" "$HOME/models"
_hf_download "byteshape/Qwen3.6-35B-A3B-MTP-GGUF" "Qwen3.6-35B-A3B-IQ3_S-3.06bpw.gguf" "$HOME/models"
_hf_download "Jackrong/Qwopus3.6-35B-A3B-v1-MTP-GGUF" "Qwopus3.6-35B-A3B-v1-MTP-Q4_K_M.gguf" "$HOME/models"
_hf_download "mradermacher/OmniCoder-2-9B-GGUF" "OmniCoder-2-9B.Q8_0.gguf" "$HOME/models/OmniCoder-2-9B.Q8_0"
_hf_download "mradermacher/OmniCoder-2-9B-GGUF" "mmproj-Q8_0.gguf" "$HOME/models/OmniCoder-2-9B.Q8_0"
_hf_download "bartowski/Qwen_Qwen3-14B-GGUF" "Qwen_Qwen3-14B-Q4_K_M.gguf" "$HOME/models"
_hf_download "bartowski/Qwen_Qwen3.6-27B-GGUF" "Qwen_Qwen3.6-27B-Q4_K_M.gguf" "$HOME/models"
fi
PRESETS_SRC="$DOTFILES_CONFIG/llama-server/presets.ini"
PRESETS_DST="$HOME/models/presets.ini"
mkdir -p "$HOME/models"
if diff -q "$PRESETS_SRC" "$PRESETS_DST" >/dev/null 2>&1; then
skip "presets.ini already up-to-date: $PRESETS_DST"
else
cp "$PRESETS_SRC" "$PRESETS_DST"
log "Installed presets.ini → $PRESETS_DST"
fi
SVC_SRC="$DOTFILES_CONFIG/llama-server/llama-server.service"
SVC_DST="/etc/systemd/system/llama-server.service"
if diff -q "$SVC_SRC" "$SVC_DST" >/dev/null 2>&1; then
skip "llama-server.service already up-to-date: $SVC_DST"
else
cp "$SVC_SRC" "$SVC_DST"
log "Installed llama-server.service → $SVC_DST"
fi
PATH_SRC="$DOTFILES_CONFIG/llama-server/llama-server-presets.path"
PATH_DST="/etc/systemd/system/llama-server-presets.path"
if diff -q "$PATH_SRC" "$PATH_DST" >/dev/null 2>&1; then
skip "llama-server-presets.path already up-to-date: $PATH_DST"
else
cp "$PATH_SRC" "$PATH_DST"
log "Installed llama-server-presets.path → $PATH_DST"
fi
PSVC_SRC="$DOTFILES_CONFIG/llama-server/llama-server-presets.service"
PSVC_DST="/etc/systemd/system/llama-server-presets.service"
if diff -q "$PSVC_SRC" "$PSVC_DST" >/dev/null 2>&1; then
skip "llama-server-presets.service already up-to-date: $PSVC_DST"
else
cp "$PSVC_SRC" "$PSVC_DST"
log "Installed llama-server-presets.service → $PSVC_DST"
fi
START_SRC="$DOTFILES_CONFIG/llama-server/start.sh"
START_DST="/opt/llama-server/start.sh"
mkdir -p "$(dirname "$START_DST")"
if diff -q "$START_SRC" "$START_DST" >/dev/null 2>&1; then
skip "start.sh already up-to-date: $START_DST"
else
cp "$START_SRC" "$START_DST"
log "Installed start.sh → $START_DST"
fi
fi
# ── 6. VS Code global MCP ────────────────────────────────────────────────────
# Primary remote/server path; falls back to local if running VS Code locally. # Primary remote/server path; falls back to local if running VS Code locally.
VSCODE_MCP_PATHS=( VSCODE_MCP_PATHS=(
"$HOME/.vscode-server/data/User/mcp.json" "$HOME/.vscode-server/data/User/mcp.json"
@ -121,6 +192,7 @@ VSCODE_MCP_PATHS=(
for VSCODE_MCP in "${VSCODE_MCP_PATHS[@]}"; do for VSCODE_MCP in "${VSCODE_MCP_PATHS[@]}"; do
if [[ -d "$(dirname "$VSCODE_MCP")" ]]; then if [[ -d "$(dirname "$VSCODE_MCP")" ]]; then
MCP_KEY="all-agents"
MCP_SERVER_CMD="node" MCP_SERVER_CMD="node"
MCP_SERVER_ARGS="[\"--experimental-strip-types\", \"$DOTFILES_AGENTS/mcp/index.ts\"]" MCP_SERVER_ARGS="[\"--experimental-strip-types\", \"$DOTFILES_AGENTS/mcp/index.ts\"]"
@ -150,7 +222,7 @@ if (changed) {
fi fi
done done
# ── 6. VS Code global prompts dir ─────────────────────────────────────────── # ── 7. VS Code global prompts dir ───────────────────────────────────────────
for VSCODE_PROMPTS_DIR in \ for VSCODE_PROMPTS_DIR in \
"$HOME/.vscode-server/data/User/prompts" \ "$HOME/.vscode-server/data/User/prompts" \
"$HOME/.vscode/data/User/prompts"; do "$HOME/.vscode/data/User/prompts"; do
@ -161,7 +233,7 @@ for VSCODE_PROMPTS_DIR in \
fi fi
done done
# ── 7. MCP server dependencies ─────────────────────────────────────────────── # ── 8. MCP server dependencies ───────────────────────────────────────────────
MCP_DIR="$DOTFILES_AGENTS/mcp" MCP_DIR="$DOTFILES_AGENTS/mcp"
if [[ ! -d "$MCP_DIR/node_modules/@modelcontextprotocol" ]]; then if [[ ! -d "$MCP_DIR/node_modules/@modelcontextprotocol" ]]; then
log "Installing MCP server dependencies (npm install in $MCP_DIR)..." log "Installing MCP server dependencies (npm install in $MCP_DIR)..."