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 +43 -3
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +20 -2
- package/dist/browser/cdp.d.ts +3 -0
- package/dist/browser/cdp.js +25 -1
- package/dist/cli.js +30 -2
- package/dist/games/tic-tac-toe.d.ts +6 -0
- package/dist/games/tic-tac-toe.js +64 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -1
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +174 -0
- package/dist/plugins.d.ts +17 -0
- package/dist/plugins.js +101 -0
- package/dist/providers/openai.js +7 -0
- package/dist/setup.js +72 -30
- package/dist/tools/batch-edit.d.ts +36 -0
- package/dist/tools/batch-edit.js +122 -0
- package/dist/tools/browser.js +122 -15
- package/dist/tools/edit.d.ts +6 -0
- package/dist/tools/edit.js +117 -2
- package/dist/tools/execute.js +28 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +5 -1
- package/dist/tools/write.d.ts +1 -0
- package/dist/tools/write.js +38 -1
- package/package.json +5 -5
package/dist/tools/edit.js
CHANGED
|
@@ -36,9 +36,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.EditFileTool = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
// Undo snapshot directory
|
|
41
|
+
const UNDO_DIR = path.join(os.homedir(), '.codebot', 'undo');
|
|
42
|
+
const MAX_UNDO = 50;
|
|
39
43
|
class EditFileTool {
|
|
40
44
|
name = 'edit_file';
|
|
41
|
-
description = 'Edit a file by replacing an exact string match with new content. The old_string must appear exactly once in the file.';
|
|
45
|
+
description = 'Edit a file by replacing an exact string match with new content. The old_string must appear exactly once in the file. Shows a diff preview and creates an undo snapshot.';
|
|
42
46
|
permission = 'prompt';
|
|
43
47
|
parameters = {
|
|
44
48
|
type: 'object',
|
|
@@ -73,9 +77,120 @@ class EditFileTool {
|
|
|
73
77
|
if (count > 1) {
|
|
74
78
|
throw new Error(`String found ${count} times in ${filePath}. Provide more surrounding context to make it unique.`);
|
|
75
79
|
}
|
|
80
|
+
// Save undo snapshot
|
|
81
|
+
this.saveSnapshot(filePath, content);
|
|
76
82
|
const updated = content.replace(oldStr, newStr);
|
|
77
83
|
fs.writeFileSync(filePath, updated, 'utf-8');
|
|
78
|
-
|
|
84
|
+
// Generate diff preview
|
|
85
|
+
const diff = this.generateDiff(oldStr, newStr, content, filePath);
|
|
86
|
+
return diff;
|
|
87
|
+
}
|
|
88
|
+
generateDiff(oldStr, newStr, content, filePath) {
|
|
89
|
+
const lines = content.split('\n');
|
|
90
|
+
const matchIdx = content.indexOf(oldStr);
|
|
91
|
+
const linesBefore = content.substring(0, matchIdx).split('\n');
|
|
92
|
+
const startLine = linesBefore.length;
|
|
93
|
+
const oldLines = oldStr.split('\n');
|
|
94
|
+
const newLines = newStr.split('\n');
|
|
95
|
+
let diff = `Edited ${filePath}\n`;
|
|
96
|
+
// Show context (2 lines before)
|
|
97
|
+
const contextStart = Math.max(0, startLine - 3);
|
|
98
|
+
for (let i = contextStart; i < startLine - 1; i++) {
|
|
99
|
+
diff += ` ${i + 1} │ ${lines[i]}\n`;
|
|
100
|
+
}
|
|
101
|
+
// Show removed lines
|
|
102
|
+
for (const line of oldLines) {
|
|
103
|
+
diff += ` - │ ${line}\n`;
|
|
104
|
+
}
|
|
105
|
+
// Show added lines
|
|
106
|
+
for (const line of newLines) {
|
|
107
|
+
diff += ` + │ ${line}\n`;
|
|
108
|
+
}
|
|
109
|
+
// Show context (2 lines after)
|
|
110
|
+
const endLine = startLine - 1 + oldLines.length;
|
|
111
|
+
for (let i = endLine; i < Math.min(lines.length, endLine + 2); i++) {
|
|
112
|
+
diff += ` ${i + 1} │ ${lines[i]}\n`;
|
|
113
|
+
}
|
|
114
|
+
return diff.trimEnd();
|
|
115
|
+
}
|
|
116
|
+
/** Save a snapshot for undo */
|
|
117
|
+
saveSnapshot(filePath, content) {
|
|
118
|
+
try {
|
|
119
|
+
fs.mkdirSync(UNDO_DIR, { recursive: true });
|
|
120
|
+
const manifest = this.loadManifest();
|
|
121
|
+
const entry = {
|
|
122
|
+
file: filePath,
|
|
123
|
+
timestamp: Date.now(),
|
|
124
|
+
snapshotFile: `${Date.now()}-${path.basename(filePath)}`,
|
|
125
|
+
};
|
|
126
|
+
// Write snapshot content
|
|
127
|
+
fs.writeFileSync(path.join(UNDO_DIR, entry.snapshotFile), content);
|
|
128
|
+
manifest.push(entry);
|
|
129
|
+
// Prune old snapshots
|
|
130
|
+
while (manifest.length > MAX_UNDO) {
|
|
131
|
+
const old = manifest.shift();
|
|
132
|
+
try {
|
|
133
|
+
fs.unlinkSync(path.join(UNDO_DIR, old.snapshotFile));
|
|
134
|
+
}
|
|
135
|
+
catch { /* ok */ }
|
|
136
|
+
}
|
|
137
|
+
fs.writeFileSync(path.join(UNDO_DIR, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Best-effort, don't fail the edit
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
loadManifest() {
|
|
144
|
+
try {
|
|
145
|
+
const raw = fs.readFileSync(path.join(UNDO_DIR, 'manifest.json'), 'utf-8');
|
|
146
|
+
return JSON.parse(raw);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Undo the last edit to a file. Returns result message. */
|
|
153
|
+
static undo(filePath) {
|
|
154
|
+
try {
|
|
155
|
+
const manifestPath = path.join(UNDO_DIR, 'manifest.json');
|
|
156
|
+
if (!fs.existsSync(manifestPath))
|
|
157
|
+
return 'No undo history available.';
|
|
158
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
159
|
+
if (manifest.length === 0)
|
|
160
|
+
return 'No undo history available.';
|
|
161
|
+
// Find the entry to undo
|
|
162
|
+
let entry;
|
|
163
|
+
if (filePath) {
|
|
164
|
+
const resolved = path.resolve(filePath);
|
|
165
|
+
for (let i = manifest.length - 1; i >= 0; i--) {
|
|
166
|
+
if (manifest[i].file === resolved) {
|
|
167
|
+
entry = manifest.splice(i, 1)[0];
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (!entry)
|
|
172
|
+
return `No undo history for ${filePath}`;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
entry = manifest.pop();
|
|
176
|
+
}
|
|
177
|
+
// Restore the snapshot
|
|
178
|
+
const snapshotPath = path.join(UNDO_DIR, entry.snapshotFile);
|
|
179
|
+
if (!fs.existsSync(snapshotPath))
|
|
180
|
+
return 'Snapshot file missing.';
|
|
181
|
+
const content = fs.readFileSync(snapshotPath, 'utf-8');
|
|
182
|
+
fs.writeFileSync(entry.file, content, 'utf-8');
|
|
183
|
+
// Cleanup
|
|
184
|
+
try {
|
|
185
|
+
fs.unlinkSync(snapshotPath);
|
|
186
|
+
}
|
|
187
|
+
catch { /* ok */ }
|
|
188
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
189
|
+
return `Restored ${entry.file} to state before last edit.`;
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
return `Undo failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
193
|
+
}
|
|
79
194
|
}
|
|
80
195
|
}
|
|
81
196
|
exports.EditFileTool = EditFileTool;
|
package/dist/tools/execute.js
CHANGED
|
@@ -3,14 +3,42 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ExecuteTool = void 0;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
5
|
const BLOCKED_PATTERNS = [
|
|
6
|
+
// Destructive filesystem operations
|
|
6
7
|
/rm\s+-rf\s+\//,
|
|
7
8
|
/rm\s+-rf\s+~/,
|
|
8
9
|
/rm\s+-rf\s+\*/,
|
|
10
|
+
/rm\s+-rf\s+\.\s/,
|
|
11
|
+
/rm\s+(-[a-z]*f[a-z]*\s+)?--no-preserve-root/,
|
|
12
|
+
// Disk/partition destruction
|
|
9
13
|
/mkfs\./,
|
|
10
14
|
/dd\s+if=.*of=\/dev\//,
|
|
15
|
+
/wipefs/,
|
|
16
|
+
/fdisk\s+\/dev\//,
|
|
17
|
+
/parted\s+\/dev\//,
|
|
18
|
+
// Fork bomb
|
|
11
19
|
/:\(\)\s*\{[^}]*:\|:.*\}/,
|
|
20
|
+
// Permission escalation
|
|
12
21
|
/chmod\s+-R\s+777\s+\//,
|
|
22
|
+
/chmod\s+777\s+\//,
|
|
23
|
+
/chown\s+-R\s+.*\s+\//,
|
|
24
|
+
// Windows destructive
|
|
13
25
|
/format\s+c:/i,
|
|
26
|
+
/del\s+\/[sfq]\s+c:\\/i,
|
|
27
|
+
/rd\s+\/s\s+\/q\s+c:\\/i,
|
|
28
|
+
// Curl to shell pipes (common attack vector)
|
|
29
|
+
/curl\s+.*\|\s*(ba)?sh/,
|
|
30
|
+
/wget\s+.*\|\s*(ba)?sh/,
|
|
31
|
+
// History/log destruction
|
|
32
|
+
/>\s*\/dev\/sda/,
|
|
33
|
+
/history\s+-c.*&&.*rm/,
|
|
34
|
+
// Shutdown/reboot
|
|
35
|
+
/shutdown\s+(-h\s+)?now/,
|
|
36
|
+
/reboot\b/,
|
|
37
|
+
/init\s+[06]/,
|
|
38
|
+
// Kernel module manipulation
|
|
39
|
+
/insmod\b/,
|
|
40
|
+
/rmmod\b/,
|
|
41
|
+
/modprobe\s+-r/,
|
|
14
42
|
];
|
|
15
43
|
class ExecuteTool {
|
|
16
44
|
name = 'execute';
|
package/dist/tools/index.d.ts
CHANGED
package/dist/tools/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ToolRegistry = void 0;
|
|
3
|
+
exports.ToolRegistry = exports.EditFileTool = void 0;
|
|
4
4
|
const read_1 = require("./read");
|
|
5
5
|
const write_1 = require("./write");
|
|
6
6
|
const edit_1 = require("./edit");
|
|
@@ -11,12 +11,16 @@ const think_1 = require("./think");
|
|
|
11
11
|
const memory_1 = require("./memory");
|
|
12
12
|
const web_fetch_1 = require("./web-fetch");
|
|
13
13
|
const browser_1 = require("./browser");
|
|
14
|
+
const batch_edit_1 = require("./batch-edit");
|
|
15
|
+
var edit_2 = require("./edit");
|
|
16
|
+
Object.defineProperty(exports, "EditFileTool", { enumerable: true, get: function () { return edit_2.EditFileTool; } });
|
|
14
17
|
class ToolRegistry {
|
|
15
18
|
tools = new Map();
|
|
16
19
|
constructor(projectRoot) {
|
|
17
20
|
this.register(new read_1.ReadFileTool());
|
|
18
21
|
this.register(new write_1.WriteFileTool());
|
|
19
22
|
this.register(new edit_1.EditFileTool());
|
|
23
|
+
this.register(new batch_edit_1.BatchEditTool());
|
|
20
24
|
this.register(new execute_1.ExecuteTool());
|
|
21
25
|
this.register(new glob_1.GlobTool());
|
|
22
26
|
this.register(new grep_1.GrepTool());
|
package/dist/tools/write.d.ts
CHANGED
package/dist/tools/write.js
CHANGED
|
@@ -36,9 +36,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.WriteFileTool = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const UNDO_DIR = path.join(os.homedir(), '.codebot', 'undo');
|
|
39
41
|
class WriteFileTool {
|
|
40
42
|
name = 'write_file';
|
|
41
|
-
description = 'Create a new file or overwrite an existing file with the given content.';
|
|
43
|
+
description = 'Create a new file or overwrite an existing file with the given content. Automatically saves an undo snapshot for existing files.';
|
|
42
44
|
permission = 'prompt';
|
|
43
45
|
parameters = {
|
|
44
46
|
type: 'object',
|
|
@@ -62,10 +64,45 @@ class WriteFileTool {
|
|
|
62
64
|
fs.mkdirSync(dir, { recursive: true });
|
|
63
65
|
}
|
|
64
66
|
const existed = fs.existsSync(filePath);
|
|
67
|
+
// Save undo snapshot before overwriting
|
|
68
|
+
if (existed) {
|
|
69
|
+
try {
|
|
70
|
+
const oldContent = fs.readFileSync(filePath, 'utf-8');
|
|
71
|
+
this.saveSnapshot(filePath, oldContent);
|
|
72
|
+
}
|
|
73
|
+
catch { /* best effort */ }
|
|
74
|
+
}
|
|
65
75
|
fs.writeFileSync(filePath, content, 'utf-8');
|
|
66
76
|
const lines = content.split('\n').length;
|
|
67
77
|
return `${existed ? 'Overwrote' : 'Created'} ${filePath} (${lines} lines, ${content.length} bytes)`;
|
|
68
78
|
}
|
|
79
|
+
saveSnapshot(filePath, content) {
|
|
80
|
+
try {
|
|
81
|
+
fs.mkdirSync(UNDO_DIR, { recursive: true });
|
|
82
|
+
const manifestPath = path.join(UNDO_DIR, 'manifest.json');
|
|
83
|
+
let manifest = [];
|
|
84
|
+
try {
|
|
85
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
86
|
+
}
|
|
87
|
+
catch { /* empty */ }
|
|
88
|
+
const entry = {
|
|
89
|
+
file: filePath,
|
|
90
|
+
timestamp: Date.now(),
|
|
91
|
+
snapshotFile: `${Date.now()}-${path.basename(filePath)}`,
|
|
92
|
+
};
|
|
93
|
+
fs.writeFileSync(path.join(UNDO_DIR, entry.snapshotFile), content);
|
|
94
|
+
manifest.push(entry);
|
|
95
|
+
while (manifest.length > 50) {
|
|
96
|
+
const old = manifest.shift();
|
|
97
|
+
try {
|
|
98
|
+
fs.unlinkSync(path.join(UNDO_DIR, old.snapshotFile));
|
|
99
|
+
}
|
|
100
|
+
catch { /* ok */ }
|
|
101
|
+
}
|
|
102
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
103
|
+
}
|
|
104
|
+
catch { /* best effort */ }
|
|
105
|
+
}
|
|
69
106
|
}
|
|
70
107
|
exports.WriteFileTool = WriteFileTool;
|
|
71
108
|
//# sourceMappingURL=write.js.map
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebot-ai",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Local-first AI coding assistant. Zero dependencies. Works with Ollama, LM Studio, vLLM, Claude, GPT, Gemini, and more.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"bin": {
|
|
8
|
-
"codebot": "
|
|
8
|
+
"codebot": "bin/codebot"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"repository": {
|
|
35
35
|
"type": "git",
|
|
36
|
-
"url": "git+https://github.com/
|
|
36
|
+
"url": "git+https://github.com/zanderone1980/codebot-ai.git"
|
|
37
37
|
},
|
|
38
|
-
"homepage": "https://github.com/
|
|
38
|
+
"homepage": "https://github.com/zanderone1980/codebot-ai#readme",
|
|
39
39
|
"bugs": {
|
|
40
|
-
"url": "https://github.com/
|
|
40
|
+
"url": "https://github.com/zanderone1980/codebot-ai/issues"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
43
|
"node": ">=18.0.0"
|