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 CHANGED
@@ -115,6 +115,8 @@ codebot --resume <session-id> # Resume specific session
115
115
  /models List all supported models
116
116
  /sessions List saved sessions
117
117
  /auto Toggle autonomous mode
118
+ /undo Undo last file edit (/undo [path])
119
+ /usage Show token usage for this session
118
120
  /clear Clear conversation
119
121
  /compact Force context compaction
120
122
  /config Show configuration
@@ -123,13 +125,14 @@ codebot --resume <session-id> # Resume specific session
123
125
 
124
126
  ## Tools
125
127
 
126
- CodeBot has 10 built-in tools:
128
+ CodeBot has 11 built-in tools:
127
129
 
128
130
  | Tool | Description | Permission |
129
131
  |------|-------------|-----------|
130
132
  | `read_file` | Read files with line numbers | auto |
131
- | `write_file` | Create or overwrite files | prompt |
132
- | `edit_file` | Find-and-replace edits | prompt |
133
+ | `write_file` | Create or overwrite files (with undo snapshots) | prompt |
134
+ | `edit_file` | Find-and-replace edits with diff preview + undo | prompt |
135
+ | `batch_edit` | Multi-file atomic find-and-replace | prompt |
133
136
  | `execute` | Run shell commands | always-ask |
134
137
  | `glob` | Find files by pattern | auto |
135
138
  | `grep` | Search file contents with regex | auto |
@@ -168,6 +171,40 @@ CodeBot has persistent memory that survives across sessions:
168
171
  - Memory is automatically injected into the system prompt
169
172
  - The agent can read/write its own memory using the `memory` tool
170
173
 
174
+ ### Plugins
175
+
176
+ Extend CodeBot with custom tools. Drop `.js` files in `.codebot/plugins/` (project) or `~/.codebot/plugins/` (global):
177
+
178
+ ```javascript
179
+ // .codebot/plugins/my-tool.js
180
+ module.exports = {
181
+ name: 'my_tool',
182
+ description: 'Does something useful',
183
+ permission: 'prompt',
184
+ parameters: { type: 'object', properties: { input: { type: 'string' } }, required: ['input'] },
185
+ execute: async (args) => { return `Result: ${args.input}`; }
186
+ };
187
+ ```
188
+
189
+ ### MCP Servers
190
+
191
+ Connect external tool servers via [Model Context Protocol](https://modelcontextprotocol.io). Create `.codebot/mcp.json`:
192
+
193
+ ```json
194
+ {
195
+ "servers": [
196
+ {
197
+ "name": "my-server",
198
+ "command": "npx",
199
+ "args": ["-y", "@my/mcp-server"],
200
+ "env": {}
201
+ }
202
+ ]
203
+ }
204
+ ```
205
+
206
+ MCP tools appear automatically with the `mcp_<server>_<tool>` prefix.
207
+
171
208
  ## Supported Models
172
209
 
173
210
  ### Local (Ollama / LM Studio / vLLM)
@@ -204,8 +241,11 @@ src/
204
241
  registry.ts Model registry, provider detection
205
242
  browser/
206
243
  cdp.ts Chrome DevTools Protocol client (zero-dep WebSocket)
244
+ plugins.ts Plugin loader (.codebot/plugins/)
245
+ mcp.ts MCP (Model Context Protocol) client
207
246
  tools/
208
247
  read.ts, write.ts, edit.ts, execute.ts
248
+ batch-edit.ts Multi-file atomic editing
209
249
  glob.ts, grep.ts, think.ts
210
250
  memory.ts, web-fetch.ts, browser.ts
211
251
  ```
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
@@ -41,6 +41,7 @@ const manager_1 = require("./context/manager");
41
41
  const repo_map_1 = require("./context/repo-map");
42
42
  const memory_1 = require("./memory");
43
43
  const registry_1 = require("./providers/registry");
44
+ const plugins_1 = require("./plugins");
44
45
  class Agent {
45
46
  provider;
46
47
  tools;
@@ -60,12 +61,24 @@ class Agent {
60
61
  this.autoApprove = opts.autoApprove || false;
61
62
  this.askPermission = opts.askPermission || defaultAskPermission;
62
63
  this.onMessage = opts.onMessage;
64
+ // Load plugins
65
+ try {
66
+ const plugins = (0, plugins_1.loadPlugins)(process.cwd());
67
+ for (const plugin of plugins) {
68
+ this.tools.register(plugin);
69
+ }
70
+ }
71
+ catch { /* plugins unavailable */ }
63
72
  const supportsTools = (0, registry_1.getModelInfo)(opts.model).supportsToolCalling;
64
73
  this.messages.push({
65
74
  role: 'system',
66
75
  content: this.buildSystemPrompt(supportsTools),
67
76
  });
68
77
  }
78
+ /** Update auto-approve mode at runtime (e.g., from /auto command) */
79
+ setAutoApprove(value) {
80
+ this.autoApprove = value;
81
+ }
69
82
  /** Load messages from a previous session for resume */
70
83
  loadMessages(messages) {
71
84
  this.messages = messages;
@@ -215,9 +228,14 @@ class Agent {
215
228
  catch {
216
229
  // memory unavailable
217
230
  }
218
- 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.
219
232
 
220
- 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.
221
239
 
222
240
  Rules:
223
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
@@ -42,7 +42,10 @@ const registry_1 = require("./providers/registry");
42
42
  const history_1 = require("./history");
43
43
  const setup_1 = require("./setup");
44
44
  const banner_1 = require("./banner");
45
- const VERSION = '1.0.0';
45
+ const tools_1 = require("./tools");
46
+ const VERSION = '1.1.0';
47
+ // Session-wide token tracking
48
+ let sessionTokens = { input: 0, output: 0, total: 0 };
46
49
  const C = {
47
50
  reset: '\x1b[0m',
48
51
  bold: '\x1b[1m',
@@ -229,6 +232,12 @@ function renderEvent(event) {
229
232
  break;
230
233
  case 'usage':
231
234
  if (event.usage) {
235
+ if (event.usage.inputTokens)
236
+ sessionTokens.input += event.usage.inputTokens;
237
+ if (event.usage.outputTokens)
238
+ sessionTokens.output += event.usage.outputTokens;
239
+ if (event.usage.totalTokens)
240
+ sessionTokens.total += event.usage.totalTokens;
232
241
  const parts = [];
233
242
  if (event.usage.inputTokens)
234
243
  parts.push(`in: ${event.usage.inputTokens}`);
@@ -278,6 +287,8 @@ function handleSlashCommand(input, agent, config) {
278
287
  /clear Clear conversation history
279
288
  /compact Force context compaction
280
289
  /auto Toggle autonomous mode
290
+ /undo Undo last file edit (/undo [path])
291
+ /usage Show token usage for this session
281
292
  /config Show current config
282
293
  /quit Exit`);
