@phenx-inc/ctlsurf 0.1.0 → 0.1.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.
- package/bin/ctlsurf-worker.js +108 -15
- package/package.json +2 -1
- package/scripts/bundle.sh +90 -0
- package/scripts/setup-mcp.sh +696 -0
package/bin/ctlsurf-worker.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* ctlsurf launcher
|
|
5
5
|
*
|
|
6
6
|
* Subcommands:
|
|
7
|
-
* ctlsurf
|
|
7
|
+
* ctlsurf setup — configure MCP for all detected coding agents
|
|
8
|
+
* ctlsurf update — update to latest version
|
|
8
9
|
* ctlsurf doctor — check all dependencies
|
|
9
10
|
* ctlsurf version — print version
|
|
10
11
|
*
|
|
@@ -16,11 +17,95 @@
|
|
|
16
17
|
const { execFileSync, execSync } = require('child_process')
|
|
17
18
|
const path = require('path')
|
|
18
19
|
const fs = require('fs')
|
|
20
|
+
const os = require('os')
|
|
19
21
|
|
|
20
22
|
const ROOT = path.resolve(__dirname, '..')
|
|
21
23
|
const args = process.argv.slice(2)
|
|
22
24
|
const subcommand = args[0]
|
|
23
25
|
|
|
26
|
+
// ─── Colors ───────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const G = '\x1b[32m'
|
|
29
|
+
const Y = '\x1b[33m'
|
|
30
|
+
const B = '\x1b[34m'
|
|
31
|
+
const D = '\x1b[90m'
|
|
32
|
+
const R = '\x1b[0m'
|
|
33
|
+
|
|
34
|
+
// ─── MCP check ────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
function isMcpConfigured() {
|
|
37
|
+
// Check env var
|
|
38
|
+
if (process.env.CTLSURF_API_KEY) return true
|
|
39
|
+
|
|
40
|
+
// Check Claude Code settings
|
|
41
|
+
try {
|
|
42
|
+
const claudeSettings = path.join(os.homedir(), '.claude', 'settings.json')
|
|
43
|
+
if (fs.existsSync(claudeSettings)) {
|
|
44
|
+
const data = JSON.parse(fs.readFileSync(claudeSettings, 'utf-8'))
|
|
45
|
+
if (data.mcpServers && data.mcpServers.ctlsurf) return true
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
|
|
49
|
+
// Check Codex config
|
|
50
|
+
try {
|
|
51
|
+
const codexConfig = path.join(os.homedir(), '.codex', 'config.toml')
|
|
52
|
+
if (fs.existsSync(codexConfig)) {
|
|
53
|
+
const data = fs.readFileSync(codexConfig, 'utf-8')
|
|
54
|
+
if (data.includes('[mcp_servers.ctlsurf]')) return true
|
|
55
|
+
}
|
|
56
|
+
} catch {}
|
|
57
|
+
|
|
58
|
+
// Check Cursor config
|
|
59
|
+
try {
|
|
60
|
+
const cursorMcp = path.join(os.homedir(), '.cursor', 'mcp.json')
|
|
61
|
+
if (fs.existsSync(cursorMcp)) {
|
|
62
|
+
const data = JSON.parse(fs.readFileSync(cursorMcp, 'utf-8'))
|
|
63
|
+
if (data.mcpServers && data.mcpServers.ctlsurf) return true
|
|
64
|
+
}
|
|
65
|
+
} catch {}
|
|
66
|
+
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function runSetup() {
|
|
71
|
+
const setupScript = path.join(ROOT, 'scripts', 'setup-mcp.sh')
|
|
72
|
+
if (!fs.existsSync(setupScript)) {
|
|
73
|
+
console.error(`${Y}!${R} Setup script not found: ${setupScript}`)
|
|
74
|
+
console.error(` Run the web installer instead: curl -fsSL https://app.ctlsurf.com/install.sh | bash`)
|
|
75
|
+
process.exit(1)
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
execFileSync('bash', [setupScript, ...args.slice(1)], { stdio: 'inherit' })
|
|
79
|
+
} catch (err) {
|
|
80
|
+
process.exit(err.status || 1)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function promptSetup() {
|
|
85
|
+
console.log(`\n${B} ctlsurf${R}\n`)
|
|
86
|
+
console.log(` MCP is not configured for any coding agent.`)
|
|
87
|
+
console.log(` Run setup to connect ctlsurf to Claude Code, Codex, Copilot, and Cursor.\n`)
|
|
88
|
+
console.log(` ${B}ctlsurf setup${R} ${D}# interactive setup${R}`)
|
|
89
|
+
console.log(` ${D}ctlsurf --terminal${R} ${D}# skip setup, launch anyway${R}\n`)
|
|
90
|
+
|
|
91
|
+
// Interactive prompt
|
|
92
|
+
if (process.stdin.isTTY) {
|
|
93
|
+
const readline = require('readline')
|
|
94
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
95
|
+
rl.question(` Run setup now? ${D}(Y/n)${R} `, (answer) => {
|
|
96
|
+
rl.close()
|
|
97
|
+
if (!answer || answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
98
|
+
console.log('')
|
|
99
|
+
runSetup()
|
|
100
|
+
} else {
|
|
101
|
+
console.log('')
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
return true // handled
|
|
105
|
+
}
|
|
106
|
+
return false
|
|
107
|
+
}
|
|
108
|
+
|
|
24
109
|
// ─── Subcommands ──────────────────────────────────
|
|
25
110
|
|
|
26
111
|
if (subcommand === 'version' || args.includes('--version') || args.includes('-v')) {
|
|
@@ -33,20 +118,18 @@ if (subcommand === 'version' || args.includes('--version') || args.includes('-v'
|
|
|
33
118
|
process.exit(0)
|
|
34
119
|
}
|
|
35
120
|
|
|
36
|
-
if (subcommand === '
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const D = '\x1b[90m'
|
|
41
|
-
const R = '\x1b[0m'
|
|
121
|
+
if (subcommand === 'setup') {
|
|
122
|
+
runSetup()
|
|
123
|
+
process.exit(0)
|
|
124
|
+
}
|
|
42
125
|
|
|
126
|
+
if (subcommand === 'update') {
|
|
43
127
|
console.log(`\n${B} ctlsurf update${R}\n`)
|
|
44
128
|
|
|
45
129
|
try {
|
|
46
130
|
console.log(`${D}Updating via npm...${R}`)
|
|
47
131
|
execSync('npm update -g @phenx-inc/ctlsurf', { stdio: 'inherit' })
|
|
48
132
|
|
|
49
|
-
// Re-read version after update
|
|
50
133
|
let ver = '?'
|
|
51
134
|
try { ver = require(path.join(ROOT, 'package.json')).version } catch {}
|
|
52
135
|
console.log(`\n${G}✓${R} ctlsurf v${ver}\n`)
|
|
@@ -58,12 +141,6 @@ if (subcommand === 'update') {
|
|
|
58
141
|
}
|
|
59
142
|
|
|
60
143
|
if (subcommand === 'doctor') {
|
|
61
|
-
const G = '\x1b[32m'
|
|
62
|
-
const Y = '\x1b[33m'
|
|
63
|
-
const R = '\x1b[0m'
|
|
64
|
-
const D = '\x1b[90m'
|
|
65
|
-
const B = '\x1b[34m'
|
|
66
|
-
|
|
67
144
|
console.log(`\n${B} ctlsurf doctor${R}\n`)
|
|
68
145
|
|
|
69
146
|
function check(name, cmd, required, fix) {
|
|
@@ -88,7 +165,7 @@ if (subcommand === 'doctor') {
|
|
|
88
165
|
check('npm', 'npm --version', true)
|
|
89
166
|
check('git', 'git --version', true)
|
|
90
167
|
|
|
91
|
-
// node-pty
|
|
168
|
+
// node-pty
|
|
92
169
|
try {
|
|
93
170
|
require(path.join(ROOT, 'node_modules/node-pty'))
|
|
94
171
|
console.log(` ${G}✓${R} node-pty ${D}(native addon loaded)${R}`)
|
|
@@ -105,6 +182,14 @@ if (subcommand === 'doctor') {
|
|
|
105
182
|
|
|
106
183
|
console.log('')
|
|
107
184
|
|
|
185
|
+
// MCP config
|
|
186
|
+
if (isMcpConfigured()) {
|
|
187
|
+
console.log(` ${G}✓${R} MCP configured ${D}(ctlsurf server connected)${R}`)
|
|
188
|
+
} else {
|
|
189
|
+
console.log(` ${Y}✗${R} MCP not configured`)
|
|
190
|
+
console.log(` ${D}Run: ctlsurf setup${R}`)
|
|
191
|
+
}
|
|
192
|
+
|
|
108
193
|
// Desktop
|
|
109
194
|
try {
|
|
110
195
|
require.resolve('electron')
|
|
@@ -125,6 +210,14 @@ if (subcommand === 'doctor') {
|
|
|
125
210
|
process.exit(0)
|
|
126
211
|
}
|
|
127
212
|
|
|
213
|
+
// ─── Auto-detect MCP on first run ─────────────────
|
|
214
|
+
|
|
215
|
+
if (!subcommand || subcommand.startsWith('--')) {
|
|
216
|
+
if (!isMcpConfigured() && !args.includes('--skip-setup')) {
|
|
217
|
+
if (promptSetup()) return // async prompt, exits after
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
128
221
|
// ─── Mode detection ───────────────────────────────
|
|
129
222
|
|
|
130
223
|
function detectMode(argv) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phenx-inc/ctlsurf",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Agent-agnostic terminal and desktop app for ctlsurf — run Claude Code, Codex, or any coding agent with live session logging and remote control",
|
|
5
5
|
"main": "out/main/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"bin/",
|
|
12
12
|
"out/",
|
|
13
13
|
"resources/",
|
|
14
|
+
"scripts/",
|
|
14
15
|
"src/",
|
|
15
16
|
"package.json",
|
|
16
17
|
"electron-vite.config.ts",
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# ─── Bundle ctlsurf for distribution ──────────────
|
|
5
|
+
#
|
|
6
|
+
# Creates a platform-specific tarball with everything needed to run.
|
|
7
|
+
# No build tools required on the target machine.
|
|
8
|
+
#
|
|
9
|
+
# Output: dist/ctlsurf-<version>-<os>-<arch>.tar.gz
|
|
10
|
+
|
|
11
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
12
|
+
cd "$ROOT"
|
|
13
|
+
|
|
14
|
+
VERSION=$(node -e "console.log(require('./package.json').version)")
|
|
15
|
+
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
16
|
+
ARCH=$(uname -m)
|
|
17
|
+
|
|
18
|
+
# Normalize arch names
|
|
19
|
+
case "$ARCH" in
|
|
20
|
+
x86_64) ARCH="x64" ;;
|
|
21
|
+
aarch64) ARCH="arm64" ;;
|
|
22
|
+
arm64) ARCH="arm64" ;;
|
|
23
|
+
esac
|
|
24
|
+
|
|
25
|
+
BUNDLE_NAME="ctlsurf-${VERSION}-${OS}-${ARCH}"
|
|
26
|
+
DIST_DIR="$ROOT/dist"
|
|
27
|
+
STAGE_DIR="$DIST_DIR/$BUNDLE_NAME"
|
|
28
|
+
|
|
29
|
+
echo "Building ctlsurf v${VERSION} for ${OS}-${ARCH}..."
|
|
30
|
+
|
|
31
|
+
# ─── Build ─────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
echo "Building headless..."
|
|
34
|
+
npm run build:headless
|
|
35
|
+
|
|
36
|
+
echo "Building desktop..."
|
|
37
|
+
npx electron-vite build 2>/dev/null || echo "(desktop build skipped — no electron)"
|
|
38
|
+
|
|
39
|
+
# ─── Stage ─────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
rm -rf "$STAGE_DIR"
|
|
42
|
+
mkdir -p "$STAGE_DIR"
|
|
43
|
+
|
|
44
|
+
# Bin
|
|
45
|
+
cp -r bin "$STAGE_DIR/bin"
|
|
46
|
+
chmod +x "$STAGE_DIR/bin/ctlsurf-worker.js"
|
|
47
|
+
|
|
48
|
+
# Built output
|
|
49
|
+
cp -r out "$STAGE_DIR/out"
|
|
50
|
+
|
|
51
|
+
# Package metadata
|
|
52
|
+
cp package.json "$STAGE_DIR/package.json"
|
|
53
|
+
|
|
54
|
+
# Resources (icons)
|
|
55
|
+
if [ -d resources ]; then
|
|
56
|
+
cp -r resources "$STAGE_DIR/resources"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# node_modules — only runtime deps, with native addons pre-compiled
|
|
60
|
+
mkdir -p "$STAGE_DIR/node_modules"
|
|
61
|
+
|
|
62
|
+
# node-pty (native addon — platform-specific)
|
|
63
|
+
if [ -d node_modules/node-pty ]; then
|
|
64
|
+
cp -r node_modules/node-pty "$STAGE_DIR/node_modules/node-pty"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# nan (node-pty dependency)
|
|
68
|
+
if [ -d node_modules/nan ]; then
|
|
69
|
+
cp -r node_modules/nan "$STAGE_DIR/node_modules/nan"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# node-addon-api (node-pty dependency)
|
|
73
|
+
if [ -d node_modules/node-addon-api ]; then
|
|
74
|
+
cp -r node_modules/node-addon-api "$STAGE_DIR/node_modules/node-addon-api"
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# ─── Tarball ───────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
echo "Creating tarball..."
|
|
80
|
+
cd "$DIST_DIR"
|
|
81
|
+
tar -czf "${BUNDLE_NAME}.tar.gz" "$BUNDLE_NAME"
|
|
82
|
+
rm -rf "$STAGE_DIR"
|
|
83
|
+
|
|
84
|
+
SIZE=$(du -h "${BUNDLE_NAME}.tar.gz" | awk '{print $1}')
|
|
85
|
+
echo ""
|
|
86
|
+
echo "✓ dist/${BUNDLE_NAME}.tar.gz (${SIZE})"
|
|
87
|
+
echo ""
|
|
88
|
+
echo "Install on target machine:"
|
|
89
|
+
echo " tar -xzf ${BUNDLE_NAME}.tar.gz -C ~/.ctlsurf --strip-components=1"
|
|
90
|
+
echo " ln -sf ~/.ctlsurf/bin/ctlsurf-worker.js /usr/local/bin/ctlsurf"
|
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# ctlsurf MCP Setup
|
|
4
|
+
# Usage: curl -fsSL https://app.ctlsurf.com/install.sh | bash
|
|
5
|
+
#
|
|
6
|
+
# Options (pass via: curl ... | bash -s -- --flag):
|
|
7
|
+
# --force-codex Force Codex configuration even if not detected
|
|
8
|
+
# --force-claude Force Claude configuration even if not detected
|
|
9
|
+
# --force-copilot Force GitHub Copilot configuration even if not detected
|
|
10
|
+
# --force-cursor Force Cursor configuration even if not detected
|
|
11
|
+
# --force Force all configurations
|
|
12
|
+
#
|
|
13
|
+
# Detects and configures:
|
|
14
|
+
# - Claude Code CLI
|
|
15
|
+
# - Claude Code VSCode Extension
|
|
16
|
+
# - OpenAI Codex CLI
|
|
17
|
+
# - OpenAI Codex VSCode Extension
|
|
18
|
+
# - GitHub Copilot VSCode Extension
|
|
19
|
+
# - Cursor Editor
|
|
20
|
+
#
|
|
21
|
+
|
|
22
|
+
set -e
|
|
23
|
+
|
|
24
|
+
# Parse command line arguments
|
|
25
|
+
FORCE_CODEX=false
|
|
26
|
+
FORCE_CLAUDE=false
|
|
27
|
+
FORCE_COPILOT=false
|
|
28
|
+
FORCE_CURSOR=false
|
|
29
|
+
while [[ $# -gt 0 ]]; do
|
|
30
|
+
case $1 in
|
|
31
|
+
--force-codex) FORCE_CODEX=true; shift ;;
|
|
32
|
+
--force-claude) FORCE_CLAUDE=true; shift ;;
|
|
33
|
+
--force-copilot) FORCE_COPILOT=true; shift ;;
|
|
34
|
+
--force-cursor) FORCE_CURSOR=true; shift ;;
|
|
35
|
+
--force) FORCE_CODEX=true; FORCE_CLAUDE=true; FORCE_COPILOT=true; FORCE_CURSOR=true; shift ;;
|
|
36
|
+
*) shift ;;
|
|
37
|
+
esac
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
echo "========================================"
|
|
41
|
+
echo " ctlsurf MCP Setup"
|
|
42
|
+
echo "========================================"
|
|
43
|
+
echo ""
|
|
44
|
+
|
|
45
|
+
RED='\033[0;31m'
|
|
46
|
+
GREEN='\033[0;32m'
|
|
47
|
+
YELLOW='\033[0;33m'
|
|
48
|
+
BLUE='\033[0;34m'
|
|
49
|
+
NC='\033[0m'
|
|
50
|
+
|
|
51
|
+
FOUND_CLAUDE_CLI=false
|
|
52
|
+
FOUND_CLAUDE_VSCODE=false
|
|
53
|
+
FOUND_CODEX_CLI=false
|
|
54
|
+
FOUND_CODEX_VSCODE=false
|
|
55
|
+
FOUND_COPILOT=false
|
|
56
|
+
FOUND_CURSOR=false
|
|
57
|
+
|
|
58
|
+
# Determine VSCode settings path based on OS
|
|
59
|
+
get_vscode_settings_path() {
|
|
60
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
61
|
+
echo "$HOME/Library/Application Support/Code/User/settings.json"
|
|
62
|
+
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
63
|
+
echo "$HOME/.config/Code/User/settings.json"
|
|
64
|
+
elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "win32" ]]; then
|
|
65
|
+
echo "$APPDATA/Code/User/settings.json"
|
|
66
|
+
else
|
|
67
|
+
echo ""
|
|
68
|
+
fi
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# Determine VSCode mcp.json path based on OS (for Copilot)
|
|
72
|
+
get_vscode_mcp_path() {
|
|
73
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
74
|
+
echo "$HOME/Library/Application Support/Code/User/mcp.json"
|
|
75
|
+
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
76
|
+
echo "$HOME/.config/Code/User/mcp.json"
|
|
77
|
+
elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "win32" ]]; then
|
|
78
|
+
echo "$APPDATA/Code/User/mcp.json"
|
|
79
|
+
else
|
|
80
|
+
echo ""
|
|
81
|
+
fi
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Check if a VSCode extension is installed
|
|
85
|
+
check_vscode_extension() {
|
|
86
|
+
local ext_id="$1"
|
|
87
|
+
|
|
88
|
+
# Method 1: Try using code CLI
|
|
89
|
+
if command -v code &> /dev/null; then
|
|
90
|
+
if code --list-extensions 2>/dev/null | grep -qi "$ext_id"; then
|
|
91
|
+
return 0
|
|
92
|
+
fi
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Method 2: Check extensions folder directly (fallback when code not in PATH)
|
|
96
|
+
local ext_dir="$HOME/.vscode/extensions"
|
|
97
|
+
if [ -d "$ext_dir" ]; then
|
|
98
|
+
if ls "$ext_dir" 2>/dev/null | grep -qi "$ext_id"; then
|
|
99
|
+
return 0
|
|
100
|
+
fi
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
return 1
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
echo -e "${BLUE}Detecting installed tools...${NC}"
|
|
107
|
+
echo ""
|
|
108
|
+
|
|
109
|
+
# Check for Claude CLI
|
|
110
|
+
if [ "$FORCE_CLAUDE" = true ]; then
|
|
111
|
+
echo -e "${GREEN}✓ Claude Code CLI (forced)${NC}"
|
|
112
|
+
FOUND_CLAUDE_CLI=true
|
|
113
|
+
elif command -v claude &> /dev/null; then
|
|
114
|
+
echo -e "${GREEN}✓ Found Claude Code CLI${NC}"
|
|
115
|
+
FOUND_CLAUDE_CLI=true
|
|
116
|
+
else
|
|
117
|
+
echo -e "${YELLOW}○ Claude Code CLI not found${NC}"
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# Check for Claude Code VSCode Extension
|
|
121
|
+
if check_vscode_extension "anthropic.claude-code"; then
|
|
122
|
+
echo -e "${GREEN}✓ Found Claude Code VSCode Extension${NC}"
|
|
123
|
+
FOUND_CLAUDE_VSCODE=true
|
|
124
|
+
else
|
|
125
|
+
echo -e "${YELLOW}○ Claude Code VSCode Extension not found${NC}"
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# Check for Codex CLI (also check ~/.codex dir for installations not in PATH)
|
|
129
|
+
if [ "$FORCE_CODEX" = true ]; then
|
|
130
|
+
echo -e "${GREEN}✓ OpenAI Codex CLI (forced)${NC}"
|
|
131
|
+
FOUND_CODEX_CLI=true
|
|
132
|
+
elif command -v codex &> /dev/null; then
|
|
133
|
+
echo -e "${GREEN}✓ Found OpenAI Codex CLI${NC}"
|
|
134
|
+
FOUND_CODEX_CLI=true
|
|
135
|
+
elif [ -d "$HOME/.codex" ]; then
|
|
136
|
+
echo -e "${GREEN}✓ Found OpenAI Codex config directory${NC}"
|
|
137
|
+
FOUND_CODEX_CLI=true
|
|
138
|
+
else
|
|
139
|
+
echo -e "${YELLOW}○ OpenAI Codex CLI not found${NC}"
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# Check for Codex/ChatGPT VSCode Extension (openai.codex or openai.chatgpt)
|
|
143
|
+
if check_vscode_extension "openai.codex" || check_vscode_extension "openai.chatgpt"; then
|
|
144
|
+
echo -e "${GREEN}✓ Found OpenAI Codex VSCode Extension${NC}"
|
|
145
|
+
FOUND_CODEX_VSCODE=true
|
|
146
|
+
else
|
|
147
|
+
echo -e "${YELLOW}○ OpenAI Codex VSCode Extension not found${NC}"
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# Check for GitHub Copilot VSCode Extension
|
|
151
|
+
if [ "$FORCE_COPILOT" = true ]; then
|
|
152
|
+
echo -e "${GREEN}✓ GitHub Copilot (forced)${NC}"
|
|
153
|
+
FOUND_COPILOT=true
|
|
154
|
+
elif check_vscode_extension "github.copilot"; then
|
|
155
|
+
echo -e "${GREEN}✓ Found GitHub Copilot VSCode Extension${NC}"
|
|
156
|
+
FOUND_COPILOT=true
|
|
157
|
+
else
|
|
158
|
+
echo -e "${YELLOW}○ GitHub Copilot VSCode Extension not found${NC}"
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# Check for Cursor Editor (check command or config directory)
|
|
162
|
+
if [ "$FORCE_CURSOR" = true ]; then
|
|
163
|
+
echo -e "${GREEN}✓ Cursor Editor (forced)${NC}"
|
|
164
|
+
FOUND_CURSOR=true
|
|
165
|
+
elif command -v cursor &> /dev/null; then
|
|
166
|
+
echo -e "${GREEN}✓ Found Cursor Editor${NC}"
|
|
167
|
+
FOUND_CURSOR=true
|
|
168
|
+
elif [ -d "$HOME/.cursor" ]; then
|
|
169
|
+
echo -e "${GREEN}✓ Found Cursor config directory${NC}"
|
|
170
|
+
FOUND_CURSOR=true
|
|
171
|
+
elif [ -d "/Applications/Cursor.app" ]; then
|
|
172
|
+
echo -e "${GREEN}✓ Found Cursor.app${NC}"
|
|
173
|
+
FOUND_CURSOR=true
|
|
174
|
+
else
|
|
175
|
+
echo -e "${YELLOW}○ Cursor Editor not found${NC}"
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
echo ""
|
|
179
|
+
|
|
180
|
+
# Exit if nothing found
|
|
181
|
+
if [ "$FOUND_CLAUDE_CLI" = false ] && [ "$FOUND_CLAUDE_VSCODE" = false ] && \
|
|
182
|
+
[ "$FOUND_CODEX_CLI" = false ] && [ "$FOUND_CODEX_VSCODE" = false ] && \
|
|
183
|
+
[ "$FOUND_COPILOT" = false ] && [ "$FOUND_CURSOR" = false ]; then
|
|
184
|
+
echo -e "${RED}ERROR: No supported AI tools found.${NC}"
|
|
185
|
+
echo ""
|
|
186
|
+
echo "Install one of the following:"
|
|
187
|
+
echo " - Claude Code CLI: https://claude.ai/download"
|
|
188
|
+
echo " - Claude Code VSCode: Search 'Claude Code' in VSCode Extensions"
|
|
189
|
+
echo " - Codex CLI: npm install -g @openai/codex"
|
|
190
|
+
echo " - Codex VSCode: Search 'OpenAI Codex' in VSCode Extensions"
|
|
191
|
+
echo " - GitHub Copilot: Search 'GitHub Copilot' in VSCode Extensions"
|
|
192
|
+
echo " - Cursor: https://cursor.com"
|
|
193
|
+
echo ""
|
|
194
|
+
echo "Or force configuration if detection failed:"
|
|
195
|
+
echo " curl -fsSL https://app.ctlsurf.com/install.sh | bash -s -- --force-codex"
|
|
196
|
+
echo " curl -fsSL https://app.ctlsurf.com/install.sh | bash -s -- --force-claude"
|
|
197
|
+
echo " curl -fsSL https://app.ctlsurf.com/install.sh | bash -s -- --force-copilot"
|
|
198
|
+
echo " curl -fsSL https://app.ctlsurf.com/install.sh | bash -s -- --force-cursor"
|
|
199
|
+
exit 1
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
# Get API key (read from /dev/tty for curl|bash compatibility)
|
|
203
|
+
echo "Get your API key from: https://app.ctlsurf.com/settings (API Keys tab)"
|
|
204
|
+
echo -n "Enter your ctlsurf API key: "
|
|
205
|
+
read API_KEY < /dev/tty
|
|
206
|
+
|
|
207
|
+
if [ -z "$API_KEY" ]; then
|
|
208
|
+
echo -e "${RED}ERROR: API key is required${NC}"
|
|
209
|
+
exit 1
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
echo ""
|
|
213
|
+
echo -n "Server URL [https://app.ctlsurf.com]: "
|
|
214
|
+
read SERVER_URL < /dev/tty
|
|
215
|
+
SERVER_URL=${SERVER_URL:-https://app.ctlsurf.com}
|
|
216
|
+
# Normalize URL
|
|
217
|
+
SERVER_URL="${SERVER_URL%/}"
|
|
218
|
+
SERVER_URL="${SERVER_URL%/api}"
|
|
219
|
+
SERVER_URL="${SERVER_URL%/api/mcp}"
|
|
220
|
+
MCP_URL="${SERVER_URL}/api/mcp"
|
|
221
|
+
|
|
222
|
+
echo ""
|
|
223
|
+
echo "Configuring ctlsurf..."
|
|
224
|
+
echo ""
|
|
225
|
+
|
|
226
|
+
# Persist API key to shell profile
|
|
227
|
+
SHELL_PROFILE=""
|
|
228
|
+
if [ -f "$HOME/.zshrc" ]; then
|
|
229
|
+
SHELL_PROFILE="$HOME/.zshrc"
|
|
230
|
+
elif [ -f "$HOME/.bashrc" ]; then
|
|
231
|
+
SHELL_PROFILE="$HOME/.bashrc"
|
|
232
|
+
elif [ -f "$HOME/.bash_profile" ]; then
|
|
233
|
+
SHELL_PROFILE="$HOME/.bash_profile"
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
if [ -n "$SHELL_PROFILE" ]; then
|
|
237
|
+
# Remove existing CTLSURF exports
|
|
238
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
239
|
+
sed -i '' '/^export CTLSURF_/d' "$SHELL_PROFILE" 2>/dev/null || true
|
|
240
|
+
else
|
|
241
|
+
sed -i '/^export CTLSURF_/d' "$SHELL_PROFILE" 2>/dev/null || true
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
# Add new exports
|
|
245
|
+
echo "" >> "$SHELL_PROFILE"
|
|
246
|
+
echo "# ctlsurf MCP configuration" >> "$SHELL_PROFILE"
|
|
247
|
+
echo "export CTLSURF_API_KEY=\"$API_KEY\"" >> "$SHELL_PROFILE"
|
|
248
|
+
echo "export CTLSURF_URL=\"$MCP_URL\"" >> "$SHELL_PROFILE"
|
|
249
|
+
|
|
250
|
+
echo -e "${GREEN}✓ Added API key to $SHELL_PROFILE${NC}"
|
|
251
|
+
echo ""
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
CONFIGURED_COUNT=0
|
|
255
|
+
CONFIGURED_TOOLS=""
|
|
256
|
+
|
|
257
|
+
# ============================================================
|
|
258
|
+
# Configure Claude Code CLI
|
|
259
|
+
# ============================================================
|
|
260
|
+
if [ "$FOUND_CLAUDE_CLI" = true ]; then
|
|
261
|
+
echo "Setting up Claude Code CLI..."
|
|
262
|
+
|
|
263
|
+
# Check for stale .mcp.json files that could shadow the user-level config
|
|
264
|
+
for MCP_JSON_PATH in "$HOME/.mcp.json" "./.mcp.json"; do
|
|
265
|
+
if [ -f "$MCP_JSON_PATH" ] && grep -q "ctlsurf" "$MCP_JSON_PATH" 2>/dev/null; then
|
|
266
|
+
# Check if it has an old stdio-based config (command-based, not HTTP)
|
|
267
|
+
if grep -q '"command"' "$MCP_JSON_PATH" 2>/dev/null && ! grep -q '"type".*"http"' "$MCP_JSON_PATH" 2>/dev/null; then
|
|
268
|
+
echo -e "${YELLOW} ⚠ Found stale ctlsurf config in $MCP_JSON_PATH (old stdio transport)${NC}"
|
|
269
|
+
echo -e "${YELLOW} This will shadow the HTTP config and cause connection failures.${NC}"
|
|
270
|
+
if command -v jq &> /dev/null; then
|
|
271
|
+
# Remove ctlsurf entry from .mcp.json using jq
|
|
272
|
+
TEMP_MCP_JSON=$(mktemp)
|
|
273
|
+
jq 'del(.mcpServers.ctlsurf)' "$MCP_JSON_PATH" > "$TEMP_MCP_JSON" 2>/dev/null
|
|
274
|
+
# If .mcp.json is now empty (no other servers), remove the file
|
|
275
|
+
if jq -e '.mcpServers | length == 0' "$TEMP_MCP_JSON" &>/dev/null; then
|
|
276
|
+
rm "$MCP_JSON_PATH"
|
|
277
|
+
rm "$TEMP_MCP_JSON"
|
|
278
|
+
echo -e "${GREEN} Removed empty $MCP_JSON_PATH${NC}"
|
|
279
|
+
else
|
|
280
|
+
mv "$TEMP_MCP_JSON" "$MCP_JSON_PATH"
|
|
281
|
+
echo -e "${GREEN} Removed stale ctlsurf entry from $MCP_JSON_PATH${NC}"
|
|
282
|
+
fi
|
|
283
|
+
else
|
|
284
|
+
echo -e "${YELLOW} Please remove ctlsurf from $MCP_JSON_PATH manually${NC}"
|
|
285
|
+
fi
|
|
286
|
+
fi
|
|
287
|
+
fi
|
|
288
|
+
done
|
|
289
|
+
|
|
290
|
+
# Remove existing config
|
|
291
|
+
claude mcp remove ctlsurf -s user 2>/dev/null || true
|
|
292
|
+
# Add HTTP MCP server
|
|
293
|
+
claude mcp add ctlsurf -s user --transport http "$MCP_URL" \
|
|
294
|
+
--header "Authorization: Bearer $API_KEY" 2>/dev/null || true
|
|
295
|
+
|
|
296
|
+
# Verify the config was actually persisted
|
|
297
|
+
CLAUDE_SETTINGS="$HOME/.claude/settings.json"
|
|
298
|
+
CLAUDE_CONFIG_OK=false
|
|
299
|
+
if [ -f "$CLAUDE_SETTINGS" ] && command -v jq &> /dev/null; then
|
|
300
|
+
if jq -e '.mcpServers.ctlsurf.url' "$CLAUDE_SETTINGS" &>/dev/null; then
|
|
301
|
+
CLAUDE_CONFIG_OK=true
|
|
302
|
+
fi
|
|
303
|
+
elif [ -f "$CLAUDE_SETTINGS" ] && grep -q "ctlsurf" "$CLAUDE_SETTINGS" 2>/dev/null; then
|
|
304
|
+
CLAUDE_CONFIG_OK=true
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
# Fallback: write directly to settings.json if claude mcp add didn't persist
|
|
308
|
+
if [ "$CLAUDE_CONFIG_OK" = false ]; then
|
|
309
|
+
echo -e "${YELLOW} claude mcp add didn't persist config, writing directly...${NC}"
|
|
310
|
+
mkdir -p "$HOME/.claude"
|
|
311
|
+
|
|
312
|
+
if command -v jq &> /dev/null; then
|
|
313
|
+
# Merge into existing settings.json using jq
|
|
314
|
+
if [ ! -f "$CLAUDE_SETTINGS" ] || [ ! -s "$CLAUDE_SETTINGS" ] || [ "$(cat "$CLAUDE_SETTINGS")" = "{}" ]; then
|
|
315
|
+
echo '{}' > "$CLAUDE_SETTINGS"
|
|
316
|
+
fi
|
|
317
|
+
TEMP_CLAUDE=$(mktemp)
|
|
318
|
+
MCP_CONFIG=$(cat <<EOF
|
|
319
|
+
{
|
|
320
|
+
"type": "http",
|
|
321
|
+
"url": "$MCP_URL",
|
|
322
|
+
"headers": {
|
|
323
|
+
"Authorization": "Bearer $API_KEY"
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
EOF
|
|
327
|
+
)
|
|
328
|
+
jq --argjson mcpConfig "$MCP_CONFIG" \
|
|
329
|
+
'.mcpServers.ctlsurf = $mcpConfig' \
|
|
330
|
+
"$CLAUDE_SETTINGS" > "$TEMP_CLAUDE" 2>/dev/null || \
|
|
331
|
+
jq --argjson mcpConfig "$MCP_CONFIG" \
|
|
332
|
+
'. + {"mcpServers": {"ctlsurf": $mcpConfig}}' \
|
|
333
|
+
"$CLAUDE_SETTINGS" > "$TEMP_CLAUDE"
|
|
334
|
+
mv "$TEMP_CLAUDE" "$CLAUDE_SETTINGS"
|
|
335
|
+
else
|
|
336
|
+
# No jq - write settings.json directly (only if empty or missing)
|
|
337
|
+
if [ ! -f "$CLAUDE_SETTINGS" ] || [ ! -s "$CLAUDE_SETTINGS" ] || [ "$(cat "$CLAUDE_SETTINGS")" = "{}" ]; then
|
|
338
|
+
cat > "$CLAUDE_SETTINGS" << EOF
|
|
339
|
+
{
|
|
340
|
+
"mcpServers": {
|
|
341
|
+
"ctlsurf": {
|
|
342
|
+
"type": "http",
|
|
343
|
+
"url": "$MCP_URL",
|
|
344
|
+
"headers": {
|
|
345
|
+
"Authorization": "Bearer $API_KEY"
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
EOF
|
|
351
|
+
else
|
|
352
|
+
echo -e "${YELLOW} ⚠ settings.json has existing config and jq is not available${NC}"
|
|
353
|
+
echo " Please manually add ctlsurf to: $CLAUDE_SETTINGS"
|
|
354
|
+
fi
|
|
355
|
+
fi
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
echo -e "${GREEN}✓ Claude Code CLI configured${NC}"
|
|
359
|
+
CONFIGURED_COUNT=$((CONFIGURED_COUNT + 1))
|
|
360
|
+
CONFIGURED_TOOLS="$CONFIGURED_TOOLS\n - Claude Code CLI: Restart terminal to use"
|
|
361
|
+
fi
|
|
362
|
+
|
|
363
|
+
# ============================================================
|
|
364
|
+
# Configure Claude Code VSCode Extension
|
|
365
|
+
# ============================================================
|
|
366
|
+
if [ "$FOUND_CLAUDE_VSCODE" = true ]; then
|
|
367
|
+
echo "Setting up Claude Code VSCode Extension..."
|
|
368
|
+
|
|
369
|
+
VSCODE_SETTINGS=$(get_vscode_settings_path)
|
|
370
|
+
|
|
371
|
+
if [ -n "$VSCODE_SETTINGS" ]; then
|
|
372
|
+
# Create settings directory if it doesn't exist
|
|
373
|
+
mkdir -p "$(dirname "$VSCODE_SETTINGS")"
|
|
374
|
+
|
|
375
|
+
# Create empty settings if file doesn't exist
|
|
376
|
+
if [ ! -f "$VSCODE_SETTINGS" ]; then
|
|
377
|
+
echo "{}" > "$VSCODE_SETTINGS"
|
|
378
|
+
fi
|
|
379
|
+
|
|
380
|
+
# Check if jq is available for safe JSON manipulation
|
|
381
|
+
if command -v jq &> /dev/null; then
|
|
382
|
+
# Use jq to safely update settings
|
|
383
|
+
TEMP_SETTINGS=$(mktemp)
|
|
384
|
+
|
|
385
|
+
# Create the MCP server config
|
|
386
|
+
MCP_CONFIG=$(cat <<EOF
|
|
387
|
+
{
|
|
388
|
+
"type": "http",
|
|
389
|
+
"url": "$MCP_URL",
|
|
390
|
+
"headers": {
|
|
391
|
+
"Authorization": "Bearer $API_KEY"
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
EOF
|
|
395
|
+
)
|
|
396
|
+
# Update settings with new MCP config
|
|
397
|
+
jq --argjson mcpConfig "$MCP_CONFIG" \
|
|
398
|
+
'.["claude-code.mcpServers"].ctlsurf = $mcpConfig' \
|
|
399
|
+
"$VSCODE_SETTINGS" > "$TEMP_SETTINGS" 2>/dev/null || \
|
|
400
|
+
jq --argjson mcpConfig "$MCP_CONFIG" \
|
|
401
|
+
'. + {"claude-code.mcpServers": {"ctlsurf": $mcpConfig}}' \
|
|
402
|
+
"$VSCODE_SETTINGS" > "$TEMP_SETTINGS"
|
|
403
|
+
|
|
404
|
+
mv "$TEMP_SETTINGS" "$VSCODE_SETTINGS"
|
|
405
|
+
echo -e "${GREEN}✓ Claude Code VSCode configured${NC}"
|
|
406
|
+
echo " Config: $VSCODE_SETTINGS"
|
|
407
|
+
else
|
|
408
|
+
# Fallback: provide manual instructions
|
|
409
|
+
echo -e "${YELLOW}⚠ jq not found - manual configuration required${NC}"
|
|
410
|
+
echo ""
|
|
411
|
+
echo "Add this to your VSCode settings.json:"
|
|
412
|
+
echo ""
|
|
413
|
+
echo -e "${BLUE}\"claude-code.mcpServers\": {"
|
|
414
|
+
echo " \"ctlsurf\": {"
|
|
415
|
+
echo " \"type\": \"http\","
|
|
416
|
+
echo " \"url\": \"$MCP_URL\","
|
|
417
|
+
echo " \"headers\": {"
|
|
418
|
+
echo " \"Authorization\": \"Bearer $API_KEY\""
|
|
419
|
+
echo " }"
|
|
420
|
+
echo " }"
|
|
421
|
+
echo -e "}${NC}"
|
|
422
|
+
echo ""
|
|
423
|
+
echo "Settings file: $VSCODE_SETTINGS"
|
|
424
|
+
fi
|
|
425
|
+
CONFIGURED_COUNT=$((CONFIGURED_COUNT + 1))
|
|
426
|
+
CONFIGURED_TOOLS="$CONFIGURED_TOOLS\n - Claude Code VSCode: Reload VSCode window"
|
|
427
|
+
else
|
|
428
|
+
echo -e "${YELLOW}⚠ Could not determine VSCode settings path${NC}"
|
|
429
|
+
fi
|
|
430
|
+
fi
|
|
431
|
+
|
|
432
|
+
# ============================================================
|
|
433
|
+
# Configure Codex CLI
|
|
434
|
+
# ============================================================
|
|
435
|
+
if [ "$FOUND_CODEX_CLI" = true ]; then
|
|
436
|
+
echo "Setting up OpenAI Codex CLI..."
|
|
437
|
+
|
|
438
|
+
CODEX_CONFIG_DIR="$HOME/.codex"
|
|
439
|
+
CODEX_CONFIG="$CODEX_CONFIG_DIR/config.toml"
|
|
440
|
+
|
|
441
|
+
# Create config directory if needed
|
|
442
|
+
mkdir -p "$CODEX_CONFIG_DIR"
|
|
443
|
+
|
|
444
|
+
# Remove existing ctlsurf config if present (use awk for reliable multi-line TOML block removal)
|
|
445
|
+
if [ -f "$CODEX_CONFIG" ] && grep -q "\[mcp_servers.ctlsurf\]" "$CODEX_CONFIG" 2>/dev/null; then
|
|
446
|
+
# awk approach: skip lines from [mcp_servers.ctlsurf] until next section or EOF
|
|
447
|
+
awk '
|
|
448
|
+
/^\[mcp_servers\.ctlsurf\]/ { skip=1; next }
|
|
449
|
+
/^\[/ { skip=0 }
|
|
450
|
+
!skip { print }
|
|
451
|
+
' "$CODEX_CONFIG" > "$CODEX_CONFIG.tmp" && mv "$CODEX_CONFIG.tmp" "$CODEX_CONFIG"
|
|
452
|
+
fi
|
|
453
|
+
|
|
454
|
+
# Ensure config file exists
|
|
455
|
+
touch "$CODEX_CONFIG"
|
|
456
|
+
|
|
457
|
+
# Append ctlsurf HTTP configuration (Codex format)
|
|
458
|
+
cat >> "$CODEX_CONFIG" << EOF
|
|
459
|
+
|
|
460
|
+
# ctlsurf (Control Surface) MCP Server - HTTP Transport
|
|
461
|
+
[mcp_servers.ctlsurf]
|
|
462
|
+
url = "$MCP_URL"
|
|
463
|
+
http_headers = { "Authorization" = "Bearer $API_KEY" }
|
|
464
|
+
EOF
|
|
465
|
+
|
|
466
|
+
echo -e "${GREEN}✓ Codex CLI configured${NC}"
|
|
467
|
+
echo " Config: $CODEX_CONFIG"
|
|
468
|
+
CONFIGURED_COUNT=$((CONFIGURED_COUNT + 1))
|
|
469
|
+
CONFIGURED_TOOLS="$CONFIGURED_TOOLS\n - Codex CLI: Run 'codex' and type /mcp to verify"
|
|
470
|
+
fi
|
|
471
|
+
|
|
472
|
+
# ============================================================
|
|
473
|
+
# Configure Codex/ChatGPT VSCode Extension
|
|
474
|
+
# ============================================================
|
|
475
|
+
if [ "$FOUND_CODEX_VSCODE" = true ]; then
|
|
476
|
+
echo "Setting up OpenAI ChatGPT VSCode Extension..."
|
|
477
|
+
|
|
478
|
+
VSCODE_SETTINGS=$(get_vscode_settings_path)
|
|
479
|
+
|
|
480
|
+
if [ -n "$VSCODE_SETTINGS" ]; then
|
|
481
|
+
# Create settings directory if it doesn't exist
|
|
482
|
+
mkdir -p "$(dirname "$VSCODE_SETTINGS")"
|
|
483
|
+
|
|
484
|
+
# Create empty settings if file doesn't exist
|
|
485
|
+
if [ ! -f "$VSCODE_SETTINGS" ]; then
|
|
486
|
+
echo "{}" > "$VSCODE_SETTINGS"
|
|
487
|
+
fi
|
|
488
|
+
|
|
489
|
+
# Check if jq is available for safe JSON manipulation
|
|
490
|
+
if command -v jq &> /dev/null; then
|
|
491
|
+
# Use jq to safely update settings
|
|
492
|
+
TEMP_SETTINGS=$(mktemp)
|
|
493
|
+
|
|
494
|
+
# Create the MCP server config for ChatGPT
|
|
495
|
+
MCP_CONFIG=$(cat <<EOF
|
|
496
|
+
{
|
|
497
|
+
"type": "http",
|
|
498
|
+
"url": "$MCP_URL",
|
|
499
|
+
"headers": {
|
|
500
|
+
"Authorization": "Bearer $API_KEY"
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
EOF
|
|
504
|
+
)
|
|
505
|
+
# Update settings with new MCP config (chatgpt.mcpServers)
|
|
506
|
+
jq --argjson mcpConfig "$MCP_CONFIG" \
|
|
507
|
+
'.["chatgpt.mcpServers"].ctlsurf = $mcpConfig' \
|
|
508
|
+
"$VSCODE_SETTINGS" > "$TEMP_SETTINGS" 2>/dev/null || \
|
|
509
|
+
jq --argjson mcpConfig "$MCP_CONFIG" \
|
|
510
|
+
'. + {"chatgpt.mcpServers": {"ctlsurf": $mcpConfig}}' \
|
|
511
|
+
"$VSCODE_SETTINGS" > "$TEMP_SETTINGS"
|
|
512
|
+
|
|
513
|
+
mv "$TEMP_SETTINGS" "$VSCODE_SETTINGS"
|
|
514
|
+
echo -e "${GREEN}✓ ChatGPT VSCode configured${NC}"
|
|
515
|
+
echo " Config: $VSCODE_SETTINGS"
|
|
516
|
+
else
|
|
517
|
+
# Fallback: provide manual instructions
|
|
518
|
+
echo -e "${YELLOW}⚠ jq not found - manual configuration required${NC}"
|
|
519
|
+
echo ""
|
|
520
|
+
echo "Add this to your VSCode settings.json:"
|
|
521
|
+
echo ""
|
|
522
|
+
echo -e "${BLUE}\"chatgpt.mcpServers\": {"
|
|
523
|
+
echo " \"ctlsurf\": {"
|
|
524
|
+
echo " \"type\": \"http\","
|
|
525
|
+
echo " \"url\": \"$MCP_URL\","
|
|
526
|
+
echo " \"headers\": {"
|
|
527
|
+
echo " \"Authorization\": \"Bearer $API_KEY\""
|
|
528
|
+
echo " }"
|
|
529
|
+
echo " }"
|
|
530
|
+
echo -e "}${NC}"
|
|
531
|
+
echo ""
|
|
532
|
+
echo "Settings file: $VSCODE_SETTINGS"
|
|
533
|
+
fi
|
|
534
|
+
CONFIGURED_COUNT=$((CONFIGURED_COUNT + 1))
|
|
535
|
+
CONFIGURED_TOOLS="$CONFIGURED_TOOLS\n - ChatGPT VSCode: Reload VSCode window"
|
|
536
|
+
else
|
|
537
|
+
echo -e "${YELLOW}⚠ Could not determine VSCode settings path${NC}"
|
|
538
|
+
fi
|
|
539
|
+
fi
|
|
540
|
+
|
|
541
|
+
# ============================================================
|
|
542
|
+
# Configure GitHub Copilot VSCode Extension
|
|
543
|
+
# ============================================================
|
|
544
|
+
if [ "$FOUND_COPILOT" = true ]; then
|
|
545
|
+
echo "Setting up GitHub Copilot VSCode Extension..."
|
|
546
|
+
|
|
547
|
+
VSCODE_MCP=$(get_vscode_mcp_path)
|
|
548
|
+
|
|
549
|
+
if [ -n "$VSCODE_MCP" ]; then
|
|
550
|
+
# Create config directory if it doesn't exist
|
|
551
|
+
mkdir -p "$(dirname "$VSCODE_MCP")"
|
|
552
|
+
|
|
553
|
+
# Create empty mcp.json if file doesn't exist
|
|
554
|
+
if [ ! -f "$VSCODE_MCP" ]; then
|
|
555
|
+
echo '{"servers":{}}' > "$VSCODE_MCP"
|
|
556
|
+
fi
|
|
557
|
+
|
|
558
|
+
# Check if jq is available for safe JSON manipulation
|
|
559
|
+
if command -v jq &> /dev/null; then
|
|
560
|
+
# Use jq to safely update mcp.json
|
|
561
|
+
TEMP_MCP=$(mktemp)
|
|
562
|
+
|
|
563
|
+
# Create the MCP server config for Copilot
|
|
564
|
+
MCP_CONFIG=$(cat <<EOF
|
|
565
|
+
{
|
|
566
|
+
"type": "http",
|
|
567
|
+
"url": "$MCP_URL",
|
|
568
|
+
"headers": {
|
|
569
|
+
"Authorization": "Bearer $API_KEY"
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
EOF
|
|
573
|
+
)
|
|
574
|
+
# Update mcp.json with new config
|
|
575
|
+
# Copilot uses { "servers": { "name": { ... } } } format
|
|
576
|
+
jq --argjson mcpConfig "$MCP_CONFIG" \
|
|
577
|
+
'.servers.ctlsurf = $mcpConfig' \
|
|
578
|
+
"$VSCODE_MCP" > "$TEMP_MCP" 2>/dev/null || \
|
|
579
|
+
jq --argjson mcpConfig "$MCP_CONFIG" \
|
|
580
|
+
'. + {"servers": {"ctlsurf": $mcpConfig}}' \
|
|
581
|
+
"$VSCODE_MCP" > "$TEMP_MCP"
|
|
582
|
+
|
|
583
|
+
mv "$TEMP_MCP" "$VSCODE_MCP"
|
|
584
|
+
echo -e "${GREEN}✓ GitHub Copilot configured${NC}"
|
|
585
|
+
echo " Config: $VSCODE_MCP"
|
|
586
|
+
else
|
|
587
|
+
# Fallback: provide manual instructions
|
|
588
|
+
echo -e "${YELLOW}⚠ jq not found - manual configuration required${NC}"
|
|
589
|
+
echo ""
|
|
590
|
+
echo "Create or edit: $VSCODE_MCP"
|
|
591
|
+
echo ""
|
|
592
|
+
echo -e "${BLUE}{"
|
|
593
|
+
echo " \"servers\": {"
|
|
594
|
+
echo " \"ctlsurf\": {"
|
|
595
|
+
echo " \"type\": \"http\","
|
|
596
|
+
echo " \"url\": \"$MCP_URL\","
|
|
597
|
+
echo " \"headers\": {"
|
|
598
|
+
echo " \"Authorization\": \"Bearer $API_KEY\""
|
|
599
|
+
echo " }"
|
|
600
|
+
echo " }"
|
|
601
|
+
echo " }"
|
|
602
|
+
echo -e "}${NC}"
|
|
603
|
+
fi
|
|
604
|
+
CONFIGURED_COUNT=$((CONFIGURED_COUNT + 1))
|
|
605
|
+
CONFIGURED_TOOLS="$CONFIGURED_TOOLS\n - GitHub Copilot: Reload VSCode window"
|
|
606
|
+
else
|
|
607
|
+
echo -e "${YELLOW}⚠ Could not determine VSCode mcp.json path${NC}"
|
|
608
|
+
fi
|
|
609
|
+
fi
|
|
610
|
+
|
|
611
|
+
# ============================================================
|
|
612
|
+
# Configure Cursor Editor
|
|
613
|
+
# ============================================================
|
|
614
|
+
if [ "$FOUND_CURSOR" = true ]; then
|
|
615
|
+
echo "Setting up Cursor Editor..."
|
|
616
|
+
|
|
617
|
+
CURSOR_CONFIG_DIR="$HOME/.cursor"
|
|
618
|
+
CURSOR_MCP="$CURSOR_CONFIG_DIR/mcp.json"
|
|
619
|
+
|
|
620
|
+
# Create config directory if needed
|
|
621
|
+
mkdir -p "$CURSOR_CONFIG_DIR"
|
|
622
|
+
|
|
623
|
+
# Create empty mcp.json if file doesn't exist
|
|
624
|
+
if [ ! -f "$CURSOR_MCP" ]; then
|
|
625
|
+
echo '{"mcpServers":{}}' > "$CURSOR_MCP"
|
|
626
|
+
fi
|
|
627
|
+
|
|
628
|
+
# Check if jq is available for safe JSON manipulation
|
|
629
|
+
if command -v jq &> /dev/null; then
|
|
630
|
+
# Use jq to safely update mcp.json
|
|
631
|
+
TEMP_MCP=$(mktemp)
|
|
632
|
+
|
|
633
|
+
# Create the MCP server config for Cursor
|
|
634
|
+
# Cursor uses { "mcpServers": { "name": { url, headers } } } format (no "type" field for HTTP)
|
|
635
|
+
MCP_CONFIG=$(cat <<EOF
|
|
636
|
+
{
|
|
637
|
+
"url": "$MCP_URL",
|
|
638
|
+
"headers": {
|
|
639
|
+
"Authorization": "Bearer $API_KEY"
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
EOF
|
|
643
|
+
)
|
|
644
|
+
# Update mcp.json with new config
|
|
645
|
+
jq --argjson mcpConfig "$MCP_CONFIG" \
|
|
646
|
+
'.mcpServers.ctlsurf = $mcpConfig' \
|
|
647
|
+
"$CURSOR_MCP" > "$TEMP_MCP" 2>/dev/null || \
|
|
648
|
+
jq --argjson mcpConfig "$MCP_CONFIG" \
|
|
649
|
+
'. + {"mcpServers": {"ctlsurf": $mcpConfig}}' \
|
|
650
|
+
"$CURSOR_MCP" > "$TEMP_MCP"
|
|
651
|
+
|
|
652
|
+
mv "$TEMP_MCP" "$CURSOR_MCP"
|
|
653
|
+
echo -e "${GREEN}✓ Cursor configured${NC}"
|
|
654
|
+
echo " Config: $CURSOR_MCP"
|
|
655
|
+
else
|
|
656
|
+
# Fallback: provide manual instructions
|
|
657
|
+
echo -e "${YELLOW}⚠ jq not found - manual configuration required${NC}"
|
|
658
|
+
echo ""
|
|
659
|
+
echo "Create or edit: $CURSOR_MCP"
|
|
660
|
+
echo ""
|
|
661
|
+
echo -e "${BLUE}{"
|
|
662
|
+
echo " \"mcpServers\": {"
|
|
663
|
+
echo " \"ctlsurf\": {"
|
|
664
|
+
echo " \"url\": \"$MCP_URL\","
|
|
665
|
+
echo " \"headers\": {"
|
|
666
|
+
echo " \"Authorization\": \"Bearer $API_KEY\""
|
|
667
|
+
echo " }"
|
|
668
|
+
echo " }"
|
|
669
|
+
echo " }"
|
|
670
|
+
echo -e "}${NC}"
|
|
671
|
+
fi
|
|
672
|
+
CONFIGURED_COUNT=$((CONFIGURED_COUNT + 1))
|
|
673
|
+
CONFIGURED_TOOLS="$CONFIGURED_TOOLS\n - Cursor: Restart Cursor to use"
|
|
674
|
+
fi
|
|
675
|
+
|
|
676
|
+
echo ""
|
|
677
|
+
echo "========================================"
|
|
678
|
+
echo -e "${GREEN} Setup Complete!${NC}"
|
|
679
|
+
echo "========================================"
|
|
680
|
+
echo ""
|
|
681
|
+
echo "Server: $SERVER_URL"
|
|
682
|
+
echo "Endpoint: $MCP_URL"
|
|
683
|
+
echo "Tools configured: $CONFIGURED_COUNT"
|
|
684
|
+
echo ""
|
|
685
|
+
|
|
686
|
+
if [ -n "$SHELL_PROFILE" ]; then
|
|
687
|
+
echo "To use the API key in current shell:"
|
|
688
|
+
echo " source $SHELL_PROFILE"
|
|
689
|
+
echo ""
|
|
690
|
+
fi
|
|
691
|
+
|
|
692
|
+
if [ -n "$CONFIGURED_TOOLS" ]; then
|
|
693
|
+
echo "Next steps:"
|
|
694
|
+
echo -e "$CONFIGURED_TOOLS"
|
|
695
|
+
echo ""
|
|
696
|
+
fi
|