codebot-ai 1.2.3 → 1.4.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.
Files changed (42) hide show
  1. package/README.md +135 -116
  2. package/dist/agent.js +51 -27
  3. package/dist/cli.js +18 -2
  4. package/dist/providers/anthropic.js +38 -18
  5. package/dist/providers/openai.js +35 -14
  6. package/dist/retry.d.ts +22 -0
  7. package/dist/retry.js +59 -0
  8. package/dist/scheduler.d.ts +2 -0
  9. package/dist/scheduler.js +25 -17
  10. package/dist/tools/code-analysis.d.ts +33 -0
  11. package/dist/tools/code-analysis.js +232 -0
  12. package/dist/tools/code-review.d.ts +32 -0
  13. package/dist/tools/code-review.js +228 -0
  14. package/dist/tools/database.d.ts +35 -0
  15. package/dist/tools/database.js +129 -0
  16. package/dist/tools/diff-viewer.d.ts +39 -0
  17. package/dist/tools/diff-viewer.js +145 -0
  18. package/dist/tools/docker.d.ts +26 -0
  19. package/dist/tools/docker.js +101 -0
  20. package/dist/tools/git.d.ts +26 -0
  21. package/dist/tools/git.js +58 -0
  22. package/dist/tools/http-client.d.ts +39 -0
  23. package/dist/tools/http-client.js +114 -0
  24. package/dist/tools/image-info.d.ts +23 -0
  25. package/dist/tools/image-info.js +170 -0
  26. package/dist/tools/index.js +34 -0
  27. package/dist/tools/multi-search.d.ts +28 -0
  28. package/dist/tools/multi-search.js +153 -0
  29. package/dist/tools/notification.d.ts +38 -0
  30. package/dist/tools/notification.js +96 -0
  31. package/dist/tools/package-manager.d.ts +31 -0
  32. package/dist/tools/package-manager.js +161 -0
  33. package/dist/tools/pdf-extract.d.ts +33 -0
  34. package/dist/tools/pdf-extract.js +178 -0
  35. package/dist/tools/ssh-remote.d.ts +39 -0
  36. package/dist/tools/ssh-remote.js +84 -0
  37. package/dist/tools/task-planner.d.ts +42 -0
  38. package/dist/tools/task-planner.js +161 -0
  39. package/dist/tools/test-runner.d.ts +36 -0
  40. package/dist/tools/test-runner.js +193 -0
  41. package/dist/tools/web-fetch.js +11 -2
  42. package/package.json +16 -8
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HttpClientTool = void 0;
4
+ class HttpClientTool {
5
+ name = 'http_client';
6
+ description = 'Make HTTP requests. Supports GET, POST, PUT, DELETE, PATCH with headers, auth, and body.';
7
+ permission = 'prompt';
8
+ parameters = {
9
+ type: 'object',
10
+ properties: {
11
+ method: { type: 'string', description: 'HTTP method: GET, POST, PUT, DELETE, PATCH, HEAD (default: GET)' },
12
+ url: { type: 'string', description: 'Full URL to request' },
13
+ headers: { type: 'object', description: 'Request headers as key-value pairs' },
14
+ body: { type: 'string', description: 'Request body (JSON string or plain text)' },
15
+ auth: { type: 'string', description: 'Authorization header value (e.g., "Bearer token123")' },
16
+ timeout: { type: 'number', description: 'Timeout in ms (default: 30000)' },
17
+ },
18
+ required: ['url'],
19
+ };
20
+ async execute(args) {
21
+ const url = args.url;
22
+ if (!url)
23
+ return 'Error: url is required';
24
+ // Validate URL
25
+ let parsedUrl;
26
+ try {
27
+ parsedUrl = new URL(url);
28
+ }
29
+ catch {
30
+ return `Error: invalid URL: ${url}`;
31
+ }
32
+ // Block private IPs and file protocol
33
+ if (this.isBlocked(parsedUrl)) {
34
+ return 'Error: requests to private/local addresses are blocked for security.';
35
+ }
36
+ const method = (args.method || 'GET').toUpperCase();
37
+ const timeoutMs = args.timeout || 30_000;
38
+ const headers = {};
39
+ // Set headers
40
+ if (args.headers && typeof args.headers === 'object') {
41
+ for (const [k, v] of Object.entries(args.headers)) {
42
+ headers[k] = String(v);
43
+ }
44
+ }
45
+ if (args.auth) {
46
+ headers['Authorization'] = args.auth;
47
+ }
48
+ // Auto-set content type for body
49
+ const body = args.body;
50
+ if (body && !headers['Content-Type'] && !headers['content-type']) {
51
+ try {
52
+ JSON.parse(body);
53
+ headers['Content-Type'] = 'application/json';
54
+ }
55
+ catch { /* leave as-is */ }
56
+ }
57
+ const controller = new AbortController();
58
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
59
+ try {
60
+ const res = await fetch(url, {
61
+ method,
62
+ headers,
63
+ body: ['GET', 'HEAD'].includes(method) ? undefined : body,
64
+ signal: controller.signal,
65
+ });
66
+ const contentType = res.headers.get('content-type') || '';
67
+ let responseBody;
68
+ try {
69
+ responseBody = await res.text();
70
+ }
71
+ finally {
72
+ clearTimeout(timer);
73
+ }
74
+ // Try to pretty-print JSON
75
+ if (contentType.includes('json') || responseBody.startsWith('{') || responseBody.startsWith('[')) {
76
+ try {
77
+ const parsed = JSON.parse(responseBody);
78
+ responseBody = JSON.stringify(parsed, null, 2);
79
+ }
80
+ catch { /* keep raw */ }
81
+ }
82
+ // Truncate huge responses
83
+ if (responseBody.length > 10_000) {
84
+ responseBody = responseBody.substring(0, 10_000) + '\n...(truncated)';
85
+ }
86
+ const headerLines = Array.from(res.headers.entries())
87
+ .slice(0, 10)
88
+ .map(([k, v]) => ` ${k}: ${v}`)
89
+ .join('\n');
90
+ return `${res.status} ${res.statusText}\n\nHeaders:\n${headerLines}\n\nBody:\n${responseBody}`;
91
+ }
92
+ catch (err) {
93
+ clearTimeout(timer);
94
+ const msg = err instanceof Error ? err.message : String(err);
95
+ if (msg.includes('abort'))
96
+ return `Error: request timed out after ${timeoutMs}ms`;
97
+ return `Error: ${msg}`;
98
+ }
99
+ }
100
+ isBlocked(url) {
101
+ const host = url.hostname.toLowerCase();
102
+ if (url.protocol === 'file:')
103
+ return true;
104
+ if (host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '0.0.0.0')
105
+ return true;
106
+ if (host === '169.254.169.254')
107
+ return true; // cloud metadata
108
+ if (/^10\./.test(host) || /^192\.168\./.test(host) || /^172\.(1[6-9]|2\d|3[01])\./.test(host))
109
+ return true;
110
+ return false;
111
+ }
112
+ }
113
+ exports.HttpClientTool = HttpClientTool;
114
+ //# sourceMappingURL=http-client.js.map
@@ -0,0 +1,23 @@
1
+ import { Tool } from '../types';
2
+ export declare class ImageInfoTool 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
+ base64: {
14
+ type: string;
15
+ description: string;
16
+ };
17
+ };
18
+ required: string[];
19
+ };
20
+ execute(args: Record<string, unknown>): Promise<string>;
21
+ private readJpegDimensions;
22
+ }
23
+ //# sourceMappingURL=image-info.d.ts.map
@@ -0,0 +1,170 @@
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.ImageInfoTool = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ class ImageInfoTool {
40
+ name = 'image_info';
41
+ description = 'Get image file information — dimensions, format, file size. Supports PNG, JPEG, GIF, BMP, SVG.';
42
+ permission = 'auto';
43
+ parameters = {
44
+ type: 'object',
45
+ properties: {
46
+ path: { type: 'string', description: 'Path to image file' },
47
+ base64: { type: 'boolean', description: 'Also return base64-encoded content (default: false)' },
48
+ },
49
+ required: ['path'],
50
+ };
51
+ async execute(args) {
52
+ const filePath = args.path;
53
+ if (!filePath)
54
+ return 'Error: path is required';
55
+ if (!fs.existsSync(filePath))
56
+ return `Error: file not found: ${filePath}`;
57
+ const stat = fs.statSync(filePath);
58
+ const ext = path.extname(filePath).toLowerCase();
59
+ const sizeKB = (stat.size / 1024).toFixed(1);
60
+ let width = 0, height = 0, format = 'unknown';
61
+ try {
62
+ const buf = Buffer.alloc(Math.min(stat.size, 32));
63
+ const fd = fs.openSync(filePath, 'r');
64
+ fs.readSync(fd, buf, 0, buf.length, 0);
65
+ fs.closeSync(fd);
66
+ // PNG: bytes 16-23 contain width (4 bytes) and height (4 bytes)
67
+ if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4E && buf[3] === 0x47) {
68
+ format = 'PNG';
69
+ width = buf.readUInt32BE(16);
70
+ height = buf.readUInt32BE(20);
71
+ }
72
+ // JPEG: SOI marker
73
+ else if (buf[0] === 0xFF && buf[1] === 0xD8) {
74
+ format = 'JPEG';
75
+ const dims = this.readJpegDimensions(filePath);
76
+ width = dims.width;
77
+ height = dims.height;
78
+ }
79
+ // GIF: GIF87a or GIF89a
80
+ else if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46) {
81
+ format = 'GIF';
82
+ width = buf.readUInt16LE(6);
83
+ height = buf.readUInt16LE(8);
84
+ }
85
+ // BMP
86
+ else if (buf[0] === 0x42 && buf[1] === 0x4D) {
87
+ format = 'BMP';
88
+ const fullBuf = Buffer.alloc(26);
89
+ const fd2 = fs.openSync(filePath, 'r');
90
+ fs.readSync(fd2, fullBuf, 0, 26, 0);
91
+ fs.closeSync(fd2);
92
+ width = fullBuf.readInt32LE(18);
93
+ height = Math.abs(fullBuf.readInt32LE(22));
94
+ }
95
+ // SVG
96
+ else if (ext === '.svg') {
97
+ format = 'SVG';
98
+ const content = fs.readFileSync(filePath, 'utf-8').substring(0, 1000);
99
+ const wMatch = content.match(/width=["'](\d+)/);
100
+ const hMatch = content.match(/height=["'](\d+)/);
101
+ const vbMatch = content.match(/viewBox=["']\s*\d+\s+\d+\s+(\d+)\s+(\d+)/);
102
+ if (wMatch)
103
+ width = parseInt(wMatch[1]);
104
+ if (hMatch)
105
+ height = parseInt(hMatch[1]);
106
+ if (!width && vbMatch) {
107
+ width = parseInt(vbMatch[1]);
108
+ height = parseInt(vbMatch[2]);
109
+ }
110
+ }
111
+ }
112
+ catch {
113
+ // Could not read dimensions
114
+ }
115
+ let result = `File: ${path.basename(filePath)}\nFormat: ${format}\nSize: ${sizeKB} KB`;
116
+ if (width > 0 && height > 0) {
117
+ result += `\nDimensions: ${width} x ${height}`;
118
+ }
119
+ result += `\nModified: ${stat.mtime.toISOString()}`;
120
+ if (args.base64) {
121
+ try {
122
+ const content = fs.readFileSync(filePath);
123
+ const b64 = content.toString('base64');
124
+ if (b64.length > 50_000) {
125
+ result += '\n\nBase64: (too large — over 50KB encoded)';
126
+ }
127
+ else {
128
+ result += `\n\nBase64:\n${b64}`;
129
+ }
130
+ }
131
+ catch {
132
+ result += '\n\nBase64: Error reading file';
133
+ }
134
+ }
135
+ return result;
136
+ }
137
+ readJpegDimensions(filePath) {
138
+ try {
139
+ const buf = fs.readFileSync(filePath);
140
+ let offset = 2; // Skip SOI
141
+ while (offset < buf.length - 1) {
142
+ if (buf[offset] !== 0xFF)
143
+ break;
144
+ const marker = buf[offset + 1];
145
+ // SOF markers (C0-C3, C5-C7, C9-CB, CD-CF)
146
+ if ((marker >= 0xC0 && marker <= 0xC3) || (marker >= 0xC5 && marker <= 0xC7) ||
147
+ (marker >= 0xC9 && marker <= 0xCB) || (marker >= 0xCD && marker <= 0xCF)) {
148
+ const height = buf.readUInt16BE(offset + 5);
149
+ const width = buf.readUInt16BE(offset + 7);
150
+ return { width, height };
151
+ }
152
+ // Skip non-SOF markers
153
+ if (marker === 0xD8 || marker === 0xD9) {
154
+ offset += 2;
155
+ continue;
156
+ }
157
+ if (marker >= 0xD0 && marker <= 0xD7) {
158
+ offset += 2;
159
+ continue;
160
+ }
161
+ const len = buf.readUInt16BE(offset + 2);
162
+ offset += 2 + len;
163
+ }
164
+ }
165
+ catch { /* fallback */ }
166
+ return { width: 0, height: 0 };
167
+ }
168
+ }
169
+ exports.ImageInfoTool = ImageInfoTool;
170
+ //# sourceMappingURL=image-info.js.map
@@ -14,11 +14,28 @@ const web_search_1 = require("./web-search");
14
14
  const browser_1 = require("./browser");
15
15
  const batch_edit_1 = require("./batch-edit");
16
16
  const routine_1 = require("./routine");
17
+ // v1.4.0 — 15 new tools
18
+ const git_1 = require("./git");
19
+ const code_analysis_1 = require("./code-analysis");
20
+ const multi_search_1 = require("./multi-search");
21
+ const task_planner_1 = require("./task-planner");
22
+ const diff_viewer_1 = require("./diff-viewer");
23
+ const docker_1 = require("./docker");
24
+ const database_1 = require("./database");
25
+ const test_runner_1 = require("./test-runner");
26
+ const http_client_1 = require("./http-client");
27
+ const image_info_1 = require("./image-info");
28
+ const ssh_remote_1 = require("./ssh-remote");
29
+ const notification_1 = require("./notification");
30
+ const pdf_extract_1 = require("./pdf-extract");
31
+ const package_manager_1 = require("./package-manager");
32
+ const code_review_1 = require("./code-review");
17
33
  var edit_2 = require("./edit");
18
34
  Object.defineProperty(exports, "EditFileTool", { enumerable: true, get: function () { return edit_2.EditFileTool; } });
19
35
  class ToolRegistry {
20
36
  tools = new Map();
21
37
  constructor(projectRoot) {
38
+ // Core file tools
22
39
  this.register(new read_1.ReadFileTool());
23
40
  this.register(new write_1.WriteFileTool());
24
41
  this.register(new edit_1.EditFileTool());
@@ -28,10 +45,27 @@ class ToolRegistry {
28
45
  this.register(new grep_1.GrepTool());
29
46
  this.register(new think_1.ThinkTool());
30
47
  this.register(new memory_1.MemoryTool(projectRoot));
48
+ // Web & browser
31
49
  this.register(new web_fetch_1.WebFetchTool());
32
50
  this.register(new web_search_1.WebSearchTool());
33
51
  this.register(new browser_1.BrowserTool());
34
52
  this.register(new routine_1.RoutineTool());
53
+ // v1.4.0 — intelligence & dev tools
54
+ this.register(new git_1.GitTool());
55
+ this.register(new code_analysis_1.CodeAnalysisTool());
56
+ this.register(new multi_search_1.MultiSearchTool());
57
+ this.register(new task_planner_1.TaskPlannerTool());
58
+ this.register(new diff_viewer_1.DiffViewerTool());
59
+ this.register(new docker_1.DockerTool());
60
+ this.register(new database_1.DatabaseTool());
61
+ this.register(new test_runner_1.TestRunnerTool());
62
+ this.register(new http_client_1.HttpClientTool());
63
+ this.register(new image_info_1.ImageInfoTool());
64
+ this.register(new ssh_remote_1.SshRemoteTool());
65
+ this.register(new notification_1.NotificationTool());
66
+ this.register(new pdf_extract_1.PdfExtractTool());
67
+ this.register(new package_manager_1.PackageManagerTool());
68
+ this.register(new code_review_1.CodeReviewTool());
35
69
  }
36
70
  register(tool) {
37
71
  this.tools.set(tool.name, tool);
@@ -0,0 +1,28 @@
1
+ import { Tool } from '../types';
2
+ export declare class MultiSearchTool implements Tool {
3
+ name: string;
4
+ description: string;
5
+ permission: Tool['permission'];
6
+ parameters: {
7
+ type: string;
8
+ properties: {
9
+ query: {
10
+ type: string;
11
+ description: string;
12
+ };
13
+ path: {
14
+ type: string;
15
+ description: string;
16
+ };
17
+ max_results: {
18
+ type: string;
19
+ description: string;
20
+ };
21
+ };
22
+ required: string[];
23
+ };
24
+ execute(args: Record<string, unknown>): Promise<string>;
25
+ private searchDir;
26
+ private fuzzyScore;
27
+ }
28
+ //# sourceMappingURL=multi-search.d.ts.map
@@ -0,0 +1,153 @@
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.MultiSearchTool = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ class MultiSearchTool {
40
+ name = 'multi_search';
41
+ description = 'Fuzzy search across filenames, file contents, and code symbols. Returns ranked results by relevance.';
42
+ permission = 'auto';
43
+ parameters = {
44
+ type: 'object',
45
+ properties: {
46
+ query: { type: 'string', description: 'Search query (fuzzy matched against filenames, content, and symbols)' },
47
+ path: { type: 'string', description: 'Directory to search (defaults to current)' },
48
+ max_results: { type: 'number', description: 'Max results to return (default: 20)' },
49
+ },
50
+ required: ['query'],
51
+ };
52
+ async execute(args) {
53
+ const query = args.query;
54
+ if (!query)
55
+ return 'Error: query is required';
56
+ const searchPath = args.path || process.cwd();
57
+ const maxResults = args.max_results || 20;
58
+ if (!fs.existsSync(searchPath))
59
+ return `Error: path not found: ${searchPath}`;
60
+ const results = [];
61
+ const queryLower = query.toLowerCase();
62
+ const queryParts = queryLower.split(/\s+/);
63
+ this.searchDir(searchPath, queryLower, queryParts, results);
64
+ // Sort by score descending
65
+ results.sort((a, b) => b.score - a.score);
66
+ const top = results.slice(0, maxResults);
67
+ if (top.length === 0)
68
+ return `No results for "${query}".`;
69
+ const lines = top.map(r => {
70
+ const tag = r.type === 'filename' ? '[file]' : r.type === 'symbol' ? '[symbol]' : '[content]';
71
+ const loc = r.line ? `:${r.line}` : '';
72
+ const preview = r.text ? ` — ${r.text.substring(0, 80)}` : '';
73
+ return ` ${tag} ${r.file}${loc}${preview}`;
74
+ });
75
+ return `Search results for "${query}" (${top.length} matches):\n${lines.join('\n')}`;
76
+ }
77
+ searchDir(dir, query, parts, results) {
78
+ const skip = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '__pycache__', '.next']);
79
+ const codeExts = new Set(['.ts', '.js', '.tsx', '.jsx', '.py', '.go', '.rs', '.java', '.rb', '.c', '.cpp', '.h', '.css', '.html', '.json', '.md', '.yaml', '.yml', '.toml']);
80
+ let entries;
81
+ try {
82
+ entries = fs.readdirSync(dir, { withFileTypes: true });
83
+ }
84
+ catch {
85
+ return;
86
+ }
87
+ for (const entry of entries) {
88
+ if (entry.name.startsWith('.') || skip.has(entry.name))
89
+ continue;
90
+ const full = path.join(dir, entry.name);
91
+ if (entry.isDirectory()) {
92
+ // Score directory name
93
+ const dirScore = this.fuzzyScore(entry.name.toLowerCase(), query, parts);
94
+ if (dirScore > 0) {
95
+ results.push({ file: full + '/', score: dirScore * 0.5, type: 'filename' });
96
+ }
97
+ this.searchDir(full, query, parts, results);
98
+ }
99
+ else {
100
+ // Filename match
101
+ const nameScore = this.fuzzyScore(entry.name.toLowerCase(), query, parts);
102
+ if (nameScore > 0) {
103
+ results.push({ file: full, score: nameScore, type: 'filename' });
104
+ }
105
+ // Content + symbol search for code files
106
+ const ext = path.extname(entry.name).toLowerCase();
107
+ if (!codeExts.has(ext))
108
+ continue;
109
+ try {
110
+ const content = fs.readFileSync(full, 'utf-8');
111
+ if (content.length > 500_000 || content.includes('\0'))
112
+ continue; // skip huge/binary
113
+ const lines = content.split('\n');
114
+ for (let i = 0; i < lines.length; i++) {
115
+ const line = lines[i];
116
+ const lineLower = line.toLowerCase();
117
+ // Content match
118
+ if (lineLower.includes(query)) {
119
+ results.push({ file: full, line: i + 1, text: line.trimEnd(), score: 3, type: 'content' });
120
+ }
121
+ // Symbol match (class, function, method definitions)
122
+ const symbolMatch = line.match(/(?:class|function|interface|type|const|let|var|def|fn|func)\s+(\w+)/);
123
+ if (symbolMatch) {
124
+ const symScore = this.fuzzyScore(symbolMatch[1].toLowerCase(), query, parts);
125
+ if (symScore > 0) {
126
+ results.push({ file: full, line: i + 1, text: line.trimEnd(), score: symScore * 1.5, type: 'symbol' });
127
+ }
128
+ }
129
+ }
130
+ }
131
+ catch { /* skip */ }
132
+ }
133
+ }
134
+ }
135
+ fuzzyScore(target, query, parts) {
136
+ // Exact match
137
+ if (target === query)
138
+ return 10;
139
+ // Contains full query
140
+ if (target.includes(query))
141
+ return 7;
142
+ // All parts present
143
+ if (parts.every(p => target.includes(p)))
144
+ return 5;
145
+ // Some parts present
146
+ const matched = parts.filter(p => target.includes(p)).length;
147
+ if (matched > 0)
148
+ return matched * 2;
149
+ return 0;
150
+ }
151
+ }
152
+ exports.MultiSearchTool = MultiSearchTool;
153
+ //# sourceMappingURL=multi-search.js.map
@@ -0,0 +1,38 @@
1
+ import { Tool } from '../types';
2
+ export declare class NotificationTool 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
+ };
13
+ url: {
14
+ type: string;
15
+ description: string;
16
+ };
17
+ message: {
18
+ type: string;
19
+ description: string;
20
+ };
21
+ title: {
22
+ type: string;
23
+ description: string;
24
+ };
25
+ severity: {
26
+ type: string;
27
+ description: string;
28
+ };
29
+ };
30
+ required: string[];
31
+ };
32
+ execute(args: Record<string, unknown>): Promise<string>;
33
+ private sendSlack;
34
+ private sendDiscord;
35
+ private sendGeneric;
36
+ private post;
37
+ }
38
+ //# sourceMappingURL=notification.d.ts.map
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NotificationTool = void 0;
4
+ class NotificationTool {
5
+ name = 'notification';
6
+ description = 'Send notifications via webhook (Slack, Discord, or generic). Actions: slack, discord, webhook.';
7
+ permission = 'prompt';
8
+ parameters = {
9
+ type: 'object',
10
+ properties: {
11
+ action: { type: 'string', description: 'Action: slack, discord, webhook' },
12
+ url: { type: 'string', description: 'Webhook URL' },
13
+ message: { type: 'string', description: 'Message text' },
14
+ title: { type: 'string', description: 'Optional title/subject' },
15
+ severity: { type: 'string', description: 'Severity: info, warning, error, success (affects color)' },
16
+ },
17
+ required: ['action', 'url', 'message'],
18
+ };
19
+ async execute(args) {
20
+ const action = args.action;
21
+ const url = args.url;
22
+ const message = args.message;
23
+ if (!action)
24
+ return 'Error: action is required';
25
+ if (!url)
26
+ return 'Error: url is required';
27
+ if (!message)
28
+ return 'Error: message is required';
29
+ // Validate URL
30
+ try {
31
+ new URL(url);
32
+ }
33
+ catch {
34
+ return `Error: invalid URL: ${url}`;
35
+ }
36
+ const title = args.title || '';
37
+ const severity = args.severity || 'info';
38
+ switch (action) {
39
+ case 'slack': return this.sendSlack(url, message, title, severity);
40
+ case 'discord': return this.sendDiscord(url, message, title, severity);
41
+ case 'webhook': return this.sendGeneric(url, message, title, severity);
42
+ default: return `Error: unknown action "${action}". Use: slack, discord, webhook`;
43
+ }
44
+ }
45
+ async sendSlack(url, message, title, severity) {
46
+ const colors = { info: '#2196F3', warning: '#FF9800', error: '#F44336', success: '#4CAF50' };
47
+ const payload = {
48
+ attachments: [{
49
+ color: colors[severity] || colors.info,
50
+ title: title || undefined,
51
+ text: message,
52
+ ts: Math.floor(Date.now() / 1000),
53
+ }],
54
+ };
55
+ return this.post(url, payload);
56
+ }
57
+ async sendDiscord(url, message, title, severity) {
58
+ const colors = { info: 0x2196F3, warning: 0xFF9800, error: 0xF44336, success: 0x4CAF50 };
59
+ const payload = {
60
+ embeds: [{
61
+ title: title || undefined,
62
+ description: message,
63
+ color: colors[severity] || colors.info,
64
+ timestamp: new Date().toISOString(),
65
+ }],
66
+ };
67
+ return this.post(url, payload);
68
+ }
69
+ async sendGeneric(url, message, title, severity) {
70
+ const payload = { title, message, severity, timestamp: new Date().toISOString() };
71
+ return this.post(url, payload);
72
+ }
73
+ async post(url, payload) {
74
+ const controller = new AbortController();
75
+ const timer = setTimeout(() => controller.abort(), 15_000);
76
+ try {
77
+ const res = await fetch(url, {
78
+ method: 'POST',
79
+ headers: { 'Content-Type': 'application/json' },
80
+ body: JSON.stringify(payload),
81
+ signal: controller.signal,
82
+ });
83
+ clearTimeout(timer);
84
+ if (res.ok)
85
+ return `Notification sent (${res.status}).`;
86
+ const body = await res.text().catch(() => '');
87
+ return `Error: webhook returned ${res.status} ${res.statusText}${body ? `: ${body.substring(0, 200)}` : ''}`;
88
+ }
89
+ catch (err) {
90
+ clearTimeout(timer);
91
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
92
+ }
93
+ }
94
+ }
95
+ exports.NotificationTool = NotificationTool;
96
+ //# sourceMappingURL=notification.js.map