283
294
  break;
@@ -311,6 +322,7 @@ function handleSlashCommand(input, agent, config) {
311
322
  }
312
323
  case '/auto':
313
324
  config.autoApprove = !config.autoApprove;
325
+ agent.setAutoApprove(config.autoApprove);
314
326
  console.log(c(`Autonomous mode: ${config.autoApprove ? 'ON' : 'OFF'}`, config.autoApprove ? 'yellow' : 'green'));
315
327
  break;
316
328
  case '/sessions': {
@@ -328,6 +340,19 @@ function handleSlashCommand(input, agent, config) {
328
340
  }
329
341
  break;
330
342
  }
343
+ case '/undo': {
344
+ const undoPath = rest.length > 0 ? rest.join(' ') : undefined;
345
+ const undoResult = tools_1.EditFileTool.undo(undoPath);
346
+ console.log(c(undoResult, undoResult.includes('Restored') ? 'green' : 'yellow'));
347
+ break;
348
+ }
349
+ case '/usage': {
350
+ console.log(c('\nToken Usage (this session):', 'bold'));
351
+ console.log(` Input: ${sessionTokens.input.toLocaleString()} tokens`);
352
+ console.log(` Output: ${sessionTokens.output.toLocaleString()} tokens`);
353
+ console.log(` Total: ${(sessionTokens.input + sessionTokens.output).toLocaleString()} tokens`);
354
+ break;
355
+ }
331
356
  case '/config':
332
357
  console.log(JSON.stringify({ ...config, apiKey: config.apiKey ? '***' : undefined }, null, 2));
333
358
  break;
@@ -377,7 +402,10 @@ async function resolveConfig(args) {
377
402
  config.apiKey = process.env[defaults.envKey] || process.env.CODEBOT_API_KEY || '';
378
403
  }
379
404
  }
