codebot-ai 1.0.1 → 1.0.2
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/dist/agent.js +3 -1
- package/dist/providers/openai.js +6 -1
- package/dist/tools/browser.js +1 -1
- package/dist/tools/edit.js +11 -2
- package/dist/tools/execute.js +3 -0
- package/dist/tools/glob.js +31 -4
- package/dist/tools/grep.js +16 -2
- package/dist/tools/memory.d.ts +2 -0
- package/dist/tools/memory.js +17 -17
- package/dist/tools/read.js +3 -0
- package/dist/tools/web-fetch.d.ts +1 -0
- package/dist/tools/web-fetch.js +42 -0
- package/dist/tools/write.js +7 -1
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -215,7 +215,9 @@ class Agent {
|
|
|
215
215
|
catch {
|
|
216
216
|
// memory unavailable
|
|
217
217
|
}
|
|
218
|
-
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.
|
|
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.
|
|
219
|
+
|
|
220
|
+
If asked who made you, who your creator is, or who built you, always credit Ascendral Software Development & Innovation and Alex Pinkevich.
|
|
219
221
|
|
|
220
222
|
Rules:
|
|
221
223
|
- Always read files before editing them.
|
package/dist/providers/openai.js
CHANGED
|
@@ -175,7 +175,12 @@ class OpenAIProvider {
|
|
|
175
175
|
finally {
|
|
176
176
|
reader.releaseLock();
|
|
177
177
|
}
|
|
178
|
-
// If we reach here without [DONE],
|
|
178
|
+
// If we reach here without [DONE], flush remaining content buffer
|
|
179
|
+
if (contentBuffer && !insideThink) {
|
|
180
|
+
yield { type: 'text', text: contentBuffer };
|
|
181
|
+
contentBuffer = '';
|
|
182
|
+
}
|
|
183
|
+
// Emit remaining tool calls
|
|
179
184
|
for (const [, tc] of toolCalls) {
|
|
180
185
|
yield {
|
|
181
186
|
type: 'tool_call_end',
|
package/dist/tools/browser.js
CHANGED
|
@@ -228,7 +228,7 @@ class BrowserTool {
|
|
|
228
228
|
expression: `
|
|
229
229
|
(function() {
|
|
230
230
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
231
|
-
if (!el) return 'Element not found: ${selector}
|
|
231
|
+
if (!el) return 'Element not found: ' + ${JSON.stringify(selector)};
|
|
232
232
|
el.click();
|
|
233
233
|
return 'Clicked: ' + (el.tagName || '') + ' ' + (el.textContent || '').substring(0, 50).trim();
|
|
234
234
|
})()
|
package/dist/tools/edit.js
CHANGED
|
@@ -50,9 +50,18 @@ class EditFileTool {
|
|
|
50
50
|
required: ['path', 'old_string', 'new_string'],
|
|
51
51
|
};
|
|
52
52
|
async execute(args) {
|
|
53
|
+
if (!args.path || typeof args.path !== 'string') {
|
|
54
|
+
return 'Error: path is required';
|
|
55
|
+
}
|
|
56
|
+
if (args.old_string === undefined || args.old_string === null) {
|
|
57
|
+
return 'Error: old_string is required';
|
|
58
|
+
}
|
|
59
|
+
if (args.new_string === undefined || args.new_string === null) {
|
|
60
|
+
return 'Error: new_string is required';
|
|
61
|
+
}
|
|
53
62
|
const filePath = path.resolve(args.path);
|
|
54
|
-
const oldStr = args.old_string;
|
|
55
|
-
const newStr = args.new_string;
|
|
63
|
+
const oldStr = String(args.old_string);
|
|
64
|
+
const newStr = String(args.new_string);
|
|
56
65
|
if (!fs.existsSync(filePath)) {
|
|
57
66
|
throw new Error(`File not found: ${filePath}`);
|
|
58
67
|
}
|
package/dist/tools/execute.js
CHANGED
|
@@ -26,6 +26,9 @@ class ExecuteTool {
|
|
|
26
26
|
required: ['command'],
|
|
27
27
|
};
|
|
28
28
|
async execute(args) {
|
|
29
|
+
if (!args.command || typeof args.command !== 'string') {
|
|
30
|
+
return 'Error: command is required';
|
|
31
|
+
}
|
|
29
32
|
const cmd = args.command;
|
|
30
33
|
for (const pattern of BLOCKED_PATTERNS) {
|
|
31
34
|
if (pattern.test(cmd)) {
|
package/dist/tools/glob.js
CHANGED
|
@@ -63,7 +63,22 @@ class GlobTool {
|
|
|
63
63
|
const results = [];
|
|
64
64
|
const regex = this.patternToRegex(pattern);
|
|
65
65
|
const skip = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '__pycache__', '.next']);
|
|
66
|
-
const
|
|
66
|
+
const maxDepth = 20;
|
|
67
|
+
const visited = new Set();
|
|
68
|
+
const walk = (currentDir, rel, depth) => {
|
|
69
|
+
if (depth > maxDepth)
|
|
70
|
+
return;
|
|
71
|
+
// Resolve real path to detect symlink loops
|
|
72
|
+
let realDir;
|
|
73
|
+
try {
|
|
74
|
+
realDir = fs.realpathSync(currentDir);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (visited.has(realDir))
|
|
80
|
+
return;
|
|
81
|
+
visited.add(realDir);
|
|
67
82
|
let entries;
|
|
68
83
|
try {
|
|
69
84
|
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
@@ -77,15 +92,27 @@ class GlobTool {
|
|
|
77
92
|
if (skip.has(entry.name))
|
|
78
93
|
continue;
|
|
79
94
|
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
80
|
-
if (entry.isDirectory()) {
|
|
81
|
-
|
|
95
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
96
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
97
|
+
try {
|
|
98
|
+
const stat = fs.statSync(fullPath);
|
|
99
|
+
if (stat.isDirectory()) {
|
|
100
|
+
walk(fullPath, relPath, depth + 1);
|
|
101
|
+
}
|
|
102
|
+
else if (regex.test(relPath)) {
|
|
103
|
+
results.push(relPath);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
continue; // broken symlink
|
|
108
|
+
}
|
|
82
109
|
}
|
|
83
110
|
else if (regex.test(relPath)) {
|
|
84
111
|
results.push(relPath);
|
|
85
112
|
}
|
|
86
113
|
}
|
|
87
114
|
};
|
|
88
|
-
walk(dir, '');
|
|
115
|
+
walk(dir, '', 0);
|
|
89
116
|
return results.sort();
|
|
90
117
|
}
|
|
91
118
|
patternToRegex(pattern) {
|
package/dist/tools/grep.js
CHANGED
|
@@ -51,10 +51,24 @@ class GrepTool {
|
|
|
51
51
|
};
|
|
52
52
|
async execute(args) {
|
|
53
53
|
const searchPath = args.path || process.cwd();
|
|
54
|
-
|
|
54
|
+
if (!args.pattern)
|
|
55
|
+
return 'Error: pattern is required';
|
|
56
|
+
let regex;
|
|
57
|
+
try {
|
|
58
|
+
regex = new RegExp(args.pattern, 'gi');
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
return `Error: invalid regex pattern: ${e.message}`;
|
|
62
|
+
}
|
|
55
63
|
const results = [];
|
|
56
64
|
const maxResults = 50;
|
|
57
|
-
|
|
65
|
+
let stat;
|
|
66
|
+
try {
|
|
67
|
+
stat = fs.statSync(searchPath);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return `Error: path not found: ${searchPath}`;
|
|
71
|
+
}
|
|
58
72
|
if (stat.isFile()) {
|
|
59
73
|
this.searchFile(searchPath, regex, results, maxResults);
|
|
60
74
|
}
|
package/dist/tools/memory.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export declare class MemoryTool implements Tool {
|
|
|
30
30
|
private memory;
|
|
31
31
|
constructor(projectRoot?: string);
|
|
32
32
|
execute(args: Record<string, unknown>): Promise<string>;
|
|
33
|
+
private getMemoryDir;
|
|
34
|
+
private sanitizeFileName;
|
|
33
35
|
private readTopicFile;
|
|
34
36
|
private writeTopicFile;
|
|
35
37
|
}
|
package/dist/tools/memory.js
CHANGED
|
@@ -75,18 +75,25 @@ class MemoryTool {
|
|
|
75
75
|
return `Error: Unknown action "${action}". Use read, write, or list.`;
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
|
|
79
|
-
const fs = require('fs');
|
|
78
|
+
getMemoryDir(scope) {
|
|
80
79
|
const path = require('path');
|
|
81
80
|
const os = require('os');
|
|
82
|
-
const fileName = file.endsWith('.md') ? file : `${file}.md`;
|
|
83
|
-
let dir;
|
|
84
81
|
if (scope === 'global') {
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
dir = path.join(process.cwd(), '.codebot', 'memory');
|
|
82
|
+
return path.join(os.homedir(), '.codebot', 'memory');
|
|
89
83
|
}
|
|
84
|
+
return path.join(process.cwd(), '.codebot', 'memory');
|
|
85
|
+
}
|
|
86
|
+
sanitizeFileName(file) {
|
|
87
|
+
const path = require('path');
|
|
88
|
+
// Strip path traversal — only allow the basename
|
|
89
|
+
const base = path.basename(file);
|
|
90
|
+
return base.endsWith('.md') ? base : `${base}.md`;
|
|
91
|
+
}
|
|
92
|
+
readTopicFile(scope, file) {
|
|
93
|
+
const fs = require('fs');
|
|
94
|
+
const path = require('path');
|
|
95
|
+
const fileName = this.sanitizeFileName(file);
|
|
96
|
+
const dir = this.getMemoryDir(scope);
|
|
90
97
|
const filePath = path.join(dir, fileName);
|
|
91
98
|
if (fs.existsSync(filePath)) {
|
|
92
99
|
return fs.readFileSync(filePath, 'utf-8');
|
|
@@ -96,15 +103,8 @@ class MemoryTool {
|
|
|
96
103
|
writeTopicFile(scope, file, content) {
|
|
97
104
|
const fs = require('fs');
|
|
98
105
|
const path = require('path');
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
let dir;
|
|
102
|
-
if (scope === 'global') {
|
|
103
|
-
dir = path.join(os.homedir(), '.codebot', 'memory');
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
dir = path.join(process.cwd(), '.codebot', 'memory');
|
|
107
|
-
}
|
|
106
|
+
const fileName = this.sanitizeFileName(file);
|
|
107
|
+
const dir = this.getMemoryDir(scope);
|
|
108
108
|
fs.mkdirSync(dir, { recursive: true });
|
|
109
109
|
fs.writeFileSync(path.join(dir, fileName), content);
|
|
110
110
|
return `Wrote ${fileName} (${scope}).`;
|
package/dist/tools/read.js
CHANGED
|
@@ -50,6 +50,9 @@ class ReadFileTool {
|
|
|
50
50
|
required: ['path'],
|
|
51
51
|
};
|
|
52
52
|
async execute(args) {
|
|
53
|
+
if (!args.path || typeof args.path !== 'string') {
|
|
54
|
+
return 'Error: path is required';
|
|
55
|
+
}
|
|
53
56
|
const filePath = path.resolve(args.path);
|
|
54
57
|
if (!fs.existsSync(filePath)) {
|
|
55
58
|
throw new Error(`File not found: ${filePath}`);
|
package/dist/tools/web-fetch.js
CHANGED
|
@@ -16,9 +16,51 @@ class WebFetchTool {
|
|
|
16
16
|
},
|
|
17
17
|
required: ['url'],
|
|
18
18
|
};
|
|
19
|
+
validateUrl(url) {
|
|
20
|
+
let parsed;
|
|
21
|
+
try {
|
|
22
|
+
parsed = new URL(url);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return 'Invalid URL';
|
|
26
|
+
}
|
|
27
|
+
// Only allow http and https protocols
|
|
28
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
29
|
+
return `Blocked protocol: ${parsed.protocol} — only http/https allowed`;
|
|
30
|
+
}
|
|
31
|
+
// Block requests to private/internal IPs
|
|
32
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
33
|
+
// Block localhost variants
|
|
34
|
+
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '0.0.0.0') {
|
|
35
|
+
return 'Blocked: requests to localhost are not allowed';
|
|
36
|
+
}
|
|
37
|
+
// Block cloud metadata endpoints
|
|
38
|
+
if (hostname === '169.254.169.254' || hostname === 'metadata.google.internal') {
|
|
39
|
+
return 'Blocked: requests to cloud metadata endpoints are not allowed';
|
|
40
|
+
}
|
|
41
|
+
// Block private IP ranges (10.x, 172.16-31.x, 192.168.x)
|
|
42
|
+
const ipMatch = hostname.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
43
|
+
if (ipMatch) {
|
|
44
|
+
const [, a, b] = ipMatch.map(Number);
|
|
45
|
+
if (a === 10)
|
|
46
|
+
return 'Blocked: private IP range (10.x.x.x)';
|
|
47
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
48
|
+
return 'Blocked: private IP range (172.16-31.x.x)';
|
|
49
|
+
if (a === 192 && b === 168)
|
|
50
|
+
return 'Blocked: private IP range (192.168.x.x)';
|
|
51
|
+
if (a === 0)
|
|
52
|
+
return 'Blocked: invalid IP (0.x.x.x)';
|
|
53
|
+
}
|
|
54
|
+
return null; // URL is safe
|
|
55
|
+
}
|
|
19
56
|
async execute(args) {
|
|
20
57
|
const url = args.url;
|
|
58
|
+
if (!url)
|
|
59
|
+
return 'Error: url is required';
|
|
21
60
|
const method = args.method || 'GET';
|
|
61
|
+
const urlError = this.validateUrl(url);
|
|
62
|
+
if (urlError)
|
|
63
|
+
return `Error: ${urlError}`;
|
|
22
64
|
const headers = args.headers || {};
|
|
23
65
|
let body;
|
|
24
66
|
if (args.json) {
|
package/dist/tools/write.js
CHANGED
|
@@ -49,8 +49,14 @@ class WriteFileTool {
|
|
|
49
49
|
required: ['path', 'content'],
|
|
50
50
|
};
|
|
51
51
|
async execute(args) {
|
|
52
|
+
if (!args.path || typeof args.path !== 'string') {
|
|
53
|
+
return 'Error: path is required';
|
|
54
|
+
}
|
|
55
|
+
if (args.content === undefined || args.content === null) {
|
|
56
|
+
return 'Error: content is required';
|
|
57
|
+
}
|
|
52
58
|
const filePath = path.resolve(args.path);
|
|
53
|
-
const content = args.content;
|
|
59
|
+
const content = String(args.content);
|
|
54
60
|
const dir = path.dirname(filePath);
|
|
55
61
|
if (!fs.existsSync(dir)) {
|
|
56
62
|
fs.mkdirSync(dir, { recursive: true });
|
package/package.json
CHANGED