@johpaz/hive-tools 1.0.10

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,379 @@
1
+ import type { Tool } from "@johpaz/hive-core";
2
+ import { z } from "zod";
3
+ import * as fs from "fs/promises";
4
+ import * as path from "path";
5
+
6
+ export interface FileSystemConfig {
7
+ allowedPaths: string[];
8
+ sandboxed: boolean;
9
+ maxFileSize: number;
10
+ }
11
+
12
+ function validatePath(targetPath: string, allowedPaths: string[]): void {
13
+ const resolved = path.resolve(targetPath);
14
+ const isAllowed = allowedPaths.some((allowed) => resolved.startsWith(path.resolve(allowed)));
15
+
16
+ if (!isAllowed) {
17
+ throw new Error(`Path not allowed: ${targetPath}. Allowed paths: ${allowedPaths.join(", ")}`);
18
+ }
19
+ }
20
+
21
+ function getFSConfig(config: Partial<FileSystemConfig>): FileSystemConfig {
22
+ return {
23
+ allowedPaths: config.allowedPaths ?? [process.cwd()],
24
+ sandboxed: config.sandboxed ?? true,
25
+ maxFileSize: config.maxFileSize ?? 10 * 1024 * 1024,
26
+ };
27
+ }
28
+
29
+ export function createFSReadTool(config: Partial<FileSystemConfig> = {}): Tool {
30
+ const fsConfig = getFSConfig(config);
31
+
32
+ return {
33
+ name: "fs_read",
34
+ description: "Read a file from the filesystem",
35
+ parameters: {
36
+ type: "object",
37
+ properties: {
38
+ path: {
39
+ type: "string",
40
+ description: "Path to the file to read",
41
+ },
42
+ encoding: {
43
+ type: "string",
44
+ description: "File encoding (default: utf-8)",
45
+ },
46
+ startLine: {
47
+ type: "number",
48
+ description: "Start reading from this line (1-indexed)",
49
+ },
50
+ endLine: {
51
+ type: "number",
52
+ description: "Stop reading at this line",
53
+ },
54
+ },
55
+ required: ["path"],
56
+ },
57
+ execute: async (params: Record<string, unknown>) => {
58
+ const filePath = params.path as string;
59
+ const encoding = (params.encoding as BufferEncoding) ?? "utf-8";
60
+ const startLine = params.startLine as number | undefined;
61
+ const endLine = params.endLine as number | undefined;
62
+
63
+ if (fsConfig.sandboxed) {
64
+ validatePath(filePath, fsConfig.allowedPaths);
65
+ }
66
+
67
+ const stats = await fs.stat(filePath);
68
+ if (stats.size > fsConfig.maxFileSize) {
69
+ throw new Error(`File too large: ${stats.size} bytes (max: ${fsConfig.maxFileSize})`);
70
+ }
71
+
72
+ let content = await fs.readFile(filePath, encoding);
73
+
74
+ if (startLine !== undefined || endLine !== undefined) {
75
+ const lines = content.split("\n");
76
+ const start = Math.max(0, (startLine ?? 1) - 1);
77
+ const end = endLine !== undefined ? endLine : lines.length;
78
+ content = lines.slice(start, end).join("\n");
79
+ }
80
+
81
+ return {
82
+ success: true,
83
+ path: filePath,
84
+ content,
85
+ size: stats.size,
86
+ modified: stats.mtime.toISOString(),
87
+ };
88
+ },
89
+ };
90
+ }
91
+
92
+ export function createFSWriteTool(config: Partial<FileSystemConfig> = {}): Tool {
93
+ const fsConfig = getFSConfig(config);
94
+
95
+ return {
96
+ name: "fs_write",
97
+ description: "Write content to a file",
98
+ parameters: {
99
+ type: "object",
100
+ properties: {
101
+ path: {
102
+ type: "string",
103
+ description: "Path to the file to write",
104
+ },
105
+ content: {
106
+ type: "string",
107
+ description: "Content to write",
108
+ },
109
+ mode: {
110
+ type: "string",
111
+ enum: ["overwrite", "append"],
112
+ description: "Write mode (default: overwrite)",
113
+ },
114
+ },
115
+ required: ["path", "content"],
116
+ },
117
+ execute: async (params: Record<string, unknown>) => {
118
+ const filePath = params.path as string;
119
+ const content = params.content as string;
120
+ const mode = (params.mode as string) ?? "overwrite";
121
+
122
+ if (fsConfig.sandboxed) {
123
+ validatePath(filePath, fsConfig.allowedPaths);
124
+ }
125
+
126
+ if (content.length > fsConfig.maxFileSize) {
127
+ throw new Error(`Content too large: ${content.length} bytes (max: ${fsConfig.maxFileSize})`);
128
+ }
129
+
130
+ const dir = path.dirname(filePath);
131
+ await fs.mkdir(dir, { recursive: true });
132
+
133
+ if (mode === "append") {
134
+ await fs.appendFile(filePath, content, "utf-8");
135
+ } else {
136
+ await fs.writeFile(filePath, content, "utf-8");
137
+ }
138
+
139
+ return {
140
+ success: true,
141
+ path: filePath,
142
+ bytesWritten: content.length,
143
+ };
144
+ },
145
+ };
146
+ }
147
+
148
+ export function createFSListTool(config: Partial<FileSystemConfig> = {}): Tool {
149
+ const fsConfig = getFSConfig(config);
150
+
151
+ return {
152
+ name: "fs_list",
153
+ description: "List contents of a directory",
154
+ parameters: {
155
+ type: "object",
156
+ properties: {
157
+ path: {
158
+ type: "string",
159
+ description: "Path to the directory",
160
+ },
161
+ recursive: {
162
+ type: "boolean",
163
+ description: "List recursively (default: false)",
164
+ },
165
+ },
166
+ required: ["path"],
167
+ },
168
+ execute: async (params: Record<string, unknown>) => {
169
+ const dirPath = params.path as string;
170
+ const recursive = (params.recursive as boolean) ?? false;
171
+
172
+ if (fsConfig.sandboxed) {
173
+ validatePath(dirPath, fsConfig.allowedPaths);
174
+ }
175
+
176
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
177
+
178
+ const results: Array<{
179
+ name: string;
180
+ path: string;
181
+ type: string;
182
+ size: number;
183
+ modified: string;
184
+ }> = [];
185
+
186
+ for (const entry of entries) {
187
+ const fullPath = path.join(dirPath, entry.name);
188
+ const stats = await fs.stat(fullPath);
189
+
190
+ results.push({
191
+ name: entry.name,
192
+ path: fullPath,
193
+ type: entry.isDirectory() ? "directory" : "file",
194
+ size: stats.size,
195
+ modified: stats.mtime.toISOString(),
196
+ });
197
+ }
198
+
199
+ if (recursive) {
200
+ const subdirs = results.filter((r) => r.type === "directory");
201
+ for (const subdir of subdirs) {
202
+ try {
203
+ const subResult = await createFSListTool(config).execute({ path: subdir.path, recursive: true });
204
+ const subFiles = (subResult as { entries: typeof results }).entries;
205
+ results.push(...subFiles);
206
+ } catch {
207
+ // Skip directories we can't read
208
+ }
209
+ }
210
+ }
211
+
212
+ return {
213
+ success: true,
214
+ path: dirPath,
215
+ entries: results,
216
+ count: results.length,
217
+ };
218
+ },
219
+ };
220
+ }
221
+
222
+ export function createFSMkdirTool(config: Partial<FileSystemConfig> = {}): Tool {
223
+ const fsConfig = getFSConfig(config);
224
+
225
+ return {
226
+ name: "fs_mkdir",
227
+ description: "Create a directory",
228
+ parameters: {
229
+ type: "object",
230
+ properties: {
231
+ path: {
232
+ type: "string",
233
+ description: "Path to create",
234
+ },
235
+ },
236
+ required: ["path"],
237
+ },
238
+ execute: async (params: Record<string, unknown>) => {
239
+ const dirPath = params.path as string;
240
+
241
+ if (fsConfig.sandboxed) {
242
+ validatePath(dirPath, fsConfig.allowedPaths);
243
+ }
244
+
245
+ await fs.mkdir(dirPath, { recursive: true });
246
+
247
+ return { success: true, path: dirPath };
248
+ },
249
+ };
250
+ }
251
+
252
+ export function createFSDeleteTool(config: Partial<FileSystemConfig> = {}): Tool {
253
+ const fsConfig = getFSConfig(config);
254
+
255
+ return {
256
+ name: "fs_delete",
257
+ description: "Delete a file or directory",
258
+ parameters: {
259
+ type: "object",
260
+ properties: {
261
+ path: {
262
+ type: "string",
263
+ description: "Path to delete",
264
+ },
265
+ recursive: {
266
+ type: "boolean",
267
+ description: "Delete recursively for directories",
268
+ },
269
+ },
270
+ required: ["path"],
271
+ },
272
+ execute: async (params: Record<string, unknown>) => {
273
+ const targetPath = params.path as string;
274
+ const recursive = (params.recursive as boolean) ?? false;
275
+
276
+ if (fsConfig.sandboxed) {
277
+ validatePath(targetPath, fsConfig.allowedPaths);
278
+ }
279
+
280
+ const stats = await fs.stat(targetPath);
281
+
282
+ if (stats.isDirectory()) {
283
+ if (recursive) {
284
+ await fs.rm(targetPath, { recursive: true });
285
+ } else {
286
+ await fs.rmdir(targetPath);
287
+ }
288
+ } else {
289
+ await fs.unlink(targetPath);
290
+ }
291
+
292
+ return { success: true, path: targetPath };
293
+ },
294
+ };
295
+ }
296
+
297
+ export function createFSCopyTool(config: Partial<FileSystemConfig> = {}): Tool {
298
+ const fsConfig = getFSConfig(config);
299
+
300
+ return {
301
+ name: "fs_copy",
302
+ description: "Copy a file",
303
+ parameters: {
304
+ type: "object",
305
+ properties: {
306
+ source: {
307
+ type: "string",
308
+ description: "Source file path",
309
+ },
310
+ destination: {
311
+ type: "string",
312
+ description: "Destination file path",
313
+ },
314
+ },
315
+ required: ["source", "destination"],
316
+ },
317
+ execute: async (params: Record<string, unknown>) => {
318
+ const source = params.source as string;
319
+ const destination = params.destination as string;
320
+
321
+ if (fsConfig.sandboxed) {
322
+ validatePath(source, fsConfig.allowedPaths);
323
+ validatePath(destination, fsConfig.allowedPaths);
324
+ }
325
+
326
+ await fs.copyFile(source, destination);
327
+
328
+ return { success: true, source, destination };
329
+ },
330
+ };
331
+ }
332
+
333
+ export function createFSMoveTool(config: Partial<FileSystemConfig> = {}): Tool {
334
+ const fsConfig = getFSConfig(config);
335
+
336
+ return {
337
+ name: "fs_move",
338
+ description: "Move or rename a file",
339
+ parameters: {
340
+ type: "object",
341
+ properties: {
342
+ source: {
343
+ type: "string",
344
+ description: "Source file path",
345
+ },
346
+ destination: {
347
+ type: "string",
348
+ description: "Destination file path",
349
+ },
350
+ },
351
+ required: ["source", "destination"],
352
+ },
353
+ execute: async (params: Record<string, unknown>) => {
354
+ const source = params.source as string;
355
+ const destination = params.destination as string;
356
+
357
+ if (fsConfig.sandboxed) {
358
+ validatePath(source, fsConfig.allowedPaths);
359
+ validatePath(destination, fsConfig.allowedPaths);
360
+ }
361
+
362
+ await fs.rename(source, destination);
363
+
364
+ return { success: true, source, destination };
365
+ },
366
+ };
367
+ }
368
+
369
+ export function createFSTools(config: Partial<FileSystemConfig> = {}): Tool[] {
370
+ return [
371
+ createFSReadTool(config),
372
+ createFSWriteTool(config),
373
+ createFSListTool(config),
374
+ createFSMkdirTool(config),
375
+ createFSDeleteTool(config),
376
+ createFSCopyTool(config),
377
+ createFSMoveTool(config),
378
+ ];
379
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./browser/index.ts";
2
+ export * from "./cron/index.ts";
3
+ export * from "./filesystem/index.ts";
4
+ export * from "./canvas/index.ts";