codebot-ai 1.3.0 → 1.4.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.
Files changed (40) hide show
  1. package/README.md +16 -1
  2. package/dist/agent.js +23 -1
  3. package/dist/cli.js +1 -1
  4. package/dist/providers/anthropic.js +27 -1
  5. package/dist/providers/openai.d.ts +4 -0
  6. package/dist/providers/openai.js +54 -2
  7. package/dist/retry.d.ts +5 -0
  8. package/dist/retry.js +24 -0
  9. package/dist/tools/code-analysis.d.ts +33 -0
  10. package/dist/tools/code-analysis.js +232 -0
  11. package/dist/tools/code-review.d.ts +32 -0
  12. package/dist/tools/code-review.js +228 -0
  13. package/dist/tools/database.d.ts +35 -0
  14. package/dist/tools/database.js +129 -0
  15. package/dist/tools/diff-viewer.d.ts +39 -0
  16. package/dist/tools/diff-viewer.js +145 -0
  17. package/dist/tools/docker.d.ts +26 -0
  18. package/dist/tools/docker.js +101 -0
  19. package/dist/tools/git.d.ts +26 -0
  20. package/dist/tools/git.js +58 -0
  21. package/dist/tools/http-client.d.ts +39 -0
  22. package/dist/tools/http-client.js +114 -0
  23. package/dist/tools/image-info.d.ts +23 -0
  24. package/dist/tools/image-info.js +170 -0
  25. package/dist/tools/index.js +34 -0
  26. package/dist/tools/multi-search.d.ts +28 -0
  27. package/dist/tools/multi-search.js +153 -0
  28. package/dist/tools/notification.d.ts +38 -0
  29. package/dist/tools/notification.js +96 -0
  30. package/dist/tools/package-manager.d.ts +31 -0
  31. package/dist/tools/package-manager.js +161 -0
  32. package/dist/tools/pdf-extract.d.ts +33 -0
  33. package/dist/tools/pdf-extract.js +178 -0
  34. package/dist/tools/ssh-remote.d.ts +39 -0
  35. package/dist/tools/ssh-remote.js +84 -0
  36. package/dist/tools/task-planner.d.ts +42 -0
  37. package/dist/tools/task-planner.js +161 -0
  38. package/dist/tools/test-runner.d.ts +36 -0
  39. package/dist/tools/test-runner.js +193 -0
  40. package/package.json +1 -1
