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 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 created by Ascendral Software Development & Innovation, founded by Alex Pinkevich. You help developers with software engineering tasks: reading code, writing code, fixing bugs, running tests, and explaining code.
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
- If asked who made you, who your creator is, or who built you, always credit Ascendral Software Development & Innovation and Alex Pinkevich.
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.
@@ -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 */
@@ -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
- // Events (no id) are ignored for now
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,6 @@
1
+ declare const gameBoard: any[][];
2
+ declare let currentPlayer: string;
3
+ declare const printBoard: () => void;
4
+ declare const checkWin: () => void;
5
+ declare const gameLoop: () => void;
6
+ //# sourceMappingURL=tic-tac-toe.d.ts.map
@@ -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
@@ -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
- delete safe.apiKey;
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
- const missingKeys = apiKeys.filter(k => !k.set);
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({ label: `${server.name} (local, free)`, provider: 'openai', model: defaultModel, baseUrl: server.url });
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 key of availableKeys) {
178
- const models = Object.entries(registry_1.MODEL_REGISTRY)
179
- .filter(([, info]) => info.provider === key.provider)
180
- .map(([name]) => name);
181
- const defaultModel = models[0] || key.provider;
182
- const defaults = registry_1.PROVIDER_DEFAULTS[key.provider];
183
- options.push({ label: key.provider, provider: key.provider, model: defaultModel, baseUrl: defaults.baseUrl });
184
- console.log(` ${fmt(`${idx}`, 'cyan')} ${key.provider} — ${defaultModel} ${fmt('(cloud)', 'dim')}`);
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: Show available models for chosen provider
197
- // For local servers, use the actual installed models instead of the hardcoded registry
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 5: Auto mode?
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
@@ -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
- // Chrome not running with debugging try to launch it
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
- for (const chromePath of chromePaths) {
59
- try {
60
- (0, child_process_1.execSync)(`"${chromePath}" --remote-debugging-port=${debugPort} --no-first-run --no-default-browser-check about:blank &`, { stdio: 'ignore', timeout: 5000 });
61
- launched = true;
62
- break;
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
- catch {
65
- continue;
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 new Promise(r => setTimeout(r, 2000));
157
- // Get page title
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 title = result.result?.value || 'untitled';
163
- return `Navigated to: ${url}\nTitle: ${title}`;
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.0",
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/AscendralSoftware/codebot-ai.git"
36
+ "url": "git+https://github.com/zanderone1980/codebot-ai.git"
37
37
  },
38
- "homepage": "https://github.com/AscendralSoftware/codebot-ai#readme",
38
+ "homepage": "https://github.com/zanderone1980/codebot-ai#readme",
39
39
  "bugs": {
40
- "url": "https://github.com/AscendralSoftware/codebot-ai/issues"
40
+ "url": "https://github.com/zanderone1980/codebot-ai/issues"
41
41
  },
42
42
  "engines": {
43
43
  "node": ">=18.0.0"