codebot-ai 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/bin/codebot +5 -0
- package/dist/agent.d.ts +31 -0
- package/dist/agent.js +256 -0
- package/dist/banner.d.ts +19 -0
- package/dist/banner.js +148 -0
- package/dist/browser/cdp.d.ts +29 -0
- package/dist/browser/cdp.js +292 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +518 -0
- package/dist/context/manager.d.ts +27 -0
- package/dist/context/manager.js +139 -0
- package/dist/context/repo-map.d.ts +5 -0
- package/dist/context/repo-map.js +100 -0
- package/dist/history.d.ts +27 -0
- package/dist/history.js +146 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +42 -0
- package/dist/memory.d.ts +39 -0
- package/dist/memory.js +168 -0
- package/dist/parser.d.ts +8 -0
- package/dist/parser.js +79 -0
- package/dist/providers/anthropic.d.ts +9 -0
- package/dist/providers/anthropic.js +288 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/providers/index.js +13 -0
- package/dist/providers/openai.d.ts +11 -0
- package/dist/providers/openai.js +173 -0
- package/dist/providers/registry.d.ts +15 -0
- package/dist/providers/registry.js +115 -0
- package/dist/setup.d.ts +17 -0
- package/dist/setup.js +243 -0
- package/dist/tools/browser.d.ts +43 -0
- package/dist/tools/browser.js +329 -0
- package/dist/tools/edit.d.ts +26 -0
- package/dist/tools/edit.js +73 -0
- package/dist/tools/execute.d.ts +26 -0
- package/dist/tools/execute.js +52 -0
- package/dist/tools/glob.d.ts +24 -0
- package/dist/tools/glob.js +102 -0
- package/dist/tools/grep.d.ts +29 -0
- package/dist/tools/grep.js +125 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.js +49 -0
- package/dist/tools/memory.d.ts +36 -0
- package/dist/tools/memory.js +114 -0
- package/dist/tools/read.d.ts +26 -0
- package/dist/tools/read.js +75 -0
- package/dist/tools/think.d.ts +18 -0
- package/dist/tools/think.js +20 -0
- package/dist/tools/web-fetch.d.ts +36 -0
- package/dist/tools/web-fetch.js +83 -0
- package/dist/tools/write.d.ts +22 -0
- package/dist/tools/write.js +65 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.js +3 -0
- package/package.json +57 -0
package/dist/setup.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
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.loadConfig = loadConfig;
|
|
37
|
+
exports.saveConfig = saveConfig;
|
|
38
|
+
exports.isFirstRun = isFirstRun;
|
|
39
|
+
exports.runSetup = runSetup;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const readline = __importStar(require("readline"));
|
|
44
|
+
const registry_1 = require("./providers/registry");
|
|
45
|
+
const CONFIG_DIR = path.join(os.homedir(), '.codebot');
|
|
46
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
47
|
+
/** Load saved config from ~/.codebot/config.json */
|
|
48
|
+
function loadConfig() {
|
|
49
|
+
try {
|
|
50
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
51
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// corrupt config, ignore
|
|
56
|
+
}
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
/** Save config to ~/.codebot/config.json */
|
|
60
|
+
function saveConfig(config) {
|
|
61
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
62
|
+
// Never persist API keys to disk — use env vars
|
|
63
|
+
const safe = { ...config };
|
|
64
|
+
delete safe.apiKey;
|
|
65
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(safe, null, 2) + '\n');
|
|
66
|
+
}
|
|
67
|
+
/** Check if this is the first run (no config, no env keys) */
|
|
68
|
+
function isFirstRun() {
|
|
69
|
+
if (fs.existsSync(CONFIG_FILE))
|
|
70
|
+
return false;
|
|
71
|
+
// Check if any provider API keys are set
|
|
72
|
+
const envKeys = [
|
|
73
|
+
'ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'GEMINI_API_KEY',
|
|
74
|
+
'DEEPSEEK_API_KEY', 'GROQ_API_KEY', 'MISTRAL_API_KEY',
|
|
75
|
+
'XAI_API_KEY', 'CODEBOT_API_KEY',
|
|
76
|
+
];
|
|
77
|
+
for (const key of envKeys) {
|
|
78
|
+
if (process.env[key])
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
/** Detect what local LLM servers are running */
|
|
84
|
+
async function detectLocalServers() {
|
|
85
|
+
const servers = [];
|
|
86
|
+
const candidates = [
|
|
87
|
+
{ url: 'http://localhost:11434', name: 'Ollama' },
|
|
88
|
+
{ url: 'http://localhost:1234', name: 'LM Studio' },
|
|
89
|
+
{ url: 'http://localhost:8000', name: 'vLLM' },
|
|
90
|
+
];
|
|
91
|
+
for (const { url, name } of candidates) {
|
|
92
|
+
try {
|
|
93
|
+
const res = await fetch(`${url}/v1/models`, {
|
|
94
|
+
signal: AbortSignal.timeout(2000),
|
|
95
|
+
});
|
|
96
|
+
if (res.ok) {
|
|
97
|
+
const data = await res.json();
|
|
98
|
+
const models = (data.data || []).map(m => m.id);
|
|
99
|
+
servers.push({ name, url, models });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// not running
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return servers;
|
|
107
|
+
}
|
|
108
|
+
/** Detect which cloud API keys are available */
|
|
109
|
+
function detectApiKeys() {
|
|
110
|
+
return Object.entries(registry_1.PROVIDER_DEFAULTS).map(([provider, defaults]) => ({
|
|
111
|
+
provider,
|
|
112
|
+
envVar: defaults.envKey,
|
|
113
|
+
set: !!process.env[defaults.envKey],
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
const C = {
|
|
117
|
+
reset: '\x1b[0m',
|
|
118
|
+
bold: '\x1b[1m',
|
|
119
|
+
dim: '\x1b[2m',
|
|
120
|
+
red: '\x1b[31m',
|
|
121
|
+
green: '\x1b[32m',
|
|
122
|
+
yellow: '\x1b[33m',
|
|
123
|
+
cyan: '\x1b[36m',
|
|
124
|
+
};
|
|
125
|
+
function fmt(text, style) {
|
|
126
|
+
return `${C[style]}${text}${C.reset}`;
|
|
127
|
+
}
|
|
128
|
+
function ask(rl, question) {
|
|
129
|
+
return new Promise(resolve => {
|
|
130
|
+
rl.question(question, answer => resolve(answer.trim()));
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/** Interactive setup wizard */
|
|
134
|
+
async function runSetup() {
|
|
135
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
136
|
+
console.log(fmt('\n⚡ CodeBot AI — Setup', 'bold'));
|
|
137
|
+
console.log(fmt(' Let\'s get you configured.\n', 'dim'));
|
|
138
|
+
// Step 1: Detect local servers
|
|
139
|
+
console.log(fmt('Scanning for local LLM servers...', 'dim'));
|
|
140
|
+
const localServers = await detectLocalServers();
|
|
141
|
+
// Step 2: Detect API keys
|
|
142
|
+
const apiKeys = detectApiKeys();
|
|
143
|
+
const availableKeys = apiKeys.filter(k => k.set);
|
|
144
|
+
// Show what was found
|
|
145
|
+
if (localServers.length > 0) {
|
|
146
|
+
for (const server of localServers) {
|
|
147
|
+
console.log(fmt(` ✓ ${server.name} detected (${server.models.length} models)`, 'green'));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
console.log(fmt(' No local LLM servers detected.', 'dim'));
|
|
152
|
+
}
|
|
153
|
+
if (availableKeys.length > 0) {
|
|
154
|
+
for (const key of availableKeys) {
|
|
155
|
+
console.log(fmt(` ✓ ${key.provider} API key found (${key.envVar})`, 'green'));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
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
|
|
166
|
+
console.log(fmt('\nChoose your setup:', 'bold'));
|
|
167
|
+
const options = [];
|
|
168
|
+
let idx = 1;
|
|
169
|
+
// Local options first
|
|
170
|
+
for (const server of localServers) {
|
|
171
|
+
const defaultModel = server.models[0] || 'qwen2.5-coder:32b';
|
|
172
|
+
options.push({ label: `${server.name} (local, free)`, provider: 'openai', model: defaultModel, baseUrl: server.url });
|
|
173
|
+
console.log(` ${fmt(`${idx}`, 'cyan')} ${server.name} — ${defaultModel} ${fmt('(local, free, private)', 'green')}`);
|
|
174
|
+
idx++;
|
|
175
|
+
}
|
|
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
|
+
idx++;
|
|
186
|
+
}
|
|
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
|
+
const choice = await ask(rl, fmt(`\nSelect [1-${options.length}]: `, 'cyan'));
|
|
195
|
+
const selected = options[parseInt(choice, 10) - 1] || options[0];
|
|
196
|
+
// Step 4: Show available models for chosen provider
|
|
197
|
+
const providerModels = Object.entries(registry_1.MODEL_REGISTRY)
|
|
198
|
+
.filter(([, info]) => {
|
|
199
|
+
if (selected.provider === 'openai' && !info.provider)
|
|
200
|
+
return true; // local models
|
|
201
|
+
return info.provider === selected.provider;
|
|
202
|
+
})
|
|
203
|
+
.map(([name]) => name);
|
|
204
|
+
if (providerModels.length > 1) {
|
|
205
|
+
console.log(fmt(`\nAvailable models for ${selected.label}:`, 'bold'));
|
|
206
|
+
providerModels.slice(0, 10).forEach((m, i) => {
|
|
207
|
+
const marker = m === selected.model ? fmt(' (default)', 'green') : '';
|
|
208
|
+
console.log(` ${fmt(`${i + 1}`, 'cyan')} ${m}${marker}`);
|
|
209
|
+
});
|
|
210
|
+
const modelChoice = await ask(rl, fmt(`\nModel [Enter for ${selected.model}]: `, 'cyan'));
|
|
211
|
+
if (modelChoice) {
|
|
212
|
+
const modelIdx = parseInt(modelChoice, 10) - 1;
|
|
213
|
+
if (providerModels[modelIdx]) {
|
|
214
|
+
selected.model = providerModels[modelIdx];
|
|
215
|
+
}
|
|
216
|
+
else if (modelChoice.length > 2) {
|
|
217
|
+
// Treat as model name
|
|
218
|
+
selected.model = modelChoice;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Step 5: Auto mode?
|
|
223
|
+
const autoChoice = await ask(rl, fmt('\nEnable autonomous mode? (skip permission prompts) [y/N]: ', 'cyan'));
|
|
224
|
+
const autoApprove = autoChoice.toLowerCase().startsWith('y');
|
|
225
|
+
rl.close();
|
|
226
|
+
// Save config
|
|
227
|
+
const config = {
|
|
228
|
+
model: selected.model,
|
|
229
|
+
provider: selected.provider,
|
|
230
|
+
baseUrl: selected.baseUrl,
|
|
231
|
+
autoApprove,
|
|
232
|
+
};
|
|
233
|
+
saveConfig(config);
|
|
234
|
+
console.log(fmt('\n✓ Config saved to ~/.codebot/config.json', 'green'));
|
|
235
|
+
console.log(fmt(` Model: ${config.model}`, 'dim'));
|
|
236
|
+
console.log(fmt(` Provider: ${config.provider}`, 'dim'));
|
|
237
|
+
if (autoApprove) {
|
|
238
|
+
console.log(fmt(` Mode: AUTONOMOUS`, 'yellow'));
|
|
239
|
+
}
|
|
240
|
+
console.log(fmt(`\nRun ${fmt('codebot', 'bold')} to start. Run ${fmt('codebot --setup', 'bold')} to reconfigure.\n`, 'dim'));
|
|
241
|
+
return config;
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Tool } from '../types';
|
|
2
|
+
export declare class BrowserTool implements Tool {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
permission: Tool['permission'];
|
|
6
|
+
parameters: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
action: {
|
|
10
|
+
type: string;
|
|
11
|
+
description: string;
|
|
12
|
+
enum: string[];
|
|
13
|
+
};
|
|
14
|
+
url: {
|
|
15
|
+
type: string;
|
|
16
|
+
description: string;
|
|
17
|
+
};
|
|
18
|
+
selector: {
|
|
19
|
+
type: string;
|
|
20
|
+
description: string;
|
|
21
|
+
};
|
|
22
|
+
text: {
|
|
23
|
+
type: string;
|
|
24
|
+
description: string;
|
|
25
|
+
};
|
|
26
|
+
expression: {
|
|
27
|
+
type: string;
|
|
28
|
+
description: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
required: string[];
|
|
32
|
+
};
|
|
33
|
+
execute(args: Record<string, unknown>): Promise<string>;
|
|
34
|
+
private navigate;
|
|
35
|
+
private getContent;
|
|
36
|
+
private screenshot;
|
|
37
|
+
private click;
|
|
38
|
+
private typeText;
|
|
39
|
+
private evaluate;
|
|
40
|
+
private listTabs;
|
|
41
|
+
private closeBrowser;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BrowserTool = void 0;
|
|
4
|
+
const cdp_1 = require("../browser/cdp");
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
// Shared browser instance across tool calls
|
|
7
|
+
let client = null;
|
|
8
|
+
let debugPort = 9222;
|
|
9
|
+
async function ensureConnected() {
|
|
10
|
+
if (client?.isConnected())
|
|
11
|
+
return client;
|
|
12
|
+
// Try connecting to existing Chrome
|
|
13
|
+
try {
|
|
14
|
+
const wsUrl = await (0, cdp_1.getDebuggerUrl)(debugPort);
|
|
15
|
+
client = new cdp_1.CDPClient();
|
|
16
|
+
await client.connect(wsUrl);
|
|
17
|
+
// Get or create a page target
|
|
18
|
+
const targets = await (0, cdp_1.getTargets)(debugPort);
|
|
19
|
+
const page = targets.find(t => t.url !== 'about:blank' && !t.url.startsWith('devtools://'))
|
|
20
|
+
|| targets[0];
|
|
21
|
+
if (page?.webSocketDebuggerUrl) {
|
|
22
|
+
client.close();
|
|
23
|
+
client = new cdp_1.CDPClient();
|
|
24
|
+
await client.connect(page.webSocketDebuggerUrl);
|
|
25
|
+
}
|
|
26
|
+
await client.send('Page.enable');
|
|
27
|
+
await client.send('Runtime.enable');
|
|
28
|
+
return client;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Chrome not running with debugging — try to launch it
|
|
32
|
+
}
|
|
33
|
+
// Launch Chrome with debugging
|
|
34
|
+
const chromePaths = process.platform === 'win32'
|
|
35
|
+
? [
|
|
36
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
37
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
38
|
+
'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
39
|
+
]
|
|
40
|
+
: process.platform === 'linux'
|
|
41
|
+
? [
|
|
42
|
+
'google-chrome',
|
|
43
|
+
'google-chrome-stable',
|
|
44
|
+
'chromium',
|
|
45
|
+
'chromium-browser',
|
|
46
|
+
'/usr/bin/google-chrome',
|
|
47
|
+
'/usr/bin/chromium',
|
|
48
|
+
'/snap/bin/chromium',
|
|
49
|
+
]
|
|
50
|
+
: [
|
|
51
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
52
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
53
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
54
|
+
'google-chrome',
|
|
55
|
+
'chromium',
|
|
56
|
+
];
|
|
57
|
+
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;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!launched) {
|
|
69
|
+
throw new Error('Could not launch Chrome. Start Chrome manually with:\n' +
|
|
70
|
+
` chrome --remote-debugging-port=${debugPort}\n` +
|
|
71
|
+
'Or on macOS:\n' +
|
|
72
|
+
` /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=${debugPort}`);
|
|
73
|
+
}
|
|
74
|
+
// Wait for Chrome to start
|
|
75
|
+
for (let i = 0; i < 10; i++) {
|
|
76
|
+
try {
|
|
77
|
+
await new Promise(r => setTimeout(r, 500));
|
|
78
|
+
const wsUrl = await (0, cdp_1.getDebuggerUrl)(debugPort);
|
|
79
|
+
client = new cdp_1.CDPClient();
|
|
80
|
+
await client.connect(wsUrl);
|
|
81
|
+
const targets = await (0, cdp_1.getTargets)(debugPort);
|
|
82
|
+
const page = targets[0];
|
|
83
|
+
if (page?.webSocketDebuggerUrl) {
|
|
84
|
+
client.close();
|
|
85
|
+
client = new cdp_1.CDPClient();
|
|
86
|
+
await client.connect(page.webSocketDebuggerUrl);
|
|
87
|
+
}
|
|
88
|
+
await client.send('Page.enable');
|
|
89
|
+
await client.send('Runtime.enable');
|
|
90
|
+
return client;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
throw new Error('Chrome launched but could not connect via CDP');
|
|
97
|
+
}
|
|
98
|
+
class BrowserTool {
|
|
99
|
+
name = 'browser';
|
|
100
|
+
description = 'Control a web browser. Navigate to URLs, read page content, click elements, type text, run JavaScript, take screenshots. Use for web browsing, social media, testing, and automation.';
|
|
101
|
+
permission = 'prompt';
|
|
102
|
+
parameters = {
|
|
103
|
+
type: 'object',
|
|
104
|
+
properties: {
|
|
105
|
+
action: {
|
|
106
|
+
type: 'string',
|
|
107
|
+
description: 'Action to perform',
|
|
108
|
+
enum: ['navigate', 'content', 'screenshot', 'click', 'type', 'evaluate', 'tabs', 'close'],
|
|
109
|
+
},
|
|
110
|
+
url: { type: 'string', description: 'URL to navigate to (for navigate action)' },
|
|
111
|
+
selector: { type: 'string', description: 'CSS selector for element (for click/type)' },
|
|
112
|
+
text: { type: 'string', description: 'Text to type (for type action)' },
|
|
113
|
+
expression: { type: 'string', description: 'JavaScript to evaluate (for evaluate action)' },
|
|
114
|
+
},
|
|
115
|
+
required: ['action'],
|
|
116
|
+
};
|
|
117
|
+
async execute(args) {
|
|
118
|
+
const action = args.action;
|
|
119
|
+
try {
|
|
120
|
+
switch (action) {
|
|
121
|
+
case 'navigate':
|
|
122
|
+
return await this.navigate(args.url);
|
|
123
|
+
case 'content':
|
|
124
|
+
return await this.getContent();
|
|
125
|
+
case 'screenshot':
|
|
126
|
+
return await this.screenshot();
|
|
127
|
+
case 'click':
|
|
128
|
+
return await this.click(args.selector);
|
|
129
|
+
case 'type':
|
|
130
|
+
return await this.typeText(args.selector, args.text);
|
|
131
|
+
case 'evaluate':
|
|
132
|
+
return await this.evaluate(args.expression);
|
|
133
|
+
case 'tabs':
|
|
134
|
+
return await this.listTabs();
|
|
135
|
+
case 'close':
|
|
136
|
+
return this.closeBrowser();
|
|
137
|
+
default:
|
|
138
|
+
return `Unknown action: ${action}. Use: navigate, content, screenshot, click, type, evaluate, tabs, close`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
143
|
+
return `Browser error: ${msg}`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async navigate(url) {
|
|
147
|
+
if (!url)
|
|
148
|
+
return 'Error: url is required';
|
|
149
|
+
const cdp = await ensureConnected();
|
|
150
|
+
// Auto-add protocol
|
|
151
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
152
|
+
url = 'https://' + url;
|
|
153
|
+
}
|
|
154
|
+
await cdp.send('Page.navigate', { url });
|
|
155
|
+
// Wait for page load
|
|
156
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
157
|
+
// Get page title
|
|
158
|
+
const result = await cdp.send('Runtime.evaluate', {
|
|
159
|
+
expression: 'document.title',
|
|
160
|
+
returnByValue: true,
|
|
161
|
+
});
|
|
162
|
+
const title = result.result?.value || 'untitled';
|
|
163
|
+
return `Navigated to: ${url}\nTitle: ${title}`;
|
|
164
|
+
}
|
|
165
|
+
async getContent() {
|
|
166
|
+
const cdp = await ensureConnected();
|
|
167
|
+
const result = await cdp.send('Runtime.evaluate', {
|
|
168
|
+
expression: `
|
|
169
|
+
(function() {
|
|
170
|
+
// Remove scripts and styles
|
|
171
|
+
const clone = document.body.cloneNode(true);
|
|
172
|
+
clone.querySelectorAll('script, style, noscript, svg, iframe').forEach(el => el.remove());
|
|
173
|
+
|
|
174
|
+
// Get text content, preserving structure
|
|
175
|
+
function getText(node, depth) {
|
|
176
|
+
if (depth > 10) return '';
|
|
177
|
+
let text = '';
|
|
178
|
+
for (const child of node.childNodes) {
|
|
179
|
+
if (child.nodeType === 3) {
|
|
180
|
+
text += child.textContent.trim() + ' ';
|
|
181
|
+
} else if (child.nodeType === 1) {
|
|
182
|
+
const tag = child.tagName.toLowerCase();
|
|
183
|
+
if (['div', 'p', 'br', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'tr', 'section', 'article'].includes(tag)) {
|
|
184
|
+
text += '\\n';
|
|
185
|
+
}
|
|
186
|
+
// Include link hrefs
|
|
187
|
+
if (tag === 'a' && child.href) {
|
|
188
|
+
text += getText(child, depth + 1) + ' [' + child.href + '] ';
|
|
189
|
+
} else if (tag === 'img' && child.alt) {
|
|
190
|
+
text += '[image: ' + child.alt + '] ';
|
|
191
|
+
} else {
|
|
192
|
+
text += getText(child, depth + 1);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return text;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let content = 'URL: ' + window.location.href + '\\n';
|
|
200
|
+
content += 'Title: ' + document.title + '\\n\\n';
|
|
201
|
+
content += getText(clone, 0);
|
|
202
|
+
return content.replace(/\\n\\s*\\n\\s*\\n/g, '\\n\\n').substring(0, 30000);
|
|
203
|
+
})()
|
|
204
|
+
`,
|
|
205
|
+
returnByValue: true,
|
|
206
|
+
});
|
|
207
|
+
return result.result?.value || 'No content';
|
|
208
|
+
}
|
|
209
|
+
async screenshot() {
|
|
210
|
+
const cdp = await ensureConnected();
|
|
211
|
+
const result = await cdp.send('Page.captureScreenshot', { format: 'png' });
|
|
212
|
+
const data = result.data;
|
|
213
|
+
if (!data)
|
|
214
|
+
return 'Failed to capture screenshot';
|
|
215
|
+
// Save to temp file
|
|
216
|
+
const fs = require('fs');
|
|
217
|
+
const path = require('path');
|
|
218
|
+
const os = require('os');
|
|
219
|
+
const filePath = path.join(os.tmpdir(), `codebot-screenshot-${Date.now()}.png`);
|
|
220
|
+
fs.writeFileSync(filePath, Buffer.from(data, 'base64'));
|
|
221
|
+
return `Screenshot saved: ${filePath} (${Math.round(data.length * 0.75 / 1024)}KB)`;
|
|
222
|
+
}
|
|
223
|
+
async click(selector) {
|
|
224
|
+
if (!selector)
|
|
225
|
+
return 'Error: selector is required';
|
|
226
|
+
const cdp = await ensureConnected();
|
|
227
|
+
const result = await cdp.send('Runtime.evaluate', {
|
|
228
|
+
expression: `
|
|
229
|
+
(function() {
|
|
230
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
231
|
+
if (!el) return 'Element not found: ${selector}';
|
|
232
|
+
el.click();
|
|
233
|
+
return 'Clicked: ' + (el.tagName || '') + ' ' + (el.textContent || '').substring(0, 50).trim();
|
|
234
|
+
})()
|
|
235
|
+
`,
|
|
236
|
+
returnByValue: true,
|
|
237
|
+
});
|
|
238
|
+
return result.result?.value || 'Click executed';
|
|
239
|
+
}
|
|
240
|
+
async typeText(selector, text) {
|
|
241
|
+
if (!selector)
|
|
242
|
+
return 'Error: selector is required';
|
|
243
|
+
if (!text)
|
|
244
|
+
return 'Error: text is required';
|
|
245
|
+
const cdp = await ensureConnected();
|
|
246
|
+
// Focus the element
|
|
247
|
+
await cdp.send('Runtime.evaluate', {
|
|
248
|
+
expression: `
|
|
249
|
+
(function() {
|
|
250
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
251
|
+
if (el) { el.focus(); el.value = ''; }
|
|
252
|
+
})()
|
|
253
|
+
`,
|
|
254
|
+
});
|
|
255
|
+
// Type character by character using Input.dispatchKeyEvent
|
|
256
|
+
for (const char of text) {
|
|
257
|
+
await cdp.send('Input.dispatchKeyEvent', {
|
|
258
|
+
type: 'keyDown',
|
|
259
|
+
text: char,
|
|
260
|
+
key: char,
|
|
261
|
+
code: `Key${char.toUpperCase()}`,
|
|
262
|
+
});
|
|
263
|
+
await cdp.send('Input.dispatchKeyEvent', {
|
|
264
|
+
type: 'keyUp',
|
|
265
|
+
key: char,
|
|
266
|
+
code: `Key${char.toUpperCase()}`,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
// Also set value directly as fallback
|
|
270
|
+
await cdp.send('Runtime.evaluate', {
|
|
271
|
+
expression: `
|
|
272
|
+
(function() {
|
|
273
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
274
|
+
if (el) {
|
|
275
|
+
el.value = ${JSON.stringify(text)};
|
|
276
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
277
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
278
|
+
}
|
|
279
|
+
})()
|
|
280
|
+
`,
|
|
281
|
+
});
|
|
282
|
+
return `Typed "${text.length > 50 ? text.substring(0, 50) + '...' : text}" into ${selector}`;
|
|
283
|
+
}
|
|
284
|
+
async evaluate(expression) {
|
|
285
|
+
if (!expression)
|
|
286
|
+
return 'Error: expression is required';
|
|
287
|
+
const cdp = await ensureConnected();
|
|
288
|
+
const result = await cdp.send('Runtime.evaluate', {
|
|
289
|
+
expression,
|
|
290
|
+
returnByValue: true,
|
|
291
|
+
awaitPromise: true,
|
|
292
|
+
});
|
|
293
|
+
const val = result.result;
|
|
294
|
+
if (val?.type === 'undefined')
|
|
295
|
+
return 'undefined';
|
|
296
|
+
if (val?.value !== undefined) {
|
|
297
|
+
return typeof val.value === 'string' ? val.value : JSON.stringify(val.value, null, 2);
|
|
298
|
+
}
|
|
299
|
+
const exception = result.exceptionDetails;
|
|
300
|
+
if (exception) {
|
|
301
|
+
const ex = exception.exception;
|
|
302
|
+
return `Error: ${ex?.description || ex?.value || 'Unknown error'}`;
|
|
303
|
+
}
|
|
304
|
+
return JSON.stringify(val, null, 2);
|
|
305
|
+
}
|
|
306
|
+
async listTabs() {
|
|
307
|
+
try {
|
|
308
|
+
const targets = await (0, cdp_1.getTargets)(debugPort);
|
|
309
|
+
if (targets.length === 0)
|
|
310
|
+
return 'No tabs open.';
|
|
311
|
+
return targets
|
|
312
|
+
.filter(t => t.url && !t.url.startsWith('devtools://'))
|
|
313
|
+
.map((t, i) => `${i + 1}. ${t.title || '(no title)'}\n ${t.url}`)
|
|
314
|
+
.join('\n');
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return 'Browser not connected. Use navigate first.';
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
closeBrowser() {
|
|
321
|
+
if (client) {
|
|
322
|
+
client.close();
|
|
323
|
+
client = null;
|
|
324
|
+
}
|
|
325
|
+
return 'Browser connection closed.';
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
exports.BrowserTool = BrowserTool;
|
|
329
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Tool } from '../types';
|
|
2
|
+
export declare class EditFileTool implements Tool {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
permission: Tool['permission'];
|
|
6
|
+
parameters: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
path: {
|
|
10
|
+
type: string;
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
old_string: {
|
|
14
|
+
type: string;
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
new_string: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
required: string[];
|
|
23
|
+
};
|
|
24
|
+
execute(args: Record<string, unknown>): Promise<string>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=edit.d.ts.map
|