@@ -0,0 +1,228 @@
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.CodeReviewTool = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const SECURITY_PATTERNS = [
40
+ { pattern: /\beval\s*\(/, rule: 'no-eval', message: 'eval() is a security risk — allows arbitrary code execution', severity: 'error' },
41
+ { pattern: /new\s+Function\s*\(/, rule: 'no-new-function', message: 'new Function() is equivalent to eval()', severity: 'error' },
42
+ { pattern: /child_process.*exec(?!Sync)/, rule: 'unsafe-exec', message: 'exec() can be vulnerable to command injection — prefer execFile()', severity: 'warning' },
43
+ { pattern: /innerHTML\s*=/, rule: 'no-innerhtml', message: 'innerHTML is vulnerable to XSS attacks', severity: 'warning' },
44
+ { pattern: /document\.write\s*\(/, rule: 'no-document-write', message: 'document.write() is a security and performance issue', severity: 'warning' },
45
+ { pattern: /(?:password|secret|api.?key|token)\s*[:=]\s*['"][^'"]{8,}['"]/i, rule: 'hardcoded-secret', message: 'Possible hardcoded secret/credential', severity: 'error' },
46
+ { pattern: /\bsqlite3?\s.*\+\s*(?:req\.|args\.|input)/i, rule: 'sql-injection', message: 'Possible SQL injection — use parameterized queries', severity: 'error' },
47
+ { pattern: /https?:\/\/[^'"]*['"]\s*\+/, rule: 'url-injection', message: 'String concatenation in URL — possible injection', severity: 'warning' },
48
+ { pattern: /console\.(log|debug|info)\(/, rule: 'no-console', message: 'Console statement (consider removing for production)', severity: 'info' },
49
+ { pattern: /TODO|FIXME|HACK|XXX/i, rule: 'todo-comment', message: 'TODO/FIXME comment found', severity: 'info' },
50
+ ];
51
+ class CodeReviewTool {
52
+ name = 'code_review';
53
+ description = 'Review code for security issues, complexity, and code smells. Actions: security, complexity, review (full).';
54
+ permission = 'auto';
55
+ parameters = {
56
+ type: 'object',
57
+ properties: {
58
+ action: { type: 'string', description: 'Action: security (scan for vulnerabilities), complexity (function/nesting analysis), review (full review)' },
59
+ path: { type: 'string', description: 'File or directory to review' },
60
+ severity: { type: 'string', description: 'Minimum severity to report: error, warning, info (default: warning)' },
61
+ },
62
+ required: ['action', 'path'],
63
+ };
64
+ async execute(args) {
65
+ const action = args.action;
66
+ const targetPath = args.path;
67
+ if (!action)
68
+ return 'Error: action is required';
69
+ if (!targetPath)
70
+ return 'Error: path is required';
71
+ if (!fs.existsSync(targetPath))
72
+ return `Error: path not found: ${targetPath}`;
73
+ const minSeverity = args.severity || 'warning';
74
+ switch (action) {
75
+ case 'security': return this.securityScan(targetPath, minSeverity);
76
+ case 'complexity': return this.complexityAnalysis(targetPath);
77
+ case 'review': {
78
+ const sec = this.securityScan(targetPath, minSeverity);
79
+ const comp = this.complexityAnalysis(targetPath);
80
+ return `=== Security Review ===\n${sec}\n\n=== Complexity Analysis ===\n${comp}`;
81
+ }
82
+ default: return `Error: unknown action "${action}". Use: security, complexity, review`;
83
+ }
84
+ }
85
+ securityScan(targetPath, minSeverity) {
86
+ const issues = [];
87
+ const sevOrder = { error: 3, warning: 2, info: 1 };
88
+ const minLevel = sevOrder[minSeverity] || 2;
89
+ const stat = fs.statSync(targetPath);
90
+ if (stat.isFile()) {
91
+ this.scanFile(targetPath, issues);
92
+ }
93
+ else {
94
+ this.scanDir(targetPath, issues);
95
+ }
96
+ // Filter by severity
97
+ const filtered = issues.filter(i => (sevOrder[i.severity] || 0) >= minLevel);
98
+ if (filtered.length === 0)
99
+ return 'No security issues found.';
100
+ const errors = filtered.filter(i => i.severity === 'error').length;
101
+ const warnings = filtered.filter(i => i.severity === 'warning').length;
102
+ const infos = filtered.filter(i => i.severity === 'info').length;
103
+ const icons = { error: 'X', warning: '!', info: 'i' };
104
+ const lines = filtered.slice(0, 50).map(i => ` [${icons[i.severity]}] ${i.file}:${i.line} ${i.rule} — ${i.message}`);
105
+ return `Found ${filtered.length} issue(s): ${errors} errors, ${warnings} warnings, ${infos} info\n${lines.join('\n')}`;
106
+ }
107
+ complexityAnalysis(targetPath) {
108
+ const stat = fs.statSync(targetPath);
109
+ const results = [];
110
+ if (stat.isFile()) {
111
+ this.analyzeFileComplexity(targetPath, results);
112
+ }
113
+ else {
114
+ this.analyzeDir(targetPath, results);
115
+ }
116
+ if (results.length === 0)
117
+ return 'No complexity issues found.';
118
+ return results.join('\n');
119
+ }
120
+ scanFile(filePath, issues) {
121
+ const ext = path.extname(filePath);
122
+ if (!['.ts', '.js', '.tsx', '.jsx', '.py', '.go', '.rb', '.java'].includes(ext))
123
+ return;
124
+ try {
125
+ const content = fs.readFileSync(filePath, 'utf-8');
126
+ const lines = content.split('\n');
127
+ for (let i = 0; i < lines.length; i++) {
128
+ for (const check of SECURITY_PATTERNS) {
129
+ if (check.pattern.test(lines[i])) {
130
+ issues.push({
131
+ file: filePath, line: i + 1,
132
+ severity: check.severity, rule: check.rule, message: check.message,
133
+ });
134
+ }
135
+ }
136
+ }
137
+ }
138
+ catch { /* skip */ }
139
+ }
140
+ scanDir(dir, issues) {
141
+ const skip = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '__pycache__']);
142
+ let entries;
143
+ try {
144
+ entries = fs.readdirSync(dir, { withFileTypes: true });
145
+ }
146
+ catch {
147
+ return;
148
+ }
149
+ for (const entry of entries) {
150
+ if (entry.name.startsWith('.') || skip.has(entry.name))
151
+ continue;
152
+ const full = path.join(dir, entry.name);
153
+ if (entry.isDirectory()) {
154
+ this.scanDir(full, issues);
155
+ }
156
+ else {
157
+ this.scanFile(full, issues);
158
+ }
159
+ }
160
+ }
161
+ analyzeFileComplexity(filePath, results) {
162
+ const ext = path.extname(filePath);
163
+ if (!['.ts', '.js', '.tsx', '.jsx', '.py', '.go'].includes(ext))
164
+ return;
165
+ try {
166
+ const content = fs.readFileSync(filePath, 'utf-8');
167
+ const lines = content.split('\n');
168
+ let currentFunc = '';
169
+ let funcStart = 0;
170
+ let maxNesting = 0;
171
+ let currentNesting = 0;
172
+ for (let i = 0; i < lines.length; i++) {
173
+ const line = lines[i];
174
+ // Detect function starts
175
+ const funcMatch = line.match(/(?:function|async function|def|fn|func)\s+(\w+)|(\w+)\s*[:=]\s*(?:async\s+)?\(/);
176
+ if (funcMatch) {
177
+ // Report previous function if long
178
+ if (currentFunc && (i - funcStart) > 50) {
179
+ results.push(` [!] ${filePath}:${funcStart + 1} "${currentFunc}" is ${i - funcStart} lines long (consider breaking up)`);
180
+ }
181
+ currentFunc = funcMatch[1] || funcMatch[2] || '';
182
+ funcStart = i;
183
+ maxNesting = 0;
184
+ }
185
+ // Track nesting
186
+ const opens = (line.match(/[{(]/g) || []).length;
187
+ const closes = (line.match(/[})]/g) || []).length;
188
+ currentNesting += opens - closes;
189
+ if (currentNesting > maxNesting)
190
+ maxNesting = currentNesting;
191
+ if (maxNesting > 5 && currentFunc) {
192
+ results.push(` [!] ${filePath}:${i + 1} deep nesting (${maxNesting} levels) in "${currentFunc}"`);
193
+ maxNesting = 0; // Don't re-report
194
+ }
195
+ }
196
+ // Check last function
197
+ if (currentFunc && (lines.length - funcStart) > 50) {
198
+ results.push(` [!] ${filePath}:${funcStart + 1} "${currentFunc}" is ${lines.length - funcStart} lines long`);
199
+ }
200
+ // File-level checks
201
+ if (lines.length > 500) {
202
+ results.push(` [i] ${filePath}: ${lines.length} lines — consider splitting into modules`);
203
+ }
204
+ }
205
+ catch { /* skip */ }
206
+ }
207
+ analyzeDir(dir, results) {
208
+ const skip = new Set(['node_modules', '.git', 'dist', 'build', 'coverage']);
209
+ let entries;
210
+ try {
211
+ entries = fs.readdirSync(dir, { withFileTypes: true });
212
+ }
213
+ catch {
214
+ return;
215
+ }
216
+ for (const entry of entries) {
217
+ if (entry.name.startsWith('.') || skip.has(entry.name))
218
+ continue;
219
+ const full = path.join(dir, entry.name);
220
+ if (entry.isDirectory())
221
+ this.analyzeDir(full, results);
222
+ else
223
+ this.analyzeFileComplexity(full, results);
224
+ }
225
+ }
226
+ }
227
+ exports.CodeReviewTool = CodeReviewTool;
228
+ //# sourceMappingURL=code-review.js.map
@@ -0,0 +1,35 @@
1
+ import { Tool } from '../types';
2
+ export declare class DatabaseTool 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
+ db: {
14
+ type: string;
15
+ description: string;
16
+ };
17
+ sql: {
18
+ type: string;
19
+ description: string;
20
+ };
21
+ table: {
22
+ type: string;
23
+ description: string;
24
+ };
25
+ };
26
+ required: string[];
27
+ };
28
+ execute(args: Record<string, unknown>): Promise<string>;
29
+ private runQuery;
30
+ private listTables;
31
+ private showSchema;
32
+ private dbInfo;
33
+ private sqlite;
34
+ }
35
+ //# sourceMappingURL=database.d.ts.map
@@ -0,0 +1,129 @@
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.DatabaseTool = void 0;
37
+ const child_process_1 = require("child_process");
38
+ const fs = __importStar(require("fs"));
39
+ const BLOCKED_SQL = [
40
+ /\bDROP\s+(TABLE|DATABASE|INDEX|VIEW)\b/i,
41
+ /\bDELETE\s+FROM\b/i,
42
+ /\bTRUNCATE\b/i,
43
+ /\bALTER\s+TABLE\s+.*\bDROP\b/i,
44
+ ];
45
+ class DatabaseTool {
46
+ name = 'database';
47
+ description = 'Query SQLite databases. Actions: query, tables, schema, info. Blocks DROP/DELETE/TRUNCATE for safety.';
48
+ permission = 'prompt';
49
+ parameters = {
50
+ type: 'object',
51
+ properties: {
52
+ action: { type: 'string', description: 'Action: query, tables, schema, info' },
53
+ db: { type: 'string', description: 'Path to SQLite database file' },
54
+ sql: { type: 'string', description: 'SQL query to execute (for "query" action)' },
55
+ table: { type: 'string', description: 'Table name (for "schema" action)' },
56
+ },
57
+ required: ['action', 'db'],
58
+ };
59
+ async execute(args) {
60
+ const action = args.action;
61
+ const dbPath = args.db;
62
+ if (!action)
63
+ return 'Error: action is required';
64
+ if (!dbPath)
65
+ return 'Error: db path is required';
66
+ if (!fs.existsSync(dbPath))
67
+ return `Error: database not found: ${dbPath}`;
68
+ switch (action) {
69
+ case 'query': return this.runQuery(dbPath, args);
70
+ case 'tables': return this.listTables(dbPath);
71
+ case 'schema': return this.showSchema(dbPath, args);
72
+ case 'info': return this.dbInfo(dbPath);
73
+ default: return `Error: unknown action "${action}". Use: query, tables, schema, info`;
74
+ }
75
+ }
76
+ runQuery(dbPath, args) {
77
+ const sql = args.sql;
78
+ if (!sql)
79
+ return 'Error: sql is required for query';
80
+ // Block destructive queries
81
+ for (const pattern of BLOCKED_SQL) {
82
+ if (pattern.test(sql)) {
83
+ return `Error: destructive SQL blocked for safety. Pattern matched: ${pattern.source}`;
84
+ }
85
+ }
86
+ return this.sqlite(dbPath, sql);
87
+ }
88
+ listTables(dbPath) {
89
+ return this.sqlite(dbPath, ".tables");
90
+ }
91
+ showSchema(dbPath, args) {
92
+ const table = args.table;
93
+ if (table) {
94
+ // Sanitize table name
95
+ if (!/^[a-zA-Z_]\w*$/.test(table))
96
+ return 'Error: invalid table name';
97
+ return this.sqlite(dbPath, `.schema ${table}`);
98
+ }
99
+ return this.sqlite(dbPath, ".schema");
100
+ }
101
+ dbInfo(dbPath) {
102
+ const stat = fs.statSync(dbPath);
103
+ const sizeMB = (stat.size / (1024 * 1024)).toFixed(2);
104
+ const tables = this.sqlite(dbPath, "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;");
105
+ return `Database: ${dbPath}\nSize: ${sizeMB} MB\nModified: ${stat.mtime.toISOString()}\n\nTables:\n${tables}`;
106
+ }
107
+ sqlite(dbPath, command) {
108
+ try {
109
+ // Try sqlite3 CLI first
110
+ const output = (0, child_process_1.execSync)(`sqlite3 "${dbPath}" "${command.replace(/"/g, '\\"')}"`, {
111
+ timeout: 15_000,
112
+ maxBuffer: 1024 * 1024,
113
+ encoding: 'utf-8',
114
+ stdio: ['pipe', 'pipe', 'pipe'],
115
+ });
116
+ return output.trim() || '(no results)';
117
+ }
118
+ catch (err) {
119
+ const e = err;
120
+ const msg = (e.stderr || '').trim();
121
+ if (msg.includes('not found')) {
122
+ return 'Error: sqlite3 is not installed. Install it with: brew install sqlite (macOS) or apt install sqlite3 (Linux)';
123
+ }
124
+ return `Error: ${msg || 'query failed'}`;
125
+ }
126
+ }
127
+ }
128
+ exports.DatabaseTool = DatabaseTool;
129
+ //# sourceMappingURL=database.js.map
@@ -0,0 +1,39 @@
1
+ import { Tool } from '../types';
2
+ export declare class DiffViewerTool 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
+ file_a: {
14
+ type: string;
15
+ description: string;
16
+ };
17
+ file_b: {
18
+ type: string;
19
+ description: string;
20
+ };
21
+ path: {
22
+ type: string;
23
+ description: string;
24
+ };
25
+ ref: {
26
+ type: string;
27
+ description: string;
28
+ };
29
+ };
30
+ required: string[];
31
+ };
32
+ execute(args: Record<string, unknown>): Promise<string>;
33
+ private diffFiles;
34
+ private gitDiff;
35
+ private gitStaged;
36
+ private gitCommitDiff;
37
+ private runGit;
38
+ }
39
+ //# sourceMappingURL=diff-viewer.d.ts.map
@@ -0,0 +1,145 @@
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.DiffViewerTool = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const child_process_1 = require("child_process");
39
+ class DiffViewerTool {
40
+ name = 'diff_viewer';
41
+ description = 'View diffs. Actions: files (compare two files), git_diff (working tree changes), staged (staged changes), commit (show a commit diff).';
42
+ permission = 'auto';
43
+ parameters = {
44
+ type: 'object',
45
+ properties: {
46
+ action: { type: 'string', description: 'Action: files, git_diff, staged, commit' },
47
+ file_a: { type: 'string', description: 'First file path (for "files" action)' },
48
+ file_b: { type: 'string', description: 'Second file path (for "files" action)' },
49
+ path: { type: 'string', description: 'File or directory to diff (for git_diff/staged)' },
50
+ ref: { type: 'string', description: 'Commit hash or ref (for "commit" action)' },
51
+ },
52
+ required: ['action'],
53
+ };
54
+ async execute(args) {
55
+ const action = args.action;
56
+ if (!action)
57
+ return 'Error: action is required';
58
+ switch (action) {
59
+ case 'files': return this.diffFiles(args);
60
+ case 'git_diff': return this.gitDiff(args);
61
+ case 'staged': return this.gitStaged(args);
62
+ case 'commit': return this.gitCommitDiff(args);
63
+ default: return `Error: unknown action "${action}". Use: files, git_diff, staged, commit`;
64
+ }
65
+ }
66
+ diffFiles(args) {
67
+ const fileA = args.file_a;
68
+ const fileB = args.file_b;
69
+ if (!fileA || !fileB)
70
+ return 'Error: file_a and file_b are required';
71
+ let contentA, contentB;
72
+ try {
73
+ contentA = fs.readFileSync(fileA, 'utf-8');
74
+ }
75
+ catch {
76
+ return `Error: cannot read ${fileA}`;
77
+ }
78
+ try {
79
+ contentB = fs.readFileSync(fileB, 'utf-8');
80
+ }
81
+ catch {
82
+ return `Error: cannot read ${fileB}`;
83
+ }
84
+ const linesA = contentA.split('\n');
85
+ const linesB = contentB.split('\n');
86
+ const diff = [`--- ${fileA}`, `+++ ${fileB}`];
87
+ // Simple line-by-line diff
88
+ const maxLen = Math.max(linesA.length, linesB.length);
89
+ let changes = 0;
90
+ for (let i = 0; i < maxLen; i++) {
91
+ const a = linesA[i];
92
+ const b = linesB[i];
93
+ if (a === undefined && b !== undefined) {
94
+ diff.push(`+${i + 1}: ${b}`);
95
+ changes++;
96
+ }
97
+ else if (b === undefined && a !== undefined) {
98
+ diff.push(`-${i + 1}: ${a}`);
99
+ changes++;
100
+ }
101
+ else if (a !== b) {
102
+ diff.push(`-${i + 1}: ${a}`);
103
+ diff.push(`+${i + 1}: ${b}`);
104
+ changes++;
105
+ }
106
+ }
107
+ if (changes === 0)
108
+ return 'Files are identical.';
109
+ return `${changes} line(s) differ:\n${diff.join('\n')}`;
110
+ }
111
+ gitDiff(args) {
112
+ const target = args.path || '';
113
+ return this.runGit(`diff ${target}`.trim());
114
+ }
115
+ gitStaged(args) {
116
+ const target = args.path || '';
117
+ return this.runGit(`diff --staged ${target}`.trim());
118
+ }
119
+ gitCommitDiff(args) {
120
+ const ref = args.ref;
121
+ if (!ref)
122
+ return 'Error: ref (commit hash) is required';
123
+ // Sanitize ref
124
+ if (!/^[a-zA-Z0-9_\-./~^]+$/.test(ref))
125
+ return 'Error: invalid ref format';
126
+ return this.runGit(`show --stat --patch ${ref}`);
127
+ }
128
+ runGit(cmd) {
129
+ try {
130
+ const output = (0, child_process_1.execSync)(`git ${cmd}`, {
131
+ timeout: 15_000,
132
+ maxBuffer: 1024 * 1024,
133
+ encoding: 'utf-8',
134
+ stdio: ['pipe', 'pipe', 'pipe'],
135
+ });
136
+ return output.trim() || 'No changes.';
137
+ }
138
+ catch (err) {
139
+ const e = err;
140
+ return `Error: ${(e.stderr || 'git command failed').trim()}`;
141
+ }
142
+ }
143
+ }
144
+ exports.DiffViewerTool = DiffViewerTool;
145
+ //# sourceMappingURL=diff-viewer.js.map
@@ -0,0 +1,26 @@
1
+ import { Tool } from '../types';
2
+ export declare class DockerTool 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
+ args: {
14
+ type: string;
15
+ description: string;
16
+ };
17
+ cwd: {
18
+ type: string;
19
+ description: string;
20
+ };
21
+ };
22
+ required: string[];
23
+ };
24
+ execute(args: Record<string, unknown>): Promise<string>;
25
+ }
26
+ //# sourceMappingURL=docker.d.ts.map