aigo 1.0.0
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/AGENTS.md +1 -0
- package/README.md +453 -0
- package/bin/vigo.js +282 -0
- package/lib/cli.js +81 -0
- package/lib/config.js +80 -0
- package/lib/cursor.js +74 -0
- package/lib/port.js +39 -0
- package/lib/server.js +284 -0
- package/lib/tmux.js +86 -0
- package/lib/tunnel.js +150 -0
- package/package.json +27 -0
- package/public/index.html +545 -0
- package/public/terminal.js +759 -0
- package/specs/claude-code-web-terminal.md +258 -0
- package/specs/cursor-cli-support.md +139 -0
package/bin/vigo.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { parseArgs } from '../lib/cli.js';
|
|
4
|
+
import { isTmuxInstalled, hasSession, createSession } from '../lib/tmux.js';
|
|
5
|
+
import { isAgentInstalled, checkAgentAuth } from '../lib/cursor.js';
|
|
6
|
+
import { startServer } from '../lib/server.js';
|
|
7
|
+
import { startTunnel, checkNgrokRunning } from '../lib/tunnel.js';
|
|
8
|
+
import { findAvailablePort } from '../lib/port.js';
|
|
9
|
+
import { config } from '../lib/config.js';
|
|
10
|
+
import crypto from 'crypto';
|
|
11
|
+
|
|
12
|
+
const VERSION = '1.2.0';
|
|
13
|
+
|
|
14
|
+
function printBanner(info) {
|
|
15
|
+
const accessUrl = info.tunnelUrl
|
|
16
|
+
? `${info.tunnelUrl}/t/${info.token}`
|
|
17
|
+
: `${info.localUrl}/t/${info.token}`;
|
|
18
|
+
|
|
19
|
+
const lockStr = info.lockTimeout > 0 ? `${info.lockTimeout}min` : 'off';
|
|
20
|
+
const exitStr = info.exitTimeout > 0 ? `${info.exitTimeout}min` : 'off';
|
|
21
|
+
const timeoutStr = `lock:${lockStr} exit:${exitStr}`;
|
|
22
|
+
|
|
23
|
+
const toolTitle = info.tool === 'cursor' ? 'Cursor Agent' : 'Claude Code';
|
|
24
|
+
const header = `vigo - ${toolTitle} Web Terminal`;
|
|
25
|
+
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log('┌────────────────────────────────────────┐');
|
|
28
|
+
console.log(`│ ${header.padEnd(37)}│`);
|
|
29
|
+
console.log('├────────────────────────────────────────┤');
|
|
30
|
+
console.log(`│ Session: ${info.session.padEnd(27)}│`);
|
|
31
|
+
console.log(`│ Local: ${info.localUrl.padEnd(27)}│`);
|
|
32
|
+
if (info.tunnelUrl) {
|
|
33
|
+
console.log(`│ Tunnel: ${info.tunnelUrl.padEnd(27)}│`);
|
|
34
|
+
}
|
|
35
|
+
console.log(`│ Password: ${info.password.padEnd(27)}│`);
|
|
36
|
+
console.log(`│ Timeout: ${timeoutStr.padEnd(27)}│`);
|
|
37
|
+
console.log('└────────────────────────────────────────┘');
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log('Access URL:');
|
|
40
|
+
console.log(` ${accessUrl}`);
|
|
41
|
+
console.log('');
|
|
42
|
+
console.log('Press Ctrl+C to stop (tmux session persists)');
|
|
43
|
+
console.log('');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function printHelp() {
|
|
47
|
+
console.log(`
|
|
48
|
+
vigo v${VERSION} - Stream Claude Code or Cursor Agent to the web
|
|
49
|
+
|
|
50
|
+
Usage:
|
|
51
|
+
vigo [options] claude [claude-args]
|
|
52
|
+
vigo [options] cursor [cursor-args]
|
|
53
|
+
vigo [options] agent [agent-args]
|
|
54
|
+
vigo [options] --attach <session>
|
|
55
|
+
|
|
56
|
+
Tools:
|
|
57
|
+
claude Use Claude Code CLI
|
|
58
|
+
cursor, agent Use Cursor Agent CLI
|
|
59
|
+
|
|
60
|
+
Options:
|
|
61
|
+
--port, -p <port> Web server port (default: ${config.defaultPort}, auto-selects if busy)
|
|
62
|
+
--session, -s <name> tmux session name (default: claude-code or cursor-agent)
|
|
63
|
+
--tunnel, -t <type> Tunnel: ngrok, cloudflared, none (default: ${config.defaultTunnel})
|
|
64
|
+
--attach, -a <session> Attach to existing tmux session (don't start new)
|
|
65
|
+
--password, -P <pass> Custom password (default: auto-generated ${config.passwordLength}-char)
|
|
66
|
+
--timeout, -T <mins> Lock screen after inactivity (default: disabled)
|
|
67
|
+
--exit-timeout, -E <mins> Exit session after inactivity (default: disabled)
|
|
68
|
+
--help, -h Show this help message
|
|
69
|
+
--version, -v Show version
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
vigo claude Start Claude Code with ngrok tunnel
|
|
73
|
+
vigo cursor Start Cursor Agent with ngrok tunnel
|
|
74
|
+
vigo --tunnel none claude Local only (no tunnel)
|
|
75
|
+
vigo -p 8080 -s myproject cursor Custom port and session
|
|
76
|
+
vigo claude --model sonnet Pass args to Claude
|
|
77
|
+
vigo cursor --model gpt-5 Pass args to Cursor Agent
|
|
78
|
+
vigo --attach myproject Attach to existing session
|
|
79
|
+
vigo -P mypass123 cursor Custom password
|
|
80
|
+
`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function main() {
|
|
84
|
+
const { vigoArgs, toolArgs, tool } = parseArgs(process.argv.slice(2));
|
|
85
|
+
|
|
86
|
+
// Handle help and version
|
|
87
|
+
if (vigoArgs.help) {
|
|
88
|
+
printHelp();
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (vigoArgs.version) {
|
|
93
|
+
console.log(`vigo v${VERSION}`);
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Check if tmux is installed
|
|
98
|
+
if (!isTmuxInstalled()) {
|
|
99
|
+
console.error('Error: tmux is not installed or not in PATH');
|
|
100
|
+
console.error('');
|
|
101
|
+
console.error('Install tmux:');
|
|
102
|
+
console.error(' macOS: brew install tmux');
|
|
103
|
+
console.error(' Ubuntu: sudo apt install tmux');
|
|
104
|
+
console.error(' Arch: sudo pacman -S tmux');
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Determine which tool to use (from config default)
|
|
109
|
+
const activeTool = tool || config.defaultTool;
|
|
110
|
+
const toolConfig = config.tools[activeTool];
|
|
111
|
+
|
|
112
|
+
// Check if the tool CLI is installed (for non-attach mode)
|
|
113
|
+
if (!vigoArgs.attach && activeTool === 'cursor') {
|
|
114
|
+
if (!isAgentInstalled()) {
|
|
115
|
+
console.error('Error: Cursor Agent CLI is not installed or not in PATH');
|
|
116
|
+
console.error('');
|
|
117
|
+
console.error('Install Cursor CLI:');
|
|
118
|
+
console.error(' curl https://cursor.com/install -fsS | bash');
|
|
119
|
+
console.error('');
|
|
120
|
+
console.error('Then authenticate:');
|
|
121
|
+
console.error(' agent login');
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check authentication
|
|
126
|
+
const authStatus = checkAgentAuth();
|
|
127
|
+
if (!authStatus.authenticated) {
|
|
128
|
+
console.error('Warning: Cursor Agent may not be authenticated');
|
|
129
|
+
console.error(` ${authStatus.message}`);
|
|
130
|
+
console.error('');
|
|
131
|
+
console.error('To authenticate:');
|
|
132
|
+
console.error(' agent login');
|
|
133
|
+
console.error(' # or set CURSOR_API_KEY environment variable');
|
|
134
|
+
console.error('');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const sessionName = vigoArgs.session || toolConfig.defaultSession;
|
|
139
|
+
const requestedPort = vigoArgs.port || config.defaultPort;
|
|
140
|
+
const tunnelType = vigoArgs.tunnel || config.defaultTunnel;
|
|
141
|
+
|
|
142
|
+
// Find available port
|
|
143
|
+
let port;
|
|
144
|
+
try {
|
|
145
|
+
port = await findAvailablePort(requestedPort);
|
|
146
|
+
if (port !== requestedPort) {
|
|
147
|
+
console.log(`⚠ Port ${requestedPort} in use, using port ${port} instead`);
|
|
148
|
+
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error(`Error: ${err.message}`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Generate secure token
|
|
155
|
+
const token = crypto.randomBytes(config.tokenBytes).toString('hex');
|
|
156
|
+
|
|
157
|
+
// Use custom password or generate password from config
|
|
158
|
+
let password;
|
|
159
|
+
if (vigoArgs.password) {
|
|
160
|
+
password = vigoArgs.password;
|
|
161
|
+
} else {
|
|
162
|
+
password = '';
|
|
163
|
+
const randomBytes = crypto.randomBytes(config.passwordLength);
|
|
164
|
+
for (let i = 0; i < config.passwordLength; i++) {
|
|
165
|
+
password += config.passwordChars[randomBytes[i] % config.passwordChars.length];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Timeout settings (in minutes) - use config defaults
|
|
170
|
+
const lockTimeout = vigoArgs.timeout !== null ? vigoArgs.timeout : config.lockTimeout;
|
|
171
|
+
const exitTimeout = vigoArgs.exitTimeout !== null ? vigoArgs.exitTimeout : config.exitTimeout;
|
|
172
|
+
|
|
173
|
+
// Handle --attach mode
|
|
174
|
+
if (vigoArgs.attach) {
|
|
175
|
+
const attachSession = typeof vigoArgs.attach === 'string' ? vigoArgs.attach : sessionName;
|
|
176
|
+
if (!hasSession(attachSession)) {
|
|
177
|
+
console.error(`Error: tmux session '${attachSession}' does not exist`);
|
|
178
|
+
console.error(`Create it first with: tmux new-session -s ${attachSession}`);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
console.log(`✓ Attaching to existing tmux session: ${attachSession}`);
|
|
182
|
+
} else {
|
|
183
|
+
// Check if session already exists
|
|
184
|
+
if (hasSession(sessionName)) {
|
|
185
|
+
console.log(`✓ Using existing tmux session: ${sessionName}`);
|
|
186
|
+
} else {
|
|
187
|
+
// Create new session with the selected tool
|
|
188
|
+
console.log(`✓ Creating tmux session: ${sessionName} (${toolConfig.name})`);
|
|
189
|
+
createSession(sessionName, toolConfig.command, toolArgs);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const actualSession = vigoArgs.attach || sessionName;
|
|
194
|
+
|
|
195
|
+
// Start web server
|
|
196
|
+
console.log(`✓ Starting web server on port ${port}...`);
|
|
197
|
+
const server = await startServer({
|
|
198
|
+
port,
|
|
199
|
+
token,
|
|
200
|
+
password,
|
|
201
|
+
session: actualSession,
|
|
202
|
+
lockTimeout,
|
|
203
|
+
exitTimeout
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Start tunnel if requested (ngrok is now default)
|
|
207
|
+
let tunnelUrl = null;
|
|
208
|
+
let tunnel = null;
|
|
209
|
+
if (tunnelType !== 'none') {
|
|
210
|
+
// Check for existing ngrok process before starting
|
|
211
|
+
if (tunnelType === 'ngrok') {
|
|
212
|
+
const ngrokStatus = checkNgrokRunning();
|
|
213
|
+
if (ngrokStatus.running) {
|
|
214
|
+
console.error('');
|
|
215
|
+
console.error('Error: An ngrok process is already running.');
|
|
216
|
+
console.error('Free ngrok accounts only allow 1 simultaneous tunnel.');
|
|
217
|
+
console.error('');
|
|
218
|
+
console.error('To kill the existing ngrok process, run:');
|
|
219
|
+
if (process.platform === 'win32') {
|
|
220
|
+
console.error(` taskkill /F /PID ${ngrokStatus.pids.join(' /PID ')}`);
|
|
221
|
+
} else {
|
|
222
|
+
console.error(` kill ${ngrokStatus.pids.join(' ')}`);
|
|
223
|
+
}
|
|
224
|
+
console.error('');
|
|
225
|
+
console.error('Or to run without a tunnel:');
|
|
226
|
+
console.error(' vigo --tunnel none cursor');
|
|
227
|
+
console.error('');
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
console.log(`✓ Starting ${tunnelType} tunnel...`);
|
|
233
|
+
try {
|
|
234
|
+
tunnel = await startTunnel(tunnelType, port);
|
|
235
|
+
tunnelUrl = tunnel.url;
|
|
236
|
+
} catch (err) {
|
|
237
|
+
console.error(`Warning: Failed to start ${tunnelType} tunnel: ${err.message}`);
|
|
238
|
+
console.error('Continuing without tunnel...');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Print access info
|
|
243
|
+
printBanner({
|
|
244
|
+
session: actualSession,
|
|
245
|
+
localUrl: `http://localhost:${port}`,
|
|
246
|
+
tunnelUrl,
|
|
247
|
+
token,
|
|
248
|
+
password,
|
|
249
|
+
lockTimeout,
|
|
250
|
+
exitTimeout,
|
|
251
|
+
tool: activeTool
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Keep process alive and handle cleanup
|
|
255
|
+
process.on('SIGINT', () => {
|
|
256
|
+
console.log('\nShutting down server...');
|
|
257
|
+
|
|
258
|
+
// Kill tunnel if running
|
|
259
|
+
if (tunnel) {
|
|
260
|
+
tunnel.kill();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
console.log(`tmux session '${actualSession}' is still running.`);
|
|
264
|
+
console.log(`Re-attach with: vigo --attach ${actualSession}`);
|
|
265
|
+
console.log(`Or directly: tmux attach -t ${actualSession}`);
|
|
266
|
+
server.close();
|
|
267
|
+
process.exit(0);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
process.on('SIGTERM', () => {
|
|
271
|
+
if (tunnel) {
|
|
272
|
+
tunnel.kill();
|
|
273
|
+
}
|
|
274
|
+
server.close();
|
|
275
|
+
process.exit(0);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
main().catch(err => {
|
|
280
|
+
console.error('Error:', err.message);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
});
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse command line arguments, separating vigo args from tool args
|
|
3
|
+
*
|
|
4
|
+
* Example: vigo --port 8080 --tunnel ngrok claude --model sonnet
|
|
5
|
+
* Example: vigo --port 8080 cursor --model gpt-5
|
|
6
|
+
* Returns:
|
|
7
|
+
* vigoArgs: { port: 8080, tunnel: 'ngrok' }
|
|
8
|
+
* toolArgs: ['--model', 'sonnet']
|
|
9
|
+
* tool: 'claude' | 'cursor' | null
|
|
10
|
+
*/
|
|
11
|
+
export function parseArgs(args) {
|
|
12
|
+
const vigoArgs = {
|
|
13
|
+
port: null,
|
|
14
|
+
session: null,
|
|
15
|
+
tunnel: null,
|
|
16
|
+
attach: null,
|
|
17
|
+
password: null,
|
|
18
|
+
timeout: null, // Lock screen timeout in minutes (default: 15)
|
|
19
|
+
exitTimeout: null, // Exit after inactivity in minutes (default: 30)
|
|
20
|
+
help: false,
|
|
21
|
+
version: false
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
let toolArgs = [];
|
|
25
|
+
let tool = null;
|
|
26
|
+
let i = 0;
|
|
27
|
+
|
|
28
|
+
// Supported tool commands
|
|
29
|
+
const TOOL_COMMANDS = ['claude', 'cursor', 'agent'];
|
|
30
|
+
|
|
31
|
+
while (i < args.length) {
|
|
32
|
+
const arg = args[i];
|
|
33
|
+
|
|
34
|
+
// Once we hit a tool command, everything after is tool args
|
|
35
|
+
if (TOOL_COMMANDS.includes(arg)) {
|
|
36
|
+
// Normalize 'agent' to 'cursor' for consistency
|
|
37
|
+
tool = arg === 'agent' ? 'cursor' : arg;
|
|
38
|
+
toolArgs = args.slice(i + 1);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Parse vigo options
|
|
43
|
+
if (arg === '--port' || arg === '-p') {
|
|
44
|
+
vigoArgs.port = parseInt(args[++i], 10);
|
|
45
|
+
} else if (arg === '--session' || arg === '-s') {
|
|
46
|
+
vigoArgs.session = args[++i];
|
|
47
|
+
} else if (arg === '--tunnel' || arg === '-t') {
|
|
48
|
+
vigoArgs.tunnel = args[++i];
|
|
49
|
+
} else if (arg === '--attach' || arg === '-a') {
|
|
50
|
+
const next = args[i + 1];
|
|
51
|
+
// Check if next arg is a session name (not another flag)
|
|
52
|
+
if (next && !next.startsWith('-')) {
|
|
53
|
+
vigoArgs.attach = args[++i];
|
|
54
|
+
} else {
|
|
55
|
+
vigoArgs.attach = true; // Use default session name
|
|
56
|
+
}
|
|
57
|
+
} else if (arg === '--password' || arg === '-P') {
|
|
58
|
+
vigoArgs.password = args[++i];
|
|
59
|
+
} else if (arg === '--timeout' || arg === '-T') {
|
|
60
|
+
vigoArgs.timeout = parseInt(args[++i], 10);
|
|
61
|
+
} else if (arg === '--exit-timeout' || arg === '-E') {
|
|
62
|
+
vigoArgs.exitTimeout = parseInt(args[++i], 10);
|
|
63
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
64
|
+
vigoArgs.help = true;
|
|
65
|
+
} else if (arg === '--version' || arg === '-v') {
|
|
66
|
+
vigoArgs.version = true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// For backward compatibility, also export as claudeArgs/foundClaude
|
|
73
|
+
return {
|
|
74
|
+
vigoArgs,
|
|
75
|
+
toolArgs,
|
|
76
|
+
tool,
|
|
77
|
+
// Backward compatibility
|
|
78
|
+
claudeArgs: toolArgs,
|
|
79
|
+
foundClaude: tool === 'claude'
|
|
80
|
+
};
|
|
81
|
+
}
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vigo configuration
|
|
3
|
+
*
|
|
4
|
+
* Edit these values to customize vigo's default behavior.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const config = {
|
|
8
|
+
// ============================================
|
|
9
|
+
// Server Settings
|
|
10
|
+
// ============================================
|
|
11
|
+
|
|
12
|
+
/** Default web server port (auto-selects next available if busy) */
|
|
13
|
+
defaultPort: 3000,
|
|
14
|
+
|
|
15
|
+
/** Default tunnel type: 'ngrok', 'cloudflared', or 'none' */
|
|
16
|
+
defaultTunnel: 'ngrok',
|
|
17
|
+
|
|
18
|
+
// ============================================
|
|
19
|
+
// Session Timeouts
|
|
20
|
+
// ============================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Lock screen timeout in minutes (0 = disabled)
|
|
24
|
+
* After this period of inactivity, the session locks and requires password re-entry
|
|
25
|
+
* Default: 0 (disabled) for persistent sessions
|
|
26
|
+
*/
|
|
27
|
+
lockTimeout: 0,
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Exit timeout in minutes (0 = disabled)
|
|
31
|
+
* After this period of total inactivity, the session terminates completely
|
|
32
|
+
* Default: 0 (disabled) for persistent sessions
|
|
33
|
+
*/
|
|
34
|
+
exitTimeout: 0,
|
|
35
|
+
|
|
36
|
+
// ============================================
|
|
37
|
+
// Tool Configurations
|
|
38
|
+
// ============================================
|
|
39
|
+
|
|
40
|
+
tools: {
|
|
41
|
+
claude: {
|
|
42
|
+
name: 'Claude Code',
|
|
43
|
+
command: 'claude',
|
|
44
|
+
defaultSession: 'claude-code'
|
|
45
|
+
},
|
|
46
|
+
cursor: {
|
|
47
|
+
name: 'Cursor Agent',
|
|
48
|
+
command: 'agent',
|
|
49
|
+
defaultSession: 'cursor-agent'
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/** Default tool when none specified */
|
|
54
|
+
defaultTool: 'claude',
|
|
55
|
+
|
|
56
|
+
// ============================================
|
|
57
|
+
// Security Settings
|
|
58
|
+
// ============================================
|
|
59
|
+
|
|
60
|
+
/** Length of auto-generated password */
|
|
61
|
+
passwordLength: 6,
|
|
62
|
+
|
|
63
|
+
/** Characters used for auto-generated passwords (no ambiguous chars like 0/O, 1/l/I) */
|
|
64
|
+
passwordChars: 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789',
|
|
65
|
+
|
|
66
|
+
/** Length of URL token (in bytes, will be hex encoded = 2x chars) */
|
|
67
|
+
tokenBytes: 2,
|
|
68
|
+
|
|
69
|
+
// ============================================
|
|
70
|
+
// Reconnection Settings
|
|
71
|
+
// ============================================
|
|
72
|
+
|
|
73
|
+
/** Maximum reconnection attempts before giving up */
|
|
74
|
+
maxReconnectAttempts: 10,
|
|
75
|
+
|
|
76
|
+
/** Base delay between reconnection attempts (ms) */
|
|
77
|
+
reconnectDelay: 2000
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default config;
|
package/lib/cursor.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
|
|
3
|
+
// Cache the agent path
|
|
4
|
+
let agentPath = null;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get the full path to Cursor agent CLI binary
|
|
8
|
+
*/
|
|
9
|
+
export function getAgentPath() {
|
|
10
|
+
if (agentPath) return agentPath;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
agentPath = execSync('which agent', { encoding: 'utf-8' }).trim();
|
|
14
|
+
return agentPath;
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if Cursor agent CLI is installed
|
|
22
|
+
*/
|
|
23
|
+
export function isAgentInstalled() {
|
|
24
|
+
return getAgentPath() !== null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if Cursor CLI is authenticated
|
|
29
|
+
* Returns: { authenticated: boolean, message: string }
|
|
30
|
+
*/
|
|
31
|
+
export function checkAgentAuth() {
|
|
32
|
+
const agentBin = getAgentPath();
|
|
33
|
+
if (!agentBin) {
|
|
34
|
+
return {
|
|
35
|
+
authenticated: false,
|
|
36
|
+
message: 'Cursor agent CLI is not installed'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
// Try to check auth status
|
|
42
|
+
const output = execSync(`${agentBin} status 2>&1`, {
|
|
43
|
+
encoding: 'utf-8',
|
|
44
|
+
timeout: 10000
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Check for common authenticated patterns
|
|
48
|
+
if (output.includes('logged in') || output.includes('authenticated')) {
|
|
49
|
+
return { authenticated: true, message: 'Authenticated' };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
authenticated: false,
|
|
54
|
+
message: 'Not authenticated. Run: agent login'
|
|
55
|
+
};
|
|
56
|
+
} catch (err) {
|
|
57
|
+
// If CURSOR_API_KEY is set, assume authenticated
|
|
58
|
+
if (process.env.CURSOR_API_KEY) {
|
|
59
|
+
return { authenticated: true, message: 'Using CURSOR_API_KEY' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
authenticated: false,
|
|
64
|
+
message: 'Unable to verify auth status. Try: agent login'
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the command to run for Cursor CLI
|
|
71
|
+
*/
|
|
72
|
+
export function getAgentCommand() {
|
|
73
|
+
return getAgentPath() || 'agent';
|
|
74
|
+
}
|
package/lib/port.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createServer } from 'net';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Check if a port is available
|
|
5
|
+
*/
|
|
6
|
+
export function isPortAvailable(port) {
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
const server = createServer();
|
|
9
|
+
|
|
10
|
+
server.once('error', (err) => {
|
|
11
|
+
if (err.code === 'EADDRINUSE') {
|
|
12
|
+
resolve(false);
|
|
13
|
+
} else {
|
|
14
|
+
resolve(false);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
server.once('listening', () => {
|
|
19
|
+
server.close();
|
|
20
|
+
resolve(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
server.listen(port, '127.0.0.1');
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Find an available port starting from the given port
|
|
29
|
+
* Tries up to maxAttempts ports
|
|
30
|
+
*/
|
|
31
|
+
export async function findAvailablePort(startPort, maxAttempts = 100) {
|
|
32
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
33
|
+
const port = startPort + i;
|
|
34
|
+
if (await isPortAvailable(port)) {
|
|
35
|
+
return port;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`Could not find available port after ${maxAttempts} attempts starting from ${startPort}`);
|
|
39
|
+
}
|