@johpaz/hive 1.1.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.
- package/CONTRIBUTING.md +44 -0
- package/README.md +310 -0
- package/package.json +96 -0
- package/packages/cli/package.json +28 -0
- package/packages/cli/src/commands/agent-run.ts +168 -0
- package/packages/cli/src/commands/agents.ts +398 -0
- package/packages/cli/src/commands/chat.ts +142 -0
- package/packages/cli/src/commands/config.ts +50 -0
- package/packages/cli/src/commands/cron.ts +161 -0
- package/packages/cli/src/commands/dev.ts +95 -0
- package/packages/cli/src/commands/doctor.ts +133 -0
- package/packages/cli/src/commands/gateway.ts +443 -0
- package/packages/cli/src/commands/logs.ts +57 -0
- package/packages/cli/src/commands/mcp.ts +175 -0
- package/packages/cli/src/commands/message.ts +77 -0
- package/packages/cli/src/commands/onboard.ts +1868 -0
- package/packages/cli/src/commands/security.ts +144 -0
- package/packages/cli/src/commands/service.ts +50 -0
- package/packages/cli/src/commands/sessions.ts +116 -0
- package/packages/cli/src/commands/skills.ts +187 -0
- package/packages/cli/src/commands/update.ts +25 -0
- package/packages/cli/src/index.ts +185 -0
- package/packages/cli/src/utils/token.ts +6 -0
- package/packages/code-bridge/README.md +78 -0
- package/packages/code-bridge/package.json +18 -0
- package/packages/code-bridge/src/index.ts +95 -0
- package/packages/code-bridge/src/process-manager.ts +212 -0
- package/packages/code-bridge/src/schemas.ts +133 -0
- package/packages/core/package.json +46 -0
- package/packages/core/src/agent/agent-loop.ts +369 -0
- package/packages/core/src/agent/compaction.ts +140 -0
- package/packages/core/src/agent/context-compiler.ts +378 -0
- package/packages/core/src/agent/context-guard.ts +91 -0
- package/packages/core/src/agent/context.ts +138 -0
- package/packages/core/src/agent/conversation-store.ts +198 -0
- package/packages/core/src/agent/curator.ts +158 -0
- package/packages/core/src/agent/hooks.ts +166 -0
- package/packages/core/src/agent/index.ts +116 -0
- package/packages/core/src/agent/llm-client.ts +503 -0
- package/packages/core/src/agent/native-tools.ts +505 -0
- package/packages/core/src/agent/prompt-builder.ts +532 -0
- package/packages/core/src/agent/providers/index.ts +167 -0
- package/packages/core/src/agent/providers.ts +1 -0
- package/packages/core/src/agent/reflector.ts +170 -0
- package/packages/core/src/agent/service.ts +64 -0
- package/packages/core/src/agent/stuck-loop.ts +133 -0
- package/packages/core/src/agent/supervisor.ts +39 -0
- package/packages/core/src/agent/tracer.ts +102 -0
- package/packages/core/src/agent/workspace.ts +110 -0
- package/packages/core/src/canvas/canvas-manager.test.ts +161 -0
- package/packages/core/src/canvas/canvas-manager.ts +319 -0
- package/packages/core/src/canvas/canvas-tools.ts +420 -0
- package/packages/core/src/canvas/emitter.ts +115 -0
- package/packages/core/src/canvas/index.ts +2 -0
- package/packages/core/src/channels/base.ts +138 -0
- package/packages/core/src/channels/discord.ts +260 -0
- package/packages/core/src/channels/index.ts +7 -0
- package/packages/core/src/channels/manager.ts +383 -0
- package/packages/core/src/channels/slack.ts +287 -0
- package/packages/core/src/channels/telegram.ts +502 -0
- package/packages/core/src/channels/webchat.ts +128 -0
- package/packages/core/src/channels/whatsapp.ts +375 -0
- package/packages/core/src/config/index.ts +12 -0
- package/packages/core/src/config/loader.ts +529 -0
- package/packages/core/src/events/event-bus.ts +169 -0
- package/packages/core/src/gateway/index.ts +5 -0
- package/packages/core/src/gateway/initializer.ts +290 -0
- package/packages/core/src/gateway/lane-queue.ts +169 -0
- package/packages/core/src/gateway/resolver.ts +108 -0
- package/packages/core/src/gateway/router.ts +124 -0
- package/packages/core/src/gateway/server.ts +3317 -0
- package/packages/core/src/gateway/session.ts +95 -0
- package/packages/core/src/gateway/slash-commands.ts +192 -0
- package/packages/core/src/heartbeat/index.ts +157 -0
- package/packages/core/src/index.ts +19 -0
- package/packages/core/src/integrations/catalog.ts +286 -0
- package/packages/core/src/integrations/env.ts +64 -0
- package/packages/core/src/integrations/index.ts +2 -0
- package/packages/core/src/memory/index.ts +1 -0
- package/packages/core/src/memory/notes.ts +68 -0
- package/packages/core/src/plugins/api.ts +128 -0
- package/packages/core/src/plugins/index.ts +2 -0
- package/packages/core/src/plugins/loader.ts +365 -0
- package/packages/core/src/resilience/circuit-breaker.ts +225 -0
- package/packages/core/src/security/google-chat.ts +269 -0
- package/packages/core/src/security/index.ts +192 -0
- package/packages/core/src/security/pairing.ts +250 -0
- package/packages/core/src/security/rate-limit.ts +270 -0
- package/packages/core/src/security/signal.ts +321 -0
- package/packages/core/src/state/store.ts +312 -0
- package/packages/core/src/storage/bun-sqlite-store.ts +188 -0
- package/packages/core/src/storage/crypto.ts +101 -0
- package/packages/core/src/storage/db-context.ts +333 -0
- package/packages/core/src/storage/onboarding.ts +1087 -0
- package/packages/core/src/storage/schema.ts +541 -0
- package/packages/core/src/storage/seed.ts +571 -0
- package/packages/core/src/storage/sqlite.ts +387 -0
- package/packages/core/src/storage/usage.ts +212 -0
- package/packages/core/src/tools/bridge-events.ts +74 -0
- package/packages/core/src/tools/browser.ts +275 -0
- package/packages/core/src/tools/codebridge.ts +421 -0
- package/packages/core/src/tools/coordinator-tools.ts +179 -0
- package/packages/core/src/tools/cron.ts +611 -0
- package/packages/core/src/tools/exec.ts +140 -0
- package/packages/core/src/tools/fs.ts +364 -0
- package/packages/core/src/tools/index.ts +12 -0
- package/packages/core/src/tools/memory.ts +176 -0
- package/packages/core/src/tools/notify.ts +113 -0
- package/packages/core/src/tools/project-management.ts +376 -0
- package/packages/core/src/tools/project.ts +375 -0
- package/packages/core/src/tools/read.ts +158 -0
- package/packages/core/src/tools/web.ts +436 -0
- package/packages/core/src/tools/workspace.ts +171 -0
- package/packages/core/src/utils/benchmark.ts +80 -0
- package/packages/core/src/utils/crypto.ts +73 -0
- package/packages/core/src/utils/date.ts +42 -0
- package/packages/core/src/utils/index.ts +4 -0
- package/packages/core/src/utils/logger.ts +388 -0
- package/packages/core/src/utils/retry.ts +70 -0
- package/packages/core/src/voice/index.ts +583 -0
- package/packages/core/tsconfig.json +9 -0
- package/packages/mcp/package.json +26 -0
- package/packages/mcp/src/config.ts +13 -0
- package/packages/mcp/src/index.ts +1 -0
- package/packages/mcp/src/logger.ts +42 -0
- package/packages/mcp/src/manager.ts +434 -0
- package/packages/mcp/src/transports/index.ts +67 -0
- package/packages/mcp/src/transports/sse.ts +241 -0
- package/packages/mcp/src/transports/websocket.ts +159 -0
- package/packages/skills/package.json +21 -0
- package/packages/skills/src/bundled/agent_management/SKILL.md +24 -0
- package/packages/skills/src/bundled/browser_automation/SKILL.md +30 -0
- package/packages/skills/src/bundled/context_compact/SKILL.md +35 -0
- package/packages/skills/src/bundled/cron_manager/SKILL.md +52 -0
- package/packages/skills/src/bundled/file_manager/SKILL.md +76 -0
- package/packages/skills/src/bundled/http_client/SKILL.md +24 -0
- package/packages/skills/src/bundled/memory/SKILL.md +42 -0
- package/packages/skills/src/bundled/project_management/SKILL.md +26 -0
- package/packages/skills/src/bundled/shell/SKILL.md +43 -0
- package/packages/skills/src/bundled/system_notify/SKILL.md +52 -0
- package/packages/skills/src/bundled/voice/SKILL.md +25 -0
- package/packages/skills/src/bundled/web_search/SKILL.md +29 -0
- package/packages/skills/src/index.ts +1 -0
- package/packages/skills/src/loader.ts +282 -0
- package/packages/tools/package.json +43 -0
- package/packages/tools/src/browser/browser.test.ts +111 -0
- package/packages/tools/src/browser/index.ts +272 -0
- package/packages/tools/src/canvas/index.ts +220 -0
- package/packages/tools/src/cron/cron.test.ts +164 -0
- package/packages/tools/src/cron/index.ts +304 -0
- package/packages/tools/src/filesystem/filesystem.test.ts +240 -0
- package/packages/tools/src/filesystem/index.ts +379 -0
- package/packages/tools/src/git/index.ts +239 -0
- package/packages/tools/src/index.ts +4 -0
- package/packages/tools/src/shell/detect-env.ts +70 -0
- package/packages/tools/tsconfig.json +9 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import type { Tool } from "../agent/native-tools.ts";
|
|
2
|
+
import { logger } from "../utils/logger.ts";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
|
|
6
|
+
function isAbsolutePath(p: string): boolean {
|
|
7
|
+
return path.isAbsolute(p) || /^[a-zA-Z]:[/\\]/.test(p);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ProjectToolConfig {
|
|
11
|
+
projectPath?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createProjectTools(config?: ProjectToolConfig): Tool[] {
|
|
15
|
+
const log = logger.child("project");
|
|
16
|
+
const projectPath = config?.projectPath ?? process.cwd();
|
|
17
|
+
|
|
18
|
+
const projectReadTool: Tool = {
|
|
19
|
+
name: "project_read",
|
|
20
|
+
description: "Read files from the user's project. Use this to read source code, configuration files, or any text files in the project. Works on Windows, macOS, and Linux.",
|
|
21
|
+
parameters: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
path: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "Relative or absolute path to the file to read",
|
|
27
|
+
},
|
|
28
|
+
offset: {
|
|
29
|
+
type: "number",
|
|
30
|
+
description: "Line number to start reading from (1-indexed)",
|
|
31
|
+
},
|
|
32
|
+
limit: {
|
|
33
|
+
type: "number",
|
|
34
|
+
description: "Maximum number of lines to read (default: 2000)",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ["path"],
|
|
38
|
+
},
|
|
39
|
+
execute: async (params: Record<string, unknown>) => {
|
|
40
|
+
const filePath = isAbsolutePath(params.path as string)
|
|
41
|
+
? (params.path as string)
|
|
42
|
+
: path.join(projectPath, params.path as string);
|
|
43
|
+
const offset = (params.offset as number) ?? 1;
|
|
44
|
+
const limit = (params.limit as number) ?? 2000;
|
|
45
|
+
|
|
46
|
+
log.debug(`Reading project file: ${filePath}`);
|
|
47
|
+
|
|
48
|
+
if (!fs.existsSync(filePath)) {
|
|
49
|
+
throw new Error(`File not found: ${filePath}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const stat = fs.statSync(filePath);
|
|
53
|
+
if (stat.isDirectory()) {
|
|
54
|
+
const entries = fs.readdirSync(filePath, { withFileTypes: true });
|
|
55
|
+
return entries.map((e) => ({
|
|
56
|
+
name: e.name,
|
|
57
|
+
type: e.isDirectory() ? "directory" : "file",
|
|
58
|
+
path: path.join(filePath, e.name),
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
63
|
+
const lines = content.split("\n");
|
|
64
|
+
|
|
65
|
+
const start = Math.max(0, offset - 1);
|
|
66
|
+
const end = Math.min(lines.length, start + limit);
|
|
67
|
+
|
|
68
|
+
const selected = lines.slice(start, end);
|
|
69
|
+
|
|
70
|
+
return selected
|
|
71
|
+
.map((line, i) => `${start + i + 1}: ${line}`)
|
|
72
|
+
.join("\n");
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const projectWriteTool: Tool = {
|
|
77
|
+
name: "project_write",
|
|
78
|
+
description: "Create or modify files in the user's project. Use this to write code, configuration, or any text content to the project.",
|
|
79
|
+
parameters: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
path: {
|
|
83
|
+
type: "string",
|
|
84
|
+
description: "Relative or absolute path to the file to write",
|
|
85
|
+
},
|
|
86
|
+
content: {
|
|
87
|
+
type: "string",
|
|
88
|
+
description: "The content to write to the file",
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
required: ["path", "content"],
|
|
92
|
+
},
|
|
93
|
+
execute: async (params: Record<string, unknown>) => {
|
|
94
|
+
const filePath = isAbsolutePath(params.path as string)
|
|
95
|
+
? (params.path as string)
|
|
96
|
+
: path.join(projectPath, params.path as string);
|
|
97
|
+
const content = params.content as string;
|
|
98
|
+
|
|
99
|
+
log.debug(`Writing project file: ${filePath}`);
|
|
100
|
+
|
|
101
|
+
const dir = path.dirname(filePath);
|
|
102
|
+
if (!fs.existsSync(dir)) {
|
|
103
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
107
|
+
|
|
108
|
+
return { success: true, path: filePath, bytes: content.length };
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const projectEditTool: Tool = {
|
|
113
|
+
name: "project_edit",
|
|
114
|
+
description: "Edit a specific section of a file in the project by replacing text.",
|
|
115
|
+
parameters: {
|
|
116
|
+
type: "object",
|
|
117
|
+
properties: {
|
|
118
|
+
path: {
|
|
119
|
+
type: "string",
|
|
120
|
+
description: "Relative or absolute path to the file to edit",
|
|
121
|
+
},
|
|
122
|
+
oldString: {
|
|
123
|
+
type: "string",
|
|
124
|
+
description: "The text to search for",
|
|
125
|
+
},
|
|
126
|
+
newString: {
|
|
127
|
+
type: "string",
|
|
128
|
+
description: "The text to replace with",
|
|
129
|
+
},
|
|
130
|
+
replaceAll: {
|
|
131
|
+
type: "boolean",
|
|
132
|
+
description: "Replace all occurrences (default: false)",
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
required: ["path", "oldString", "newString"],
|
|
136
|
+
},
|
|
137
|
+
execute: async (params: Record<string, unknown>) => {
|
|
138
|
+
const filePath = isAbsolutePath(params.path as string)
|
|
139
|
+
? (params.path as string)
|
|
140
|
+
: path.join(projectPath, params.path as string);
|
|
141
|
+
const oldString = params.oldString as string;
|
|
142
|
+
const newString = params.newString as string;
|
|
143
|
+
const replaceAll = (params.replaceAll as boolean) ?? false;
|
|
144
|
+
|
|
145
|
+
log.debug(`Editing project file: ${filePath}`);
|
|
146
|
+
|
|
147
|
+
if (!fs.existsSync(filePath)) {
|
|
148
|
+
throw new Error(`File not found: ${filePath}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
152
|
+
|
|
153
|
+
if (!content.includes(oldString)) {
|
|
154
|
+
throw new Error(`String not found in file: ${oldString.substring(0, 50)}...`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let newContent: string;
|
|
158
|
+
if (replaceAll) {
|
|
159
|
+
newContent = content.split(oldString).join(newString);
|
|
160
|
+
} else {
|
|
161
|
+
const index = content.indexOf(oldString);
|
|
162
|
+
if (index === -1) {
|
|
163
|
+
throw new Error(`String not found in file`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const occurrences = content.split(oldString).length - 1;
|
|
167
|
+
if (occurrences > 1) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`Found ${occurrences} occurrences. Use replaceAll: true or provide more context.`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
newContent = content.replace(oldString, newString);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fs.writeFileSync(filePath, newContent, "utf-8");
|
|
177
|
+
|
|
178
|
+
return { success: true, path: filePath };
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const projectListTool: Tool = {
|
|
183
|
+
name: "project_list",
|
|
184
|
+
description: "Explore the project structure by listing files and directories.",
|
|
185
|
+
parameters: {
|
|
186
|
+
type: "object",
|
|
187
|
+
properties: {
|
|
188
|
+
path: {
|
|
189
|
+
type: "string",
|
|
190
|
+
description: "Relative or absolute path to list (default: project root)",
|
|
191
|
+
},
|
|
192
|
+
recursive: {
|
|
193
|
+
type: "boolean",
|
|
194
|
+
description: "List recursively (default: false)",
|
|
195
|
+
},
|
|
196
|
+
maxDepth: {
|
|
197
|
+
type: "number",
|
|
198
|
+
description: "Maximum depth for recursive listing (default: 3)",
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
required: [],
|
|
202
|
+
},
|
|
203
|
+
execute: async (params: Record<string, unknown>) => {
|
|
204
|
+
const basePath = isAbsolutePath(params.path as string || "")
|
|
205
|
+
? (params.path as string)
|
|
206
|
+
: path.join(projectPath, (params.path as string) ?? ".");
|
|
207
|
+
const recursive = (params.recursive as boolean) ?? false;
|
|
208
|
+
const maxDepth = (params.maxDepth as number) ?? 3;
|
|
209
|
+
|
|
210
|
+
log.debug(`Listing project: ${basePath}`);
|
|
211
|
+
|
|
212
|
+
if (!fs.existsSync(basePath)) {
|
|
213
|
+
throw new Error(`Path not found: ${basePath}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const stat = fs.statSync(basePath);
|
|
217
|
+
if (!stat.isDirectory()) {
|
|
218
|
+
return { name: path.basename(basePath), type: "file", path: basePath };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
interface FileEntry {
|
|
222
|
+
name: string;
|
|
223
|
+
type: "file" | "directory";
|
|
224
|
+
path: string;
|
|
225
|
+
children?: FileEntry[];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function listDir(dirPath: string, depth: number): FileEntry[] {
|
|
229
|
+
if (depth > maxDepth) return [];
|
|
230
|
+
|
|
231
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
232
|
+
return entries.map((entry) => {
|
|
233
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
234
|
+
const result: FileEntry = {
|
|
235
|
+
name: entry.name,
|
|
236
|
+
type: entry.isDirectory() ? "directory" : "file",
|
|
237
|
+
path: fullPath,
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
if (entry.isDirectory() && recursive && depth < maxDepth) {
|
|
241
|
+
result.children = listDir(fullPath, depth + 1);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return result;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const entries = listDir(basePath, 0);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
path: basePath,
|
|
252
|
+
entries,
|
|
253
|
+
count: entries.length,
|
|
254
|
+
};
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const projectGlobTool: Tool = {
|
|
259
|
+
name: "project_glob",
|
|
260
|
+
description: "Find files in the project using glob patterns.",
|
|
261
|
+
parameters: {
|
|
262
|
+
type: "object",
|
|
263
|
+
properties: {
|
|
264
|
+
pattern: {
|
|
265
|
+
type: "string",
|
|
266
|
+
description: "Glob pattern (e.g., '**/*.ts', 'src/**/*.js')",
|
|
267
|
+
},
|
|
268
|
+
basePath: {
|
|
269
|
+
type: "string",
|
|
270
|
+
description: "Base path to search from (default: project root)",
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
required: ["pattern"],
|
|
274
|
+
},
|
|
275
|
+
execute: async (params: Record<string, unknown>) => {
|
|
276
|
+
const pattern = params.pattern as string;
|
|
277
|
+
const base = isAbsolutePath(params.basePath as string || "")
|
|
278
|
+
? (params.basePath as string)
|
|
279
|
+
: path.join(projectPath, (params.basePath as string) ?? ".");
|
|
280
|
+
|
|
281
|
+
log.debug(`Globbing project: ${pattern} in ${base}`);
|
|
282
|
+
|
|
283
|
+
const results: string[] = [];
|
|
284
|
+
|
|
285
|
+
function matchGlob(dir: string, pat: string, depth: number): void {
|
|
286
|
+
if (depth > 5) return;
|
|
287
|
+
|
|
288
|
+
const parts = pat.split("/");
|
|
289
|
+
const firstPart = parts[0];
|
|
290
|
+
const remainingParts = parts.slice(1);
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
294
|
+
|
|
295
|
+
for (const entry of entries) {
|
|
296
|
+
const fullPath = path.join(dir, entry.name);
|
|
297
|
+
|
|
298
|
+
if (firstPart === "**") {
|
|
299
|
+
if (entry.isDirectory()) {
|
|
300
|
+
matchGlob(fullPath, parts.slice(1).join("/"), depth + 1);
|
|
301
|
+
matchGlob(fullPath, pat, depth + 1);
|
|
302
|
+
}
|
|
303
|
+
if (remainingParts.length === 0 || matchesPattern(entry.name, remainingParts[0])) {
|
|
304
|
+
results.push(fullPath);
|
|
305
|
+
}
|
|
306
|
+
} else if (matchesPattern(entry.name, firstPart)) {
|
|
307
|
+
if (remainingParts.length === 0) {
|
|
308
|
+
results.push(fullPath);
|
|
309
|
+
} else if (entry.isDirectory()) {
|
|
310
|
+
matchGlob(fullPath, remainingParts.join("/"), depth + 1);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
} catch {
|
|
315
|
+
// Ignore permission errors
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function matchesPattern(name: string, pattern: string): boolean {
|
|
320
|
+
if (pattern === "*") return true;
|
|
321
|
+
if (pattern === "**") return true;
|
|
322
|
+
const regex = new RegExp("^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$");
|
|
323
|
+
return regex.test(name);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
matchGlob(base, pattern, 0);
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
pattern,
|
|
330
|
+
basePath: base,
|
|
331
|
+
files: results.slice(0, 100),
|
|
332
|
+
count: results.length,
|
|
333
|
+
};
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const projectExistsTool: Tool = {
|
|
338
|
+
name: "project_exists",
|
|
339
|
+
description: "Check if a file or directory exists in the project.",
|
|
340
|
+
parameters: {
|
|
341
|
+
type: "object",
|
|
342
|
+
properties: {
|
|
343
|
+
path: {
|
|
344
|
+
type: "string",
|
|
345
|
+
description: "Relative or absolute path to check",
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
required: ["path"],
|
|
349
|
+
},
|
|
350
|
+
execute: async (params: Record<string, unknown>) => {
|
|
351
|
+
const filePath = isAbsolutePath(params.path as string)
|
|
352
|
+
? (params.path as string)
|
|
353
|
+
: path.join(projectPath, params.path as string);
|
|
354
|
+
|
|
355
|
+
const exists = fs.existsSync(filePath);
|
|
356
|
+
let type: "file" | "directory" | "none" = "none";
|
|
357
|
+
|
|
358
|
+
if (exists) {
|
|
359
|
+
const stat = fs.statSync(filePath);
|
|
360
|
+
type = stat.isDirectory() ? "directory" : "file";
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return { path: filePath, exists, type };
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
return [
|
|
368
|
+
projectReadTool,
|
|
369
|
+
projectWriteTool,
|
|
370
|
+
projectEditTool,
|
|
371
|
+
projectListTool,
|
|
372
|
+
projectGlobTool,
|
|
373
|
+
projectExistsTool,
|
|
374
|
+
];
|
|
375
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { readdirSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { Tool } from "../agent/native-tools.ts";
|
|
4
|
+
|
|
5
|
+
export const readTool: Tool = {
|
|
6
|
+
name: "read",
|
|
7
|
+
description: "Read the contents of a file",
|
|
8
|
+
parameters: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
path: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "The path to the file to read",
|
|
14
|
+
},
|
|
15
|
+
offset: {
|
|
16
|
+
type: "number",
|
|
17
|
+
description: "Line number to start reading from (1-indexed)",
|
|
18
|
+
},
|
|
19
|
+
limit: {
|
|
20
|
+
type: "number",
|
|
21
|
+
description: "Maximum number of lines to read",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
required: ["path"],
|
|
25
|
+
},
|
|
26
|
+
execute: async (params: Record<string, unknown>) => {
|
|
27
|
+
const filePath = params.path as string;
|
|
28
|
+
const offset = (params.offset as number) ?? 1;
|
|
29
|
+
const limit = (params.limit as number) ?? 2000;
|
|
30
|
+
|
|
31
|
+
const file = Bun.file(filePath);
|
|
32
|
+
if (!(await file.exists())) {
|
|
33
|
+
throw new Error(`File not found: ${filePath}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check if it's a directory by trying readdir
|
|
37
|
+
try {
|
|
38
|
+
const entries = readdirSync(filePath, { withFileTypes: true });
|
|
39
|
+
return entries.map((e) => ({
|
|
40
|
+
name: e.name,
|
|
41
|
+
type: e.isDirectory() ? "directory" : "file",
|
|
42
|
+
}));
|
|
43
|
+
} catch {
|
|
44
|
+
// Not a directory, read as file
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const content = await file.text();
|
|
48
|
+
const lines = content.split("\n");
|
|
49
|
+
|
|
50
|
+
const start = Math.max(0, offset - 1);
|
|
51
|
+
const end = Math.min(lines.length, start + limit);
|
|
52
|
+
|
|
53
|
+
const selected = lines.slice(start, end);
|
|
54
|
+
|
|
55
|
+
return selected
|
|
56
|
+
.map((line, i) => `${start + i + 1}: ${line}`)
|
|
57
|
+
.join("\n");
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const writeTool: Tool = {
|
|
62
|
+
name: "write",
|
|
63
|
+
description: "Write content to a file (creates or overwrites)",
|
|
64
|
+
parameters: {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
path: {
|
|
68
|
+
type: "string",
|
|
69
|
+
description: "The path to the file to write",
|
|
70
|
+
},
|
|
71
|
+
content: {
|
|
72
|
+
type: "string",
|
|
73
|
+
description: "The content to write to the file",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
required: ["path", "content"],
|
|
77
|
+
},
|
|
78
|
+
execute: async (params: Record<string, unknown>) => {
|
|
79
|
+
const filePath = params.path as string;
|
|
80
|
+
const content = params.content as string;
|
|
81
|
+
|
|
82
|
+
const dir = path.dirname(filePath);
|
|
83
|
+
if (!existsSync(dir)) {
|
|
84
|
+
mkdirSync(dir, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await Bun.write(filePath, content);
|
|
88
|
+
|
|
89
|
+
return { success: true, path: filePath, bytes: content.length };
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const editTool: Tool = {
|
|
94
|
+
name: "edit",
|
|
95
|
+
description: "Edit a file by replacing specific text",
|
|
96
|
+
parameters: {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: {
|
|
99
|
+
path: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "The path to the file to edit",
|
|
102
|
+
},
|
|
103
|
+
oldString: {
|
|
104
|
+
type: "string",
|
|
105
|
+
description: "The text to search for",
|
|
106
|
+
},
|
|
107
|
+
newString: {
|
|
108
|
+
type: "string",
|
|
109
|
+
description: "The text to replace with",
|
|
110
|
+
},
|
|
111
|
+
replaceAll: {
|
|
112
|
+
type: "boolean",
|
|
113
|
+
description: "Replace all occurrences",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
required: ["path", "oldString", "newString"],
|
|
117
|
+
},
|
|
118
|
+
execute: async (params: Record<string, unknown>) => {
|
|
119
|
+
const filePath = params.path as string;
|
|
120
|
+
const oldString = params.oldString as string;
|
|
121
|
+
const newString = params.newString as string;
|
|
122
|
+
const replaceAll = (params.replaceAll as boolean) ?? false;
|
|
123
|
+
|
|
124
|
+
const file = Bun.file(filePath);
|
|
125
|
+
if (!(await file.exists())) {
|
|
126
|
+
throw new Error(`File not found: ${filePath}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const content = await file.text();
|
|
130
|
+
|
|
131
|
+
if (!content.includes(oldString)) {
|
|
132
|
+
throw new Error(`String not found in file: ${oldString.substring(0, 50)}...`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let newContent: string;
|
|
136
|
+
if (replaceAll) {
|
|
137
|
+
newContent = content.split(oldString).join(newString);
|
|
138
|
+
} else {
|
|
139
|
+
const index = content.indexOf(oldString);
|
|
140
|
+
if (index === -1) {
|
|
141
|
+
throw new Error(`String not found in file`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const occurrences = content.split(oldString).length - 1;
|
|
145
|
+
if (occurrences > 1) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Found ${occurrences} occurrences. Use replaceAll: true or provide more context.`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
newContent = content.replace(oldString, newString);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await Bun.write(filePath, newContent);
|
|
155
|
+
|
|
156
|
+
return { success: true, path: filePath };
|
|
157
|
+
},
|
|
158
|
+
};
|