prab-cli 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.
@@ -0,0 +1,283 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.GrepTool = exports.GlobTool = exports.EditFileTool = exports.WriteFileTool = exports.ReadFileTool = void 0;
7
+ const zod_1 = require("zod");
8
+ const base_1 = require("./base");
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const glob_1 = require("glob");
12
+ const diff_1 = require("diff");
13
+ /**
14
+ * Read a file with line numbers
15
+ */
16
+ class ReadFileTool extends base_1.Tool {
17
+ constructor() {
18
+ super(...arguments);
19
+ this.name = 'read_file';
20
+ this.description = 'Read the contents of a file with line numbers. Supports pagination for large files.';
21
+ this.requiresConfirmation = false;
22
+ this.destructive = false;
23
+ this.schema = zod_1.z.object({
24
+ file_path: zod_1.z.string().describe('Absolute or relative path to the file to read'),
25
+ offset: zod_1.z.number().optional().describe('Line number to start reading from (1-indexed)'),
26
+ limit: zod_1.z.number().optional().describe('Maximum number of lines to read')
27
+ });
28
+ }
29
+ async execute(params) {
30
+ try {
31
+ const filePath = path_1.default.resolve(params.file_path);
32
+ if (!fs_1.default.existsSync(filePath)) {
33
+ return this.error(`File not found: ${params.file_path}`);
34
+ }
35
+ if (fs_1.default.statSync(filePath).isDirectory()) {
36
+ return this.error(`Path is a directory: ${params.file_path}`);
37
+ }
38
+ const content = fs_1.default.readFileSync(filePath, 'utf-8');
39
+ const lines = content.split('\n');
40
+ const startLine = params.offset ? params.offset - 1 : 0;
41
+ const endLine = params.limit ? startLine + params.limit : lines.length;
42
+ const selectedLines = lines.slice(startLine, endLine);
43
+ // Format with line numbers (1-indexed)
44
+ const numberedLines = selectedLines.map((line, idx) => {
45
+ const lineNum = startLine + idx + 1;
46
+ return `${lineNum.toString().padStart(6)}→${line}`;
47
+ }).join('\n');
48
+ const totalLines = lines.length;
49
+ const showing = `Showing lines ${startLine + 1}-${Math.min(endLine, totalLines)} of ${totalLines}`;
50
+ return this.success(`${params.file_path}\n${showing}\n\n${numberedLines}`, { totalLines, startLine: startLine + 1, endLine: Math.min(endLine, totalLines) });
51
+ }
52
+ catch (error) {
53
+ return this.error(`Failed to read file: ${error.message}`);
54
+ }
55
+ }
56
+ }
57
+ exports.ReadFileTool = ReadFileTool;
58
+ /**
59
+ * Write content to a file (create or overwrite)
60
+ */
61
+ class WriteFileTool extends base_1.Tool {
62
+ constructor() {
63
+ super(...arguments);
64
+ this.name = 'write_file';
65
+ this.description = 'Write content to a file. Creates the file if it doesn\'t exist, overwrites if it does. Creates parent directories as needed.';
66
+ this.requiresConfirmation = true;
67
+ this.destructive = true;
68
+ this.schema = zod_1.z.object({
69
+ file_path: zod_1.z.string().describe('Absolute or relative path to the file to write'),
70
+ content: zod_1.z.string().describe('Content to write to the file')
71
+ });
72
+ }
73
+ async execute(params) {
74
+ try {
75
+ const filePath = path_1.default.resolve(params.file_path);
76
+ const dir = path_1.default.dirname(filePath);
77
+ // Create parent directories if needed
78
+ if (!fs_1.default.existsSync(dir)) {
79
+ fs_1.default.mkdirSync(dir, { recursive: true });
80
+ }
81
+ const fileExists = fs_1.default.existsSync(filePath);
82
+ fs_1.default.writeFileSync(filePath, params.content, 'utf-8');
83
+ const action = fileExists ? 'Updated' : 'Created';
84
+ const lines = params.content.split('\n').length;
85
+ return this.success(`${action} ${params.file_path} (${lines} lines)`, { action, path: filePath, lines });
86
+ }
87
+ catch (error) {
88
+ return this.error(`Failed to write file: ${error.message}`);
89
+ }
90
+ }
91
+ }
92
+ exports.WriteFileTool = WriteFileTool;
93
+ /**
94
+ * Edit a file by finding and replacing text
95
+ */
96
+ class EditFileTool extends base_1.Tool {
97
+ constructor() {
98
+ super(...arguments);
99
+ this.name = 'edit_file';
100
+ this.description = 'Edit a file by finding and replacing text. Shows a diff preview. Supports replace all option.';
101
+ this.requiresConfirmation = true;
102
+ this.destructive = true;
103
+ this.schema = zod_1.z.object({
104
+ file_path: zod_1.z.string().describe('Absolute or relative path to the file to edit'),
105
+ search: zod_1.z.string().describe('Text to search for (exact match)'),
106
+ replace: zod_1.z.string().describe('Text to replace with'),
107
+ replace_all: zod_1.z.boolean().optional().describe('Replace all occurrences (default: false)')
108
+ });
109
+ }
110
+ async execute(params) {
111
+ try {
112
+ const filePath = path_1.default.resolve(params.file_path);
113
+ if (!fs_1.default.existsSync(filePath)) {
114
+ return this.error(`File not found: ${params.file_path}`);
115
+ }
116
+ const originalContent = fs_1.default.readFileSync(filePath, 'utf-8');
117
+ // Perform replacement
118
+ let newContent;
119
+ if (params.replace_all) {
120
+ newContent = originalContent.split(params.search).join(params.replace);
121
+ }
122
+ else {
123
+ newContent = originalContent.replace(params.search, params.replace);
124
+ }
125
+ if (originalContent === newContent) {
126
+ return this.error(`Search text not found in file: "${params.search}"`);
127
+ }
128
+ // Generate diff
129
+ const diff = (0, diff_1.diffLines)(originalContent, newContent);
130
+ const diffOutput = diff.map(part => {
131
+ const prefix = part.added ? '+ ' : part.removed ? '- ' : ' ';
132
+ return part.value.split('\n').map(line => line ? prefix + line : '').join('\n');
133
+ }).join('');
134
+ // Write the changes
135
+ fs_1.default.writeFileSync(filePath, newContent, 'utf-8');
136
+ const occurrences = params.replace_all
137
+ ? originalContent.split(params.search).length - 1
138
+ : 1;
139
+ return this.success(`Edited ${params.file_path} (${occurrences} replacement${occurrences > 1 ? 's' : ''})\n\nDiff:\n${diffOutput}`, { occurrences, path: filePath });
140
+ }
141
+ catch (error) {
142
+ return this.error(`Failed to edit file: ${error.message}`);
143
+ }
144
+ }
145
+ }
146
+ exports.EditFileTool = EditFileTool;
147
+ /**
148
+ * Find files matching a glob pattern
149
+ */
150
+ class GlobTool extends base_1.Tool {
151
+ constructor() {
152
+ super(...arguments);
153
+ this.name = 'glob';
154
+ this.description = 'Find files matching a glob pattern (e.g., "**/*.ts", "src/**/*.js"). Returns paths sorted by modification time.';
155
+ this.requiresConfirmation = false;
156
+ this.destructive = false;
157
+ this.schema = zod_1.z.object({
158
+ pattern: zod_1.z.string().describe('Glob pattern to match files (e.g., "**/*.ts")'),
159
+ path: zod_1.z.string().optional().describe('Directory to search in (default: current directory)')
160
+ });
161
+ }
162
+ async execute(params) {
163
+ try {
164
+ const cwd = params.path ? path_1.default.resolve(params.path) : process.cwd();
165
+ const ignore = ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/.env', '**/*.lock'];
166
+ const files = await (0, glob_1.glob)(params.pattern, {
167
+ cwd,
168
+ ignore,
169
+ nodir: true
170
+ });
171
+ // Sort by modification time (most recent first)
172
+ const filesWithStats = files.map(file => {
173
+ const fullPath = path_1.default.join(cwd, file);
174
+ const stats = fs_1.default.statSync(fullPath);
175
+ return { file, mtime: stats.mtime.getTime() };
176
+ });
177
+ filesWithStats.sort((a, b) => b.mtime - a.mtime);
178
+ const sortedFiles = filesWithStats.map(f => f.file);
179
+ if (sortedFiles.length === 0) {
180
+ return this.success(`No files found matching pattern: ${params.pattern}`);
181
+ }
182
+ const output = sortedFiles.join('\n');
183
+ return this.success(`Found ${sortedFiles.length} file(s) matching "${params.pattern}":\n${output}`, { count: sortedFiles.length, files: sortedFiles });
184
+ }
185
+ catch (error) {
186
+ return this.error(`Glob search failed: ${error.message}`);
187
+ }
188
+ }
189
+ }
190
+ exports.GlobTool = GlobTool;
191
+ /**
192
+ * Search file contents with regex
193
+ */
194
+ class GrepTool extends base_1.Tool {
195
+ constructor() {
196
+ super(...arguments);
197
+ this.name = 'grep';
198
+ this.description = 'Search file contents using regex patterns. Supports multiple output modes and context lines.';
199
+ this.requiresConfirmation = false;
200
+ this.destructive = false;
201
+ this.schema = zod_1.z.object({
202
+ pattern: zod_1.z.string().describe('Regular expression pattern to search for'),
203
+ path: zod_1.z.string().optional().describe('Directory or file to search in (default: current directory)'),
204
+ glob_filter: zod_1.z.string().optional().describe('Glob pattern to filter files (e.g., "*.ts")'),
205
+ output_mode: zod_1.z.enum(['content', 'files', 'count']).optional().describe('Output mode: content (matching lines), files (file paths), count (match counts)'),
206
+ case_insensitive: zod_1.z.boolean().optional().describe('Case insensitive search (default: false)'),
207
+ context_lines: zod_1.z.number().optional().describe('Number of context lines to show before and after matches')
208
+ });
209
+ }
210
+ async execute(params) {
211
+ try {
212
+ const searchPath = params.path ? path_1.default.resolve(params.path) : process.cwd();
213
+ const outputMode = params.output_mode || 'files';
214
+ // Determine files to search
215
+ let filesToSearch;
216
+ if (fs_1.default.existsSync(searchPath) && fs_1.default.statSync(searchPath).isFile()) {
217
+ filesToSearch = [searchPath];
218
+ }
219
+ else {
220
+ const globPattern = params.glob_filter || '**/*';
221
+ const ignore = ['**/node_modules/**', '**/.git/**', '**/dist/**'];
222
+ filesToSearch = await (0, glob_1.glob)(globPattern, {
223
+ cwd: searchPath,
224
+ ignore,
225
+ nodir: true,
226
+ absolute: true
227
+ });
228
+ }
229
+ // Create regex
230
+ const flags = params.case_insensitive ? 'gi' : 'g';
231
+ const regex = new RegExp(params.pattern, flags);
232
+ const results = [];
233
+ // Search files
234
+ for (const file of filesToSearch) {
235
+ try {
236
+ const content = fs_1.default.readFileSync(file, 'utf-8');
237
+ const lines = content.split('\n');
238
+ const matches = [];
239
+ lines.forEach((line, idx) => {
240
+ if (regex.test(line)) {
241
+ matches.push({ line: idx + 1, content: line });
242
+ }
243
+ // Reset regex lastIndex for global flag
244
+ regex.lastIndex = 0;
245
+ });
246
+ if (matches.length > 0) {
247
+ results.push({ file, matches });
248
+ }
249
+ }
250
+ catch (e) {
251
+ // Skip files that can't be read (binary, permissions, etc.)
252
+ continue;
253
+ }
254
+ }
255
+ // Format output based on mode
256
+ if (outputMode === 'files') {
257
+ const fileList = results.map(r => r.file).join('\n');
258
+ return this.success(`Found matches in ${results.length} file(s):\n${fileList}`, { count: results.length, files: results.map(r => r.file) });
259
+ }
260
+ else if (outputMode === 'count') {
261
+ const counts = results.map(r => `${r.file}: ${r.matches.length} matches`).join('\n');
262
+ const total = results.reduce((sum, r) => sum + r.matches.length, 0);
263
+ return this.success(`Total matches: ${total}\n${counts}`, { total, breakdown: results.map(r => ({ file: r.file, count: r.matches.length })) });
264
+ }
265
+ else {
266
+ // content mode
267
+ const contextLines = params.context_lines || 0;
268
+ let output = '';
269
+ for (const result of results) {
270
+ output += `\n${result.file}:\n`;
271
+ for (const match of result.matches) {
272
+ output += ` ${match.line}: ${match.content}\n`;
273
+ }
274
+ }
275
+ return this.success(`Found ${results.reduce((sum, r) => sum + r.matches.length, 0)} match(es) across ${results.length} file(s):${output}`, { matchCount: results.reduce((sum, r) => sum + r.matches.length, 0), fileCount: results.length });
276
+ }
277
+ }
278
+ catch (error) {
279
+ return this.error(`Grep search failed: ${error.message}`);
280
+ }
281
+ }
282
+ }
283
+ exports.GrepTool = GrepTool;
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.GitPushTool = exports.GitBranchTool = exports.GitCommitTool = exports.GitLogTool = exports.GitDiffTool = exports.GitAddTool = exports.GitStatusTool = void 0;
7
+ const zod_1 = require("zod");
8
+ const base_1 = require("./base");
9
+ const simple_git_1 = __importDefault(require("simple-git"));
10
+ const git = (0, simple_git_1.default)();
11
+ /**
12
+ * Get git status
13
+ */
14
+ class GitStatusTool extends base_1.Tool {
15
+ constructor() {
16
+ super(...arguments);
17
+ this.name = 'git_status';
18
+ this.description = 'Show the working tree status - staged, unstaged, and untracked files.';
19
+ this.requiresConfirmation = false;
20
+ this.destructive = false;
21
+ this.schema = zod_1.z.object({});
22
+ }
23
+ async execute(params) {
24
+ try {
25
+ const status = await git.status();
26
+ const output = [];
27
+ output.push(`Branch: ${status.current || '(detached HEAD)'}`);
28
+ output.push(`Ahead: ${status.ahead}, Behind: ${status.behind}`);
29
+ if (status.files.length === 0) {
30
+ output.push('\nWorking tree clean');
31
+ }
32
+ else {
33
+ if (status.staged.length > 0) {
34
+ output.push('\nStaged files:');
35
+ status.staged.forEach(file => output.push(` + ${file}`));
36
+ }
37
+ if (status.modified.length > 0 || status.deleted.length > 0) {
38
+ output.push('\nUnstaged changes:');
39
+ status.modified.forEach(file => output.push(` M ${file}`));
40
+ status.deleted.forEach(file => output.push(` D ${file}`));
41
+ }
42
+ if (status.not_added.length > 0) {
43
+ output.push('\nUntracked files:');
44
+ status.not_added.forEach(file => output.push(` ? ${file}`));
45
+ }
46
+ }
47
+ return this.success(output.join('\n'), { status });
48
+ }
49
+ catch (error) {
50
+ return this.error(`Git status failed: ${error.message}`);
51
+ }
52
+ }
53
+ }
54
+ exports.GitStatusTool = GitStatusTool;
55
+ /**
56
+ * Stage files for commit (git add)
57
+ */
58
+ class GitAddTool extends base_1.Tool {
59
+ constructor() {
60
+ super(...arguments);
61
+ this.name = 'git_add';
62
+ this.description = 'Stage files for commit. Use "." to stage all changes, or specify specific files.';
63
+ this.requiresConfirmation = false;
64
+ this.destructive = false;
65
+ this.schema = zod_1.z.object({
66
+ files: zod_1.z.array(zod_1.z.string()).describe('Files to stage. Use ["."] to stage all changes.')
67
+ });
68
+ }
69
+ async execute(params) {
70
+ try {
71
+ await git.add(params.files);
72
+ // Get status to show what was staged
73
+ const status = await git.status();
74
+ const stagedFiles = status.staged;
75
+ return this.success(`Staged ${stagedFiles.length} file(s):\n${stagedFiles.map(f => ` + ${f}`).join('\n')}`, { staged: stagedFiles });
76
+ }
77
+ catch (error) {
78
+ return this.error(`Git add failed: ${error.message}`);
79
+ }
80
+ }
81
+ }
82
+ exports.GitAddTool = GitAddTool;
83
+ /**
84
+ * Show git diff
85
+ */
86
+ class GitDiffTool extends base_1.Tool {
87
+ constructor() {
88
+ super(...arguments);
89
+ this.name = 'git_diff';
90
+ this.description = 'Show changes in files. Can show staged or unstaged changes, or changes for specific files.';
91
+ this.requiresConfirmation = false;
92
+ this.destructive = false;
93
+ this.schema = zod_1.z.object({
94
+ files: zod_1.z.array(zod_1.z.string()).optional().describe('Specific files to show diff for'),
95
+ staged: zod_1.z.boolean().optional().describe('Show staged changes (default: false, shows unstaged)')
96
+ });
97
+ }
98
+ async execute(params) {
99
+ try {
100
+ const options = params.staged ? ['--cached'] : [];
101
+ if (params.files && params.files.length > 0) {
102
+ options.push('--', ...params.files);
103
+ }
104
+ const diff = await git.diff(options);
105
+ if (!diff || diff.trim().length === 0) {
106
+ return this.success('No changes to show');
107
+ }
108
+ return this.success(diff, { staged: params.staged || false });
109
+ }
110
+ catch (error) {
111
+ return this.error(`Git diff failed: ${error.message}`);
112
+ }
113
+ }
114
+ }
115
+ exports.GitDiffTool = GitDiffTool;
116
+ /**
117
+ * Show git log
118
+ */
119
+ class GitLogTool extends base_1.Tool {
120
+ constructor() {
121
+ super(...arguments);
122
+ this.name = 'git_log';
123
+ this.description = 'Show commit history with messages, authors, and dates.';
124
+ this.requiresConfirmation = false;
125
+ this.destructive = false;
126
+ this.schema = zod_1.z.object({
127
+ limit: zod_1.z.number().optional().describe('Maximum number of commits to show (default: 10)'),
128
+ branch: zod_1.z.string().optional().describe('Branch to show log for (default: current branch)')
129
+ });
130
+ }
131
+ async execute(params) {
132
+ try {
133
+ const options = {
134
+ maxCount: params.limit || 10
135
+ };
136
+ if (params.branch) {
137
+ options.file = params.branch;
138
+ }
139
+ const log = await git.log(options);
140
+ const output = log.all.map(commit => {
141
+ return `${commit.hash.substring(0, 7)} ${commit.date}\n ${commit.message}`;
142
+ }).join('\n\n');
143
+ return this.success(output || 'No commits found', { count: log.all.length, commits: log.all });
144
+ }
145
+ catch (error) {
146
+ return this.error(`Git log failed: ${error.message}`);
147
+ }
148
+ }
149
+ }
150
+ exports.GitLogTool = GitLogTool;
151
+ /**
152
+ * Create a git commit
153
+ */
154
+ class GitCommitTool extends base_1.Tool {
155
+ constructor() {
156
+ super(...arguments);
157
+ this.name = 'git_commit';
158
+ this.description = 'Create a git commit with staged changes. Adds Claude attribution to commit message.';
159
+ this.requiresConfirmation = true;
160
+ this.destructive = true;
161
+ this.schema = zod_1.z.object({
162
+ message: zod_1.z.string().describe('Commit message'),
163
+ files: zod_1.z.array(zod_1.z.string()).optional().describe('Files to stage before committing (if not already staged)')
164
+ });
165
+ }
166
+ async execute(params) {
167
+ try {
168
+ // Stage files if specified
169
+ if (params.files && params.files.length > 0) {
170
+ await git.add(params.files);
171
+ }
172
+ // Add Claude attribution
173
+ const fullMessage = `${params.message}\n\n🤖 Generated with CLI AI Tool\n\nCo-Authored-By: AI Assistant <noreply@example.com>`;
174
+ // Create commit
175
+ const result = await git.commit(fullMessage);
176
+ return this.success(`Created commit: ${result.commit}\n${result.summary.changes} changes, ${result.summary.insertions} insertions, ${result.summary.deletions} deletions`, { commit: result.commit, summary: result.summary });
177
+ }
178
+ catch (error) {
179
+ return this.error(`Git commit failed: ${error.message}`);
180
+ }
181
+ }
182
+ }
183
+ exports.GitCommitTool = GitCommitTool;
184
+ /**
185
+ * Manage git branches
186
+ */
187
+ class GitBranchTool extends base_1.Tool {
188
+ constructor() {
189
+ super(...arguments);
190
+ this.name = 'git_branch';
191
+ this.description = 'List, create, switch, or delete git branches.';
192
+ this.requiresConfirmation = true;
193
+ this.destructive = true;
194
+ this.schema = zod_1.z.object({
195
+ action: zod_1.z.enum(['list', 'create', 'switch', 'delete']).describe('Action to perform'),
196
+ name: zod_1.z.string().optional().describe('Branch name (required for create, switch, delete)')
197
+ });
198
+ }
199
+ async execute(params) {
200
+ try {
201
+ switch (params.action) {
202
+ case 'list': {
203
+ const branches = await git.branchLocal();
204
+ const output = branches.all.map(branch => {
205
+ return branch === branches.current ? `* ${branch}` : ` ${branch}`;
206
+ }).join('\n');
207
+ return this.success(`Branches:\n${output}`, { current: branches.current, all: branches.all });
208
+ }
209
+ case 'create': {
210
+ if (!params.name) {
211
+ return this.error('Branch name is required for create action');
212
+ }
213
+ await git.checkoutLocalBranch(params.name);
214
+ return this.success(`Created and switched to branch: ${params.name}`);
215
+ }
216
+ case 'switch': {
217
+ if (!params.name) {
218
+ return this.error('Branch name is required for switch action');
219
+ }
220
+ await git.checkout(params.name);
221
+ return this.success(`Switched to branch: ${params.name}`);
222
+ }
223
+ case 'delete': {
224
+ if (!params.name) {
225
+ return this.error('Branch name is required for delete action');
226
+ }
227
+ await git.deleteLocalBranch(params.name);
228
+ return this.success(`Deleted branch: ${params.name}`);
229
+ }
230
+ default:
231
+ return this.error(`Unknown action: ${params.action}`);
232
+ }
233
+ }
234
+ catch (error) {
235
+ return this.error(`Git branch operation failed: ${error.message}`);
236
+ }
237
+ }
238
+ }
239
+ exports.GitBranchTool = GitBranchTool;
240
+ /**
241
+ * Push to remote
242
+ */
243
+ class GitPushTool extends base_1.Tool {
244
+ constructor() {
245
+ super(...arguments);
246
+ this.name = 'git_push';
247
+ this.description = 'Push commits to remote repository. Use with caution, especially with force flag.';
248
+ this.requiresConfirmation = true;
249
+ this.destructive = true;
250
+ this.schema = zod_1.z.object({
251
+ branch: zod_1.z.string().optional().describe('Branch to push (default: current branch)'),
252
+ force: zod_1.z.boolean().optional().describe('Force push (use with extreme caution, default: false)'),
253
+ set_upstream: zod_1.z.boolean().optional().describe('Set upstream tracking (default: false)')
254
+ });
255
+ }
256
+ async execute(params) {
257
+ try {
258
+ // Warn about force push to main/master
259
+ if (params.force && (params.branch === 'main' || params.branch === 'master')) {
260
+ return this.error('Force push to main/master branch is blocked for safety. ' +
261
+ 'Please run this manually if absolutely necessary.');
262
+ }
263
+ const options = [];
264
+ if (params.force) {
265
+ options.push('--force');
266
+ }
267
+ if (params.set_upstream) {
268
+ options.push('--set-upstream');
269
+ }
270
+ const remote = 'origin';
271
+ const branch = params.branch || (await git.branchLocal()).current;
272
+ await git.push(remote, branch, options);
273
+ return this.success(`Pushed ${branch} to ${remote}${params.force ? ' (force)' : ''}`, { remote, branch, force: params.force || false });
274
+ }
275
+ catch (error) {
276
+ return this.error(`Git push failed: ${error.message}`);
277
+ }
278
+ }
279
+ }
280
+ exports.GitPushTool = GitPushTool;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BashTool = void 0;
4
+ const zod_1 = require("zod");
5
+ const base_1 = require("./base");
6
+ const child_process_1 = require("child_process");
7
+ const util_1 = require("util");
8
+ const execPromise = (0, util_1.promisify)(child_process_1.exec);
9
+ /**
10
+ * Execute bash commands
11
+ */
12
+ class BashTool extends base_1.Tool {
13
+ constructor() {
14
+ super(...arguments);
15
+ this.name = 'bash';
16
+ this.description = 'Execute bash/shell commands with timeout. Use for running terminal commands, scripts, or system operations.';
17
+ this.requiresConfirmation = true;
18
+ this.destructive = true;
19
+ this.schema = zod_1.z.object({
20
+ command: zod_1.z.string().describe('The shell command to execute'),
21
+ description: zod_1.z.string().optional().describe('Brief description of what this command does'),
22
+ timeout: zod_1.z.number().optional().describe('Timeout in milliseconds (default: 120000ms, max: 600000ms)')
23
+ });
24
+ // Dangerous commands that always require explicit confirmation
25
+ this.dangerousPatterns = [
26
+ /rm\s+-rf\s+\//,
27
+ /mkfs/,
28
+ /dd\s+if=/,
29
+ />\s*\/dev\/sd/,
30
+ /chmod\s+-R\s+777/,
31
+ /chown\s+-R/,
32
+ /:(){ :|:& };:/ // Fork bomb
33
+ ];
34
+ }
35
+ isDangerous(command) {
36
+ return this.dangerousPatterns.some(pattern => pattern.test(command));
37
+ }
38
+ async execute(params) {
39
+ try {
40
+ // Safety check for dangerous commands
41
+ if (this.isDangerous(params.command)) {
42
+ return this.error(`Dangerous command detected and blocked: "${params.command}". ` +
43
+ `This command could cause system damage. Please run it manually if you're certain.`);
44
+ }
45
+ const timeout = params.timeout && params.timeout <= 600000
46
+ ? params.timeout
47
+ : 120000; // Default 2 minutes
48
+ // Execute command
49
+ const { stdout, stderr } = await execPromise(params.command, {
50
+ timeout,
51
+ maxBuffer: 1024 * 1024 * 10, // 10MB buffer
52
+ cwd: process.cwd()
53
+ });
54
+ const output = stdout + (stderr ? `\n[stderr]\n${stderr}` : '');
55
+ return this.success(`Command executed: ${params.command}\n\nOutput:\n${output || '(no output)'}`, {
56
+ command: params.command,
57
+ stdout: stdout.trim(),
58
+ stderr: stderr.trim(),
59
+ hasError: stderr.length > 0
60
+ });
61
+ }
62
+ catch (error) {
63
+ // Handle timeout
64
+ if (error.killed && error.signal === 'SIGTERM') {
65
+ return this.error(`Command timed out after ${params.timeout || 120000}ms: ${params.command}`);
66
+ }
67
+ // Handle command failure
68
+ const errorOutput = error.stdout || error.stderr || error.message;
69
+ return this.error(`Command failed: ${params.command}\nExit code: ${error.code || 'unknown'}\nOutput:\n${errorOutput}`);
70
+ }
71
+ }
72
+ }
73
+ exports.BashTool = BashTool;