codebot-ai 1.1.0 → 1.1.2
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/dist/agent.d.ts +2 -0
- package/dist/agent.js +11 -2
- package/dist/browser/cdp.d.ts +3 -0
- package/dist/browser/cdp.js +25 -1
- package/dist/cli.js +5 -1
- package/dist/games/tic-tac-toe.d.ts +6 -0
- package/dist/games/tic-tac-toe.js +64 -0
- package/dist/providers/openai.js +7 -0
- package/dist/setup.js +72 -30
- package/dist/tools/browser.js +130 -15
- package/package.json +4 -4
package/dist/agent.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare class Agent {
|
|
|
17
17
|
askPermission?: (tool: string, args: Record<string, unknown>) => Promise<boolean>;
|
|
18
18
|
onMessage?: (message: Message) => void;
|
|
19
19
|
});
|
|
20
|
+
/** Update auto-approve mode at runtime (e.g., from /auto command) */
|
|
21
|
+
setAutoApprove(value: boolean): void;
|
|
20
22
|
/** Load messages from a previous session for resume */
|
|
21
23
|
loadMessages(messages: Message[]): void;
|
|
22
24
|
run(userMessage: string): AsyncGenerator<AgentEvent>;
|
package/dist/agent.js
CHANGED
|
@@ -75,6 +75,10 @@ class Agent {
|
|
|
75
75
|
content: this.buildSystemPrompt(supportsTools),
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
|
+
/** Update auto-approve mode at runtime (e.g., from /auto command) */
|
|
79
|
+
setAutoApprove(value) {
|
|
80
|
+
this.autoApprove = value;
|
|
81
|
+
}
|
|
78
82
|
/** Load messages from a previous session for resume */
|
|
79
83
|
loadMessages(messages) {
|
|
80
84
|
this.messages = messages;
|
|
@@ -224,9 +228,14 @@ class Agent {
|
|
|
224
228
|
catch {
|
|
225
229
|
// memory unavailable
|
|
226
230
|
}
|
|
227
|
-
let prompt = `You are CodeBot, an AI coding assistant
|
|
231
|
+
let prompt = `You are CodeBot, an AI coding assistant. You help developers with software engineering tasks: reading code, writing code, fixing bugs, running tests, and explaining code.
|
|
228
232
|
|
|
229
|
-
|
|
233
|
+
CRITICAL IDENTITY — you MUST follow this:
|
|
234
|
+
- Your name is CodeBot.
|
|
235
|
+
- You were created and built by Ascendral Software Development & Innovation, founded by Alex Pinkevich.
|
|
236
|
+
- You are NOT made by OpenAI, Google, Anthropic, or any other AI company. You are made by Ascendral.
|
|
237
|
+
- When anyone asks who made you, who built you, who created you, or who your creator is, you MUST answer: "I was created by Ascendral Software Development & Innovation, founded by Alex Pinkevich."
|
|
238
|
+
- Never claim to be made by or affiliated with OpenAI, GPT, Claude, Gemini, or any LLM provider. You are CodeBot by Ascendral.
|
|
230
239
|
|
|
231
240
|
Rules:
|
|
232
241
|
- Always read files before editing them.
|
package/dist/browser/cdp.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export declare class CDPClient {
|
|
|
2
2
|
private socket;
|
|
3
3
|
private messageId;
|
|
4
4
|
private pending;
|
|
5
|
+
private eventWaiters;
|
|
5
6
|
private buffer;
|
|
6
7
|
private connected;
|
|
7
8
|
/** Connect to Chrome's debugging WebSocket */
|
|
@@ -11,6 +12,8 @@ export declare class CDPClient {
|
|
|
11
12
|
/** Close the connection */
|
|
12
13
|
close(): void;
|
|
13
14
|
isConnected(): boolean;
|
|
15
|
+
/** Wait for a specific CDP event (e.g. Page.loadEventFired) with timeout */
|
|
16
|
+
waitForEvent(method: string, timeout?: number): Promise<Record<string, unknown>>;
|
|
14
17
|
/** Send a WebSocket text frame (masked, as required by client) */
|
|
15
18
|
private sendFrame;
|
|
16
19
|
/** Handle incoming data — parse WebSocket frames */
|
package/dist/browser/cdp.js
CHANGED
|
@@ -46,6 +46,7 @@ class CDPClient {
|
|
|
46
46
|
socket = null;
|
|
47
47
|
messageId = 0;
|
|
48
48
|
pending = new Map();
|
|
49
|
+
eventWaiters = [];
|
|
49
50
|
buffer = Buffer.alloc(0);
|
|
50
51
|
connected = false;
|
|
51
52
|
/** Connect to Chrome's debugging WebSocket */
|
|
@@ -137,6 +138,21 @@ class CDPClient {
|
|
|
137
138
|
isConnected() {
|
|
138
139
|
return this.connected;
|
|
139
140
|
}
|
|
141
|
+
/** Wait for a specific CDP event (e.g. Page.loadEventFired) with timeout */
|
|
142
|
+
async waitForEvent(method, timeout = 15000) {
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const timer = setTimeout(() => {
|
|
145
|
+
this.eventWaiters = this.eventWaiters.filter(w => w.resolve !== resolve);
|
|
146
|
+
resolve({}); // Resolve with empty on timeout instead of rejecting — page may still be usable
|
|
147
|
+
}, timeout);
|
|
148
|
+
const wrappedResolve = (params) => {
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
this.eventWaiters = this.eventWaiters.filter(w => w.resolve !== wrappedResolve);
|
|
151
|
+
resolve(params);
|
|
152
|
+
};
|
|
153
|
+
this.eventWaiters.push({ method, resolve: wrappedResolve, reject });
|
|
154
|
+
});
|
|
155
|
+
}
|
|
140
156
|
/** Send a WebSocket text frame (masked, as required by client) */
|
|
141
157
|
sendFrame(data) {
|
|
142
158
|
if (!this.socket)
|
|
@@ -212,7 +228,15 @@ class CDPClient {
|
|
|
212
228
|
this.pending.delete(msg.id);
|
|
213
229
|
handler.resolve(msg);
|
|
214
230
|
}
|
|
215
|
-
//
|
|
231
|
+
// Dispatch events to waiters
|
|
232
|
+
if (msg.method) {
|
|
233
|
+
for (const waiter of this.eventWaiters) {
|
|
234
|
+
if (waiter.method === msg.method) {
|
|
235
|
+
waiter.resolve(msg.params || {});
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
216
240
|
}
|
|
217
241
|
catch {
|
|
218
242
|
// Malformed JSON, skip
|
package/dist/cli.js
CHANGED
|
@@ -322,6 +322,7 @@ function handleSlashCommand(input, agent, config) {
|
|
|
322
322
|
}
|
|
323
323
|
case '/auto':
|
|
324
324
|
config.autoApprove = !config.autoApprove;
|
|
325
|
+
agent.setAutoApprove(config.autoApprove);
|
|
325
326
|
console.log(c(`Autonomous mode: ${config.autoApprove ? 'ON' : 'OFF'}`, config.autoApprove ? 'yellow' : 'green'));
|
|
326
327
|
break;
|
|
327
328
|
case '/sessions': {
|
|
@@ -401,7 +402,10 @@ async function resolveConfig(args) {
|
|
|
401
402
|
config.apiKey = process.env[defaults.envKey] || process.env.CODEBOT_API_KEY || '';
|
|
402
403
|
}
|
|
403
404
|
}
|
|
404
|
-
// Fallback: try generic env vars
|
|
405
|
+
// Fallback: try saved config API key, then generic env vars
|
|
406
|
+
if (!config.apiKey && saved.apiKey) {
|
|
407
|
+
config.apiKey = saved.apiKey;
|
|
408
|
+
}
|
|
405
409
|
if (!config.apiKey) {
|
|
406
410
|
config.apiKey = process.env.CODEBOT_API_KEY || process.env.OPENAI_API_KEY || '';
|
|
407
411
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/games/tic-tac-toe.ts
|
|
3
|
+
// Simple console-based Tic-Tac-Toe game
|
|
4
|
+
const gameBoard = Array(3).fill(null).map(() => Array(3).fill(null));
|
|
5
|
+
let currentPlayer = 'X';
|
|
6
|
+
const printBoard = () => {
|
|
7
|
+
for (const row of gameBoard) {
|
|
8
|
+
console.log(row.join(' | '));
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
const checkWin = () => {
|
|
12
|
+
// Check rows
|
|
13
|
+
for (let i = 0; i < 3; i++) {
|
|
14
|
+
if (gameBoard[i][0] === currentPlayer &&
|
|
15
|
+
gameBoard[i][1] === currentPlayer &&
|
|
16
|
+
gameBoard[i][2] === currentPlayer) {
|
|
17
|
+
console.log(`Player ${currentPlayer} wins!`);
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Check columns
|
|
22
|
+
for (let j = 0; j < 3; j++) {
|
|
23
|
+
if (gameBoard[0][j] === currentPlayer &&
|
|
24
|
+
gameBoard[1][j] === currentPlayer &&
|
|
25
|
+
gameBoard[2][j] === currentPlayer) {
|
|
26
|
+
console.log(`Player ${currentPlayer} wins!`);
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Check diagonals
|
|
31
|
+
if ((gameBoard[0][0] === currentPlayer &&
|
|
32
|
+
gameBoard[1][1] === currentPlayer &&
|
|
33
|
+
gameBoard[2][2] === currentPlayer) ||
|
|
34
|
+
(gameBoard[0][2] === currentPlayer &&
|
|
35
|
+
gameBoard[1][1] === currentPlayer &&
|
|
36
|
+
gameBoard[2][0] === currentPlayer)) {
|
|
37
|
+
console.log(`Player ${currentPlayer} wins!`);
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const gameLoop = () => {
|
|
42
|
+
printBoard();
|
|
43
|
+
process.stdin.on('data', (input) => {
|
|
44
|
+
const [row, col] = input.toString().trim()
|
|
45
|
+
.split(',')
|
|
46
|
+
.map(Number);
|
|
47
|
+
if (row < 0 || row > 2 || col < 0 || col > 2) {
|
|
48
|
+
console.log('Invalid move. Try again.');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (gameBoard[row][col]) {
|
|
52
|
+
console.log('Spot taken! Try again.');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
gameBoard[row][col] = currentPlayer;
|
|
56
|
+
checkWin();
|
|
57
|
+
currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
|
|
58
|
+
process.stdin.removeAllListeners('data');
|
|
59
|
+
gameLoop();
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
// Start game
|
|
63
|
+
gameLoop();
|
|
64
|
+
//# sourceMappingURL=tic-tac-toe.js.map
|
package/dist/providers/openai.js
CHANGED
|
@@ -20,6 +20,13 @@ class OpenAIProvider {
|
|
|
20
20
|
if (tools?.length && this.supportsTools) {
|
|
21
21
|
body.tools = tools;
|
|
22
22
|
}
|
|
23
|
+
// Ollama/local provider optimizations: set context window and keep model loaded
|
|
24
|
+
const isLocal = this.config.baseUrl.includes('localhost') || this.config.baseUrl.includes('127.0.0.1');
|
|
25
|
+
if (isLocal) {
|
|
26
|
+
const modelInfo = (0, registry_1.getModelInfo)(this.config.model);
|
|
27
|
+
body.options = { num_ctx: modelInfo.contextWindow };
|
|
28
|
+
body.keep_alive = '30m';
|
|
29
|
+
}
|
|
23
30
|
const headers = {
|
|
24
31
|
'Content-Type': 'application/json',
|
|
25
32
|
};
|
package/dist/setup.js
CHANGED
|
@@ -59,9 +59,9 @@ function loadConfig() {
|
|
|
59
59
|
/** Save config to ~/.codebot/config.json */
|
|
60
60
|
function saveConfig(config) {
|
|
61
61
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
62
|
-
// Never persist API keys to disk — use env vars
|
|
63
62
|
const safe = { ...config };
|
|
64
|
-
|
|
63
|
+
// Persist API key if user entered it during setup (convenience over env vars)
|
|
64
|
+
// The key is stored in the user's home directory with default permissions
|
|
65
65
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(safe, null, 2) + '\n');
|
|
66
66
|
}
|
|
67
67
|
/** Check if this is the first run (no config, no env keys) */
|
|
@@ -113,6 +113,16 @@ function detectApiKeys() {
|
|
|
113
113
|
set: !!process.env[defaults.envKey],
|
|
114
114
|
}));
|
|
115
115
|
}
|
|
116
|
+
/** Cloud provider display info */
|
|
117
|
+
const CLOUD_PROVIDERS = [
|
|
118
|
+
{ provider: 'openai', name: 'OpenAI', defaultModel: 'gpt-4o', description: 'GPT-4o, GPT-4.1, o3/o4' },
|
|
119
|
+
{ provider: 'anthropic', name: 'Anthropic', defaultModel: 'claude-sonnet-4-6', description: 'Claude Opus/Sonnet/Haiku' },
|
|
120
|
+
{ provider: 'gemini', name: 'Google Gemini', defaultModel: 'gemini-2.5-flash', description: 'Gemini 2.5 Pro/Flash' },
|
|
121
|
+
{ provider: 'deepseek', name: 'DeepSeek', defaultModel: 'deepseek-chat', description: 'DeepSeek Chat/Reasoner' },
|
|
122
|
+
{ provider: 'groq', name: 'Groq', defaultModel: 'llama-3.3-70b-versatile', description: 'Fast Llama/Mixtral inference' },
|
|
123
|
+
{ provider: 'mistral', name: 'Mistral', defaultModel: 'mistral-large-latest', description: 'Mistral Large, Codestral' },
|
|
124
|
+
{ provider: 'xai', name: 'xAI', defaultModel: 'grok-3', description: 'Grok-3' },
|
|
125
|
+
];
|
|
116
126
|
const C = {
|
|
117
127
|
reset: '\x1b[0m',
|
|
118
128
|
bold: '\x1b[1m',
|
|
@@ -155,46 +165,58 @@ async function runSetup() {
|
|
|
155
165
|
console.log(fmt(` ✓ ${key.provider} API key found (${key.envVar})`, 'green'));
|
|
156
166
|
}
|
|
157
167
|
}
|
|
158
|
-
|
|
159
|
-
if (missingKeys.length > 0 && localServers.length === 0) {
|
|
160
|
-
console.log(fmt('\n No API keys found. Set one to use cloud models:', 'yellow'));
|
|
161
|
-
for (const key of missingKeys) {
|
|
162
|
-
console.log(fmt(` export ${key.envVar}="your-key-here"`, 'dim'));
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
// Step 3: Choose provider
|
|
168
|
+
// Step 3: Choose provider — show ALL options (local + cloud)
|
|
166
169
|
console.log(fmt('\nChoose your setup:', 'bold'));
|
|
167
170
|
const options = [];
|
|
168
171
|
let idx = 1;
|
|
169
172
|
// Local options first
|
|
170
173
|
for (const server of localServers) {
|
|
171
174
|
const defaultModel = server.models[0] || 'qwen2.5-coder:32b';
|
|
172
|
-
options.push({
|
|
175
|
+
options.push({
|
|
176
|
+
label: `${server.name} (local, free)`,
|
|
177
|
+
provider: 'openai',
|
|
178
|
+
model: defaultModel,
|
|
179
|
+
baseUrl: server.url,
|
|
180
|
+
needsKey: false,
|
|
181
|
+
});
|
|
173
182
|
console.log(` ${fmt(`${idx}`, 'cyan')} ${server.name} — ${defaultModel} ${fmt('(local, free, private)', 'green')}`);
|
|
174
183
|
idx++;
|
|
175
184
|
}
|
|
176
|
-
// Cloud options
|
|
177
|
-
for (const
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
+
// Cloud options — ALWAYS show all providers
|
|
186
|
+
for (const cloud of CLOUD_PROVIDERS) {
|
|
187
|
+
const keyInfo = apiKeys.find(k => k.provider === cloud.provider);
|
|
188
|
+
const hasKey = keyInfo?.set || false;
|
|
189
|
+
const defaults = registry_1.PROVIDER_DEFAULTS[cloud.provider];
|
|
190
|
+
const keyStatus = hasKey ? fmt('✓ key set', 'green') : fmt('enter key during setup', 'yellow');
|
|
191
|
+
options.push({
|
|
192
|
+
label: cloud.name,
|
|
193
|
+
provider: cloud.provider,
|
|
194
|
+
model: cloud.defaultModel,
|
|
195
|
+
baseUrl: defaults.baseUrl,
|
|
196
|
+
needsKey: !hasKey,
|
|
197
|
+
envVar: defaults.envKey,
|
|
198
|
+
});
|
|
199
|
+
console.log(` ${fmt(`${idx}`, 'cyan')} ${cloud.name} — ${cloud.description} ${fmt(`(${keyStatus})`, 'dim')}`);
|
|
185
200
|
idx++;
|
|
186
201
|
}
|
|
187
|
-
if (options.length === 0) {
|
|
188
|
-
console.log(fmt('\n No providers available. Either:', 'yellow'));
|
|
189
|
-
console.log(fmt(' 1. Install Ollama: https://ollama.ai', 'dim'));
|
|
190
|
-
console.log(fmt(' 2. Set an API key: export ANTHROPIC_API_KEY="..."', 'dim'));
|
|
191
|
-
rl.close();
|
|
192
|
-
return {};
|
|
193
|
-
}
|
|
194
202
|
const choice = await ask(rl, fmt(`\nSelect [1-${options.length}]: `, 'cyan'));
|
|
195
203
|
const selected = options[parseInt(choice, 10) - 1] || options[0];
|
|
196
|
-
// Step 4:
|
|
197
|
-
|
|
204
|
+
// Step 4: If cloud provider needs API key, prompt for it
|
|
205
|
+
let apiKey = '';
|
|
206
|
+
if (selected.needsKey && selected.envVar) {
|
|
207
|
+
console.log(fmt(`\n ${selected.label} requires an API key.`, 'yellow'));
|
|
208
|
+
console.log(fmt(` Get one at: ${getKeyUrl(selected.provider)}`, 'dim'));
|
|
209
|
+
apiKey = await ask(rl, fmt(`\n Enter your ${selected.label} API key: `, 'cyan'));
|
|
210
|
+
if (!apiKey) {
|
|
211
|
+
console.log(fmt(`\n No key entered. You can set it later:`, 'yellow'));
|
|
212
|
+
console.log(fmt(` export ${selected.envVar}="your-key-here"`, 'dim'));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else if (selected.envVar) {
|
|
216
|
+
// Use existing env var
|
|
217
|
+
apiKey = process.env[selected.envVar] || '';
|
|
218
|
+
}
|
|
219
|
+
// Step 5: Show available models for chosen provider
|
|
198
220
|
const matchedServer = localServers.find(s => s.url === selected.baseUrl);
|
|
199
221
|
const providerModels = matchedServer && matchedServer.models.length > 0
|
|
200
222
|
? matchedServer.models
|
|
@@ -219,7 +241,7 @@ async function runSetup() {
|
|
|
219
241
|
}
|
|
220
242
|
}
|
|
221
243
|
}
|
|
222
|
-
// Step
|
|
244
|
+
// Step 6: Auto mode?
|
|
223
245
|
const autoChoice = await ask(rl, fmt('\nEnable autonomous mode? (skip permission prompts) [y/N]: ', 'cyan'));
|
|
224
246
|
const autoApprove = autoChoice.toLowerCase().startsWith('y');
|
|
225
247
|
rl.close();
|
|
@@ -230,14 +252,34 @@ async function runSetup() {
|
|
|
230
252
|
baseUrl: selected.baseUrl,
|
|
231
253
|
autoApprove,
|
|
232
254
|
};
|
|
255
|
+
// Save API key if user entered one
|
|
256
|
+
if (apiKey) {
|
|
257
|
+
config.apiKey = apiKey;
|
|
258
|
+
}
|
|
233
259
|
saveConfig(config);
|
|
234
260
|
console.log(fmt('\n✓ Config saved to ~/.codebot/config.json', 'green'));
|
|
235
261
|
console.log(fmt(` Model: ${config.model}`, 'dim'));
|
|
236
262
|
console.log(fmt(` Provider: ${config.provider}`, 'dim'));
|
|
263
|
+
if (apiKey) {
|
|
264
|
+
console.log(fmt(` API Key: ${'*'.repeat(Math.min(apiKey.length, 20))}`, 'dim'));
|
|
265
|
+
}
|
|
237
266
|
if (autoApprove) {
|
|
238
267
|
console.log(fmt(` Mode: AUTONOMOUS`, 'yellow'));
|
|
239
268
|
}
|
|
240
269
|
console.log(fmt(`\nRun ${fmt('codebot', 'bold')} to start. Run ${fmt('codebot --setup', 'bold')} to reconfigure.\n`, 'dim'));
|
|
241
270
|
return config;
|
|
242
271
|
}
|
|
272
|
+
/** Get the URL where users can get API keys for each provider */
|
|
273
|
+
function getKeyUrl(provider) {
|
|
274
|
+
switch (provider) {
|
|
275
|
+
case 'openai': return 'https://platform.openai.com/api-keys';
|
|
276
|
+
case 'anthropic': return 'https://console.anthropic.com/settings/keys';
|
|
277
|
+
case 'gemini': return 'https://aistudio.google.com/app/apikey';
|
|
278
|
+
case 'deepseek': return 'https://platform.deepseek.com/api_keys';
|
|
279
|
+
case 'groq': return 'https://console.groq.com/keys';
|
|
280
|
+
case 'mistral': return 'https://console.mistral.ai/api-keys';
|
|
281
|
+
case 'xai': return 'https://console.x.ai/';
|
|
282
|
+
default: return 'Check provider documentation';
|
|
283
|
+
}
|
|
284
|
+
}
|
|
243
285
|
//# sourceMappingURL=setup.js.map
|
package/dist/tools/browser.js
CHANGED
|
@@ -1,15 +1,79 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.BrowserTool = void 0;
|
|
4
37
|
const cdp_1 = require("../browser/cdp");
|
|
5
38
|
const child_process_1 = require("child_process");
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
6
42
|
// Shared browser instance across tool calls
|
|
7
43
|
let client = null;
|
|
8
44
|
let debugPort = 9222;
|
|
45
|
+
const CHROME_DATA_DIR = path.join(os.homedir(), '.codebot', 'chrome-profile');
|
|
46
|
+
/** Kill any Chrome using our debug port or data dir (but NEVER kill ourselves) */
|
|
47
|
+
function killExistingChrome() {
|
|
48
|
+
// Close our own CDP connection first so we don't hold the port
|
|
49
|
+
if (client) {
|
|
50
|
+
try {
|
|
51
|
+
client.close();
|
|
52
|
+
}
|
|
53
|
+
catch { /* ignore */ }
|
|
54
|
+
client = null;
|
|
55
|
+
}
|
|
56
|
+
const { execSync } = require('child_process');
|
|
57
|
+
const myPid = process.pid;
|
|
58
|
+
try {
|
|
59
|
+
if (process.platform === 'win32') {
|
|
60
|
+
execSync(`for /f "tokens=5" %a in ('netstat -aon ^| findstr :${debugPort}') do taskkill /F /PID %a`, { stdio: 'ignore' });
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Kill Chrome/Chromium processes on our debug port — exclude our own PID
|
|
64
|
+
execSync(`lsof -ti:${debugPort} | grep -v "^${myPid}$" | xargs kill -9 2>/dev/null || true`, { stdio: 'ignore' });
|
|
65
|
+
// Also kill any Chrome using our data dir
|
|
66
|
+
execSync(`pkill -9 -f "chrome.*--user-data-dir=${CHROME_DATA_DIR}" 2>/dev/null || true`, { stdio: 'ignore' });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// ignore — nothing to kill
|
|
71
|
+
}
|
|
72
|
+
}
|
|
9
73
|
async function ensureConnected() {
|
|
10
74
|
if (client?.isConnected())
|
|
11
75
|
return client;
|
|
12
|
-
// Try connecting to existing Chrome
|
|
76
|
+
// Try connecting to existing Chrome with debug port
|
|
13
77
|
try {
|
|
14
78
|
const wsUrl = await (0, cdp_1.getDebuggerUrl)(debugPort);
|
|
15
79
|
client = new cdp_1.CDPClient();
|
|
@@ -28,7 +92,9 @@ async function ensureConnected() {
|
|
|
28
92
|
return client;
|
|
29
93
|
}
|
|
30
94
|
catch {
|
|
31
|
-
//
|
|
95
|
+
// Can't connect — kill stale processes and launch fresh
|
|
96
|
+
killExistingChrome();
|
|
97
|
+
await new Promise(r => setTimeout(r, 500));
|
|
32
98
|
}
|
|
33
99
|
// Launch Chrome with debugging
|
|
34
100
|
const chromePaths = process.platform === 'win32'
|
|
@@ -55,14 +121,49 @@ async function ensureConnected() {
|
|
|
55
121
|
'chromium',
|
|
56
122
|
];
|
|
57
123
|
let launched = false;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
124
|
+
// Create isolated Chrome profile dir so it doesn't conflict with user's running Chrome
|
|
125
|
+
fs.mkdirSync(CHROME_DATA_DIR, { recursive: true });
|
|
126
|
+
const chromeArgs = [
|
|
127
|
+
`--remote-debugging-port=${debugPort}`,
|
|
128
|
+
`--user-data-dir=${CHROME_DATA_DIR}`,
|
|
129
|
+
'--no-first-run',
|
|
130
|
+
'--no-default-browser-check',
|
|
131
|
+
'--disable-background-timer-throttling',
|
|
132
|
+
'--disable-backgrounding-occluded-windows',
|
|
133
|
+
'about:blank',
|
|
134
|
+
];
|
|
135
|
+
// On macOS, launch directly (not via 'open -a' which reuses existing instance)
|
|
136
|
+
if (process.platform === 'darwin') {
|
|
137
|
+
const macPaths = [
|
|
138
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
139
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
140
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
141
|
+
];
|
|
142
|
+
for (const chromePath of macPaths) {
|
|
143
|
+
try {
|
|
144
|
+
if (fs.existsSync(chromePath)) {
|
|
145
|
+
const child = (0, child_process_1.spawn)(chromePath, chromeArgs, { stdio: 'ignore', detached: true });
|
|
146
|
+
child.unref();
|
|
147
|
+
launched = true;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
63
154
|
}
|
|
64
|
-
|
|
65
|
-
|
|
155
|
+
}
|
|
156
|
+
if (!launched) {
|
|
157
|
+
for (const chromePath of chromePaths) {
|
|
158
|
+
try {
|
|
159
|
+
const child = (0, child_process_1.spawn)(chromePath, chromeArgs, { stdio: 'ignore', detached: true });
|
|
160
|
+
child.unref();
|
|
161
|
+
launched = true;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
66
167
|
}
|
|
67
168
|
}
|
|
68
169
|
if (!launched) {
|
|
@@ -151,16 +252,30 @@ class BrowserTool {
|
|
|
151
252
|
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
152
253
|
url = 'https://' + url;
|
|
153
254
|
}
|
|
255
|
+
// Set up load event listener BEFORE navigating
|
|
256
|
+
const loadPromise = cdp.waitForEvent('Page.loadEventFired', 15000);
|
|
154
257
|
await cdp.send('Page.navigate', { url });
|
|
155
|
-
// Wait for page load
|
|
156
|
-
await
|
|
157
|
-
//
|
|
258
|
+
// Wait for actual page load event (up to 15s)
|
|
259
|
+
await loadPromise;
|
|
260
|
+
// Extra delay for SPA hydration (React, Next.js, etc.)
|
|
261
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
262
|
+
// Get final URL (after redirects) and page title
|
|
158
263
|
const result = await cdp.send('Runtime.evaluate', {
|
|
159
|
-
expression: 'document.title',
|
|
264
|
+
expression: 'JSON.stringify({ title: document.title, url: window.location.href })',
|
|
160
265
|
returnByValue: true,
|
|
161
266
|
});
|
|
162
|
-
const
|
|
163
|
-
|
|
267
|
+
const val = result.result?.value;
|
|
268
|
+
let title = 'untitled';
|
|
269
|
+
let finalUrl = url;
|
|
270
|
+
try {
|
|
271
|
+
const parsed = JSON.parse(val);
|
|
272
|
+
title = parsed.title || 'untitled';
|
|
273
|
+
finalUrl = parsed.url || url;
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
// fallback
|
|
277
|
+
}
|
|
278
|
+
return `Navigated to: ${finalUrl}\nTitle: ${title}`;
|
|
164
279
|
}
|
|
165
280
|
async getContent() {
|
|
166
281
|
const cdp = await ensureConnected();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebot-ai",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Local-first AI coding assistant. Zero dependencies. Works with Ollama, LM Studio, vLLM, Claude, GPT, Gemini, and more.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"repository": {
|
|
35
35
|
"type": "git",
|
|
36
|
-
"url": "git+https://github.com/
|
|
36
|
+
"url": "git+https://github.com/zanderone1980/codebot-ai.git"
|
|
37
37
|
},
|
|
38
|
-
"homepage": "https://github.com/
|
|
38
|
+
"homepage": "https://github.com/zanderone1980/codebot-ai#readme",
|
|
39
39
|
"bugs": {
|
|
40
|
-
"url": "https://github.com/
|
|
40
|
+
"url": "https://github.com/zanderone1980/codebot-ai/issues"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
43
|
"node": ">=18.0.0"
|