aidevops 3.13.95 → 3.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,947 +0,0 @@
1
- #!/usr/bin/env bash
2
- # SPDX-License-Identifier: MIT
3
- # SPDX-FileCopyrightText: 2025-2026 Marcus Quinn
4
- # MCP setup functions: install_mcp_packages, resolve_mcp_binary, localwp, seo, analytics, quickfile, browser-tools, opencode-plugins
5
- # Part of aidevops setup.sh modularization (t316.3)
6
-
7
- # Shell safety baseline
8
- set -Eeuo pipefail
9
- IFS=$'\n\t'
10
- # shellcheck disable=SC2154 # rc is assigned by $? in the trap string
11
- trap 'rc=$?; echo "[ERROR] ${BASH_SOURCE[0]}:${LINENO} exit $rc" >&2' ERR
12
- shopt -s inherit_errexit 2>/dev/null || true
13
-
14
- _install_mcp_packages_node() {
15
- # Install/update Node.js MCP packages globally.
16
- # Security note: MCP servers run as persistent processes with access to conversation
17
- # context, credentials, and network. The packages below are from known/vetted sources.
18
- # Before adding new MCP packages to this list, verify the source repository and scan
19
- # dependencies with: npx @socketsecurity/cli npm info <package>
20
- # See: .agents/tools/mcp-toolkit/mcporter.md "Security Considerations"
21
- local -a node_mcps=(
22
- "chrome-devtools-mcp"
23
- "mcp-server-gsc"
24
- "playwriter"
25
- "@steipete/macos-automator-mcp"
26
- "@steipete/claude-code-mcp"
27
- )
28
-
29
- local installer="npm"
30
- command -v bun &>/dev/null && installer="bun"
31
- print_info "Using $installer to install/update Node.js MCP packages..."
32
-
33
- # Always install latest (bun install -g is fast and idempotent)
34
- local updated=0
35
- local failed=0
36
- local pkg
37
- for pkg in "${node_mcps[@]}"; do
38
- local short_name="${pkg##*/}" # Strip @scope/ prefix for display
39
- if run_with_spinner "Installing $short_name" npm_global_install "${pkg}@latest"; then
40
- ((++updated))
41
- else
42
- ((++failed))
43
- print_warning "Failed to install/update $pkg"
44
- fi
45
- done
46
-
47
- if [[ $updated -gt 0 ]]; then
48
- print_success "$updated Node.js MCP packages installed/updated to latest via $installer"
49
- fi
50
- if [[ $failed -gt 0 ]]; then
51
- print_warning "$failed packages failed (check network or package names)"
52
- fi
53
- return 0
54
- }
55
-
56
- _install_mcp_packages_python() {
57
- # Install/update Python MCP packages via pipx and uv.
58
- if command -v pipx &>/dev/null; then
59
- print_info "Installing/updating analytics-mcp via pipx..."
60
- if command -v analytics-mcp &>/dev/null; then
61
- pipx upgrade analytics-mcp >/dev/null 2>&1 || true
62
- else
63
- pipx install analytics-mcp >/dev/null 2>&1 || print_warning "Failed to install analytics-mcp"
64
- fi
65
- fi
66
-
67
- if command -v uv &>/dev/null && uv tool --help &>/dev/null; then
68
- print_info "Installing/updating outscraper-mcp-server via uv..."
69
- if command -v outscraper-mcp-server &>/dev/null; then
70
- uv tool upgrade outscraper-mcp-server >/dev/null 2>&1 || true
71
- else
72
- uv tool install outscraper-mcp-server >/dev/null 2>&1 || print_warning "Failed to install outscraper-mcp-server"
73
- fi
74
- elif command -v uv &>/dev/null; then
75
- print_warning "uv is installed but too old to support 'tool' subcommand — skipping outscraper-mcp-server"
76
- print_info "Update uv with: curl -LsSf https://astral.sh/uv/install.sh | sh"
77
- fi
78
- return 0
79
- }
80
-
81
- install_mcp_packages() {
82
- # Check prerequisites before announcing setup (GH#5240)
83
- if ! command -v bun &>/dev/null && ! command -v npm &>/dev/null; then
84
- print_skip "MCP packages" "neither bun nor npm found" "Install bun: brew install oven-sh/bun/bun (or npm via Node.js)"
85
- setup_track_deferred "MCP packages" "Install bun or npm"
86
- return 0
87
- fi
88
-
89
- print_info "Installing MCP server packages globally (eliminates npx startup delay)..."
90
-
91
- _install_mcp_packages_node
92
- _install_mcp_packages_python
93
-
94
- # Update opencode.json with resolved full paths for all MCP binaries
95
- update_mcp_paths_in_opencode
96
-
97
- print_info "MCP servers will start instantly (no registry lookups on each launch)"
98
- return 0
99
- }
100
-
101
- resolve_mcp_binary_path() {
102
- local bin_name
103
- bin_name="$1"
104
- local resolved=""
105
-
106
- # Check common locations in priority order
107
- local search_paths=(
108
- "$HOME/.bun/bin/$bin_name"
109
- "/opt/homebrew/bin/$bin_name"
110
- "/usr/local/bin/$bin_name"
111
- "$HOME/.local/bin/$bin_name"
112
- "$HOME/.npm-global/bin/$bin_name"
113
- )
114
-
115
- for path in "${search_paths[@]}"; do
116
- if [[ -x "$path" ]]; then
117
- resolved="$path"
118
- break
119
- fi
120
- done
121
-
122
- # Fallback: use command -v if in PATH (portable, POSIX-compliant)
123
- if [[ -z "$resolved" ]]; then
124
- resolved=$(command -v "$bin_name" 2>/dev/null || true)
125
- fi
126
-
127
- echo "$resolved"
128
- return 0
129
- }
130
-
131
- _update_mcp_paths_resolve_local_cmds() {
132
- # Resolve local MCP command binaries to full paths. Prints update count.
133
- local tmp_config="$1"
134
- local updated=0
135
-
136
- local mcp_keys
137
- mcp_keys=$(jq -r '.mcp | to_entries[] | select(.value.type == "local") | select(.value.command != null) | .key' "$tmp_config" 2>/dev/null)
138
-
139
- while IFS= read -r mcp_key; do
140
- [[ -z "$mcp_key" ]] && continue
141
-
142
- local current_cmd
143
- current_cmd=$(jq -r --arg k "$mcp_key" '.mcp[$k].command[0]' "$tmp_config" 2>/dev/null)
144
-
145
- # Skip if already a full path
146
- if [[ "$current_cmd" == /* ]]; then
147
- # Verify the path still exists; resolve stale paths
148
- if [[ ! -x "$current_cmd" ]]; then
149
- local bin_name
150
- bin_name=$(basename "$current_cmd")
151
- local new_path
152
- new_path=$(resolve_mcp_binary_path "$bin_name")
153
- if [[ -n "$new_path" && "$new_path" != "$current_cmd" ]]; then
154
- jq --arg k "$mcp_key" --arg p "$new_path" '.mcp[$k].command[0] = $p' "$tmp_config" >"${tmp_config}.new" && mv "${tmp_config}.new" "$tmp_config"
155
- ((++updated))
156
- fi
157
- fi
158
- continue
159
- fi
160
-
161
- # Skip docker (container runtime) and node (resolved separately)
162
- case "$current_cmd" in
163
- docker | node) continue ;;
164
- esac
165
-
166
- local full_path
167
- full_path=$(resolve_mcp_binary_path "$current_cmd")
168
-
169
- if [[ -n "$full_path" && "$full_path" != "$current_cmd" ]]; then
170
- jq --arg k "$mcp_key" --arg p "$full_path" '.mcp[$k].command[0] = $p' "$tmp_config" >"${tmp_config}.new" && mv "${tmp_config}.new" "$tmp_config"
171
- ((++updated))
172
- fi
173
- done <<<"$mcp_keys"
174
-
175
- echo "$updated"
176
- return 0
177
- }
178
-
179
- _update_mcp_paths_resolve_node_cmds() {
180
- # Resolve 'node' commands to full path (e.g., quickfile, amazon-order-history).
181
- # These use ["node", "/path/to/index.js"] — node itself should be resolved.
182
- # Prints update count.
183
- local tmp_config="$1"
184
- local updated=0
185
-
186
- local node_path
187
- node_path=$(resolve_mcp_binary_path "node")
188
- if [[ -n "$node_path" ]]; then
189
- local node_mcp_keys
190
- node_mcp_keys=$(jq -r '.mcp | to_entries[] | select(.value.type == "local") | select(.value.command != null) | select(.value.command[0] == "node") | .key' "$tmp_config" 2>/dev/null)
191
- local mcp_key
192
- while IFS= read -r mcp_key; do
193
- [[ -z "$mcp_key" ]] && continue
194
- jq --arg k "$mcp_key" --arg p "$node_path" '.mcp[$k].command[0] = $p' "$tmp_config" >"${tmp_config}.new" && mv "${tmp_config}.new" "$tmp_config"
195
- ((++updated))
196
- done <<<"$node_mcp_keys"
197
- fi
198
-
199
- echo "$updated"
200
- return 0
201
- }
202
-
203
- update_mcp_paths_in_opencode() {
204
- local opencode_config
205
- opencode_config=$(find_opencode_config) || return 0
206
-
207
- if [[ ! -f "$opencode_config" ]]; then
208
- return 0
209
- fi
210
-
211
- if ! command -v jq &>/dev/null; then
212
- return 0
213
- fi
214
-
215
- local tmp_config
216
- tmp_config=$(mktemp)
217
- trap 'rm -f "${tmp_config:-}"' RETURN
218
- cp "$opencode_config" "$tmp_config"
219
-
220
- local updated=0
221
- local local_count node_count
222
- local_count=$(_update_mcp_paths_resolve_local_cmds "$tmp_config")
223
- node_count=$(_update_mcp_paths_resolve_node_cmds "$tmp_config")
224
- updated=$((local_count + node_count))
225
-
226
- if [[ $updated -gt 0 ]]; then
227
- create_backup_with_rotation "$opencode_config" "opencode"
228
- mv "$tmp_config" "$opencode_config"
229
- print_success "Updated $updated MCP commands to use full binary paths in opencode.json"
230
- else
231
- rm -f "$tmp_config"
232
- fi
233
-
234
- return 0
235
- }
236
-
237
- setup_localwp_mcp() {
238
- # Check prerequisites before announcing setup (GH#5240)
239
- local localwp_found=false
240
- if [[ -d "/Applications/Local.app" ]] || [[ -d "$HOME/Applications/Local.app" ]]; then
241
- localwp_found=true
242
- fi
243
-
244
- if [[ "$localwp_found" != "true" ]]; then
245
- print_skip "LocalWP MCP" "LocalWP not installed" "Install from https://localwp.com/ then re-run setup"
246
- setup_track_skipped "LocalWP MCP" "LocalWP not installed"
247
- return 0
248
- fi
249
-
250
- if ! command -v npm &>/dev/null; then
251
- print_skip "LocalWP MCP" "npm not found" "Install Node.js and npm first"
252
- setup_track_deferred "LocalWP MCP" "Install Node.js/npm, then re-run setup"
253
- return 0
254
- fi
255
-
256
- # Prerequisites met — proceed with setup
257
- print_info "Setting up LocalWP MCP server..."
258
-
259
- if command -v mcp-local-wp &>/dev/null; then
260
- print_success "LocalWP MCP server already installed"
261
- setup_track_configured "LocalWP MCP"
262
- return 0
263
- fi
264
-
265
- print_info "LocalWP MCP server enables AI assistants to query WordPress databases"
266
- setup_prompt install_mcp "Install LocalWP MCP server (@verygoodplugins/mcp-local-wp)? [Y/n]: " "Y"
267
-
268
- if [[ "$install_mcp" =~ ^[Yy]?$ ]]; then
269
- if run_with_spinner "Installing LocalWP MCP server" npm_global_install "@verygoodplugins/mcp-local-wp"; then
270
- print_info "Start with: ~/.aidevops/agents/scripts/localhost-helper.sh start-mcp"
271
- print_info "Or configure in OpenCode MCP settings for auto-start"
272
- setup_track_configured "LocalWP MCP"
273
- else
274
- print_info "Try manually: sudo npm install -g @verygoodplugins/mcp-local-wp"
275
- fi
276
- else
277
- print_info "Skipped LocalWP MCP server installation"
278
- print_info "Install later: npm install -g @verygoodplugins/mcp-local-wp"
279
- fi
280
-
281
- return 0
282
- }
283
-
284
- _setup_browser_tools_install_bun() {
285
- # Install Bun and add it to all shell rc files. Sets has_bun in caller scope.
286
- print_info "Installing Bun (required for dev-browser)..."
287
- if ! verified_install "Bun" "https://bun.sh/install"; then
288
- print_warning "Bun installation failed - dev-browser will need manual setup"
289
- return 0
290
- fi
291
-
292
- # Source the updated PATH
293
- export BUN_INSTALL="$HOME/.bun"
294
- export PATH="$BUN_INSTALL/bin:$PATH"
295
- if ! command -v bun &>/dev/null; then
296
- return 0
297
- fi
298
-
299
- print_success "Bun installed: $(bun --version)"
300
-
301
- # Bun's installer may only write to the running shell's rc file.
302
- # Ensure Bun PATH is in all shell rc files for cross-shell compat.
303
- # shellcheck disable=SC2016 # written to rc files; must expand at shell startup, not now
304
- local bun_path_line='export BUN_INSTALL="$HOME/.bun"'
305
- # shellcheck disable=SC2016 # written to rc files; must expand at shell startup, not now
306
- local bun_export_line='export PATH="$BUN_INSTALL/bin:$PATH"'
307
- local bun_rc
308
- while IFS= read -r bun_rc; do
309
- [[ -z "$bun_rc" ]] && continue
310
- [[ ! -f "$bun_rc" ]] && touch "$bun_rc"
311
- if ! grep -q '\.bun' "$bun_rc" 2>/dev/null; then
312
- {
313
- echo ""
314
- echo "# Bun (added by aidevops setup)"
315
- echo "$bun_path_line"
316
- echo "$bun_export_line"
317
- } >>"$bun_rc"
318
- print_info "Added Bun to PATH in $bun_rc"
319
- fi
320
- done < <(get_all_shell_rcs)
321
- return 0
322
- }
323
-
324
- _setup_browser_tools_dev_browser() {
325
- # Install dev-browser (stateful browser automation) using Bun.
326
- local dev_browser_dir="$HOME/.aidevops/dev-browser"
327
-
328
- if [[ -d "${dev_browser_dir}/skills/dev-browser" ]]; then
329
- print_success "dev-browser already installed"
330
- return 0
331
- fi
332
-
333
- print_info "Installing dev-browser (stateful browser automation)..."
334
- local dev_browser_output
335
- if dev_browser_output=$(bash "$HOME/.aidevops/agents/scripts/dev-browser-helper.sh" setup 2>&1); then
336
- print_success "dev-browser installed"
337
- print_info "Start server with: bash ~/.aidevops/agents/scripts/dev-browser-helper.sh start"
338
- else
339
- print_warning "dev-browser setup failed:"
340
- # Show last few lines of error output for debugging
341
- echo "$dev_browser_output" | tail -5 | sed 's/^/ /'
342
- echo ""
343
- print_info "Run manually to see full output:"
344
- print_info " bash ~/.aidevops/agents/scripts/dev-browser-helper.sh setup"
345
- fi
346
- return 0
347
- }
348
-
349
- _setup_browser_tools_playwright() {
350
- # Install Playwright MCP browsers (chromium, firefox, webkit).
351
- print_info "Setting up Playwright MCP..."
352
-
353
- # Check if Playwright browsers are installed (--no-install prevents auto-download)
354
- if npx --no-install playwright --version &>/dev/null 2>&1; then
355
- print_success "Playwright already installed"
356
- print_info "Playwright MCP runs via: npx playwright-mcp@latest"
357
- return 0
358
- fi
359
-
360
- local install_playwright
361
- setup_prompt install_playwright "Install Playwright MCP with browsers (chromium, firefox, webkit)? [Y/n]: " "Y"
362
-
363
- if [[ "$install_playwright" =~ ^[Yy]?$ ]]; then
364
- print_info "Installing Playwright browsers..."
365
- # Use -y to auto-confirm npx install, suppress the "install without dependencies" warning
366
- # Use PIPESTATUS to check npx exit code, not grep's exit code
367
- npx -y playwright@latest install 2>&1 | grep -v "WARNING: It looks like you are running"
368
- if [[ ${PIPESTATUS[0]} -eq 0 ]]; then
369
- print_success "Playwright browsers installed"
370
- else
371
- print_warning "Playwright browser installation failed"
372
- print_info "Run manually: npx -y playwright@latest install"
373
- fi
374
- else
375
- print_info "Skipped Playwright installation"
376
- print_info "Install later with: npx playwright install"
377
- fi
378
-
379
- print_info "Playwright MCP runs via: npx playwright-mcp@latest"
380
- return 0
381
- }
382
-
383
- setup_browser_tools() {
384
- print_info "Setting up browser automation tools..."
385
-
386
- local has_bun=false
387
- local has_node=false
388
-
389
- # Check Bun
390
- if command -v bun &>/dev/null; then
391
- has_bun=true
392
- print_success "Bun $(bun --version) found"
393
- fi
394
-
395
- # Check Node.js (for Playwriter / Playwright)
396
- if command -v node &>/dev/null; then
397
- has_node=true
398
- fi
399
-
400
- # Install Bun if not present (required for dev-browser)
401
- if [[ "$has_bun" == "false" ]]; then
402
- _setup_browser_tools_install_bun
403
- command -v bun &>/dev/null && has_bun=true
404
- fi
405
-
406
- # Setup dev-browser if Bun is available
407
- [[ "$has_bun" == "true" ]] && _setup_browser_tools_dev_browser
408
-
409
- # Playwriter MCP (Node.js based, runs via npx)
410
- if [[ "$has_node" == "true" ]]; then
411
- print_success "Playwriter MCP available (runs via npx playwriter@latest)"
412
- print_info "Install Chrome extension: https://chromewebstore.google.com/detail/playwriter-mcp/jfeammnjpkecdekppnclgkkffahnhfhe"
413
- else
414
- print_warning "Node.js not found - Playwriter MCP unavailable"
415
- fi
416
-
417
- # Playwright MCP (cross-browser testing automation)
418
- [[ "$has_node" == "true" ]] && _setup_browser_tools_playwright
419
-
420
- if [[ "$has_node" == "true" ]]; then
421
- print_info "Browser tools: dev-browser (stateful), Playwriter (extension), Playwright (testing), Stagehand (AI)"
422
- else
423
- print_info "Browser tools: dev-browser (stateful), Stagehand (AI)"
424
- fi
425
- return 0
426
- }
427
-
428
- _setup_opencode_plugins_remove_cursor_oauth() {
429
- # Remove the broken opencode-cursor-oauth plugin if present.
430
- # The opencode-cursor-oauth npm plugin crashes during startup and
431
- # silently prevents ALL plugins from loading (including ours).
432
- # Filed: https://github.com/ephraimduncan/opencode-cursor/issues/15
433
- # Re-enable when the upstream fix is released.
434
- local opencode_config="$1"
435
- local cursor_plugin="opencode-cursor-oauth"
436
-
437
- local cursor_present
438
- cursor_present=$(jq --arg p "$cursor_plugin" \
439
- '(.plugin // []) | map(select(. == $p)) | length' \
440
- "$opencode_config" 2>/dev/null || echo "0")
441
- if [[ "$cursor_present" -gt 0 ]]; then
442
- local tmp_cursor="${opencode_config}.tmp.$$"
443
- if jq --arg p "$cursor_plugin" \
444
- '.plugin = [.plugin[] | select(. != $p)]' \
445
- "$opencode_config" >"$tmp_cursor" 2>/dev/null; then
446
- mv "$tmp_cursor" "$opencode_config"
447
- print_warning "Removed opencode-cursor-oauth plugin (crashes all plugin loading)"
448
- print_info " Filed: https://github.com/ephraimduncan/opencode-cursor/issues/15"
449
- else
450
- rm -f "$tmp_cursor"
451
- print_warning "Failed to remove opencode-cursor-oauth plugin from opencode.json (file: $opencode_config)"
452
- fi
453
- fi
454
- return 0
455
- }
456
-
457
- _setup_opencode_plugins_register_file_url() {
458
- # Mechanism 1: register aidevops plugin via file:// URL in opencode.json.
459
- # Also removes the broken opencode-cursor-oauth plugin if present.
460
- # Prints "true" or "false" to indicate registration status.
461
- local opencode_config="$1"
462
- local aidevops_plugin_entrypoint="$2"
463
- local plugin_url="file://${aidevops_plugin_entrypoint}"
464
-
465
- if ! command -v jq &>/dev/null; then
466
- print_info "jq not installed — cannot update opencode.json plugin array"
467
- echo "false"
468
- return 0
469
- fi
470
-
471
- # Check if the plugin URL is already in the array
472
- local already_registered
473
- already_registered=$(jq --arg url "$plugin_url" \
474
- '(.plugin // []) | map(select(. == $url)) | length' \
475
- "$opencode_config" || echo "0")
476
-
477
- if [[ "$already_registered" -eq 0 ]]; then
478
- local tmp_config="${opencode_config}.tmp.$$"
479
- if jq --arg url "$plugin_url" \
480
- '.plugin = ((.plugin // []) + [$url] | unique)' \
481
- "$opencode_config" >"$tmp_config"; then
482
- mv "$tmp_config" "$opencode_config"
483
- print_success "aidevops plugin registered in opencode.json"
484
- else
485
- rm -f "$tmp_config"
486
- print_warning "Failed to update opencode.json plugin array (file: $opencode_config)"
487
- fi
488
- else
489
- print_success "aidevops plugin already registered in opencode.json"
490
- fi
491
-
492
- _setup_opencode_plugins_remove_cursor_oauth "$opencode_config"
493
-
494
- echo "true"
495
- return 0
496
- }
497
-
498
- _setup_opencode_plugins_register_symlink() {
499
- # Mechanism 2: symlink in ~/.config/opencode/plugins/ (belt-and-suspenders).
500
- local plugins_dir="$1"
501
- local aidevops_plugin_src="$2"
502
- local aidevops_plugin_dst="$3"
503
-
504
- mkdir -p "$plugins_dir"
505
- if [[ -L "$aidevops_plugin_dst" ]]; then
506
- if [[ ! -e "$aidevops_plugin_dst" ]]; then
507
- print_warning "Broken aidevops plugin symlink detected; recreating"
508
- ln -sfn "$aidevops_plugin_src" "$aidevops_plugin_dst"
509
- fi
510
- elif [[ ! -d "$aidevops_plugin_dst" ]]; then
511
- ln -sfn "$aidevops_plugin_src" "$aidevops_plugin_dst"
512
- fi
513
- return 0
514
- }
515
-
516
- _setup_opencode_plugins_print_pool_guidance() {
517
- # Print OAuth pool authentication instructions for OpenCode v1.2.30+.
518
- local pool_plugin_registered="$1"
519
-
520
- if [[ "$pool_plugin_registered" == "true" ]]; then
521
- print_info "Use the aidevops OAuth pool (provided by the aidevops plugin above):"
522
- print_info " 1. Run: opencode auth login"
523
- print_info " 2. Select: 'Anthropic Pool' (added by aidevops plugin)"
524
- print_info " 3. Enter your Claude account email"
525
- print_info " 4. Complete the OAuth flow in your browser"
526
- print_info " 5. Repeat to add more accounts for automatic rotation"
527
- print_info " 6. Switch to 'Anthropic' provider and select a model to start chatting"
528
- print_info ""
529
- print_info "For Cursor Pro accounts:"
530
- print_info " Run: opencode auth login --provider cursor"
531
- print_info ""
532
- print_info " Health check: /models-pool-check"
533
- print_info " Manage accounts: /model-accounts-pool list|status|remove"
534
- else
535
- print_warning "aidevops OpenCode plugin was not registered; 'Anthropic Pool' may be unavailable"
536
- print_info "Re-run aidevops setup to register the plugin, then run: opencode auth login"
537
- fi
538
- return 0
539
- }
540
-
541
- _setup_opencode_plugins_auth_guidance() {
542
- # Print version-appropriate authentication instructions.
543
- # Note: opencode-anthropic-auth is built into OpenCode v1.1.36+
544
- # Adding it as an external plugin causes TypeError due to double-loading.
545
- # Removed in v2.90.0 - see PR #230.
546
- local pool_plugin_registered="$1"
547
-
548
- # Detect OpenCode version to give appropriate auth guidance (t1546, GH#5312)
549
- # v1.2.30+ removes the built-in anthropic-auth plugin entirely.
550
- local oc_raw_version
551
- oc_raw_version=$(opencode --version 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "0.0.0")
552
-
553
- local oc_major oc_minor oc_patch
554
- IFS='.' read -r oc_major oc_minor oc_patch <<<"$oc_raw_version"
555
- oc_major="${oc_major:-0}"
556
- oc_minor="${oc_minor:-0}"
557
- oc_patch="${oc_patch:-0}"
558
-
559
- # Compare against 1.2.30 (where built-in anthropic-auth was removed)
560
- local builtin_auth_removed="false"
561
- if [[ "$oc_major" -gt 1 ]] ||
562
- [[ "$oc_major" -eq 1 && "$oc_minor" -gt 2 ]] ||
563
- [[ "$oc_major" -eq 1 && "$oc_minor" -eq 2 && "$oc_patch" -ge 30 ]]; then
564
- builtin_auth_removed="true"
565
- fi
566
-
567
- if [[ "$builtin_auth_removed" == "true" ]]; then
568
- print_info "OpenCode v${oc_raw_version}: built-in Anthropic OAuth removed in v1.2.30"
569
- _setup_opencode_plugins_print_pool_guidance "$pool_plugin_registered"
570
- else
571
- print_info "After setup, authenticate with: opencode auth login"
572
- print_info " - For Claude OAuth (v1.1.36-v1.2.29): Select 'Anthropic' -> 'Claude Pro/Max' (built-in)"
573
- print_info " - Or use the aidevops OAuth pool: Select 'Anthropic Pool' for multi-account rotation"
574
- fi
575
- return 0
576
- }
577
-
578
- setup_opencode_plugins() {
579
- # Check prerequisites before announcing setup (GH#5240)
580
- if ! command -v opencode &>/dev/null; then
581
- print_skip "OpenCode plugins" "OpenCode not installed" "Install from https://opencode.ai"
582
- setup_track_skipped "OpenCode plugins" "OpenCode not installed"
583
- return 0
584
- fi
585
-
586
- # Prerequisites met — proceed with setup
587
- print_info "Setting up OpenCode plugins..."
588
-
589
- # Register aidevops plugin using two complementary mechanisms:
590
- # 1. file:// URL in opencode.json "plugin" array (works on all tested versions)
591
- # 2. Symlink in ~/.config/opencode/plugins/ (newer OpenCode convention)
592
- # Both are idempotent — the plugin's registerPoolProvider() checks before adding.
593
- local plugins_dir="$HOME/.config/opencode/plugins"
594
- local aidevops_plugin_src="$HOME/.aidevops/agents/plugins/opencode-aidevops"
595
- local aidevops_plugin_dst="$plugins_dir/opencode-aidevops"
596
- local aidevops_plugin_entrypoint="$aidevops_plugin_src/index.mjs"
597
-
598
- if [[ ! -f "$aidevops_plugin_entrypoint" ]]; then
599
- print_skip "OpenCode plugins" "aidevops plugin entry point not found: $aidevops_plugin_entrypoint"
600
- setup_track_deferred "OpenCode plugins" "Install/restore aidevops plugin at $aidevops_plugin_entrypoint"
601
- return 0
602
- fi
603
-
604
- # Mechanism 1: file:// URL in opencode.json
605
- local pool_plugin_registered="false"
606
- local opencode_config
607
- if opencode_config=$(find_opencode_config); then
608
- pool_plugin_registered=$(_setup_opencode_plugins_register_file_url "$opencode_config" "$aidevops_plugin_entrypoint")
609
- else
610
- print_info "opencode.json not found — run 'opencode' once to create it, then re-run setup"
611
- fi
612
-
613
- # Mechanism 2: symlink in plugins directory
614
- _setup_opencode_plugins_register_symlink "$plugins_dir" "$aidevops_plugin_src" "$aidevops_plugin_dst"
615
-
616
- setup_track_configured "OpenCode plugins"
617
-
618
- # Version-appropriate auth guidance
619
- _setup_opencode_plugins_auth_guidance "$pool_plugin_registered"
620
-
621
- return 0
622
- }
623
-
624
- setup_seo_mcps() {
625
- print_info "Setting up SEO integrations..."
626
-
627
- # SEO services use curl-based subagents (no MCP needed)
628
- # Subagents: serper.md, dataforseo.md, ahrefs.md, google-search-console.md
629
- print_info "SEO uses curl-based subagents (zero context cost until invoked)"
630
-
631
- # Check if credentials are configured
632
- if [[ -f "$HOME/.config/aidevops/credentials.sh" ]]; then
633
- # shellcheck source=/dev/null
634
- source "$HOME/.config/aidevops/credentials.sh"
635
-
636
- if [[ -n "${DATAFORSEO_USERNAME:-}" ]]; then
637
- print_success "DataForSEO credentials configured"
638
- else
639
- print_info "DataForSEO: set DATAFORSEO_USERNAME and DATAFORSEO_PASSWORD in credentials.sh"
640
- fi
641
-
642
- if [[ -n "${SERPER_API_KEY:-}" ]]; then
643
- print_success "Serper API key configured"
644
- else
645
- print_info "Serper: set SERPER_API_KEY in credentials.sh"
646
- fi
647
-
648
- if [[ -n "${AHREFS_API_KEY:-}" ]]; then
649
- print_success "Ahrefs API key configured"
650
- else
651
- print_info "Ahrefs: set AHREFS_API_KEY in credentials.sh"
652
- fi
653
- else
654
- print_info "Configure SEO API credentials in ~/.config/aidevops/credentials.sh"
655
- fi
656
-
657
- # GSC uses MCP (OAuth2 complexity warrants it)
658
- local gsc_creds="$HOME/.config/aidevops/gsc-credentials.json"
659
- if [[ -f "$gsc_creds" ]]; then
660
- print_success "Google Search Console credentials configured"
661
- else
662
- print_info "GSC: Create service account JSON at $gsc_creds"
663
- print_info " See: ~/.aidevops/agents/seo/google-search-console.md"
664
- fi
665
-
666
- print_info "SEO documentation: ~/.aidevops/agents/seo/"
667
- return 0
668
- }
669
-
670
- _setup_google_analytics_mcp_detect_creds() {
671
- # Detect GSC credentials and print three lines: creds_path, project_id, enable_mcp.
672
- local gsc_creds="$1"
673
- local creds_path=""
674
- local project_id=""
675
- local enable_mcp="false"
676
-
677
- if [[ -f "$gsc_creds" ]]; then
678
- creds_path="$gsc_creds"
679
- project_id=$(jq -r '.project_id // empty' "$gsc_creds" 2>/dev/null)
680
- if [[ -n "$project_id" ]]; then
681
- enable_mcp="true"
682
- print_success "Found GSC credentials - sharing with Google Analytics MCP"
683
- print_info "Project: $project_id"
684
- fi
685
- fi
686
-
687
- printf '%s\n%s\n%s\n' "$creds_path" "$project_id" "$enable_mcp"
688
- return 0
689
- }
690
-
691
- _setup_google_analytics_mcp_update_existing() {
692
- # Update an existing google-analytics-mcp entry in opencode.json.
693
- local opencode_config="$1"
694
- local creds_path="$2"
695
- local project_id="$3"
696
- local enable_mcp="$4"
697
-
698
- if [[ "$enable_mcp" == "true" ]]; then
699
- local tmp_config
700
- tmp_config=$(mktemp)
701
- trap 'rm -f "${tmp_config:-}"' RETURN
702
- if jq --arg creds "$creds_path" --arg proj "$project_id" \
703
- '.mcp["google-analytics-mcp"].environment.GOOGLE_APPLICATION_CREDENTIALS = $creds |
704
- .mcp["google-analytics-mcp"].environment.GOOGLE_PROJECT_ID = $proj |
705
- .mcp["google-analytics-mcp"].enabled = true' \
706
- "$opencode_config" >"$tmp_config" 2>/dev/null; then
707
- mv "$tmp_config" "$opencode_config"
708
- print_success "Updated Google Analytics MCP with GSC credentials (enabled)"
709
- else
710
- rm -f "$tmp_config"
711
- print_warning "Failed to update Google Analytics MCP config"
712
- fi
713
- else
714
- print_info "Google Analytics MCP already configured in OpenCode"
715
- fi
716
- return 0
717
- }
718
-
719
- _setup_google_analytics_mcp_add_new() {
720
- # Add a new google-analytics-mcp entry to opencode.json.
721
- local opencode_config="$1"
722
- local creds_path="$2"
723
- local project_id="$3"
724
- local enable_mcp="$4"
725
- local gsc_creds="$5"
726
-
727
- local tmp_config
728
- tmp_config=$(mktemp)
729
- trap 'rm -f "${tmp_config:-}"' RETURN
730
-
731
- if jq --arg creds "$creds_path" --arg proj "$project_id" --argjson enabled "$enable_mcp" \
732
- '.mcp["google-analytics-mcp"] = {
733
- "type": "local",
734
- "command": ["analytics-mcp"],
735
- "environment": {
736
- "GOOGLE_APPLICATION_CREDENTIALS": $creds,
737
- "GOOGLE_PROJECT_ID": $proj
738
- },
739
- "enabled": $enabled
740
- }' "$opencode_config" >"$tmp_config" 2>/dev/null; then
741
- mv "$tmp_config" "$opencode_config"
742
- if [[ "$enable_mcp" == "true" ]]; then
743
- print_success "Added Google Analytics MCP to OpenCode (enabled with GSC credentials)"
744
- else
745
- print_success "Added Google Analytics MCP to OpenCode (disabled - no credentials found)"
746
- print_info "To enable: Create service account JSON at $gsc_creds"
747
- fi
748
- print_info "Or use the google-analytics subagent which enables it automatically"
749
- else
750
- rm -f "$tmp_config"
751
- print_warning "Failed to add Google Analytics MCP to config"
752
- fi
753
- return 0
754
- }
755
-
756
- _setup_google_analytics_mcp_write_config() {
757
- # Update or add the google-analytics-mcp entry in opencode.json.
758
- local opencode_config="$1"
759
- local creds_path="$2"
760
- local project_id="$3"
761
- local enable_mcp="$4"
762
- local gsc_creds="$5"
763
-
764
- # Update existing entry if present
765
- if jq -e '.mcp["google-analytics-mcp"]' "$opencode_config" >/dev/null 2>&1; then
766
- _setup_google_analytics_mcp_update_existing "$opencode_config" "$creds_path" "$project_id" "$enable_mcp"
767
- return 0
768
- fi
769
-
770
- # Add new entry
771
- _setup_google_analytics_mcp_add_new "$opencode_config" "$creds_path" "$project_id" "$enable_mcp" "$gsc_creds"
772
- return 0
773
- }
774
-
775
- setup_google_analytics_mcp() {
776
- local gsc_creds="$HOME/.config/aidevops/gsc-credentials.json"
777
-
778
- # Check prerequisites before announcing setup (GH#5240)
779
- local opencode_config
780
- if ! opencode_config=$(find_opencode_config); then
781
- print_skip "Google Analytics MCP" "OpenCode config not found" "Run 'opencode' once to create config, then re-run setup"
782
- setup_track_skipped "Google Analytics MCP" "OpenCode config not found"
783
- return 0
784
- fi
785
-
786
- if ! command -v jq &>/dev/null; then
787
- print_skip "Google Analytics MCP" "jq not installed" "Install jq: brew install jq (macOS) or apt install jq"
788
- setup_track_deferred "Google Analytics MCP" "Install jq"
789
- return 0
790
- fi
791
-
792
- if ! command -v pipx &>/dev/null; then
793
- print_skip "Google Analytics MCP" "pipx not installed" "Install pipx: brew install pipx (macOS) or pip install pipx"
794
- setup_track_deferred "Google Analytics MCP" "Install pipx"
795
- return 0
796
- fi
797
-
798
- # Prerequisites met — proceed with setup
799
- print_info "Setting up Google Analytics MCP..."
800
-
801
- # Auto-detect credentials from shared GSC service account
802
- local creds_output
803
- creds_output=$(_setup_google_analytics_mcp_detect_creds "$gsc_creds")
804
- local creds_path project_id enable_mcp
805
- creds_path=$(printf '%s\n' "$creds_output" | sed -n '1p')
806
- project_id=$(printf '%s\n' "$creds_output" | sed -n '2p')
807
- enable_mcp=$(printf '%s\n' "$creds_output" | sed -n '3p')
808
-
809
- # Update or add config entry
810
- _setup_google_analytics_mcp_write_config "$opencode_config" "$creds_path" "$project_id" "$enable_mcp" "$gsc_creds"
811
-
812
- # Show setup instructions
813
- print_info "Google Analytics MCP setup:"
814
- print_info " 1. Enable Google Analytics Admin & Data APIs in Google Cloud Console"
815
- print_info " 2. Configure ADC: gcloud auth application-default login --scopes https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/cloud-platform"
816
- print_info " 3. Update GOOGLE_APPLICATION_CREDENTIALS path in opencode.json"
817
- print_info " 4. Set GOOGLE_PROJECT_ID in opencode.json"
818
- print_info "Documentation: ~/.aidevops/agents/services/analytics/google-analytics.md"
819
-
820
- return 0
821
- }
822
-
823
- _setup_quickfile_mcp_clone_and_build() {
824
- # Clone and build the QuickFile MCP server. Returns 1 if user skips or build fails.
825
- local quickfile_dir="$1"
826
-
827
- if [[ -f "$quickfile_dir/dist/index.js" ]]; then
828
- print_success "QuickFile MCP already installed at $quickfile_dir"
829
- return 0
830
- fi
831
-
832
- print_info "QuickFile MCP provides AI access to UK accounting (invoices, clients, reports)"
833
- local install_qf
834
- setup_prompt install_qf "Clone and build QuickFile MCP server? [Y/n]: " "Y"
835
-
836
- if [[ ! "$install_qf" =~ ^[Yy]?$ ]]; then
837
- print_info "Skipped QuickFile MCP installation"
838
- print_info "Install later: git clone https://github.com/marcusquinn/quickfile-mcp.git ~/Git/quickfile-mcp"
839
- return 1
840
- fi
841
-
842
- if [[ ! -d "$quickfile_dir" ]]; then
843
- if ! run_with_spinner "Cloning quickfile-mcp" git clone https://github.com/marcusquinn/quickfile-mcp.git "$quickfile_dir"; then
844
- print_warning "Failed to clone quickfile-mcp"
845
- return 1
846
- fi
847
- print_success "Cloned quickfile-mcp"
848
- fi
849
-
850
- if ! run_with_spinner "Installing dependencies" npm install --prefix "$quickfile_dir"; then
851
- print_warning "npm install failed - try manually: cd $quickfile_dir && npm install"
852
- return 1
853
- fi
854
-
855
- if ! run_with_spinner "Building QuickFile MCP" npm run build --prefix "$quickfile_dir"; then
856
- print_warning "Build failed - try manually: cd $quickfile_dir && npm run build"
857
- return 1
858
- fi
859
-
860
- print_success "QuickFile MCP built successfully"
861
- return 0
862
- }
863
-
864
- _setup_quickfile_mcp_check_credentials() {
865
- # Check and display QuickFile credential status.
866
- local credentials_dir="$1"
867
- local credentials_file="$2"
868
-
869
- if [[ -f "$credentials_file" ]]; then
870
- print_success "QuickFile credentials configured at $credentials_file"
871
- else
872
- print_info "QuickFile credentials not found"
873
- print_info "Create credentials:"
874
- print_info " mkdir -p $credentials_dir && chmod 700 $credentials_dir"
875
- print_info " Create $credentials_file with:"
876
- print_info " accountNumber: from QuickFile dashboard (top-right)"
877
- print_info " apiKey: Account Settings > 3rd Party Integrations > API Key"
878
- print_info " applicationId: Account Settings > Create a QuickFile App"
879
- fi
880
- return 0
881
- }
882
-
883
- _setup_quickfile_mcp_update_opencode() {
884
- # Add QuickFile MCP entry to OpenCode config if not already present.
885
- local quickfile_dir="$1"
886
-
887
- local opencode_config
888
- if ! opencode_config=$(find_opencode_config); then
889
- return 0
890
- fi
891
-
892
- local quickfile_entry
893
- quickfile_entry=$(jq -r '.mcp.quickfile // empty' "$opencode_config" 2>/dev/null)
894
-
895
- if [[ -n "$quickfile_entry" ]]; then
896
- print_success "QuickFile MCP already in OpenCode config"
897
- return 0
898
- fi
899
-
900
- print_info "Adding QuickFile MCP to OpenCode config..."
901
- local node_path
902
- node_path=$(resolve_mcp_binary_path "node")
903
- [[ -z "$node_path" ]] && node_path="node"
904
-
905
- local tmp_config
906
- tmp_config=$(mktemp)
907
- trap 'rm -f "${tmp_config:-}"' RETURN
908
-
909
- if jq --arg np "$node_path" --arg dp "$quickfile_dir/dist/index.js" \
910
- '.mcp.quickfile = {"type": "local", "command": [$np, $dp], "enabled": true}' \
911
- "$opencode_config" >"$tmp_config" 2>/dev/null; then
912
- create_backup_with_rotation "$opencode_config" "opencode"
913
- mv "$tmp_config" "$opencode_config"
914
- print_success "QuickFile MCP added to OpenCode config"
915
- else
916
- rm -f "$tmp_config"
917
- print_warning "Failed to update OpenCode config - add manually"
918
- fi
919
- return 0
920
- }
921
-
922
- setup_quickfile_mcp() {
923
- local quickfile_dir="$HOME/Git/quickfile-mcp"
924
- local credentials_dir="$HOME/.config/.quickfile-mcp"
925
- local credentials_file="$credentials_dir/credentials.json"
926
-
927
- # Check prerequisites before announcing setup (GH#5240)
928
- if ! command -v node &>/dev/null; then
929
- print_skip "QuickFile MCP" "Node.js not installed" "Install Node.js 18+: brew install node (macOS) or nvm install 18"
930
- setup_track_deferred "QuickFile MCP" "Install Node.js 18+"
931
- return 0
932
- fi
933
-
934
- # Prerequisites met — proceed with setup
935
- print_info "Setting up QuickFile MCP server..."
936
-
937
- # Clone and build (returns 1 if skipped or failed)
938
- if ! _setup_quickfile_mcp_clone_and_build "$quickfile_dir"; then
939
- return 0
940
- fi
941
-
942
- _setup_quickfile_mcp_check_credentials "$credentials_dir" "$credentials_file"
943
- _setup_quickfile_mcp_update_opencode "$quickfile_dir"
944
-
945
- print_info "Documentation: ~/.aidevops/agents/services/accounting/quickfile.md"
946
- return 0
947
- }