aidevops 2.172.18 → 2.172.19

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.
@@ -0,0 +1,766 @@
1
+ #!/usr/bin/env bash
2
+ # MCP setup functions: install_mcp_packages, resolve_mcp_binary, localwp, augment, seo, analytics, quickfile, browser-tools, opencode-plugins
3
+ # Part of aidevops setup.sh modularization (t316.3)
4
+
5
+ # Shell safety baseline
6
+ set -Eeuo pipefail
7
+ IFS=$'\n\t'
8
+ # shellcheck disable=SC2154 # rc is assigned by $? in the trap string
9
+ trap 'rc=$?; echo "[ERROR] ${BASH_SOURCE[0]}:${LINENO} exit $rc" >&2' ERR
10
+ shopt -s inherit_errexit 2>/dev/null || true
11
+
12
+ install_mcp_packages() {
13
+ print_info "Installing MCP server packages globally (eliminates npx startup delay)..."
14
+
15
+ # Security note: MCP servers run as persistent processes with access to conversation
16
+ # context, credentials, and network. The packages below are from known/vetted sources.
17
+ # Before adding new MCP packages to this list, verify the source repository and scan
18
+ # dependencies with: npx @socketsecurity/cli npm info <package>
19
+ # See: .agents/tools/mcp-toolkit/mcporter.md "Security Considerations"
20
+
21
+ # Node.js MCP packages to install globally
22
+ local -a node_mcps=(
23
+ "chrome-devtools-mcp"
24
+ "mcp-server-gsc"
25
+ "playwriter"
26
+ "@steipete/macos-automator-mcp"
27
+ "@steipete/claude-code-mcp"
28
+ )
29
+
30
+ if ! command -v bun &>/dev/null && ! command -v npm &>/dev/null; then
31
+ print_warning "Neither bun nor npm found - cannot install MCP packages"
32
+ print_info "Install bun (recommended): npm install -g bun OR brew install oven-sh/bun/bun"
33
+ return 0
34
+ fi
35
+
36
+ local installer="npm"
37
+ command -v bun &>/dev/null && installer="bun"
38
+ print_info "Using $installer to install/update Node.js MCP packages..."
39
+
40
+ # Always install latest (bun install -g is fast and idempotent)
41
+ local updated=0
42
+ local failed=0
43
+ local pkg
44
+ for pkg in "${node_mcps[@]}"; do
45
+ local short_name="${pkg##*/}" # Strip @scope/ prefix for display
46
+ if run_with_spinner "Installing $short_name" npm_global_install "${pkg}@latest"; then
47
+ ((++updated))
48
+ else
49
+ ((++failed))
50
+ print_warning "Failed to install/update $pkg"
51
+ fi
52
+ done
53
+
54
+ if [[ $updated -gt 0 ]]; then
55
+ print_success "$updated Node.js MCP packages installed/updated to latest via $installer"
56
+ fi
57
+ if [[ $failed -gt 0 ]]; then
58
+ print_warning "$failed packages failed (check network or package names)"
59
+ fi
60
+
61
+ # Python MCP packages (install or upgrade)
62
+ if command -v pipx &>/dev/null; then
63
+ print_info "Installing/updating analytics-mcp via pipx..."
64
+ if command -v analytics-mcp &>/dev/null; then
65
+ pipx upgrade analytics-mcp >/dev/null 2>&1 || true
66
+ else
67
+ pipx install analytics-mcp >/dev/null 2>&1 || print_warning "Failed to install analytics-mcp"
68
+ fi
69
+ fi
70
+
71
+ if command -v uv &>/dev/null; then
72
+ print_info "Installing/updating outscraper-mcp-server via uv..."
73
+ if command -v outscraper-mcp-server &>/dev/null; then
74
+ uv tool upgrade outscraper-mcp-server >/dev/null 2>&1 || true
75
+ else
76
+ uv tool install outscraper-mcp-server >/dev/null 2>&1 || print_warning "Failed to install outscraper-mcp-server"
77
+ fi
78
+ fi
79
+
80
+ # Update opencode.json with resolved full paths for all MCP binaries
81
+ update_mcp_paths_in_opencode
82
+
83
+ print_info "MCP servers will start instantly (no registry lookups on each launch)"
84
+ return 0
85
+ }
86
+
87
+ resolve_mcp_binary_path() {
88
+ local bin_name
89
+ bin_name="$1"
90
+ local resolved=""
91
+
92
+ # Check common locations in priority order
93
+ local search_paths=(
94
+ "$HOME/.bun/bin/$bin_name"
95
+ "/opt/homebrew/bin/$bin_name"
96
+ "/usr/local/bin/$bin_name"
97
+ "$HOME/.local/bin/$bin_name"
98
+ "$HOME/.npm-global/bin/$bin_name"
99
+ )
100
+
101
+ for path in "${search_paths[@]}"; do
102
+ if [[ -x "$path" ]]; then
103
+ resolved="$path"
104
+ break
105
+ fi
106
+ done
107
+
108
+ # Fallback: use command -v if in PATH (portable, POSIX-compliant)
109
+ if [[ -z "$resolved" ]]; then
110
+ resolved=$(command -v "$bin_name" 2>/dev/null || true)
111
+ fi
112
+
113
+ echo "$resolved"
114
+ return 0
115
+ }
116
+
117
+ update_mcp_paths_in_opencode() {
118
+ local opencode_config
119
+ opencode_config=$(find_opencode_config) || return 0
120
+
121
+ if [[ ! -f "$opencode_config" ]]; then
122
+ return 0
123
+ fi
124
+
125
+ if ! command -v jq &>/dev/null; then
126
+ return 0
127
+ fi
128
+
129
+ local tmp_config
130
+ tmp_config=$(mktemp)
131
+ trap 'rm -f "${tmp_config:-}"' RETURN
132
+ cp "$opencode_config" "$tmp_config"
133
+
134
+ local updated=0
135
+
136
+ # Get all MCP entries with local commands
137
+ local mcp_keys
138
+ mcp_keys=$(jq -r '.mcp | to_entries[] | select(.value.type == "local") | select(.value.command != null) | .key' "$tmp_config" 2>/dev/null)
139
+
140
+ while IFS= read -r mcp_key; do
141
+ [[ -z "$mcp_key" ]] && continue
142
+
143
+ # Get the first element of the command array (the binary)
144
+ local current_cmd
145
+ current_cmd=$(jq -r --arg k "$mcp_key" '.mcp[$k].command[0]' "$tmp_config" 2>/dev/null)
146
+
147
+ # Skip if already a full path
148
+ if [[ "$current_cmd" == /* ]]; then
149
+ # Verify the path still exists
150
+ if [[ ! -x "$current_cmd" ]]; then
151
+ # Path is stale, try to resolve
152
+ local bin_name
153
+ bin_name=$(basename "$current_cmd")
154
+ local new_path
155
+ new_path=$(resolve_mcp_binary_path "$bin_name")
156
+ if [[ -n "$new_path" && "$new_path" != "$current_cmd" ]]; then
157
+ 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"
158
+ ((++updated))
159
+ fi
160
+ fi
161
+ continue
162
+ fi
163
+
164
+ # Skip docker (container runtime) and node (resolved separately below)
165
+ case "$current_cmd" in
166
+ docker | node) continue ;;
167
+ esac
168
+
169
+ # Resolve the full path
170
+ local full_path
171
+ full_path=$(resolve_mcp_binary_path "$current_cmd")
172
+
173
+ if [[ -n "$full_path" && "$full_path" != "$current_cmd" ]]; then
174
+ 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"
175
+ ((++updated))
176
+ fi
177
+ done <<<"$mcp_keys"
178
+
179
+ # Also resolve 'node' commands (e.g., quickfile, amazon-order-history)
180
+ # These use ["node", "/path/to/index.js"] - node itself should be resolved
181
+ local node_path
182
+ node_path=$(resolve_mcp_binary_path "node")
183
+ if [[ -n "$node_path" ]]; then
184
+ local node_mcp_keys
185
+ 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)
186
+ while IFS= read -r mcp_key; do
187
+ [[ -z "$mcp_key" ]] && continue
188
+ 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"
189
+ ((++updated))
190
+ done <<<"$node_mcp_keys"
191
+ fi
192
+
193
+ if [[ $updated -gt 0 ]]; then
194
+ create_backup_with_rotation "$opencode_config" "opencode"
195
+ mv "$tmp_config" "$opencode_config"
196
+ print_success "Updated $updated MCP commands to use full binary paths in opencode.json"
197
+ else
198
+ rm -f "$tmp_config"
199
+ fi
200
+
201
+ return 0
202
+ }
203
+
204
+ setup_localwp_mcp() {
205
+ print_info "Setting up LocalWP MCP server..."
206
+
207
+ # Check if LocalWP is installed
208
+ local localwp_found=false
209
+ if [[ -d "/Applications/Local.app" ]] || [[ -d "$HOME/Applications/Local.app" ]]; then
210
+ localwp_found=true
211
+ fi
212
+
213
+ if [[ "$localwp_found" != "true" ]]; then
214
+ print_info "LocalWP not found - skipping MCP server setup"
215
+ print_info "Install LocalWP from: https://localwp.com/"
216
+ return 0
217
+ fi
218
+
219
+ print_success "LocalWP found"
220
+
221
+ # Check if npm is available
222
+ if ! command -v npm &>/dev/null; then
223
+ print_warning "npm not found - cannot install LocalWP MCP server"
224
+ print_info "Install Node.js and npm first"
225
+ return 0
226
+ fi
227
+
228
+ # Check if mcp-local-wp is already installed
229
+ if command -v mcp-local-wp &>/dev/null; then
230
+ print_success "LocalWP MCP server already installed"
231
+ return 0
232
+ fi
233
+
234
+ # Offer to install mcp-local-wp
235
+ print_info "LocalWP MCP server enables AI assistants to query WordPress databases"
236
+ read -r -p "Install LocalWP MCP server (@verygoodplugins/mcp-local-wp)? [Y/n]: " install_mcp
237
+
238
+ if [[ "$install_mcp" =~ ^[Yy]?$ ]]; then
239
+ if run_with_spinner "Installing LocalWP MCP server" npm_global_install "@verygoodplugins/mcp-local-wp"; then
240
+ print_info "Start with: ~/.aidevops/agents/scripts/localhost-helper.sh start-mcp"
241
+ print_info "Or configure in OpenCode MCP settings for auto-start"
242
+ else
243
+ print_info "Try manually: sudo npm install -g @verygoodplugins/mcp-local-wp"
244
+ fi
245
+ else
246
+ print_info "Skipped LocalWP MCP server installation"
247
+ print_info "Install later: npm install -g @verygoodplugins/mcp-local-wp"
248
+ fi
249
+
250
+ return 0
251
+ }
252
+
253
+ setup_augment_context_engine() {
254
+ print_info "Setting up Augment Context Engine MCP..."
255
+
256
+ # Check Node.js version (requires 22+)
257
+ if ! command -v node &>/dev/null; then
258
+ print_warning "Node.js not found - Augment Context Engine setup skipped"
259
+ print_info "Install Node.js 22+ to enable Augment Context Engine"
260
+ return
261
+ fi
262
+
263
+ local node_version
264
+ node_version=$(node --version 2>/dev/null | cut -d'v' -f2 | cut -d'.' -f1)
265
+ if [[ -z "$node_version" ]] || ! [[ "$node_version" =~ ^[0-9]+$ ]]; then
266
+ print_warning "Could not determine Node.js version - Augment Context Engine setup skipped"
267
+ return
268
+ fi
269
+ if [[ "$node_version" -lt 22 ]]; then
270
+ print_warning "Node.js 22+ required for Augment Context Engine, found v$node_version"
271
+ print_info "Install: brew install node@22 (macOS) or nvm install 22"
272
+ return
273
+ fi
274
+
275
+ # Check if auggie is installed
276
+ if ! command -v auggie &>/dev/null; then
277
+ print_warning "Auggie CLI not found"
278
+ print_info "Install with: npm install -g @augmentcode/auggie@prerelease"
279
+ print_info "Then run: auggie login"
280
+ return
281
+ fi
282
+
283
+ # Check if logged in
284
+ if [[ ! -f "$HOME/.augment/session.json" ]]; then
285
+ print_warning "Auggie not logged in"
286
+ print_info "Run: auggie login"
287
+ return
288
+ fi
289
+
290
+ print_success "Auggie CLI found and authenticated"
291
+
292
+ # MCP configuration is handled by generate-opencode-agents.sh for OpenCode
293
+
294
+ print_info "Augment Context Engine available as MCP in OpenCode"
295
+ print_info "Verification: 'What is this project? Please use codebase retrieval tool.'"
296
+
297
+ return 0
298
+ }
299
+
300
+ setup_browser_tools() {
301
+ print_info "Setting up browser automation tools..."
302
+
303
+ local has_bun=false
304
+ local has_node=false
305
+
306
+ # Check Bun
307
+ if command -v bun &>/dev/null; then
308
+ has_bun=true
309
+ print_success "Bun $(bun --version) found"
310
+ fi
311
+
312
+ # Check Node.js (for Playwriter)
313
+ if command -v node &>/dev/null; then
314
+ has_node=true
315
+ fi
316
+
317
+ # Install Bun if not present (required for dev-browser)
318
+ if [[ "$has_bun" == "false" ]]; then
319
+ print_info "Installing Bun (required for dev-browser)..."
320
+ if verified_install "Bun" "https://bun.sh/install"; then
321
+ # Source the updated PATH
322
+ export BUN_INSTALL="$HOME/.bun"
323
+ export PATH="$BUN_INSTALL/bin:$PATH"
324
+ if command -v bun &>/dev/null; then
325
+ has_bun=true
326
+ print_success "Bun installed: $(bun --version)"
327
+
328
+ # Bun's installer may only write to the running shell's rc file.
329
+ # Ensure Bun PATH is in all shell rc files for cross-shell compat.
330
+ # shellcheck disable=SC2016 # written to rc files; must expand at shell startup, not now
331
+ local bun_path_line='export BUN_INSTALL="$HOME/.bun"'
332
+ # shellcheck disable=SC2016 # written to rc files; must expand at shell startup, not now
333
+ local bun_export_line='export PATH="$BUN_INSTALL/bin:$PATH"'
334
+ local bun_rc
335
+ while IFS= read -r bun_rc; do
336
+ [[ -z "$bun_rc" ]] && continue
337
+ if [[ ! -f "$bun_rc" ]]; then
338
+ touch "$bun_rc"
339
+ fi
340
+ if ! grep -q '\.bun' "$bun_rc" 2>/dev/null; then
341
+ {
342
+ echo ""
343
+ echo "# Bun (added by aidevops setup)"
344
+ echo "$bun_path_line"
345
+ echo "$bun_export_line"
346
+ } >>"$bun_rc"
347
+ print_info "Added Bun to PATH in $bun_rc"
348
+ fi
349
+ done < <(get_all_shell_rcs)
350
+ fi
351
+ else
352
+ print_warning "Bun installation failed - dev-browser will need manual setup"
353
+ fi
354
+ fi
355
+
356
+ # Setup dev-browser if Bun is available
357
+ if [[ "$has_bun" == "true" ]]; then
358
+ local dev_browser_dir="$HOME/.aidevops/dev-browser"
359
+
360
+ if [[ -d "${dev_browser_dir}/skills/dev-browser" ]]; then
361
+ print_success "dev-browser already installed"
362
+ else
363
+ print_info "Installing dev-browser (stateful browser automation)..."
364
+ local dev_browser_output
365
+ if dev_browser_output=$(bash "$HOME/.aidevops/agents/scripts/dev-browser-helper.sh" setup 2>&1); then
366
+ print_success "dev-browser installed"
367
+ print_info "Start server with: bash ~/.aidevops/agents/scripts/dev-browser-helper.sh start"
368
+ else
369
+ print_warning "dev-browser setup failed:"
370
+ # Show last few lines of error output for debugging
371
+ echo "$dev_browser_output" | tail -5 | sed 's/^/ /'
372
+ echo ""
373
+ print_info "Run manually to see full output:"
374
+ print_info " bash ~/.aidevops/agents/scripts/dev-browser-helper.sh setup"
375
+ fi
376
+ fi
377
+ fi
378
+
379
+ # Playwriter MCP (Node.js based, runs via npx)
380
+ if [[ "$has_node" == "true" ]]; then
381
+ print_success "Playwriter MCP available (runs via npx playwriter@latest)"
382
+ print_info "Install Chrome extension: https://chromewebstore.google.com/detail/playwriter-mcp/jfeammnjpkecdekppnclgkkffahnhfhe"
383
+ else
384
+ print_warning "Node.js not found - Playwriter MCP unavailable"
385
+ fi
386
+
387
+ # Playwright MCP (cross-browser testing automation)
388
+ if [[ "$has_node" == "true" ]]; then
389
+ print_info "Setting up Playwright MCP..."
390
+
391
+ # Check if Playwright browsers are installed (--no-install prevents auto-download)
392
+ if npx --no-install playwright --version &>/dev/null 2>&1; then
393
+ print_success "Playwright already installed"
394
+ else
395
+ local install_playwright
396
+ read -r -p "Install Playwright MCP with browsers (chromium, firefox, webkit)? [Y/n]: " install_playwright
397
+
398
+ if [[ "$install_playwright" =~ ^[Yy]?$ ]]; then
399
+ print_info "Installing Playwright browsers..."
400
+ # Use -y to auto-confirm npx install, suppress the "install without dependencies" warning
401
+ # Use PIPESTATUS to check npx exit code, not grep's exit code
402
+ npx -y playwright@latest install 2>&1 | grep -v "WARNING: It looks like you are running"
403
+ if [[ ${PIPESTATUS[0]} -eq 0 ]]; then
404
+ print_success "Playwright browsers installed"
405
+ else
406
+ print_warning "Playwright browser installation failed"
407
+ print_info "Run manually: npx -y playwright@latest install"
408
+ fi
409
+ else
410
+ print_info "Skipped Playwright installation"
411
+ print_info "Install later with: npx playwright install"
412
+ fi
413
+ fi
414
+
415
+ print_info "Playwright MCP runs via: npx playwright-mcp@latest"
416
+ fi
417
+
418
+ if [[ "$has_node" == "true" ]]; then
419
+ print_info "Browser tools: dev-browser (stateful), Playwriter (extension), Playwright (testing), Stagehand (AI)"
420
+ else
421
+ print_info "Browser tools: dev-browser (stateful), Stagehand (AI)"
422
+ fi
423
+ return 0
424
+ }
425
+
426
+ add_opencode_plugin() {
427
+ local plugin_name plugin_spec opencode_config
428
+ plugin_name="$1"
429
+ plugin_spec="$2"
430
+ opencode_config="$3"
431
+
432
+ # Check if plugin array exists and if plugin is already configured
433
+ local has_plugin_array
434
+ if jq -e '.plugin' "$opencode_config" >/dev/null 2>&1; then
435
+ has_plugin_array="true"
436
+ else
437
+ has_plugin_array="false"
438
+ fi
439
+
440
+ if [[ "$has_plugin_array" == "true" ]]; then
441
+ # Check if plugin is already in the array
442
+ local plugin_exists
443
+ if jq -e --arg p "$plugin_name" '.plugin | map(select(startswith($p))) | length > 0' "$opencode_config" >/dev/null 2>&1; then
444
+ plugin_exists="true"
445
+ else
446
+ plugin_exists="false"
447
+ fi
448
+
449
+ if [[ "$plugin_exists" == "true" ]]; then
450
+ # Update existing plugin to latest version
451
+ local temp_file
452
+ temp_file=$(mktemp)
453
+ trap 'rm -f "${temp_file:-}"' RETURN
454
+ jq --arg old "$plugin_name" --arg new "$plugin_spec" \
455
+ '.plugin = [.plugin[] | if startswith($old) then $new else . end]' \
456
+ "$opencode_config" >"$temp_file" && mv "$temp_file" "$opencode_config"
457
+ print_success "Updated $plugin_name to latest version"
458
+ else
459
+ # Add plugin to existing array
460
+ local temp_file
461
+ temp_file=$(mktemp)
462
+ trap 'rm -f "${temp_file:-}"' RETURN
463
+ jq --arg p "$plugin_spec" '.plugin += [$p]' "$opencode_config" >"$temp_file" && mv "$temp_file" "$opencode_config"
464
+ print_success "Added $plugin_name plugin to OpenCode config"
465
+ fi
466
+ else
467
+ # Create plugin array with the plugin
468
+ local temp_file
469
+ temp_file=$(mktemp)
470
+ trap 'rm -f "${temp_file:-}"' RETURN
471
+ jq --arg p "$plugin_spec" '. + {plugin: [$p]}' "$opencode_config" >"$temp_file" && mv "$temp_file" "$opencode_config"
472
+ print_success "Created plugin array with $plugin_name"
473
+ fi
474
+
475
+ return 0
476
+ }
477
+
478
+ setup_opencode_plugins() {
479
+ print_info "Setting up OpenCode plugins..."
480
+
481
+ # Check if OpenCode is installed
482
+ if ! command -v opencode &>/dev/null; then
483
+ print_warning "OpenCode not found - plugin setup skipped"
484
+ print_info "Install OpenCode first: https://opencode.ai"
485
+ return 0
486
+ fi
487
+
488
+ # Check if config exists
489
+ local opencode_config
490
+ if ! opencode_config=$(find_opencode_config); then
491
+ print_warning "OpenCode config not found - plugin setup skipped"
492
+ return 0
493
+ fi
494
+
495
+ # Check if jq is available
496
+ if ! command -v jq &>/dev/null; then
497
+ print_warning "jq not found - cannot update OpenCode config"
498
+ return 0
499
+ fi
500
+
501
+ # Setup aidevops compaction plugin (local file plugin)
502
+ local aidevops_plugin_path="$HOME/.aidevops/agents/plugins/opencode-aidevops/index.mjs"
503
+ if [[ -f "$aidevops_plugin_path" ]]; then
504
+ print_info "Setting up aidevops compaction plugin..."
505
+ add_opencode_plugin "file://$HOME/.aidevops" "file://${aidevops_plugin_path}" "$opencode_config"
506
+ print_success "aidevops compaction plugin registered (preserves context across compaction)"
507
+ fi
508
+
509
+ # Note: opencode-anthropic-auth is built into OpenCode v1.1.36+
510
+ # Adding it as an external plugin causes TypeError due to double-loading.
511
+ # Removed in v2.90.0 - see PR #230.
512
+
513
+ print_info "After setup, authenticate with: opencode auth login"
514
+ print_info " • For Claude OAuth: Select 'Anthropic' → 'Claude Pro/Max' (built-in)"
515
+
516
+ return 0
517
+ }
518
+
519
+ setup_seo_mcps() {
520
+ print_info "Setting up SEO integrations..."
521
+
522
+ # SEO services use curl-based subagents (no MCP needed)
523
+ # Subagents: serper.md, dataforseo.md, ahrefs.md, google-search-console.md
524
+ print_info "SEO uses curl-based subagents (zero context cost until invoked)"
525
+
526
+ # Check if credentials are configured
527
+ if [[ -f "$HOME/.config/aidevops/credentials.sh" ]]; then
528
+ # shellcheck source=/dev/null
529
+ source "$HOME/.config/aidevops/credentials.sh"
530
+
531
+ if [[ -n "$DATAFORSEO_USERNAME" ]]; then
532
+ print_success "DataForSEO credentials configured"
533
+ else
534
+ print_info "DataForSEO: set DATAFORSEO_USERNAME and DATAFORSEO_PASSWORD in credentials.sh"
535
+ fi
536
+
537
+ if [[ -n "$SERPER_API_KEY" ]]; then
538
+ print_success "Serper API key configured"
539
+ else
540
+ print_info "Serper: set SERPER_API_KEY in credentials.sh"
541
+ fi
542
+
543
+ if [[ -n "$AHREFS_API_KEY" ]]; then
544
+ print_success "Ahrefs API key configured"
545
+ else
546
+ print_info "Ahrefs: set AHREFS_API_KEY in credentials.sh"
547
+ fi
548
+ else
549
+ print_info "Configure SEO API credentials in ~/.config/aidevops/credentials.sh"
550
+ fi
551
+
552
+ # GSC uses MCP (OAuth2 complexity warrants it)
553
+ local gsc_creds="$HOME/.config/aidevops/gsc-credentials.json"
554
+ if [[ -f "$gsc_creds" ]]; then
555
+ print_success "Google Search Console credentials configured"
556
+ else
557
+ print_info "GSC: Create service account JSON at $gsc_creds"
558
+ print_info " See: ~/.aidevops/agents/seo/google-search-console.md"
559
+ fi
560
+
561
+ print_info "SEO documentation: ~/.aidevops/agents/seo/"
562
+ return 0
563
+ }
564
+
565
+ setup_google_analytics_mcp() {
566
+ print_info "Setting up Google Analytics MCP..."
567
+
568
+ local gsc_creds="$HOME/.config/aidevops/gsc-credentials.json"
569
+
570
+ # Check if opencode.json exists
571
+ local opencode_config
572
+ if ! opencode_config=$(find_opencode_config); then
573
+ print_warning "OpenCode config not found - skipping Google Analytics MCP"
574
+ return 0
575
+ fi
576
+
577
+ # Check if jq is available
578
+ if ! command -v jq &>/dev/null; then
579
+ print_warning "jq not found - cannot add Google Analytics MCP to config"
580
+ print_info "Install jq and re-run setup, or manually add the MCP config"
581
+ return 0
582
+ fi
583
+
584
+ # Check if pipx is available
585
+ if ! command -v pipx &>/dev/null; then
586
+ print_warning "pipx not found - Google Analytics MCP requires pipx"
587
+ print_info "Install pipx: brew install pipx (macOS) or pip install pipx"
588
+ print_info "Then re-run setup to add Google Analytics MCP"
589
+ return 0
590
+ fi
591
+
592
+ # Auto-detect credentials from shared GSC service account
593
+ local creds_path=""
594
+ local project_id=""
595
+ local enable_mcp="false"
596
+
597
+ if [[ -f "$gsc_creds" ]]; then
598
+ creds_path="$gsc_creds"
599
+ # Extract project_id from service account JSON
600
+ project_id=$(jq -r '.project_id // empty' "$gsc_creds" 2>/dev/null)
601
+ if [[ -n "$project_id" ]]; then
602
+ enable_mcp="true"
603
+ print_success "Found GSC credentials - sharing with Google Analytics MCP"
604
+ print_info "Project: $project_id"
605
+ fi
606
+ fi
607
+
608
+ # Check if google-analytics-mcp already exists in config
609
+ if jq -e '.mcp["google-analytics-mcp"]' "$opencode_config" >/dev/null 2>&1; then
610
+ # Update existing entry if we have credentials now
611
+ if [[ "$enable_mcp" == "true" ]]; then
612
+ local tmp_config
613
+ tmp_config=$(mktemp)
614
+ trap 'rm -f "${tmp_config:-}"' RETURN
615
+ if jq --arg creds "$creds_path" --arg proj "$project_id" \
616
+ '.mcp["google-analytics-mcp"].environment.GOOGLE_APPLICATION_CREDENTIALS = $creds |
617
+ .mcp["google-analytics-mcp"].environment.GOOGLE_PROJECT_ID = $proj |
618
+ .mcp["google-analytics-mcp"].enabled = true' \
619
+ "$opencode_config" >"$tmp_config" 2>/dev/null; then
620
+ mv "$tmp_config" "$opencode_config"
621
+ print_success "Updated Google Analytics MCP with GSC credentials (enabled)"
622
+ else
623
+ rm -f "$tmp_config"
624
+ print_warning "Failed to update Google Analytics MCP config"
625
+ fi
626
+ else
627
+ print_info "Google Analytics MCP already configured in OpenCode"
628
+ fi
629
+ return 0
630
+ fi
631
+
632
+ # Add google-analytics-mcp to opencode.json
633
+ local tmp_config
634
+ tmp_config=$(mktemp)
635
+ trap 'rm -f "${tmp_config:-}"' RETURN
636
+
637
+ if jq --arg creds "$creds_path" --arg proj "$project_id" --argjson enabled "$enable_mcp" \
638
+ '.mcp["google-analytics-mcp"] = {
639
+ "type": "local",
640
+ "command": ["analytics-mcp"],
641
+ "environment": {
642
+ "GOOGLE_APPLICATION_CREDENTIALS": $creds,
643
+ "GOOGLE_PROJECT_ID": $proj
644
+ },
645
+ "enabled": $enabled
646
+ }' "$opencode_config" >"$tmp_config" 2>/dev/null; then
647
+ mv "$tmp_config" "$opencode_config"
648
+ if [[ "$enable_mcp" == "true" ]]; then
649
+ print_success "Added Google Analytics MCP to OpenCode (enabled with GSC credentials)"
650
+ else
651
+ print_success "Added Google Analytics MCP to OpenCode (disabled - no credentials found)"
652
+ print_info "To enable: Create service account JSON at $gsc_creds"
653
+ fi
654
+ print_info "Or use the google-analytics subagent which enables it automatically"
655
+ else
656
+ rm -f "$tmp_config"
657
+ print_warning "Failed to add Google Analytics MCP to config"
658
+ fi
659
+
660
+ # Show setup instructions
661
+ print_info "Google Analytics MCP setup:"
662
+ print_info " 1. Enable Google Analytics Admin & Data APIs in Google Cloud Console"
663
+ 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"
664
+ print_info " 3. Update GOOGLE_APPLICATION_CREDENTIALS path in opencode.json"
665
+ print_info " 4. Set GOOGLE_PROJECT_ID in opencode.json"
666
+ print_info "Documentation: ~/.aidevops/agents/services/analytics/google-analytics.md"
667
+
668
+ return 0
669
+ }
670
+
671
+ setup_quickfile_mcp() {
672
+ print_info "Setting up QuickFile MCP server..."
673
+
674
+ local quickfile_dir="$HOME/Git/quickfile-mcp"
675
+ local credentials_dir="$HOME/.config/.quickfile-mcp"
676
+ local credentials_file="$credentials_dir/credentials.json"
677
+
678
+ # Check if Node.js is available
679
+ if ! command -v node &>/dev/null; then
680
+ print_warning "Node.js not found - QuickFile MCP setup skipped"
681
+ print_info "Install Node.js 18+ to enable QuickFile MCP"
682
+ return 0
683
+ fi
684
+
685
+ # Check if already cloned and built
686
+ if [[ -f "$quickfile_dir/dist/index.js" ]]; then
687
+ print_success "QuickFile MCP already installed at $quickfile_dir"
688
+ else
689
+ print_info "QuickFile MCP provides AI access to UK accounting (invoices, clients, reports)"
690
+ read -r -p "Clone and build QuickFile MCP server? [Y/n]: " install_qf
691
+
692
+ if [[ "$install_qf" =~ ^[Yy]?$ ]]; then
693
+ if [[ ! -d "$quickfile_dir" ]]; then
694
+ if run_with_spinner "Cloning quickfile-mcp" git clone https://github.com/marcusquinn/quickfile-mcp.git "$quickfile_dir"; then
695
+ print_success "Cloned quickfile-mcp"
696
+ else
697
+ print_warning "Failed to clone quickfile-mcp"
698
+ return 0
699
+ fi
700
+ fi
701
+
702
+ if run_with_spinner "Installing dependencies" npm install --prefix "$quickfile_dir"; then
703
+ if run_with_spinner "Building QuickFile MCP" npm run build --prefix "$quickfile_dir"; then
704
+ print_success "QuickFile MCP built successfully"
705
+ else
706
+ print_warning "Build failed - try manually: cd $quickfile_dir && npm run build"
707
+ return 0
708
+ fi
709
+ else
710
+ print_warning "npm install failed - try manually: cd $quickfile_dir && npm install"
711
+ return 0
712
+ fi
713
+ else
714
+ print_info "Skipped QuickFile MCP installation"
715
+ print_info "Install later: git clone https://github.com/marcusquinn/quickfile-mcp.git ~/Git/quickfile-mcp"
716
+ return 0
717
+ fi
718
+ fi
719
+
720
+ # Check credentials
721
+ if [[ -f "$credentials_file" ]]; then
722
+ print_success "QuickFile credentials configured at $credentials_file"
723
+ else
724
+ print_info "QuickFile credentials not found"
725
+ print_info "Create credentials:"
726
+ print_info " mkdir -p $credentials_dir && chmod 700 $credentials_dir"
727
+ print_info " Create $credentials_file with:"
728
+ print_info " accountNumber: from QuickFile dashboard (top-right)"
729
+ print_info " apiKey: Account Settings > 3rd Party Integrations > API Key"
730
+ print_info " applicationId: Account Settings > Create a QuickFile App"
731
+ fi
732
+
733
+ # Update OpenCode config if available
734
+ local opencode_config
735
+ if opencode_config=$(find_opencode_config); then
736
+ local quickfile_entry
737
+ quickfile_entry=$(jq -r '.mcp.quickfile // empty' "$opencode_config" 2>/dev/null)
738
+
739
+ if [[ -z "$quickfile_entry" ]]; then
740
+ print_info "Adding QuickFile MCP to OpenCode config..."
741
+ local node_path
742
+ node_path=$(resolve_mcp_binary_path "node")
743
+ [[ -z "$node_path" ]] && node_path="node"
744
+
745
+ local tmp_config
746
+ tmp_config=$(mktemp)
747
+ trap 'rm -f "${tmp_config:-}"' RETURN
748
+
749
+ if jq --arg np "$node_path" --arg dp "$quickfile_dir/dist/index.js" \
750
+ '.mcp.quickfile = {"type": "local", "command": [$np, $dp], "enabled": true}' \
751
+ "$opencode_config" >"$tmp_config" 2>/dev/null; then
752
+ create_backup_with_rotation "$opencode_config" "opencode"
753
+ mv "$tmp_config" "$opencode_config"
754
+ print_success "QuickFile MCP added to OpenCode config"
755
+ else
756
+ rm -f "$tmp_config"
757
+ print_warning "Failed to update OpenCode config - add manually"
758
+ fi
759
+ else
760
+ print_success "QuickFile MCP already in OpenCode config"
761
+ fi
762
+ fi
763
+
764
+ print_info "Documentation: ~/.aidevops/agents/services/accounting/quickfile.md"
765
+ return 0
766
+ }