380
- // 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
+ }
381
409
  if (!config.apiKey) {
382
410
  config.apiKey = process.env.CODEBOT_API_KEY || process.env.OPENAI_API_KEY || '';
383
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
package/dist/index.d.ts CHANGED
@@ -7,6 +7,8 @@ export { buildRepoMap } from './context/repo-map';
7
7
  export { SessionManager } from './history';
8
8
  export { MemoryManager } from './memory';
9
9
  export { parseToolCalls } from './parser';
10
+ export { loadPlugins } from './plugins';
11
+ export { loadMCPTools } from './mcp';
10
12
  export { MODEL_REGISTRY, PROVIDER_DEFAULTS, getModelInfo, detectProvider } from './providers/registry';
11
13
  export type { ModelInfo } from './providers/registry';
12
14
  export * from './types';
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.detectProvider = exports.getModelInfo = exports.PROVIDER_DEFAULTS = exports.MODEL_REGISTRY = exports.parseToolCalls = exports.MemoryManager = exports.SessionManager = exports.buildRepoMap = exports.ContextManager = exports.ToolRegistry = exports.AnthropicProvider = exports.OpenAIProvider = exports.Agent = void 0;
17
+ exports.detectProvider = exports.getModelInfo = exports.PROVIDER_DEFAULTS = exports.MODEL_REGISTRY = exports.loadMCPTools = exports.loadPlugins = exports.parseToolCalls = exports.MemoryManager = exports.SessionManager = exports.buildRepoMap = exports.ContextManager = exports.ToolRegistry = exports.AnthropicProvider = exports.OpenAIProvider = exports.Agent = void 0;
18
18
  var agent_1 = require("./agent");
19
19
  Object.defineProperty(exports, "Agent", { enumerable: true, get: function () { return agent_1.Agent; } });
20
20
  var openai_1 = require("./providers/openai");
@@ -33,6 +33,10 @@ var memory_1 = require("./memory");
33
33
  Object.defineProperty(exports, "MemoryManager", { enumerable: true, get: function () { return memory_1.MemoryManager; } });
34
34
  var parser_1 = require("./parser");
35
35
  Object.defineProperty(exports, "parseToolCalls", { enumerable: true, get: function () { return parser_1.parseToolCalls; } });
36
+ var plugins_1 = require("./plugins");
37
+ Object.defineProperty(exports, "loadPlugins", { enumerable: true, get: function () { return plugins_1.loadPlugins; } });
38
+ var mcp_1 = require("./mcp");
39
+ Object.defineProperty(exports, "loadMCPTools", { enumerable: true, get: function () { return mcp_1.loadMCPTools; } });
36
40
  var registry_1 = require("./providers/registry");
37
41
  Object.defineProperty(exports, "MODEL_REGISTRY", { enumerable: true, get: function () { return registry_1.MODEL_REGISTRY; } });
38
42
  Object.defineProperty(exports, "PROVIDER_DEFAULTS", { enumerable: true, get: function () { return registry_1.PROVIDER_DEFAULTS; } });
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { Tool } from './types';
2
+ /** Load MCP config and connect to all servers, returning Tool wrappers */
3
+ export declare function loadMCPTools(projectRoot?: string): Promise<{
4
+ tools: Tool[];
5
+ cleanup: () => void;
6
+ }>;
7
+ //# sourceMappingURL=mcp.d.ts.map
package/dist/mcp.js ADDED
@@ -0,0 +1,174 @@
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.loadMCPTools = loadMCPTools;
37
+ const child_process_1 = require("child_process");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const os = __importStar(require("os"));
41
+ class MCPConnection {
42
+ process;
43
+ buffer = '';
44
+ requestId = 0;
45
+ pending = new Map();
46
+ name;
47
+ constructor(config) {
48
+ this.name = config.name;
49
+ this.process = (0, child_process_1.spawn)(config.command, config.args || [], {
50
+ stdio: ['pipe', 'pipe', 'pipe'],
51
+ env: { ...process.env, ...config.env },
52
+ });
53
+ this.process.stdout?.on('data', (chunk) => {
54
+ this.buffer += chunk.toString();
55
+ this.processBuffer();
56
+ });
57
+ this.process.on('error', () => { });
58
+ }
59
+ processBuffer() {
60
+ const lines = this.buffer.split('\n');
61
+ this.buffer = lines.pop() || '';
62
+ for (const line of lines) {
63
+ if (!line.trim())
64
+ continue;
65
+ try {
66
+ const msg = JSON.parse(line);
67
+ if (msg.id !== undefined && this.pending.has(msg.id)) {
68
+ const { resolve, reject } = this.pending.get(msg.id);
69
+ this.pending.delete(msg.id);
70
+ if (msg.error) {
71
+ reject(new Error(msg.error.message || 'MCP error'));
72
+ }
73
+ else {
74
+ resolve(msg.result);
75
+ }
76
+ }
77
+ }
78
+ catch { /* skip malformed */ }
79
+ }
80
+ }
81
+ async request(method, params) {
82
+ const id = ++this.requestId;
83
+ const msg = JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n';
84
+ return new Promise((resolve, reject) => {
85
+ this.pending.set(id, { resolve, reject });
86
+ this.process.stdin?.write(msg);
87
+ // Timeout after 30s
88
+ setTimeout(() => {
89
+ if (this.pending.has(id)) {
90
+ this.pending.delete(id);
91
+ reject(new Error(`MCP request timeout: ${method}`));
92
+ }
93
+ }, 30000);
94
+ });
95
+ }
96
+ async initialize() {
97
+ await this.request('initialize', {
98
+ protocolVersion: '2024-11-05',
99
+ capabilities: {},
100
+ clientInfo: { name: 'codebot-ai', version: '1.1.0' },
101
+ });
102
+ await this.request('notifications/initialized');
103
+ }
104
+ async listTools() {
105
+ const result = await this.request('tools/list');
106
+ return result?.tools || [];
107
+ }
108
+ async callTool(name, args) {
109
+ const result = await this.request('tools/call', { name, arguments: args });
110
+ if (result?.content) {
111
+ return result.content
112
+ .filter(c => c.type === 'text')
113
+ .map(c => c.text || '')
114
+ .join('\n');
115
+ }
116
+ return JSON.stringify(result);
117
+ }
118
+ close() {
119
+ this.process.kill();
120
+ }
121
+ }
122
+ /** Create Tool wrappers from an MCP server's tools */
123
+ function mcpToolToTool(connection, def) {
124
+ return {
125
+ name: `mcp_${connection.name}_${def.name}`,
126
+ description: `[MCP:${connection.name}] ${def.description}`,
127
+ permission: 'prompt',
128
+ parameters: def.inputSchema || { type: 'object', properties: {} },
129
+ execute: async (args) => {
130
+ return connection.callTool(def.name, args);
131
+ },
132
+ };
133
+ }
134
+ /** Load MCP config and connect to all servers, returning Tool wrappers */
135
+ async function loadMCPTools(projectRoot) {
136
+ const tools = [];
137
+ const connections = [];
138
+ const configPaths = [
139
+ path.join(os.homedir(), '.codebot', 'mcp.json'),
140
+ ];
141
+ if (projectRoot) {
142
+ configPaths.push(path.join(projectRoot, '.codebot', 'mcp.json'));
143
+ }
144
+ for (const configPath of configPaths) {
145
+ if (!fs.existsSync(configPath))
146
+ continue;
147
+ let config;
148
+ try {
149
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
150
+ }
151
+ catch {
152
+ continue;
153
+ }
154
+ for (const serverConfig of config.servers || []) {
155
+ try {
156
+ const conn = new MCPConnection(serverConfig);
157
+ await conn.initialize();
158
+ const serverTools = await conn.listTools();
159
+ for (const toolDef of serverTools) {
160
+ tools.push(mcpToolToTool(conn, toolDef));
161
+ }
162
+ connections.push(conn);
163
+ }
164
+ catch (err) {
165
+ console.error(`MCP server "${serverConfig.name}" failed: ${err instanceof Error ? err.message : String(err)}`);
166
+ }
167
+ }
168
+ }
169
+ return {
170
+ tools,
171
+ cleanup: () => connections.forEach(c => c.close()),
172
+ };
173
+ }
174
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1,17 @@
1
+ import { Tool } from './types';
2
+ /**
3
+ * Plugin system for CodeBot.
4
+ *
5
+ * Plugins are .js files in `.codebot/plugins/` (project) or `~/.codebot/plugins/` (global).
6
+ * Each plugin exports a default function or object that implements the Tool interface:
7
+ *
8
+ * module.exports = {
9
+ * name: 'my_tool',
10
+ * description: 'Does something useful',
11
+ * permission: 'prompt',
12
+ * parameters: { type: 'object', properties: { ... }, required: [...] },
13
+ * execute: async (args) => { return 'result'; }
14
+ * };
15
+ */
16
+ export declare function loadPlugins(projectRoot?: string): Tool[];
17
+ //# sourceMappingURL=plugins.d.ts.map