forked from jaychempan/coding-with-beat
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall_codex.sh
More file actions
executable file
ยท414 lines (367 loc) ยท 20.1 KB
/
Copy pathinstall_codex.sh
File metadata and controls
executable file
ยท414 lines (367 loc) ยท 20.1 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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
#!/usr/bin/env bash
# coding-with-beat โ Codex CLI installer
# One-click, proxy-aware, idempotent. Re-running is safe.
#
# Steps:
# 1. Python โฅ3.10 โ shared venv at ~/.coding-with-beat/venv
# 2. coding-with-beat installed (editable) + cwb symlinked to ~/.local/bin/
# 3. Proxy normalised: detect system proxy โ set both upper/lowercase +
# NO_PROXY=127.0.0.1,localhost (so Codex reaches chatgpt.com AND local MCP)
# 4. Codex CLI installed / verified via npm
# 5. ~/.codex/config.toml (MCP) + ~/.codex/hooks.json (hooks) patched
# 6. cwb skill installed to ~/.codex/skills/cwb/
# 7. Music routing rules injected into ~/.codex/AGENTS.md
# 8. MCP server LaunchAgent started (macOS)
set -euo pipefail
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULT_MCP_URL="http://127.0.0.1:8765/mcp"
MCP_URL="${CWB_MCP_URL:-${CC_JUKEBOX_MCP_URL:-$DEFAULT_MCP_URL}}"
while [ "$#" -gt 0 ]; do
case "$1" in
--mcp-url) [ "$#" -ge 2 ] || { echo "--mcp-url requires a value" >&2; exit 2; }
MCP_URL="$2"; shift 2 ;;
-h|--help) cat <<'EOF'
Usage: ./install_codex.sh [--mcp-url URL]
--mcp-url URL MCP server URL (default: http://127.0.0.1:8765/mcp)
EOF
exit 0 ;;
*) echo "unknown option: $1" >&2; exit 2 ;;
esac
done
bold() { printf "\033[1m%s\033[0m\n" "$1"; }
ok() { printf "\033[32mโ\033[0m %s\n" "$1"; }
info() { printf "\033[34mยท\033[0m %s\n" "$1"; }
warn() { printf "\033[33m!\033[0m %s\n" "$1"; }
die() { printf "\033[31mโ %s\033[0m\n" "$1" >&2; exit 1; }
bold "coding-with-beat โ Codex CLI installer"
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 1. Find Python โฅ3.10
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
check_py() {
local cand="$1"
[ -z "$cand" ] && return 1
command -v "$cand" >/dev/null 2>&1 || [ -x "$cand" ] || return 1
local v
v="$("$cand" -c 'import sys; print("%d.%d" % sys.version_info[:2])' 2>/dev/null || true)"
case "$v" in 3.1[0-9]|3.[2-9][0-9]) return 0 ;; *) return 1 ;; esac
}
PY=""
[ -n "${CWB_PYTHON:-}" ] && check_py "$CWB_PYTHON" && PY="$CWB_PYTHON"
if [ -z "$PY" ]; then
for cand in python3.13 python3.12 python3.11 python3.10 python3; do
if check_py "$cand"; then PY="$(command -v "$cand")"; break; fi
done
fi
if [ -z "$PY" ]; then
for cand in /opt/homebrew/bin/python3.13 /opt/homebrew/bin/python3.12 \
/opt/homebrew/bin/python3.11 /opt/homebrew/bin/python3.10 \
/usr/local/bin/python3.13 /usr/local/bin/python3.12 \
/usr/local/bin/python3.11 /usr/local/bin/python3.10; do
if check_py "$cand"; then PY="$cand"; break; fi
done
fi
bootstrap_via_uv() {
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
if ! command -v uv >/dev/null 2>&1; then
command -v curl >/dev/null 2>&1 || die "need curl to bootstrap uv"
curl -LsSf https://astral.sh/uv/install.sh | sh
fi
command -v uv >/dev/null 2>&1 || die "uv install failed"
local found; found="$(uv python find 3.12 2>/dev/null || true)"
if [ -z "$found" ] || [ ! -x "$found" ]; then uv python install 3.12; found="$(uv python find 3.12)"; fi
if [ -z "$found" ] || [ ! -x "$found" ]; then die "uv python install failed"; fi
PY="$found"
}
[ -n "$PY" ] || { warn "No Python โฅ3.10 found โ bootstrapping via uv"; bootstrap_via_uv; }
ok "python: $PY ($($PY --version))"
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 2. Shared venv + coding-with-beat install
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
VENV="$HOME/.coding-with-beat/venv"
mkdir -p "$HOME/.coding-with-beat"
printf "%s\n" "$REPO" > "$HOME/.coding-with-beat/repo-path"
printf "%s\n" "$MCP_URL" > "$HOME/.coding-with-beat/mcp-url"
if [ -d "$VENV" ] && { [ ! -x "$VENV/bin/python" ] || [ ! -x "$VENV/bin/pip" ]; }; then
warn "incomplete venv โ recreating"; rm -rf "$VENV"
fi
if [ ! -d "$VENV" ]; then
"$PY" -m venv "$VENV" || { bootstrap_via_uv; "$PY" -m venv "$VENV" || die "venv creation failed"; }
ok "created venv at $VENV"
else
ok "venv exists at $VENV"
fi
VENV_PY="$VENV/bin/python"
if [ -x "$VENV/bin/cwb" ]; then
ok "coding-with-beat already installed โ skipping pip"
else
"$VENV_PY" -m pip install --quiet --upgrade pip
"$VENV_PY" -m pip install --quiet -e "$REPO"
ok "coding-with-beat installed"
fi
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 3. Symlink cwb to ~/.local/bin/
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
BIN_DIR="$HOME/.local/bin"
mkdir -p "$BIN_DIR"
TARGET="$VENV/bin/cwb"
LINK="$BIN_DIR/cwb"
[ -x "$TARGET" ] || die "expected $TARGET to exist after install"
if [ "$(readlink "$LINK" 2>/dev/null)" = "$TARGET" ]; then
ok "cwb already linked"
elif [ -L "$LINK" ] || [ ! -e "$LINK" ]; then
ln -sfn "$TARGET" "$LINK"
ok "linked cwb -> $TARGET"
fi
inject_path() {
local rc="$1"; [ -f "$rc" ] || return 0
grep -q ">>> coding-with-beat >>>" "$rc" && return 0
{ echo ""; echo "# >>> coding-with-beat >>>";
# shellcheck disable=SC2016
echo 'case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) export PATH="$HOME/.local/bin:$PATH";; esac';
echo "# <<< coding-with-beat <<<"; } >> "$rc"
ok "PATH block added to $rc"
}
[ -f "$HOME/.zshrc" ] || touch "$HOME/.zshrc"
inject_path "$HOME/.zshrc"; inject_path "$HOME/.bashrc"
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 4. Proxy normalisation
# Detect the first proxy URL found in env โ write both upper/lowercase +
# NO_PROXY=127.0.0.1,localhost into shell profiles.
# Codex (Rust) uses uppercase; curl/npm use lowercase.
# NO_PROXY ensures the local MCP server is always reached directly.
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
detect_proxy() {
# macOS system proxy via scutil (works even before shell vars are set)
local scutil_https="" scutil_http=""
if command -v scutil >/dev/null 2>&1; then
scutil_https="$(scutil --proxy 2>/dev/null | awk '/HTTPSProxy/{p=$3} /HTTPSPort/{port=$3} END{if(p) print "http://"p":"port}')"
scutil_http="$(scutil --proxy 2>/dev/null | awk '/HTTPProxy /{p=$3} /HTTPPort /{port=$3} END{if(p) print "http://"p":"port}')"
fi
# env vars override system settings
for var in HTTPS_PROXY https_proxy HTTP_PROXY http_proxy; do
local val="${!var:-}"
if [ -n "$val" ]; then echo "$val"; return; fi
done
[ -n "$scutil_https" ] && { echo "$scutil_https"; return; }
[ -n "$scutil_http" ] && { echo "$scutil_http"; return; }
}
PROXY_URL="$(detect_proxy || true)"
_NO_PROXY_BASE="127.0.0.1,localhost,::1"
inject_proxy_env() {
local rc="$1" proxy="$2"
[ -f "$rc" ] || return 0
# Remove our old proxy block if present (handles port changes on re-run)
"$PY" - "$rc" <<'PY'
import sys; path = sys.argv[1]
lines = open(path).readlines()
out, skip = [], False
for line in lines:
if ">>> coding-with-beat proxy >>>" in line: skip = True
if not skip: out.append(line)
if "<<< coding-with-beat proxy <<<" in line: skip = False
open(path, "w").writelines(out)
PY
{
echo ""
echo "# >>> coding-with-beat proxy >>>"
echo "# Set by coding-with-beat install_codex.sh โ edit here to change."
if [ -n "$proxy" ]; then
echo "export HTTP_PROXY=$proxy"
echo "export HTTPS_PROXY=$proxy"
echo "export http_proxy=$proxy"
echo "export https_proxy=$proxy"
fi
# Always add NO_PROXY so local MCP server bypasses the proxy
echo "export NO_PROXY=$_NO_PROXY_BASE"
echo "export no_proxy=$_NO_PROXY_BASE"
echo "# <<< coding-with-beat proxy <<<"
} >> "$rc"
}
if [ -n "$PROXY_URL" ]; then
inject_proxy_env "$HOME/.zshrc" "$PROXY_URL"
inject_proxy_env "$HOME/.bashrc" "$PROXY_URL"
# Apply to current shell session too
export HTTP_PROXY="$PROXY_URL" HTTPS_PROXY="$PROXY_URL"
export http_proxy="$PROXY_URL" https_proxy="$PROXY_URL"
ok "proxy normalised: $PROXY_URL (upper+lowercase, NO_PROXY=127.0.0.1,localhost)"
else
# Still inject NO_PROXY even without a proxy (future-proofs if user adds one)
inject_proxy_env "$HOME/.zshrc" ""
inject_proxy_env "$HOME/.bashrc" ""
info "no proxy detected โ NO_PROXY=127.0.0.1,localhost written anyway"
fi
export NO_PROXY="$_NO_PROXY_BASE" no_proxy="$_NO_PROXY_BASE"
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 5. Codex CLI โ install or verify
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
install_codex_cli() {
if command -v codex >/dev/null 2>&1; then
ok "codex already installed: $(codex --version 2>/dev/null || echo '(unknown version)')"
return 0
fi
if ! command -v npm >/dev/null 2>&1; then
warn "npm not found โ skipping Codex CLI install. Install Node.js then run: npm install -g @openai/codex"
return 0
fi
info "Installing Codex CLI via npm..."
local npm_args=()
[ -n "$PROXY_URL" ] && npm_args+=(--proxy "$PROXY_URL" --https-proxy "$PROXY_URL")
if npm install -g @openai/codex "${npm_args[@]}"; then
ok "Codex CLI installed: $(codex --version 2>/dev/null || echo 'ok')"
else
warn "npm install failed. Try manually: npm install -g @openai/codex --proxy $PROXY_URL"
fi
}
install_codex_cli
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 6. Patch ~/.codex/config.toml + hooks.json + install skill
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
"$VENV_PY" "$REPO/scripts/install_codex_config.py" \
--python "$VENV_PY" \
--repo "$REPO" \
--mcp-url "$MCP_URL"
ok "Codex config patched"
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 7. AGENTS.md โ inject music routing rules into ~/.codex/AGENTS.md
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
inject_agents_md() {
local agents="$HOME/.codex/AGENTS.md"
# Strip existing block on re-run so rules stay up-to-date
if [ -f "$agents" ] && grep -q ">>> coding-with-beat >>>" "$agents"; then
"$PY" - "$agents" <<'PY'
import sys; path = sys.argv[1]
lines = open(path).readlines()
out, skip = [], False
for line in lines:
if ">>> coding-with-beat >>>" in line: skip = True
if not skip: out.append(line)
if "<<< coding-with-beat <<<" in line: skip = False
open(path, "w").writelines(out)
PY
fi
touch "$agents"
cat >> "$agents" <<'AGENTS'
# >>> coding-with-beat >>>
# Music routing โ added by coding-with-beat install_codex.sh (remove block or run uninstall_codex.sh to revert)
## Music requests โ when to use smart_search vs play_song
Use `play_song(query)` only for **specific** song title / artist / album (e.g. "ๅจๆฐไผฆ ๆดๅคฉ", "Taylor Swift").
Use `smart_search(queries=[...])` for **everything else**: mood, vibe, scene, fuzzy artist requests, genre + modifier, era, activity.
Call `smart_search` **once** with 2โ3 angle queries. Do NOT call it multiple times โ each call overwrites the queue.
After showing results (numbered globally), ask the user to pick by number and call `play_number(N)`. Do NOT auto-play.
## Library-only search
When the user says "ไป่ตๆๅบๆพ"/"่ตๆๅบ้ๆๆฒกๆ"/"ๆๅทฒ็ปๆ่ฟ้ฆ"/"in my library"/"library only":
1. Call `search(query)`.
2. Only show results where `source == "library"`.
3. If none found, ask: "่ตๆๅบ้ๆฒกๆพๅฐ๏ผ่ฆไธ่ฆๆไธไธ็บฟไธ๏ผ" โ do NOT auto-expand.
## Scene dispatch
| Scene | Trigger words | queries |
|---|---|---|
| ๐ง Lofi | lofi, ๆทฑๅค, ๅไปฃ็ , chillhop | `["lofi hip hop late night coding chill", "lofi jazz rain study instrumental", "chillhop beats lo-fi bedroom producer"]` |
| ๐ง Focus | ไธๆณจ, ๅฟๆต, ambient, ๆ ไบบๅฃฐ, flow state | `["deep focus ambient instrumental no vocals", "flow state drone minimal electronic", "study music concentration piano quiet"]` |
| ๐ฅ Hype | ๅ
่ฝ, ่ฟๅจ, workout, hype, ่ทๆญฅ | `["morning energy upbeat pop indie fresh", "workout motivation electronic dance", "hype rap trap energetic beats pump"]` |
| โ Jazz | ็ตๅฃซ, jazz, ๅๅก้ฆ, bossa nova | `["smooth jazz cafe background mellow", "jazz trio acoustic bossa nova guitar", "late night jazz piano bar cool relaxed"]` |
| ๐ Synthwave | ่ตๅ, synthwave, ็ตๅญ, ๅค้ฉพ | `["synthwave retrowave night drive neon", "cyberpunk electronic dark ambient synth", "80s retro synth outrun vapor"]` |
| ๐
Relax | ๆพๆพ, ่งฃๅ, ไธ็ญ, unwind | `["relaxing downtempo chill evening unwind", "acoustic folk gentle calm soft", "nature ambient breeze afternoon easy listening"]` |
| ๐น Classical | ๅคๅ
ธ, ้ข็ด, ๅผฆไน, classical | `["classical piano solo nocturne gentle", "string quartet orchestral cinematic calm", "bach mozart ambient classical study"]` |
| ๐ Sad | ไผคๆ, ๅคฑ่ฝ, ้พ่ฟ, heartbreak | `["melancholy emotional piano sad indie", "heartbreak slow ballad rnb rainy", "sorrowful strings cinematic emotional"]` |
| ๐ Party | ๆดพๅฏน, party, edm, ่นฆ่ฟช | `["party dance pop upbeat celebratory", "edm festival club electronic banger", "latin pop reggaeton dance floor"]` |
| ๐ฎ Chinese | ๅฝ้ฃ, ๅ่ฏญ, ๆฐ่ฐฃ, ๅค้ฃ | `["ไธญๅฝ้ฃ ๅค้ฃ ๅค็ด ไผ ็ปไนๅจ", "ๅ่ฏญๆต่ก ๅฝ่ฏญๆญ indie ๆฐ่ฐฃ", "chinese traditional folk guzheng erhu instrumental"]` |
| ๐ Sleep | ๅฉ็ , ๅคฑ็ , sleep, ็ฝๅช้ณ | `["sleep music white noise ambient drone", "lullaby soft piano rain sleep calm", "meditation deep sleep binaural delta waves"]` |
## Fuzzy / artist-only requests โ smart_search
When the user names an artist without a specific song (e.g. "ๆฅ้ฆๅจๆฐไผฆ็", "ๆๆๆตฉๆ่ฟๆต่ก็"):
`smart_search(queries=["{artist} ็ญ้จ", "{artist} ๆฐๆญ 2024", "{artist} ไปฃ่กจไฝ"])`
When the user asks for something similar to an artist (e.g. "ๅTaylor Swift็"):
generate 3 queries based on that artist's known style.
## play_number โ number parsing
Resolve before calling `play_number(N)`:
- "็ฌฌไธ" / "1" / "one" / "the first" โ play_number(1)
- "็ฌฌไบ" / "2" / "second" โ play_number(2)
- "็ฌฌไธ้ฆ" / "็ฌฌไธไธช" / "ไธ" / "3" โ play_number(3)
- "ๆๅไธ้ฆ" / "last one" โ use the highest number shown in results
Chinese ordinals ็ฌฌไธ/็ฌฌไบ/็ฌฌไธ/็ฌฌๅ/็ฌฌไบ = 1/2/3/4/5.
## play_number recovery
If `play_number(N)` errors with "only"/"had"/"out of range": re-run the same `smart_search(queries=[...])` automatically, then call `play_number(N)` again. Do NOT ask the user to retry.
# <<< coding-with-beat <<<
AGENTS
ok "music routing rules injected into $agents"
}
inject_agents_md
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 8. MCP LaunchAgent (macOS)
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
start_mcp_service() {
[ "$(uname -s)" = "Darwin" ] || { warn "Auto-start is macOS-only."; return 0; }
local parts host port path
parts="$("$VENV_PY" - "$MCP_URL" <<'PY'
import sys
from urllib.parse import urlparse
u = urlparse(sys.argv[1])
if u.scheme not in ("http","https"): raise SystemExit("unsupported scheme")
print(f"{u.hostname or '127.0.0.1'}\t{u.port or 80}\t{u.path or '/mcp'}")
PY
)"
host="$(printf "%s" "$parts" | cut -f1)"
port="$(printf "%s" "$parts" | cut -f2)"
path="$(printf "%s" "$parts" | cut -f3)"
case "$host" in 127.0.0.1|localhost|::1) ;;
*) warn "MCP URL is not localhost โ skipping LaunchAgent"; return 0 ;;
esac
local label="com.coding-with-beat.server"
local plist="$HOME/Library/LaunchAgents/$label.plist"
local log_dir="$HOME/.coding-with-beat/logs"
mkdir -p "$(dirname "$plist")" "$log_dir"
"$VENV_PY" - "$plist" "$TARGET" "$host" "$port" "$path" "$log_dir" <<'PY'
import plistlib, sys
from pathlib import Path
plist, program, host, port, path, log_dir = sys.argv[1:]
data = {
"Label": "com.coding-with-beat.server",
"ProgramArguments": [program, "server", "--host", host, "--port", port, "--path", path],
"RunAtLoad": True, "KeepAlive": True,
"StandardOutPath": str(Path(log_dir)/"server.log"),
"StandardErrorPath": str(Path(log_dir)/"server.err.log"),
"EnvironmentVariables": {
"PATH": "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin",
},
}
with open(plist, "wb") as f: plistlib.dump(data, f)
PY
local domain
domain="gui/$(id -u)"
# Always restart so newly installed code is picked up.
launchctl bootout "$domain" "$plist" >/dev/null 2>&1 || true
if launchctl bootstrap "$domain" "$plist" >/dev/null 2>&1; then
launchctl kickstart -k "$domain/$label" >/dev/null 2>&1 || true
if "$VENV_PY" - "$host" "$port" <<'PY'
import socket, sys, time
h, p = sys.argv[1], int(sys.argv[2])
for _ in range(40):
try:
with socket.create_connection((h,p), timeout=0.2): raise SystemExit(0)
except OSError: time.sleep(0.1)
raise SystemExit(1)
PY
then
ok "MCP server running at $MCP_URL"
else
warn "LaunchAgent loaded but MCP not responding โ check $log_dir/server.err.log"
fi
else
warn "Could not start LaunchAgent. Run manually: cwb server --host $host --port $port --path $path"
fi
}
start_mcp_service
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Init data dir
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
"$VENV_PY" -c "from coding_with_beat.config import ensure_dirs; ensure_dirs()"
echo
"$VENV/bin/cwb" welcome 2>/dev/null || true
echo
bold "Done! Open a new shell or run: source ~/.zshrc"
echo
echo " In Codex: 'play some lofi' or 'cwb play lofi beats'"
echo " CLI: cwb watch / cwb player / cwb np"
[ -n "$PROXY_URL" ] && echo " Proxy: $PROXY_URL (both Codex โ chatgpt.com and local MCP handled)"
echo
warn "To uninstall: ./uninstall_codex.sh"