codebot-ai 1.0.2 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -3
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +20 -2
- package/dist/browser/cdp.d.ts +3 -0
- package/dist/browser/cdp.js +25 -1
- package/dist/cli.js +30 -2
- package/dist/games/tic-tac-toe.d.ts +6 -0
- package/dist/games/tic-tac-toe.js +64 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -1
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +174 -0
- package/dist/plugins.d.ts +17 -0
- package/dist/plugins.js +101 -0
- package/dist/providers/openai.js +7 -0
- package/dist/setup.js +72 -30
- package/dist/tools/batch-edit.d.ts +36 -0
- package/dist/tools/batch-edit.js +122 -0
- package/dist/tools/browser.js +122 -15
- package/dist/tools/edit.d.ts +6 -0
- package/dist/tools/edit.js +117 -2
- package/dist/tools/execute.js +28 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +5 -1
- package/dist/tools/write.d.ts +1 -0
- package/dist/tools/write.js +38 -1
- package/package.json +5 -5
package/dist/plugins.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadPlugins = loadPlugins;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
/**
|
|
40
|
+
* Plugin system for CodeBot.
|
|
41
|
+
*
|
|
42
|
+
* Plugins are .js files in `.codebot/plugins/` (project) or `~/.codebot/plugins/` (global).
|
|
43
|
+
* Each plugin exports a default function or object that implements the Tool interface:
|
|
44
|
+
*
|
|
45
|
+
* module.exports = {
|
|
46
|
+
* name: 'my_tool',
|
|
47
|
+
* description: 'Does something useful',
|
|
48
|
+
* permission: 'prompt',
|
|
49
|
+
* parameters: { type: 'object', properties: { ... }, required: [...] },
|
|
50
|
+
* execute: async (args) => { return 'result'; }
|
|
51
|
+
* };
|
|
52
|
+
*/
|
|
53
|
+
function loadPlugins(projectRoot) {
|
|
54
|
+
const plugins = [];
|
|
55
|
+
const os = require('os');
|
|
56
|
+
const dirs = [
|
|
57
|
+
path.join(os.homedir(), '.codebot', 'plugins'),
|
|
58
|
+
];
|
|
59
|
+
if (projectRoot) {
|
|
60
|
+
dirs.push(path.join(projectRoot, '.codebot', 'plugins'));
|
|
61
|
+
}
|
|
62
|
+
for (const dir of dirs) {
|
|
63
|
+
if (!fs.existsSync(dir))
|
|
64
|
+
continue;
|
|
65
|
+
let entries;
|
|
66
|
+
try {
|
|
67
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (!entry.isFile() || !entry.name.endsWith('.js'))
|
|
74
|
+
continue;
|
|
75
|
+
try {
|
|
76
|
+
const pluginPath = path.join(dir, entry.name);
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
78
|
+
const mod = require(pluginPath);
|
|
79
|
+
const plugin = mod.default || mod;
|
|
80
|
+
if (isValidTool(plugin)) {
|
|
81
|
+
plugins.push(plugin);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
console.error(`Plugin load error (${entry.name}): ${err instanceof Error ? err.message : String(err)}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return plugins;
|
|
90
|
+
}
|
|
91
|
+
function isValidTool(obj) {
|
|
92
|
+
if (!obj || typeof obj !== 'object')
|
|
93
|
+
return false;
|
|
94
|
+
const t = obj;
|
|
95
|
+
return (typeof t.name === 'string' &&
|
|
96
|
+
typeof t.description === 'string' &&
|
|
97
|
+
typeof t.execute === 'function' &&
|
|
98
|
+
typeof t.parameters === 'object' &&
|
|
99
|
+
typeof t.permission === 'string');
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=plugins.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
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Tool } from '../types';
|
|
2
|
+
export declare class BatchEditTool implements Tool {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
permission: Tool['permission'];
|
|
6
|
+
parameters: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
edits: {
|
|
10
|
+
type: string;
|
|
11
|
+
description: string;
|
|
12
|
+
items: {
|
|
13
|
+
type: string;
|
|
14
|
+
properties: {
|
|
15
|
+
path: {
|
|
16
|
+
type: string;
|
|
17
|
+
description: string;
|
|
18
|
+
};
|
|
19
|
+
old_string: {
|
|
20
|
+
type: string;
|
|
21
|
+
description: string;
|
|
22
|
+
};
|
|
23
|
+
new_string: {
|
|
24
|
+
type: string;
|
|
25
|
+
description: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
required: string[];
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
required: string[];
|
|
33
|
+
};
|
|
34
|
+
execute(args: Record<string, unknown>): Promise<string>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=batch-edit.d.ts.map
|
|
@@ -0,0 +1,122 @@
|
|
|
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
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.BatchEditTool = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
class BatchEditTool {
|
|
40
|
+
name = 'batch_edit';
|
|
41
|
+
description = 'Apply multiple find-and-replace edits across one or more files atomically. All edits are validated before any are applied. Useful for renaming, refactoring, and coordinated multi-file changes.';
|
|
42
|
+
permission = 'prompt';
|
|
43
|
+
parameters = {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
edits: {
|
|
47
|
+
type: 'array',
|
|
48
|
+
description: 'Array of edit operations: [{path, old_string, new_string}, ...]',
|
|
49
|
+
items: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
path: { type: 'string', description: 'File path' },
|
|
53
|
+
old_string: { type: 'string', description: 'Exact string to find' },
|
|
54
|
+
new_string: { type: 'string', description: 'Replacement string' },
|
|
55
|
+
},
|
|
56
|
+
required: ['path', 'old_string', 'new_string'],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
required: ['edits'],
|
|
61
|
+
};
|
|
62
|
+
async execute(args) {
|
|
63
|
+
const edits = args.edits;
|
|
64
|
+
if (!edits || !Array.isArray(edits) || edits.length === 0) {
|
|
65
|
+
return 'Error: edits array is required and must not be empty';
|
|
66
|
+
}
|
|
67
|
+
// Phase 1: Validate all edits before applying any
|
|
68
|
+
const errors = [];
|
|
69
|
+
const validated = [];
|
|
70
|
+
// Group edits by file so we can chain them
|
|
71
|
+
const byFile = new Map();
|
|
72
|
+
for (const edit of edits) {
|
|
73
|
+
if (!edit.path || !edit.old_string === undefined) {
|
|
74
|
+
errors.push(`Invalid edit: missing path or old_string`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const filePath = path.resolve(edit.path);
|
|
78
|
+
if (!byFile.has(filePath))
|
|
79
|
+
byFile.set(filePath, []);
|
|
80
|
+
byFile.get(filePath).push(edit);
|
|
81
|
+
}
|
|
82
|
+
for (const [filePath, fileEdits] of byFile) {
|
|
83
|
+
if (!fs.existsSync(filePath)) {
|
|
84
|
+
errors.push(`File not found: ${filePath}`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
let content = fs.readFileSync(filePath, 'utf-8');
|
|
88
|
+
const originalContent = content;
|
|
89
|
+
for (const edit of fileEdits) {
|
|
90
|
+
const oldStr = String(edit.old_string);
|
|
91
|
+
const newStr = String(edit.new_string);
|
|
92
|
+
const count = content.split(oldStr).length - 1;
|
|
93
|
+
if (count === 0) {
|
|
94
|
+
errors.push(`String not found in ${filePath}: "${oldStr.substring(0, 60)}${oldStr.length > 60 ? '...' : ''}"`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (count > 1) {
|
|
98
|
+
errors.push(`String found ${count} times in ${filePath} (must be unique): "${oldStr.substring(0, 60)}${oldStr.length > 60 ? '...' : ''}"`);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
content = content.replace(oldStr, newStr);
|
|
102
|
+
}
|
|
103
|
+
if (content !== originalContent) {
|
|
104
|
+
validated.push({ filePath, content: originalContent, updated: content, edit: fileEdits[0] });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (errors.length > 0) {
|
|
108
|
+
return `Validation failed (no changes made):\n${errors.map(e => ` - ${e}`).join('\n')}`;
|
|
109
|
+
}
|
|
110
|
+
// Phase 2: Apply all edits atomically
|
|
111
|
+
const results = [];
|
|
112
|
+
for (const { filePath, updated } of validated) {
|
|
113
|
+
fs.writeFileSync(filePath, updated, 'utf-8');
|
|
114
|
+
results.push(filePath);
|
|
115
|
+
}
|
|
116
|
+
const fileCount = validated.length;
|
|
117
|
+
const editCount = edits.length;
|
|
118
|
+
return `Applied ${editCount} edit${editCount > 1 ? 's' : ''} across ${fileCount} file${fileCount > 1 ? 's' : ''}:\n${results.map(f => ` ✓ ${f}`).join('\n')}`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.BatchEditTool = BatchEditTool;
|
|
122
|
+
//# sourceMappingURL=batch-edit.js.map
|
package/dist/tools/browser.js
CHANGED
|
@@ -1,15 +1,71 @@
|
|
|
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 */
|
|
47
|
+
function killExistingChrome() {
|
|
48
|
+
const { execSync } = require('child_process');
|
|
49
|
+
try {
|
|
50
|
+
if (process.platform === 'win32') {
|
|
51
|
+
execSync(`for /f "tokens=5" %a in ('netstat -aon ^| findstr :${debugPort}') do taskkill /F /PID %a`, { stdio: 'ignore' });
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Kill any process listening on our debug port
|
|
55
|
+
execSync(`lsof -ti:${debugPort} | xargs kill -9 2>/dev/null || true`, { stdio: 'ignore' });
|
|
56
|
+
// Also kill any Chrome using our data dir
|
|
57
|
+
execSync(`pkill -f "${CHROME_DATA_DIR}" 2>/dev/null || true`, { stdio: 'ignore' });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// ignore — nothing to kill
|
|
62
|
+
}
|
|
63
|
+
// Give OS time to release the port
|
|
64
|
+
}
|
|
9
65
|
async function ensureConnected() {
|
|
10
66
|
if (client?.isConnected())
|
|
11
67
|
return client;
|
|
12
|
-
// Try connecting to existing Chrome
|
|
68
|
+
// Try connecting to existing Chrome with debug port
|
|
13
69
|
try {
|
|
14
70
|
const wsUrl = await (0, cdp_1.getDebuggerUrl)(debugPort);
|
|
15
71
|
client = new cdp_1.CDPClient();
|
|
@@ -28,7 +84,9 @@ async function ensureConnected() {
|
|
|
28
84
|
return client;
|
|
29
85
|
}
|
|
30
86
|
catch {
|
|
31
|
-
//
|
|
87
|
+
// Can't connect — kill stale processes and launch fresh
|
|
88
|
+
killExistingChrome();
|
|
89
|
+
await new Promise(r => setTimeout(r, 500));
|
|
32
90
|
}
|
|
33
91
|
// Launch Chrome with debugging
|
|
34
92
|
const chromePaths = process.platform === 'win32'
|
|
@@ -55,14 +113,49 @@ async function ensureConnected() {
|
|
|
55
113
|
'chromium',
|
|
56
114
|
];
|
|
57
115
|
let launched = false;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
116
|
+
// Create isolated Chrome profile dir so it doesn't conflict with user's running Chrome
|
|
117
|
+
fs.mkdirSync(CHROME_DATA_DIR, { recursive: true });
|
|
118
|
+
const chromeArgs = [
|
|
119
|
+
`--remote-debugging-port=${debugPort}`,
|
|
120
|
+
`--user-data-dir=${CHROME_DATA_DIR}`,
|
|
121
|
+
'--no-first-run',
|
|
122
|
+
'--no-default-browser-check',
|
|
123
|
+
'--disable-background-timer-throttling',
|
|
124
|
+
'--disable-backgrounding-occluded-windows',
|
|
125
|
+
'about:blank',
|
|
126
|
+
];
|
|
127
|
+
// On macOS, launch directly (not via 'open -a' which reuses existing instance)
|
|
128
|
+
if (process.platform === 'darwin') {
|
|
129
|
+
const macPaths = [
|
|
130
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
131
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
132
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
133
|
+
];
|
|
134
|
+
for (const chromePath of macPaths) {
|
|
135
|
+
try {
|
|
136
|
+
if (fs.existsSync(chromePath)) {
|
|
137
|
+
const child = (0, child_process_1.spawn)(chromePath, chromeArgs, { stdio: 'ignore', detached: true });
|
|
138
|
+
child.unref();
|
|
139
|
+
launched = true;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
63
146
|
}
|
|
64
|
-
|
|
65
|
-
|
|
147
|
+
}
|
|
148
|
+
if (!launched) {
|
|
149
|
+
for (const chromePath of chromePaths) {
|
|
150
|
+
try {
|
|
151
|
+
const child = (0, child_process_1.spawn)(chromePath, chromeArgs, { stdio: 'ignore', detached: true });
|
|
152
|
+
child.unref();
|
|
153
|
+
launched = true;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
66
159
|
}
|
|
67
160
|
}
|
|
68
161
|
if (!launched) {
|
|
@@ -151,16 +244,30 @@ class BrowserTool {
|
|
|
151
244
|
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
152
245
|
url = 'https://' + url;
|
|
153
246
|
}
|
|
247
|
+
// Set up load event listener BEFORE navigating
|
|
248
|
+
const loadPromise = cdp.waitForEvent('Page.loadEventFired', 15000);
|
|
154
249
|
await cdp.send('Page.navigate', { url });
|
|
155
|
-
// Wait for page load
|
|
156
|
-
await
|
|
157
|
-
//
|
|
250
|
+
// Wait for actual page load event (up to 15s)
|
|
251
|
+
await loadPromise;
|
|
252
|
+
// Extra delay for SPA hydration (React, Next.js, etc.)
|
|
253
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
254
|
+
// Get final URL (after redirects) and page title
|
|
158
255
|
const result = await cdp.send('Runtime.evaluate', {
|
|
159
|
-
expression: 'document.title',
|
|
256
|
+
expression: 'JSON.stringify({ title: document.title, url: window.location.href })',
|
|
160
257
|
returnByValue: true,
|
|
161
258
|
});
|
|
162
|
-
const
|
|
163
|
-
|
|
259
|
+
const val = result.result?.value;
|
|
260
|
+
let title = 'untitled';
|
|
261
|
+
let finalUrl = url;
|
|
262
|
+
try {
|
|
263
|
+
const parsed = JSON.parse(val);
|
|
264
|
+
title = parsed.title || 'untitled';
|
|
265
|
+
finalUrl = parsed.url || url;
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// fallback
|
|
269
|
+
}
|
|
270
|
+
return `Navigated to: ${finalUrl}\nTitle: ${title}`;
|
|
164
271
|
}
|
|
165
272
|
async getContent() {
|
|
166
273
|
const cdp = await ensureConnected();
|
package/dist/tools/edit.d.ts
CHANGED
|
@@ -22,5 +22,11 @@ export declare class EditFileTool implements Tool {
|
|
|
22
22
|
required: string[];
|
|
23
23
|
};
|
|
24
24
|
execute(args: Record<string, unknown>): Promise<string>;
|
|
25
|
+
private generateDiff;
|
|
26
|
+
/** Save a snapshot for undo */
|
|
27
|
+
private saveSnapshot;
|
|
28
|
+
private loadManifest;
|
|
29
|
+
/** Undo the last edit to a file. Returns result message. */
|
|
30
|
+
static undo(filePath?: string): string;
|
|
25
31
|
}
|
|
26
32
|
//# sourceMappingURL=edit.d.ts.map
|