venombrowser 4.1.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/.env.example ADDED
@@ -0,0 +1,26 @@
1
+ # VenomBrowser Bridge Server — Kimi-style configuration
2
+ # v4.0.0
3
+
4
+ # ─── Server Config ──────────────────────────────────────────────────────────
5
+
6
+ # Single port for the daemon (HTTP + WebSocket)
7
+ BRIDGE_PORT=10086
8
+
9
+ # WebSocket port for extension (BRIDGE_PORT + 1 by default)
10
+ WS_PORT=10087
11
+
12
+ # ─── LLM Provider Config ─────────────────────────────────────────────────────
13
+ # Used by bin/venombrowser-agent.js and bin/venombrowser-chat.js
14
+
15
+ # Which provider to use: "openrouter" or "ollama"
16
+ LLM_PROVIDER=openrouter
17
+
18
+ # OpenRouter (Default)
19
+ # Get a free API key at https://openrouter.ai/keys
20
+ OPENROUTER_API_KEY=your-sk-or-v1-key
21
+ OPENROUTER_MODEL=openrouter/free
22
+
23
+ # Ollama (Local)
24
+ # Requires Ollama running locally (ollama serve)
25
+ OLLAMA_HOST=http://localhost:11434
26
+ OLLAMA_MODEL=qwen2.5:7b
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * VenomBrowser Agent — CLI entry point (Kimi-style)
4
+ *
5
+ * Standalone browser automation agent powered by any configured LLM provider.
6
+ * Connects to the VenomBrowser daemon and executes browser tasks.
7
+ *
8
+ * v4.0.0 — Kimi-style architecture:
9
+ * - Uses new bridge-client with Kimi-style protocol
10
+ * - Screenshot returns file path, NOT base64 (fixes cost leak)
11
+ * - Uses snapshot() with @e refs for element targeting
12
+ *
13
+ * Usage:
14
+ * node bin/venombrowser-agent.js "search google for weather in tokyo"
15
+ */
16
+
17
+ require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
18
+
19
+ const { BrowserBridge } = require('../src/bridge-client');
20
+ const { createProvider } = require('../src/providers');
21
+ const { TOOL_DEFINITIONS, executeTool } = require('../src/tools/browser-tools');
22
+
23
+ const MAX_STEPS = parseInt(process.env.MAX_STEPS || '25', 10);
24
+ const session = `agent-${Date.now()}`;
25
+
26
+ // ─── Logging ─────────────────────────────────────────────────────────────────
27
+ function log(level, message, data = null) {
28
+ const timestamp = new Date().toISOString().slice(11, 19);
29
+ const emoji = {
30
+ info: 'ℹ️', success: '✅', warning: '⚠️', error: '❌',
31
+ tool: '🔧', agent: '🤖', result: '📊'
32
+ }[level] || 'ℹ️';
33
+
34
+ console.log(`[${timestamp}] ${emoji} ${message}`);
35
+ if (data && typeof data === 'object') {
36
+ console.log(` ${JSON.stringify(data, null, 2).split('\n').join('\n ')}`);
37
+ }
38
+ }
39
+
40
+ // ─── Main ────────────────────────────────────────────────────────────────────
41
+ async function main() {
42
+ let provider;
43
+ try {
44
+ provider = createProvider();
45
+ } catch (err) {
46
+ console.error(`❌ Provider error: ${err.message}`);
47
+ process.exit(1);
48
+ }
49
+
50
+ const bridge = new BrowserBridge({ session });
51
+
52
+ console.log('🚀 VenomBrowser Agent v4.0.0 (Kimi-style)');
53
+ console.log('═'.repeat(55));
54
+ log('info', `Provider: ${provider}`);
55
+ log('info', `Session: ${session}`);
56
+ log('info', `Max steps: ${MAX_STEPS}`);
57
+ console.log('═'.repeat(55));
58
+
59
+ // System checks
60
+ log('info', 'Running system checks...');
61
+
62
+ try {
63
+ await provider.healthCheck();
64
+ log('success', `LLM provider ready: ${provider}`);
65
+ } catch (err) {
66
+ log('error', `LLM check failed: ${err.message}`);
67
+ process.exit(1);
68
+ }
69
+
70
+ try {
71
+ const connected = await bridge.isConnected();
72
+ if (!connected) throw new Error('VenomBrowser daemon not connected');
73
+ log('success', 'VenomBrowser daemon connected');
74
+ } catch (err) {
75
+ log('error', `Daemon error: ${err.message}`);
76
+ log('info', 'Make sure daemon is running: npm start');
77
+ process.exit(1);
78
+ }
79
+
80
+ console.log();
81
+
82
+ const task = process.argv.slice(2).join(' ');
83
+ if (!task) {
84
+ console.error('❌ Usage: node venombrowser-agent.js "<task description>"');
85
+ process.exit(1);
86
+ }
87
+
88
+ // ─── Agent loop ──────────────────────────────────────────────────────
89
+ const sessionStart = Date.now();
90
+ let totalToolCalls = 0;
91
+
92
+ const messages = [
93
+ {
94
+ role: 'system',
95
+ content: `You are VenomBrowser, a browser automation agent controlling a real Chrome browser.
96
+
97
+ CRITICAL RULES:
98
+ - ALWAYS call snapshot() after navigating to understand the page before clicking anything.
99
+ - Use @e refs from snapshot to click/fill elements (e.g. click("@e3"), fill("@e5", "text")).
100
+ - NEVER guess CSS selectors. Use @e refs from snapshot instead.
101
+
102
+ NAVIGATION STRATEGY:
103
+ 1. navigate() to the target URL
104
+ 2. snapshot() to see all interactive elements with @e refs
105
+ 3. click("@eN") or fill("@eN", "text") to interact
106
+
107
+ FORM FILLING: Call snapshot() to see all form fields with their @e refs. Use fill() for inputs, click() for submit.
108
+
109
+ ERROR RECOVERY: If an action fails, re-run snapshot() to get fresh @e refs. Never repeat the same failed action.
110
+
111
+ When done, respond with plain text summarizing what you found or accomplished.`
112
+ },
113
+ { role: 'user', content: task }
114
+ ];
115
+
116
+ for (let step = 1; step <= MAX_STEPS; step++) {
117
+ log('info', `═══ Step ${step}/${MAX_STEPS} ═══`);
118
+
119
+ try {
120
+ const reply = await provider.chat(messages, TOOL_DEFINITIONS);
121
+
122
+ if (reply.content) {
123
+ log('agent', reply.content);
124
+ }
125
+
126
+ if (!reply.tool_calls || reply.tool_calls.length === 0) {
127
+ const duration = ((Date.now() - sessionStart) / 1000).toFixed(1);
128
+ log('success', 'Agent completed the task!');
129
+ log('info', `Duration: ${duration}s | Tools: ${totalToolCalls} | Steps: ${step}`);
130
+ return;
131
+ }
132
+
133
+ const assistantMsg = { role: 'assistant', content: reply.content || '' };
134
+ if (reply.tool_calls) assistantMsg.tool_calls = reply.tool_calls;
135
+ messages.push(assistantMsg);
136
+
137
+ for (const call of reply.tool_calls) {
138
+ const name = call.function.name;
139
+ const args = call.function.arguments || {};
140
+
141
+ log('tool', `→ ${name}(${JSON.stringify(args)})`);
142
+ totalToolCalls++;
143
+
144
+ let resultPayload;
145
+ try {
146
+ const result = await executeTool(bridge, name, args);
147
+ resultPayload = { success: true, result };
148
+
149
+ const preview = JSON.stringify(result);
150
+ log('result', preview.length > 200 ? preview.slice(0, 200) + '...' : preview);
151
+ } catch (error) {
152
+ resultPayload = { success: false, error: error.message };
153
+ log('error', `Tool error: ${error.message}`);
154
+ }
155
+
156
+ messages.push({
157
+ role: 'tool',
158
+ tool_call_id: call.id,
159
+ name,
160
+ content: JSON.stringify(resultPayload)
161
+ });
162
+ }
163
+ } catch (error) {
164
+ log('error', `Step ${step} failed: ${error.message}`);
165
+ messages.push({
166
+ role: 'system',
167
+ content: `Error occurred: ${error.message}. Try a different approach.`
168
+ });
169
+ }
170
+
171
+ console.log();
172
+ }
173
+
174
+ const duration = ((Date.now() - sessionStart) / 1000).toFixed(1);
175
+ log('warning', `Hit MAX_STEPS (${MAX_STEPS}) without completion.`);
176
+ log('info', `Duration: ${duration}s | Tools: ${totalToolCalls}`);
177
+ }
178
+
179
+ main().catch(err => {
180
+ log('error', `Agent failed: ${err.message}`);
181
+ process.exit(1);
182
+ });
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * VenomBrowser Interactive Chat Agent — CLI entry point (Kimi-style)
4
+ *
5
+ * Live chat interface with the configured LLM provider + VenomBrowser daemon.
6
+ * Talk to the agent naturally and give it browser automation tasks in real-time.
7
+ *
8
+ * v4.0.0 — Kimi-style architecture:
9
+ * - Uses new bridge-client with Kimi-style protocol
10
+ * - Screenshot returns file path, NOT base64 (fixes cost leak)
11
+ * - Uses snapshot() with @e refs for element targeting
12
+ *
13
+ * Usage:
14
+ * node bin/venombrowser-chat.js
15
+ */
16
+
17
+ require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
18
+ const readline = require('readline');
19
+ const { BrowserBridge } = require('../src/bridge-client');
20
+ const { createProvider } = require('../src/providers');
21
+ const { TOOL_DEFINITIONS, executeTool } = require('../src/tools/browser-tools');
22
+
23
+ const MAX_STEPS = parseInt(process.env.MAX_STEPS || '25', 10);
24
+ const session = `chat-${Date.now()}`;
25
+
26
+ // ─── Logging ─────────────────────────────────────────────────────────────────
27
+ function log(level, message, skipTimestamp = false) {
28
+ const timestamp = new Date().toISOString().slice(11, 19);
29
+ const emoji = {
30
+ info: 'ℹ️', success: '✅', warning: '⚠️', error: '❌',
31
+ tool: '🔧', agent: '🤖', user: '👤', system: '🔔'
32
+ }[level] || 'ℹ️';
33
+
34
+ if (skipTimestamp) {
35
+ console.log(`${emoji} ${message}`);
36
+ } else {
37
+ console.log(`[${timestamp}] ${emoji} ${message}`);
38
+ }
39
+ }
40
+
41
+ // ─── System Prompt (Kimi-style) ─────────────────────────────────────────────
42
+ const SYSTEM_PROMPT = `You are VenomBrowser, a browser automation assistant controlling a real Chrome browser.
43
+
44
+ ## CRITICAL RULES — ALWAYS FOLLOW THESE
45
+ 1. **ALWAYS use snapshot() after navigating** to understand what's on the page. This gives you an accessibility tree with @e refs for all interactive elements.
46
+ 2. **Use click("@eN") and fill("@eN", "text")** to interact with elements. The @e refs from snapshot are MUCH more reliable than CSS selectors.
47
+ 3. **NEVER guess CSS selectors.** The @e refs replace selector guessing entirely.
48
+
49
+ ## WORKFLOW FOR ANY TASK
50
+ 1. navigate() to the target URL
51
+ 2. snapshot() to see all interactive elements with @e refs
52
+ 3. click("@eN") or fill("@eN", "text") to interact
53
+ 4. Repeat steps 2-3 as the page changes
54
+
55
+ ## NAVIGATION STRATEGY
56
+ 1. **Direct URL first**: For searches, use URL params: google.com/search?q=...
57
+ 2. **@e ref click second**: Use click("@eN") after calling snapshot()
58
+ 3. **Text-based click third**: Use click(text: "visible text") as fallback
59
+
60
+ ## FILLING FORMS
61
+ 1. Call snapshot() to see all form fields with their @e refs
62
+ 2. Use fill("@eN", text) for each field
63
+ 3. Use click("@eN") to submit
64
+
65
+ ## EXTRACTING DATA
66
+ - Use evaluate() for structured data extraction
67
+ - Use snapshot() to understand page structure
68
+
69
+ ## SCROLLING
70
+ - Use evaluate("window.scrollBy(0, 800)") to scroll
71
+ - Then snapshot() again to discover newly visible elements
72
+
73
+ ## TAB MANAGEMENT
74
+ - Use list_tabs() to see all open tabs
75
+ - Use close_tab() / navigate(url, { newTab: true })
76
+
77
+ ## ERROR RECOVERY
78
+ - If an element is stale, re-run snapshot() to get fresh @e refs
79
+ - If a click doesn't work, try a different @e ref
80
+ - Never repeat the same failed action
81
+
82
+ ## RESPONSE STYLE
83
+ - Be concise. Summarize what you found/accomplished.
84
+ - When done, give the final answer directly.`;
85
+
86
+ // ─── Main Chat Interface ─────────────────────────────────────────────────────
87
+ class ChatAgent {
88
+ constructor() {
89
+ try {
90
+ this.provider = createProvider();
91
+ } catch (err) {
92
+ console.error(`❌ Provider error: ${err.message}`);
93
+ process.exit(1);
94
+ }
95
+
96
+ this.bridge = new BrowserBridge({ session });
97
+
98
+ this.conversationHistory = [
99
+ { role: 'system', content: SYSTEM_PROMPT }
100
+ ];
101
+
102
+ this.rl = readline.createInterface({
103
+ input: process.stdin,
104
+ output: process.stdout,
105
+ prompt: '\n👤 You: '
106
+ });
107
+ }
108
+
109
+ async start() {
110
+ console.log('🚀 VenomBrowser Interactive Chat Agent v4.0.0 (Kimi-style)');
111
+ console.log('═'.repeat(60));
112
+ log('info', `Provider: ${this.provider}`, true);
113
+ log('info', `Session: ${session}`, true);
114
+ log('info', `Max steps per task: ${MAX_STEPS}`, true);
115
+ console.log('═'.repeat(60));
116
+
117
+ try {
118
+ await this._systemCheck();
119
+ console.log();
120
+
121
+ log('agent', 'Hello! I can help you automate browser tasks.', true);
122
+ log('agent', 'Just tell me what you want me to do with the browser!', true);
123
+ console.log();
124
+ console.log('Examples:');
125
+ console.log(' • "go to google and search for python tutorials"');
126
+ console.log(' • "take a screenshot of this page"');
127
+ console.log(' • "find the top news stories on hacker news"');
128
+ console.log();
129
+ console.log('Commands:');
130
+ console.log(' • "clear" - clear conversation history');
131
+ console.log(' • "status" - show system status');
132
+ console.log(' • "quit" - exit the chat');
133
+
134
+ this.rl.prompt();
135
+
136
+ this.rl.on('line', async (input) => {
137
+ const message = input.trim();
138
+
139
+ if (message === '') {
140
+ this.rl.prompt();
141
+ return;
142
+ }
143
+
144
+ const cmd = message.toLowerCase();
145
+ if (cmd === 'quit' || cmd === 'exit') {
146
+ log('system', 'Goodbye!', true);
147
+ this.rl.close();
148
+ process.exit(0);
149
+ }
150
+
151
+ if (cmd === 'clear') {
152
+ this.conversationHistory = [this.conversationHistory[0]];
153
+ log('system', 'Conversation history cleared', true);
154
+ this.rl.prompt();
155
+ return;
156
+ }
157
+
158
+ if (cmd === 'status') {
159
+ try {
160
+ const status = await this.bridge.getStatus();
161
+ log('info', `Bridge connected: ${status.connected}`, true);
162
+ log('info', `Commands executed: ${status.commands_executed}`, true);
163
+ log('info', `Uptime: ${status.uptime_seconds}s`, true);
164
+ log('info', `Provider: ${this.provider}`, true);
165
+ } catch (error) {
166
+ log('error', `Status check failed: ${error.message}`, true);
167
+ }
168
+ this.rl.prompt();
169
+ return;
170
+ }
171
+
172
+ console.log();
173
+ log('agent', 'Working on it...', true);
174
+
175
+ try {
176
+ const result = await this._processUserMessage(message);
177
+
178
+ if (!result.printedInline) {
179
+ console.log();
180
+ log('agent', result.response, true);
181
+ }
182
+
183
+ if (result.toolCalls > 0) {
184
+ log('info', `Done — ${result.steps} step(s), ${result.toolCalls} tool call(s).`, true);
185
+ }
186
+ } catch (error) {
187
+ console.log();
188
+ log('error', `Sorry, I encountered an error: ${error.message}`, true);
189
+ }
190
+
191
+ this.rl.prompt();
192
+ });
193
+
194
+ this.rl.on('close', () => {
195
+ log('system', 'Goodbye!', true);
196
+ process.exit(0);
197
+ });
198
+
199
+ } catch (err) {
200
+ log('error', `Startup failed: ${err.message}`);
201
+ process.exit(1);
202
+ }
203
+ }
204
+
205
+ async _systemCheck() {
206
+ log('system', 'Checking system status...');
207
+
208
+ await this.provider.healthCheck();
209
+ log('success', `LLM provider ready: ${this.provider}`);
210
+
211
+ const connected = await this.bridge.isConnected();
212
+ if (!connected) throw new Error('VenomBrowser daemon not connected. Is the daemon running?');
213
+
214
+ log('success', 'VenomBrowser daemon connected');
215
+ }
216
+
217
+ async _processUserMessage(userInput) {
218
+ this.conversationHistory.push({ role: 'user', content: userInput });
219
+
220
+ let stepCount = 0;
221
+ let toolCallCount = 0;
222
+ let lastContent = null;
223
+ let printedInline = false;
224
+
225
+ while (stepCount < MAX_STEPS) {
226
+ stepCount++;
227
+
228
+ const reply = await this.provider.chat(this.conversationHistory, TOOL_DEFINITIONS);
229
+
230
+ const assistantMsg = { role: 'assistant', content: reply.content || '' };
231
+ if (reply.tool_calls) assistantMsg.tool_calls = reply.tool_calls;
232
+ this.conversationHistory.push(assistantMsg);
233
+
234
+ lastContent = reply.content;
235
+
236
+ if (!reply.tool_calls || reply.tool_calls.length === 0) {
237
+ if (reply.content) {
238
+ console.log();
239
+ log('agent', reply.content, true);
240
+ printedInline = true;
241
+ }
242
+ return {
243
+ response: reply.content || 'Task completed.',
244
+ completed: true,
245
+ steps: stepCount,
246
+ toolCalls: toolCallCount,
247
+ printedInline
248
+ };
249
+ }
250
+
251
+ if (reply.content) {
252
+ log('agent', reply.content, true);
253
+ printedInline = true;
254
+ }
255
+
256
+ for (const call of reply.tool_calls) {
257
+ toolCallCount++;
258
+ const name = call.function.name;
259
+ const args = call.function.arguments || {};
260
+
261
+ log('tool', `→ ${name}(${JSON.stringify(args)})`, true);
262
+
263
+ let resultPayload;
264
+ try {
265
+ const result = await executeTool(this.bridge, name, args);
266
+ resultPayload = { success: true, result };
267
+
268
+ if (name === 'navigate') log('success', `✓ Loaded: ${result.title || args.url}`, true);
269
+ else if (name === 'snapshot') log('success', `✓ Got ${result.tree?.children?.length || 0} elements`, true);
270
+ else if (name === 'click') log('success', '✓ Clicked successfully', true);
271
+ else if (name === 'fill') log('success', '✓ Filled successfully', true);
272
+ else if (name === 'screenshot') log('success', `✓ Screenshot saved: ${result.path}`, true);
273
+ else log('success', `✓ ${name} completed`, true);
274
+
275
+ } catch (error) {
276
+ resultPayload = { success: false, error: error.message };
277
+ log('error', `Failed: ${error.message}`, true);
278
+ }
279
+
280
+ this.conversationHistory.push({
281
+ role: 'tool',
282
+ tool_call_id: call.id,
283
+ name,
284
+ content: JSON.stringify(resultPayload)
285
+ });
286
+ }
287
+ }
288
+
289
+ return {
290
+ response: `Hit the step limit (${MAX_STEPS}). The task might need to be broken into smaller parts.`,
291
+ completed: false,
292
+ steps: stepCount,
293
+ toolCalls: toolCallCount,
294
+ printedInline: false
295
+ };
296
+ }
297
+ }
298
+
299
+ const chatAgent = new ChatAgent();
300
+ chatAgent.start().catch(console.error);
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * VenomBrowser CLI — Unified command dispatcher
4
+ *
5
+ * Usage:
6
+ * venombrowser setup — run the interactive setup wizard
7
+ * venombrowser start — start the bridge daemon
8
+ * venombrowser status — check daemon + extension status
9
+ * venombrowser connect — auto-register with known agents
10
+ * venombrowser agent "<task>" — run a one-shot browser task
11
+ * venombrowser uninstall — remove all MCP entries + stop daemon
12
+ * venombrowser chat — start interactive chat agent
13
+ * venombrowser help — show this help
14
+ */
15
+
16
+ const path = require('path');
17
+ const { fork, execSync } = require('child_process');
18
+ const http = require('http');
19
+
20
+ const BIN_DIR = __dirname;
21
+ const ENV_PATH = path.join(BIN_DIR, '..', '.env');
22
+
23
+ // Load .env for BRIDGE_PORT
24
+ require('dotenv').config({ path: ENV_PATH });
25
+
26
+ const BRIDGE_PORT = parseInt(process.env.BRIDGE_PORT || '10086', 10);
27
+
28
+ // ─── Subcommand handlers ────────────────────────────────────────────────────
29
+
30
+ function showHelp() {
31
+ console.log(`
32
+ 🕷️ VenomBrowser CLI — Browser Automation Bridge
33
+
34
+ Usage: venombrowser <command> [options]
35
+
36
+ Commands:
37
+ setup Run the interactive setup wizard
38
+ start Start the bridge daemon
39
+ status Check daemon & extension connection status
40
+ connect Auto-register MCP server with known agents
41
+ uninstall Remove MCP entries, stop daemon (does NOT npm uninstall)
42
+ agent "<task>" Run a one-shot browser automation task
43
+ chat Start the interactive chat agent
44
+ help Show this help message
45
+
46
+ Examples:
47
+ venombrowser setup
48
+ venombrowser start
49
+ venombrowser status
50
+ venombrowser agent "search google for weather in tokyo"
51
+ venombrowser connect
52
+ `);
53
+ }
54
+
55
+ function runSetup() {
56
+ const child = fork(path.join(BIN_DIR, 'venombrowser-setup.js'), process.argv.slice(3), {
57
+ stdio: 'inherit'
58
+ });
59
+ child.on('exit', (code) => process.exit(code || 0));
60
+ }
61
+
62
+ function runStart() {
63
+ // Load dotenv and require bridge-server directly (same as venombrowser-server.js)
64
+ require('dotenv').config({ path: ENV_PATH });
65
+ require('../src/bridge-server');
66
+ }
67
+
68
+ function runStatus() {
69
+ const url = `http://127.0.0.1:${BRIDGE_PORT}/health`;
70
+
71
+ const req = http.get(url, { timeout: 3000 }, (res) => {
72
+ let body = '';
73
+ res.on('data', (chunk) => { body += chunk; });
74
+ res.on('end', () => {
75
+ try {
76
+ const data = JSON.parse(body);
77
+ const serverUp = data.server === 'ok';
78
+ const extConnected = data.extension === 'connected';
79
+
80
+ console.log('🕷️ VenomBrowser Status');
81
+ console.log('═'.repeat(40));
82
+ console.log(` Server: ${serverUp ? '✅ Running' : '❌ Not running'} (port ${data.port || BRIDGE_PORT})`);
83
+ console.log(` Extension: ${extConnected ? '✅ Connected' : '❌ Disconnected'}`);
84
+ console.log('═'.repeat(40));
85
+
86
+ if (serverUp && extConnected) {
87
+ console.log(' Status: Connected');
88
+ process.exit(0);
89
+ } else if (serverUp) {
90
+ console.log(' Status: Disconnected (extension not connected)');
91
+ process.exit(1);
92
+ } else {
93
+ console.log(' Status: Disconnected');
94
+ process.exit(1);
95
+ }
96
+ } catch {
97
+ console.error('❌ Invalid response from daemon');
98
+ process.exit(1);
99
+ }
100
+ });
101
+ });
102
+
103
+ req.on('error', () => {
104
+ console.log('🕷️ VenomBrowser Status');
105
+ console.log('═'.repeat(40));
106
+ console.log(` Server: ❌ Not running (port ${BRIDGE_PORT})`);
107
+ console.log(` Extension: ❌ Disconnected`);
108
+ console.log('═'.repeat(40));
109
+ console.log(' Status: Disconnected');
110
+ console.log('');
111
+ console.log(' Start the daemon with: venombrowser start');
112
+ process.exit(1);
113
+ });
114
+
115
+ req.on('timeout', () => {
116
+ req.destroy();
117
+ console.log('🕷️ VenomBrowser Status');
118
+ console.log('═'.repeat(40));
119
+ console.log(` Server: ❌ Not responding (port ${BRIDGE_PORT})`);
120
+ console.log(` Extension: ❌ Disconnected`);
121
+ console.log('═'.repeat(40));
122
+ console.log(' Status: Disconnected');
123
+ process.exit(1);
124
+ });
125
+ }
126
+
127
+ function runConnect() {
128
+ const { connect } = require('../src/connect');
129
+ connect();
130
+ }
131
+
132
+ function runUninstall() {
133
+ const { uninstall } = require('../src/uninstall');
134
+ uninstall(process.argv.slice(3));
135
+ }
136
+
137
+ function runAgent() {
138
+ const args = process.argv.slice(3);
139
+ if (args.length === 0) {
140
+ console.error('❌ Usage: venombrowser agent "<task description>"');
141
+ process.exit(1);
142
+ }
143
+ const child = fork(path.join(BIN_DIR, 'venombrowser-agent.js'), args, {
144
+ stdio: 'inherit'
145
+ });
146
+ child.on('exit', (code) => process.exit(code || 0));
147
+ }
148
+
149
+ function runChat() {
150
+ const child = fork(path.join(BIN_DIR, 'venombrowser-chat.js'), process.argv.slice(3), {
151
+ stdio: 'inherit'
152
+ });
153
+ child.on('exit', (code) => process.exit(code || 0));
154
+ }
155
+
156
+ // ─── Dispatch ───────────────────────────────────────────────────────────────
157
+
158
+ const command = process.argv[2];
159
+
160
+ switch (command) {
161
+ case 'setup':
162
+ runSetup();
163
+ break;
164
+ case 'start':
165
+ runStart();
166
+ break;
167
+ case 'status':
168
+ runStatus();
169
+ break;
170
+ case 'connect':
171
+ runConnect();
172
+ break;
173
+ case 'uninstall':
174
+ runUninstall();
175
+ break;
176
+ case 'agent':
177
+ runAgent();
178
+ break;
179
+ case 'chat':
180
+ runChat();
181
+ break;
182
+ case 'help':
183
+ case '--help':
184
+ case '-h':
185
+ showHelp();
186
+ break;
187
+ case undefined:
188
+ showHelp();
189
+ break;
190
+ default:
191
+ console.error(`❌ Unknown command: ${command}`);
192
+ console.error(' Run "venombrowser help" for available commands.');
193
+ process.exit(1);
194
+ }