-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup.sh
More file actions
executable file
·244 lines (225 loc) · 12.4 KB
/
Copy pathsetup.sh
File metadata and controls
executable file
·244 lines (225 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
#!/usr/bin/env bash
# Requires bash 4+. On macOS use: brew install bash
if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
if command -v /usr/local/bin/bash &>/dev/null; then
exec /usr/local/bin/bash "$0" "$@"
elif command -v /opt/homebrew/bin/bash &>/dev/null; then
exec /opt/homebrew/bin/bash "$0" "$@"
else
echo "bash 4+ required. Run: brew install bash"
exit 1
fi
fi
set -euo pipefail
# ─────────────────────────────────────────────────────────────────────────────
# devmate setup (idempotent — safe to re-run)
#
# Sets up a local, offline AI dev assistant for ANY repo/language:
# - Ollama + a local model (the "brain")
# - cocoindex-code + jina code embeddings (the retrieval backend / RAG)
# - indexes the target repo
# - registers ONE MCP server (devmate: develop / review / explain / search)
#
# Usage:
# ./setup.sh <repo-path> # first-time setup, or add/point at a repo
# ./setup.sh # uses current directory as the repo
#
# Re-running with a different <repo-path> = "add a new repo": it skips the
# already-installed parts and just indexes + points devmate at the new repo.
# ─────────────────────────────────────────────────────────────────────────────
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CCC_BIN="$HOME/.local/bin/ccc"
EMBED_MODEL="jinaai/jina-embeddings-v2-base-code"
GLOBAL_SETTINGS="$HOME/.cocoindex_code/global_settings.yml"
# Apply model (two-model split): merges develop's lazy edit
# sketches into full files. Must match the default in src/config.ts.
APPLY_MODEL="${APPLY_MODEL:-hf.co/QuantFactory/FastApply-7B-v1.0-GGUF:Q4_K_M}"
BOLD="\033[1m"; GREEN="\033[32m"; YELLOW="\033[33m"; CYAN="\033[36m"; RED="\033[31m"; RESET="\033[0m"
info() { echo -e "${CYAN}▶ $*${RESET}"; }
success() { echo -e "${GREEN}✓ $*${RESET}"; }
warn() { echo -e "${YELLOW}⚠ $*${RESET}"; }
error() { echo -e "${RED}✗ $*${RESET}"; exit 1; }
header() { echo -e "\n${BOLD}$*${RESET}"; }
# ─── Resolve target repo ──────────────────────────────────────────────────────
TARGET_REPO="${1:-$(pwd)}"
TARGET_REPO="$(cd "$TARGET_REPO" 2>/dev/null && pwd || true)"
[ -z "$TARGET_REPO" ] && error "Repo path not found: ${1:-$(pwd)}"
header "🤖 devmate setup"
echo "A local, offline AI dev assistant for any repo/language."
echo -e "Target repo: ${BOLD}${TARGET_REPO}${RESET}"
[[ "$(uname)" != "Darwin" ]] && error "This script supports macOS only. See README.md for Linux/Windows."
# ─── 1. Ollama ────────────────────────────────────────────────────────────────
header "Step 1/6 — Ollama (local model runtime)"
if command -v ollama &>/dev/null; then
success "Ollama already installed"
else
command -v brew &>/dev/null || error "Homebrew not found — install from https://brew.sh and re-run."
info "Installing Ollama..."; brew install --cask ollama; success "Ollama installed"
fi
if ! curl -s http://localhost:11434 &>/dev/null; then
info "Starting Ollama..."; open /Applications/Ollama.app 2>/dev/null || ollama serve &>/dev/null &
sleep 4
fi
curl -s http://localhost:11434 &>/dev/null && success "Ollama is running" \
|| error "Could not start Ollama. Launch it manually and re-run."
# ─── 2. Model picker ──────────────────────────────────────────────────────────
header "Step 2/6 — Choose a model (the brain)"
declare -A MODELS=( [1]="codestral:22b" [2]="gemma2:9b" [3]="qwen2.5-coder:7b" [4]="codellama:7b" [5]="llama3.2:3b" )
declare -A SIZES=( [1]="13 GB" [2]="5.4 GB" [3]="4.1 GB" [4]="3.8 GB" [5]="2.0 GB" )
declare -A NOTES=( [1]="recommended, strongest code model" [2]="good all-round" [3]="strong code, smaller" [4]="lightweight code model" [5]="smallest, fastest" )
echo ""
echo " # Model Size Notes"
echo " ─────────────────────────────────────────────────────"
for i in 1 2 3 4 5; do
pulled=""; ollama list 2>/dev/null | grep -q "^${MODELS[$i]}" && pulled=" ${GREEN}[downloaded]${RESET}"
printf " ${BOLD}%s${RESET} %-22s %-8s %s%b\n" "$i" "${MODELS[$i]}" "${SIZES[$i]}" "${NOTES[$i]}" "$pulled"
done
echo ""
SELECTED_MODEL=""
while true; do
read -rp " Enter choice (1-5): " choice
[[ "$choice" =~ ^[1-5]$ ]] && { SELECTED_MODEL="${MODELS[$choice]}"; break; }
warn "Enter a number 1-5."
done
if ollama list 2>/dev/null | grep -q "^${SELECTED_MODEL}"; then
success "Model ${SELECTED_MODEL} already downloaded"
else
info "Pulling ${SELECTED_MODEL} (may take a few minutes)..."; ollama pull "${SELECTED_MODEL}"; success "Model downloaded"
fi
# Apply model — required by `develop` to merge edit sketches into existing files.
if ollama list 2>/dev/null | grep -q "^${APPLY_MODEL}"; then
success "Apply model ${APPLY_MODEL} already downloaded"
else
info "Pulling apply model ${APPLY_MODEL} (~4 GB, one-time)..."
ollama pull "${APPLY_MODEL}" \
&& success "Apply model downloaded" \
|| warn "Could not pull apply model — 'develop' edits to existing files will fail until 'ollama pull ${APPLY_MODEL}' succeeds (set APPLY_MODEL to override)."
fi
# ─── 3. cocoindex (retrieval backend) ─────────────────────────────────────────
header "Step 3/6 — cocoindex-code (retrieval backend)"
PY312="$(command -v python3.12 || echo /usr/local/bin/python3.12)"
[ -x "$PY312" ] || error "Python 3.12 required for cocoindex (deps don't support 3.13+). Run: brew install [email protected]"
if command -v "$CCC_BIN" &>/dev/null || [ -x "$CCC_BIN" ]; then
success "cocoindex-code already installed"
else
command -v pipx &>/dev/null || { info "Installing pipx..."; brew install pipx; pipx ensurepath; }
info "Installing cocoindex-code on Python 3.12..."
pipx install --python "$PY312" 'cocoindex-code[full]' >/dev/null 2>&1 || \
pipx install --python "$PY312" --force 'cocoindex-code[full]' >/dev/null 2>&1
success "cocoindex-code installed"
fi
# ─── 4. Pin the working dependency set + jina embeddings ──────────────────────
header "Step 4/6 — Embedding model + dependency pins"
# The known-good combo for jina + cocoindex on Py3.12 (hard-won):
# numpy<2, transformers 4.44.2, sentence-transformers 3.0.1, tokenizers<0.20, einops
NEED_PINS=0
"$CCC_BIN" >/dev/null 2>&1 || true
pinned_ok=$(pipx runpip cocoindex-code show numpy 2>/dev/null | sed -n 's/^Version: \(1\..*\)/ok/p')
if [ "$pinned_ok" != "ok" ]; then NEED_PINS=1; fi
if [ "$NEED_PINS" -eq 1 ]; then
info "Pinning known-good deps (numpy<2, transformers 4.44.2, sentence-transformers 3.0.1, einops)..."
pipx runpip cocoindex-code install -q \
'numpy<2' 'transformers==4.44.2' 'sentence-transformers==3.0.1' 'tokenizers<0.20' 'einops' >/dev/null 2>&1
success "Dependencies pinned"
else
success "Dependencies already pinned"
fi
# Set jina as the embedding model in global settings.
mkdir -p "$(dirname "$GLOBAL_SETTINGS")"
python3 - "$GLOBAL_SETTINGS" "$EMBED_MODEL" <<'PY'
import sys, os
path, model = sys.argv[1], sys.argv[2]
content = f"""# CocoIndex Code — global settings.
embedding:
provider: sentence-transformers
model: {model}
indexing_params: {{}}
query_params: {{}}
"""
cur = open(path).read() if os.path.exists(path) else ""
if f"model: {model}" not in cur:
open(path, "w").write(content)
PY
success "Embedding model: ${EMBED_MODEL}"
info "Restarting cocoindex daemon..."; "$CCC_BIN" daemon restart >/dev/null 2>&1 || true
# ─── 5. Index the target repo ─────────────────────────────────────────────────
header "Step 5/6 — Index repo (first run downloads jina ~160MB)"
cd "$TARGET_REPO"
if [ -d "$TARGET_REPO/.cocoindex_code" ]; then
success "Repo already initialized for cocoindex"
else
info "Initializing cocoindex for this repo..."; "$CCC_BIN" init >/dev/null 2>&1 || true
fi
# Index agent rules (.cursor, .claude) — default ccc init uses '**/.*' which skips them.
SETTINGS_TEMPLATE="$REPO_DIR/templates/cocoindex_settings.yml"
PATCH_SCRIPT="$REPO_DIR/scripts/patch_cocoindex_settings.py"
if [ -f "$SETTINGS_TEMPLATE" ] && [ -f "$PATCH_SCRIPT" ]; then
info "Patching cocoindex settings (agent rules + skills)..."
python3 "$PATCH_SCRIPT" "$SETTINGS_TEMPLATE" "$TARGET_REPO" >/dev/null 2>&1 \
&& success "CocoIndex settings patched (.cursor / .claude indexed)" \
|| warn "Could not patch cocoindex settings"
else
warn "Settings template not found — skipping agent-knowledge index patch"
fi
info "Indexing ${TARGET_REPO##*/} (small repo ~10s, large ~1-2min)..."
"$CCC_BIN" index 2>&1 | tail -2
success "Repo indexed"
cd "$REPO_DIR"
# ─── 6. Build + register the single devmate MCP server ────────────────────────
header "Step 6/6 — Build + register devmate (develop/review/explain/search)"
command -v node &>/dev/null || error "Node.js not found — install from https://nodejs.org and re-run."
if [ ! -f "$REPO_DIR/dist/index.js" ]; then
info "Installing deps + building..."; (cd "$REPO_DIR" && npm install --silent && npm run build --silent)
fi
success "MCP server built"
register_mcp() {
local cfg="$1" label="$2"
[ -f "$cfg" ] || { warn "$label config not found — skipping"; return; }
python3 - "$cfg" "$SELECTED_MODEL" "$REPO_DIR" "$TARGET_REPO" "$CCC_BIN" "$APPLY_MODEL" <<'PY'
import json, sys
cfg, model, mcp_dir, repo, ccc_bin, apply_model = sys.argv[1:7]
with open(cfg) as f: d = json.load(f)
d.setdefault("mcpServers", {})["devmate"] = {
"command": "node",
"args": [f"{mcp_dir}/dist/index.js"],
"env": {
"LLM_BASE_URL": "http://localhost:11434/v1",
"LLM_MODEL": model,
"APPLY_MODEL": apply_model,
"REPO_ROOT": repo,
"CCC_BIN": ccc_bin,
},
}
# Remove the legacy standalone cocoindex-code MCP if present — it's now a backend.
d["mcpServers"].pop("cocoindex-code", None)
# Remove the old "llmselect" entry (renamed to "devmate").
d["mcpServers"].pop("llmselect", None)
with open(cfg, "w") as f: json.dump(d, f, indent=2)
PY
success "$label — devmate → ${TARGET_REPO##*/}"
}
register_mcp "$HOME/.claude.json" "Claude Code"
register_mcp "$HOME/.cursor/mcp.json" "Cursor"
CLAUDE_DESKTOP="$HOME/Library/Application Support/Claude/claude_desktop_config.json"
[ -f "$CLAUDE_DESKTOP" ] && register_mcp "$CLAUDE_DESKTOP" "Claude Desktop"
# ─── Done ─────────────────────────────────────────────────────────────────────
echo ""
echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo -e "${GREEN}${BOLD} ✓ devmate ready for ${TARGET_REPO##*/}${RESET}"
echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo ""
echo -e " Model : ${BOLD}${SELECTED_MODEL}${RESET}"
echo -e " Apply : ${BOLD}${APPLY_MODEL}${RESET}"
echo -e " Embeddings: ${BOLD}${EMBED_MODEL}${RESET}"
echo -e " Repo : ${BOLD}${TARGET_REPO}${RESET}"
echo ""
echo -e " ${YELLOW}Restart Claude Code / Cursor to activate.${RESET}"
echo ""
echo -e " Tools (all grounded in the indexed repo):"
echo -e " ${CYAN}develop${RESET} — write a feature/refactor/fix (+tests, +docs), writes files"
echo -e " ${CYAN}review${RESET} — review code against repo conventions"
echo -e " ${CYAN}explain${RESET} — explain / brainstorm, grounded in the codebase"
echo -e " ${CYAN}search${RESET} — semantic code search over the repo"
echo ""
echo -e " To add/point at another repo later: ${BOLD}./setup.sh /path/to/other-repo${RESET}"
echo ""