@perstack/base 0.0.21 → 0.0.22

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,1125 @@
1
+ import { realpathSync, existsSync, statSync } from 'fs';
2
+ import fs, { appendFile, mkdir, rm, rmdir, unlink, readFile, writeFile, readdir, rename, stat } from 'fs/promises';
3
+ import { dedent } from 'ts-dedent';
4
+ import { z } from 'zod';
5
+ import os from 'os';
6
+ import path, { dirname, extname, basename, resolve, join } from 'path';
7
+ import { execFile } from 'child_process';
8
+ import { promisify } from 'util';
9
+ import mime from 'mime-types';
10
+
11
+ // src/tools/append-text-file.ts
12
+ var workspacePath = realpathSync(expandHome(process.cwd()));
13
+ function expandHome(filepath) {
14
+ if (filepath.startsWith("~/") || filepath === "~") {
15
+ return path.join(os.homedir(), filepath.slice(1));
16
+ }
17
+ return filepath;
18
+ }
19
+ async function validatePath(requestedPath) {
20
+ const expandedPath = expandHome(requestedPath);
21
+ const absolute = path.isAbsolute(expandedPath) ? path.resolve(expandedPath) : path.resolve(process.cwd(), expandedPath);
22
+ if (absolute === `${workspacePath}/perstack`) {
23
+ throw new Error("Access denied - perstack directory is not allowed");
24
+ }
25
+ try {
26
+ const realAbsolute = await fs.realpath(absolute);
27
+ if (!realAbsolute.startsWith(workspacePath)) {
28
+ throw new Error("Access denied - symlink target outside allowed directories");
29
+ }
30
+ return realAbsolute;
31
+ } catch (error) {
32
+ const parentDir = path.dirname(absolute);
33
+ try {
34
+ const realParentPath = await fs.realpath(parentDir);
35
+ if (!realParentPath.startsWith(workspacePath)) {
36
+ throw new Error("Access denied - parent directory outside allowed directories");
37
+ }
38
+ return absolute;
39
+ } catch {
40
+ if (!absolute.startsWith(workspacePath)) {
41
+ throw new Error(
42
+ `Access denied - path outside allowed directories: ${absolute} not in ${workspacePath}`
43
+ );
44
+ }
45
+ throw new Error(`Parent directory does not exist: ${parentDir}`);
46
+ }
47
+ }
48
+ }
49
+
50
+ // src/lib/tool-result.ts
51
+ function successToolResult(result) {
52
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
53
+ }
54
+ function errorToolResult(e) {
55
+ return {
56
+ content: [{ type: "text", text: JSON.stringify({ error: e.name, message: e.message }) }]
57
+ };
58
+ }
59
+
60
+ // src/tools/append-text-file.ts
61
+ var inputSchema = z.object({
62
+ path: z.string().describe("Target file path to append to."),
63
+ text: z.string().min(1).max(2e3).describe("Text to append to the file. Max 2000 characters.")
64
+ });
65
+ async function appendTextFile(input) {
66
+ const { path: path2, text } = input;
67
+ const validatedPath = await validatePath(path2);
68
+ if (!existsSync(validatedPath)) {
69
+ throw new Error(`File ${path2} does not exist.`);
70
+ }
71
+ const stats = statSync(validatedPath);
72
+ if (!(stats.mode & 128)) {
73
+ throw new Error(`File ${path2} is not writable`);
74
+ }
75
+ await appendFile(validatedPath, text);
76
+ return { path: validatedPath, text };
77
+ }
78
+ function registerAppendTextFile(server) {
79
+ server.registerTool(
80
+ "appendTextFile",
81
+ {
82
+ title: "Append text file",
83
+ description: dedent`
84
+ Adding content to the end of existing files.
85
+
86
+ Use cases:
87
+ - Adding entries to log files
88
+ - Appending data to CSV or JSON files
89
+ - Adding new sections to documentation
90
+ - Extending configuration files
91
+ - Building files incrementally
92
+
93
+ How it works:
94
+ - Appends text to the end of an existing file
95
+ - Does not modify existing content
96
+ - Creates a new line before appending if needed
97
+ - Returns the appended file path
98
+
99
+ Rules:
100
+ - FILE MUST EXIST BEFORE APPENDING
101
+ - YOU MUST PROVIDE A VALID UTF-8 STRING FOR THE TEXT
102
+ - THERE IS A LIMIT ON THE NUMBER OF TOKENS THAT CAN BE GENERATED, SO DO NOT APPEND ALL THE CONTENT AT ONCE
103
+ - IF YOU WANT TO APPEND MORE THAN 2000 CHARACTERS, USE THIS TOOL MULTIPLE TIMES
104
+ `,
105
+ inputSchema: inputSchema.shape
106
+ },
107
+ async (input) => {
108
+ try {
109
+ return successToolResult(await appendTextFile(input));
110
+ } catch (e) {
111
+ if (e instanceof Error) return errorToolResult(e);
112
+ throw e;
113
+ }
114
+ }
115
+ );
116
+ }
117
+ var inputSchema2 = z.object({});
118
+ async function attemptCompletion(_input) {
119
+ return {
120
+ message: "End the agent loop and provide a final report"
121
+ };
122
+ }
123
+ function registerAttemptCompletion(server) {
124
+ server.registerTool(
125
+ "attemptCompletion",
126
+ {
127
+ title: "Attempt completion",
128
+ description: dedent`
129
+ Task completion signal that triggers immediate final report generation.
130
+ Use cases:
131
+ - Signaling task completion to Perstack runtime
132
+ - Triggering final report generation
133
+ - Ending the current expert's work cycle
134
+ How it works:
135
+ - Sends completion signal to Perstack runtime
136
+ - Runtime immediately proceeds to final report generation
137
+ - No confirmation or approval step required
138
+ - No parameters needed for this signal
139
+ Notes:
140
+ - Triggers immediate transition to final report
141
+ - Should only be used when task is fully complete
142
+ - Cannot be reverted once called
143
+ `,
144
+ inputSchema: inputSchema2.shape
145
+ },
146
+ async (input) => {
147
+ try {
148
+ return successToolResult(await attemptCompletion(input));
149
+ } catch (e) {
150
+ if (e instanceof Error) return errorToolResult(e);
151
+ throw e;
152
+ }
153
+ }
154
+ );
155
+ }
156
+ var inputSchema3 = z.object({
157
+ path: z.string()
158
+ });
159
+ async function createDirectory(input) {
160
+ const { path: path2 } = input;
161
+ const validatedPath = await validatePath(path2);
162
+ const exists = existsSync(validatedPath);
163
+ if (exists) {
164
+ throw new Error(`Directory ${path2} already exists`);
165
+ }
166
+ const parentDir = dirname(validatedPath);
167
+ if (existsSync(parentDir)) {
168
+ const parentStats = statSync(parentDir);
169
+ if (!(parentStats.mode & 128)) {
170
+ throw new Error(`Parent directory ${parentDir} is not writable`);
171
+ }
172
+ }
173
+ await mkdir(validatedPath, { recursive: true });
174
+ return {
175
+ path: validatedPath
176
+ };
177
+ }
178
+ function registerCreateDirectory(server) {
179
+ server.registerTool(
180
+ "createDirectory",
181
+ {
182
+ title: "Create directory",
183
+ description: dedent`
184
+ Directory creator for establishing folder structures in the workspace.
185
+
186
+ Use cases:
187
+ - Setting up project directory structure
188
+ - Creating output folders for generated content
189
+ - Organizing files into logical groups
190
+ - Preparing directory hierarchies
191
+
192
+ How it works:
193
+ - Creates directories recursively
194
+ - Handles existing directories gracefully
195
+ - Creates parent directories as needed
196
+ - Returns creation status
197
+
198
+ Parameters:
199
+ - path: Directory path to create
200
+ `,
201
+ inputSchema: inputSchema3.shape
202
+ },
203
+ async (input) => {
204
+ try {
205
+ return successToolResult(await createDirectory(input));
206
+ } catch (e) {
207
+ if (e instanceof Error) return errorToolResult(e);
208
+ throw e;
209
+ }
210
+ }
211
+ );
212
+ }
213
+ var inputSchema4 = z.object({
214
+ path: z.string(),
215
+ recursive: z.boolean().optional().describe("Whether to delete contents recursively. Required for non-empty directories.")
216
+ });
217
+ async function deleteDirectory(input) {
218
+ const { path: path2, recursive } = input;
219
+ const validatedPath = await validatePath(path2);
220
+ if (!existsSync(validatedPath)) {
221
+ throw new Error(`Directory ${path2} does not exist.`);
222
+ }
223
+ const stats = statSync(validatedPath);
224
+ if (!stats.isDirectory()) {
225
+ throw new Error(`Path ${path2} is not a directory. Use deleteFile tool instead.`);
226
+ }
227
+ if (!(stats.mode & 128)) {
228
+ throw new Error(`Directory ${path2} is not writable`);
229
+ }
230
+ if (recursive) {
231
+ await rm(validatedPath, { recursive: true });
232
+ } else {
233
+ await rmdir(validatedPath);
234
+ }
235
+ return {
236
+ path: validatedPath
237
+ };
238
+ }
239
+ function registerDeleteDirectory(server) {
240
+ server.registerTool(
241
+ "deleteDirectory",
242
+ {
243
+ title: "Delete directory",
244
+ description: dedent`
245
+ Directory deleter for removing directories from the workspace.
246
+
247
+ Use cases:
248
+ - Removing temporary directories
249
+ - Cleaning up build artifacts
250
+ - Deleting empty directories after moving files
251
+
252
+ How it works:
253
+ - Validates directory existence and permissions
254
+ - Removes directory (and contents if recursive is true)
255
+ - Returns deletion status
256
+
257
+ Parameters:
258
+ - path: Directory path to delete
259
+ - recursive: Set to true to delete non-empty directories
260
+ `,
261
+ inputSchema: inputSchema4.shape
262
+ },
263
+ async (input) => {
264
+ try {
265
+ return successToolResult(await deleteDirectory(input));
266
+ } catch (e) {
267
+ if (e instanceof Error) return errorToolResult(e);
268
+ throw e;
269
+ }
270
+ }
271
+ );
272
+ }
273
+ var inputSchema5 = z.object({
274
+ path: z.string()
275
+ });
276
+ async function deleteFile(input) {
277
+ const { path: path2 } = input;
278
+ const validatedPath = await validatePath(path2);
279
+ if (!existsSync(validatedPath)) {
280
+ throw new Error(`File ${path2} does not exist.`);
281
+ }
282
+ const stats = statSync(validatedPath);
283
+ if (stats.isDirectory()) {
284
+ throw new Error(`Path ${path2} is a directory. Use delete directory tool instead.`);
285
+ }
286
+ if (!(stats.mode & 128)) {
287
+ throw new Error(`File ${path2} is not writable`);
288
+ }
289
+ await unlink(validatedPath);
290
+ return {
291
+ path: validatedPath
292
+ };
293
+ }
294
+ function registerDeleteFile(server) {
295
+ server.registerTool(
296
+ "deleteFile",
297
+ {
298
+ title: "Delete file",
299
+ description: dedent`
300
+ File deleter for removing files from the workspace.
301
+
302
+ Use cases:
303
+ - Removing temporary files
304
+ - Cleaning up generated files
305
+ - Deleting outdated configuration files
306
+ - Removing unwanted artifacts
307
+
308
+ How it works:
309
+ - Validates file existence and permissions
310
+ - Performs atomic delete operation
311
+ - Returns deletion status
312
+
313
+ Parameters:
314
+ - path: File path to delete
315
+ `,
316
+ inputSchema: inputSchema5.shape
317
+ },
318
+ async (input) => {
319
+ try {
320
+ return successToolResult(await deleteFile(input));
321
+ } catch (e) {
322
+ if (e instanceof Error) return errorToolResult(e);
323
+ throw e;
324
+ }
325
+ }
326
+ );
327
+ }
328
+ var inputSchema6 = z.object({
329
+ path: z.string().describe("Target file path to edit."),
330
+ newText: z.string().min(1).max(2e3).describe("Text to append to the file. Max 2000 characters."),
331
+ oldText: z.string().min(1).max(2e3).describe("Exact text to find and replace. Max 2000 characters.")
332
+ });
333
+ async function editTextFile(input) {
334
+ const { path: path2, newText, oldText } = input;
335
+ const validatedPath = await validatePath(path2);
336
+ if (!existsSync(validatedPath)) {
337
+ throw new Error(`File ${path2} does not exist.`);
338
+ }
339
+ const stats = statSync(validatedPath);
340
+ if (!(stats.mode & 128)) {
341
+ throw new Error(`File ${path2} is not writable`);
342
+ }
343
+ await applyFileEdit(validatedPath, newText, oldText);
344
+ return {
345
+ path: validatedPath,
346
+ newText,
347
+ oldText
348
+ };
349
+ }
350
+ function normalizeLineEndings(text) {
351
+ return text.replace(/\r\n/g, "\n");
352
+ }
353
+ async function applyFileEdit(filePath, newText, oldText) {
354
+ const content = normalizeLineEndings(await readFile(filePath, "utf-8"));
355
+ const normalizedOld = normalizeLineEndings(oldText);
356
+ const normalizedNew = normalizeLineEndings(newText);
357
+ if (!content.includes(normalizedOld)) {
358
+ throw new Error(`Could not find exact match for oldText in file ${filePath}`);
359
+ }
360
+ const modifiedContent = content.replace(normalizedOld, normalizedNew);
361
+ await writeFile(filePath, modifiedContent, "utf-8");
362
+ }
363
+ function registerEditTextFile(server) {
364
+ server.registerTool(
365
+ "editTextFile",
366
+ {
367
+ title: "Edit text file",
368
+ description: dedent`
369
+ Text file editor for modifying existing files with precise text replacement.
370
+
371
+ Use cases:
372
+ - Updating configuration values
373
+ - Modifying code snippets
374
+ - Replacing specific text blocks
375
+ - Making targeted edits to files
376
+
377
+ How it works:
378
+ - Reads existing file content
379
+ - Performs exact text replacement of oldText with newText
380
+ - Normalizes line endings for consistent behavior
381
+ - Returns summary of changes made
382
+ - For appending text to files, use the appendTextFile tool instead
383
+
384
+ Rules:
385
+ - YOU MUST PROVIDE A VALID UTF-8 STRING FOR THE TEXT
386
+ - THERE IS A LIMIT ON THE NUMBER OF TOKENS THAT CAN BE GENERATED, SO DO NOT WRITE ALL THE CONTENT AT ONCE (IT WILL CAUSE AN ERROR)
387
+ - IF YOU WANT TO EDIT MORE THAN 2000 CHARACTERS, USE THIS TOOL MULTIPLE TIMES
388
+ - DO NOT USE THIS TOOL FOR APPENDING TEXT TO FILES - USE appendTextFile TOOL INSTEAD
389
+ `,
390
+ inputSchema: inputSchema6.shape
391
+ },
392
+ async (input) => {
393
+ try {
394
+ return successToolResult(await editTextFile(input));
395
+ } catch (e) {
396
+ if (e instanceof Error) return errorToolResult(e);
397
+ throw e;
398
+ }
399
+ }
400
+ );
401
+ }
402
+ var execFileAsync = promisify(execFile);
403
+ var inputSchema7 = z.object({
404
+ command: z.string().describe("The command to execute"),
405
+ args: z.array(z.string()).describe("The arguments to pass to the command"),
406
+ env: z.record(z.string(), z.string()).describe("The environment variables to set"),
407
+ cwd: z.string().describe("The working directory to execute the command in"),
408
+ stdout: z.boolean().describe("Whether to capture the standard output"),
409
+ stderr: z.boolean().describe("Whether to capture the standard error"),
410
+ timeout: z.number().optional().describe("Timeout in milliseconds")
411
+ });
412
+ async function exec(input) {
413
+ const validatedCwd = await validatePath(input.cwd);
414
+ const { stdout, stderr } = await execFileAsync(input.command, input.args, {
415
+ cwd: validatedCwd,
416
+ env: { ...process.env, ...input.env },
417
+ timeout: input.timeout
418
+ });
419
+ let output = "";
420
+ if (input.stdout) {
421
+ output += stdout;
422
+ }
423
+ if (input.stderr) {
424
+ output += stderr;
425
+ }
426
+ if (!output.trim()) {
427
+ output = "Command executed successfully, but produced no output.";
428
+ }
429
+ return { output };
430
+ }
431
+ function registerExec(server) {
432
+ server.registerTool(
433
+ "exec",
434
+ {
435
+ title: "Execute Command",
436
+ description: dedent`
437
+ Command executor for running system commands and scripts.
438
+
439
+ Use cases:
440
+ - Running system tasks or scripts
441
+ - Automating command-line tools or utilities
442
+ - Executing build commands or test runners
443
+
444
+ How it works:
445
+ - Executes the specified command with arguments
446
+ - Captures stdout and/or stderr based on flags
447
+ - Returns command output or error information
448
+
449
+ Parameters:
450
+ - command: The command to execute (e.g., ls, python)
451
+ - args: Arguments to pass to the command
452
+ - env: Environment variables for the execution
453
+ - cwd: Working directory for command execution
454
+ - stdout: Whether to capture standard output
455
+ - stderr: Whether to capture standard error
456
+ - timeout: Timeout in milliseconds (optional)
457
+
458
+ Rules:
459
+ - Only execute commands from trusted sources
460
+ - Do not execute long-running foreground commands (e.g., tail -f)
461
+ - Be cautious with resource-intensive commands
462
+ `,
463
+ inputSchema: inputSchema7.shape
464
+ },
465
+ async (input) => {
466
+ try {
467
+ return successToolResult(await exec(input));
468
+ } catch (error) {
469
+ const execError = error;
470
+ let message = "";
471
+ if (execError && (execError.killed || execError.signal === "SIGTERM") && typeof input.timeout === "number") {
472
+ message = `Command timed out after ${input.timeout}ms.`;
473
+ } else if (error instanceof Error) {
474
+ if (error.message.includes("timeout")) {
475
+ message = `Command timed out after ${input.timeout}ms.`;
476
+ } else {
477
+ message = error.message;
478
+ }
479
+ } else {
480
+ message = "An unknown error occurred.";
481
+ }
482
+ const result = { error: message };
483
+ if (execError.stdout && input.stdout) {
484
+ result.stdout = execError.stdout;
485
+ }
486
+ if (execError.stderr && input.stderr) {
487
+ result.stderr = execError.stderr;
488
+ }
489
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
490
+ }
491
+ }
492
+ );
493
+ }
494
+ var inputSchema8 = z.object({
495
+ path: z.string()
496
+ });
497
+ async function getFileInfo(input) {
498
+ const { path: path2 } = input;
499
+ const validatedPath = await validatePath(path2);
500
+ if (!existsSync(validatedPath)) {
501
+ throw new Error(`File or directory ${path2} does not exist`);
502
+ }
503
+ const stats = statSync(validatedPath);
504
+ const isDirectory = stats.isDirectory();
505
+ const mimeType = isDirectory ? null : mime.lookup(validatedPath) || "application/octet-stream";
506
+ const formatSize = (bytes) => {
507
+ if (bytes === 0) return "0 B";
508
+ const units = ["B", "KB", "MB", "GB", "TB"];
509
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
510
+ return `${(bytes / 1024 ** i).toFixed(2)} ${units[i]}`;
511
+ };
512
+ return {
513
+ exists: true,
514
+ path: validatedPath,
515
+ absolutePath: resolve(validatedPath),
516
+ name: basename(validatedPath),
517
+ directory: dirname(validatedPath),
518
+ extension: isDirectory ? null : extname(validatedPath),
519
+ type: isDirectory ? "directory" : "file",
520
+ mimeType,
521
+ size: stats.size,
522
+ sizeFormatted: formatSize(stats.size),
523
+ created: stats.birthtime.toISOString(),
524
+ modified: stats.mtime.toISOString(),
525
+ accessed: stats.atime.toISOString(),
526
+ permissions: {
527
+ readable: true,
528
+ writable: Boolean(stats.mode & 128),
529
+ executable: Boolean(stats.mode & 64)
530
+ }
531
+ };
532
+ }
533
+ function registerGetFileInfo(server) {
534
+ server.registerTool(
535
+ "getFileInfo",
536
+ {
537
+ title: "Get file info",
538
+ description: dedent`
539
+ File information retriever for detailed metadata about files and directories.
540
+
541
+ Use cases:
542
+ - Checking file existence and type
543
+ - Getting file size and timestamps
544
+ - Determining MIME types
545
+ - Validating file accessibility
546
+
547
+ How it works:
548
+ - Retrieves comprehensive file system metadata
549
+ - Detects MIME type from file extension
550
+ - Provides both absolute and relative paths
551
+ - Returns human-readable file sizes
552
+
553
+ Parameters:
554
+ - path: File or directory path to inspect
555
+ `,
556
+ inputSchema: inputSchema8.shape
557
+ },
558
+ async (input) => {
559
+ try {
560
+ return successToolResult(await getFileInfo(input));
561
+ } catch (e) {
562
+ if (e instanceof Error) return errorToolResult(e);
563
+ throw e;
564
+ }
565
+ }
566
+ );
567
+ }
568
+ var inputSchema9 = z.object({
569
+ path: z.string()
570
+ });
571
+ async function listDirectory(input) {
572
+ const { path: path2 } = input;
573
+ const validatedPath = await validatePath(path2);
574
+ if (!existsSync(validatedPath)) {
575
+ throw new Error(`Directory ${path2} does not exist.`);
576
+ }
577
+ const stats = statSync(validatedPath);
578
+ if (!stats.isDirectory()) {
579
+ throw new Error(`Path ${path2} is not a directory.`);
580
+ }
581
+ const entries = await readdir(validatedPath);
582
+ const items = [];
583
+ for (const entry of entries.sort()) {
584
+ try {
585
+ const fullPath = await validatePath(join(validatedPath, entry));
586
+ const entryStats = statSync(fullPath);
587
+ const item = {
588
+ name: entry,
589
+ path: entry,
590
+ type: entryStats.isDirectory() ? "directory" : "file",
591
+ size: entryStats.size,
592
+ modified: entryStats.mtime.toISOString()
593
+ };
594
+ items.push(item);
595
+ } catch (e) {
596
+ if (e instanceof Error && e.message.includes("perstack directory is not allowed")) {
597
+ continue;
598
+ }
599
+ throw e;
600
+ }
601
+ }
602
+ return {
603
+ path: validatedPath,
604
+ items
605
+ };
606
+ }
607
+ function registerListDirectory(server) {
608
+ server.registerTool(
609
+ "listDirectory",
610
+ {
611
+ title: "List directory",
612
+ description: dedent`
613
+ Directory content lister with detailed file information.
614
+
615
+ Use cases:
616
+ - Exploring project structure
617
+ - Finding files in a directory
618
+ - Checking directory contents before operations
619
+ - Understanding file organization
620
+
621
+ How it works:
622
+ - Lists all files and subdirectories in specified directory only
623
+ - Provides file type, size, and modification time
624
+ - Sorts entries alphabetically
625
+ - Handles empty directories
626
+
627
+ Parameters:
628
+ - path: Directory path to list (optional, defaults to workspace root)
629
+ `,
630
+ inputSchema: inputSchema9.shape
631
+ },
632
+ async (input) => {
633
+ try {
634
+ return successToolResult(await listDirectory(input));
635
+ } catch (e) {
636
+ if (e instanceof Error) return errorToolResult(e);
637
+ throw e;
638
+ }
639
+ }
640
+ );
641
+ }
642
+ var inputSchema10 = z.object({
643
+ source: z.string(),
644
+ destination: z.string()
645
+ });
646
+ async function moveFile(input) {
647
+ const { source, destination } = input;
648
+ const validatedSource = await validatePath(source);
649
+ const validatedDestination = await validatePath(destination);
650
+ if (!existsSync(validatedSource)) {
651
+ throw new Error(`Source file ${source} does not exist.`);
652
+ }
653
+ const sourceStats = statSync(validatedSource);
654
+ if (!(sourceStats.mode & 128)) {
655
+ throw new Error(`Source file ${source} is not writable`);
656
+ }
657
+ if (existsSync(validatedDestination)) {
658
+ throw new Error(`Destination ${destination} already exists.`);
659
+ }
660
+ const destDir = dirname(validatedDestination);
661
+ await mkdir(destDir, { recursive: true });
662
+ await rename(validatedSource, validatedDestination);
663
+ return {
664
+ source: validatedSource,
665
+ destination: validatedDestination
666
+ };
667
+ }
668
+ function registerMoveFile(server) {
669
+ server.registerTool(
670
+ "moveFile",
671
+ {
672
+ title: "Move file",
673
+ description: dedent`
674
+ File mover for relocating or renaming files within the workspace.
675
+
676
+ Use cases:
677
+ - Renaming files to follow naming conventions
678
+ - Moving files to different directories
679
+ - Organizing project structure
680
+ - Backing up files before modifications
681
+
682
+ How it works:
683
+ - Validates source file existence
684
+ - Creates destination directory if needed
685
+ - Performs atomic move operation
686
+ - Preserves file permissions and timestamps
687
+
688
+ Parameters:
689
+ - source: Current file path
690
+ - destination: Target file path
691
+ `,
692
+ inputSchema: inputSchema10.shape
693
+ },
694
+ async (input) => {
695
+ try {
696
+ return successToolResult(await moveFile(input));
697
+ } catch (e) {
698
+ if (e instanceof Error) return errorToolResult(e);
699
+ throw e;
700
+ }
701
+ }
702
+ );
703
+ }
704
+ var MAX_IMAGE_SIZE = 15 * 1024 * 1024;
705
+ var inputSchema11 = z.object({
706
+ path: z.string()
707
+ });
708
+ async function readImageFile(input) {
709
+ const { path: path2 } = input;
710
+ const validatedPath = await validatePath(path2);
711
+ const isFile = existsSync(validatedPath);
712
+ if (!isFile) {
713
+ throw new Error(`File ${path2} does not exist.`);
714
+ }
715
+ const mimeType = mime.lookup(validatedPath);
716
+ if (!mimeType || !["image/png", "image/jpeg", "image/gif", "image/webp"].includes(mimeType)) {
717
+ throw new Error(`File ${path2} is not supported.`);
718
+ }
719
+ const fileStats = await stat(validatedPath);
720
+ const fileSizeMB = fileStats.size / (1024 * 1024);
721
+ if (fileStats.size > MAX_IMAGE_SIZE) {
722
+ throw new Error(
723
+ `Image file too large (${fileSizeMB.toFixed(1)}MB). Maximum supported size is ${MAX_IMAGE_SIZE / (1024 * 1024)}MB. Please use a smaller image file.`
724
+ );
725
+ }
726
+ return {
727
+ path: validatedPath,
728
+ mimeType,
729
+ size: fileStats.size
730
+ };
731
+ }
732
+ function registerReadImageFile(server) {
733
+ server.registerTool(
734
+ "readImageFile",
735
+ {
736
+ title: "Read image file",
737
+ description: dedent`
738
+ Image file reader that converts images to base64 encoded strings with MIME type validation.
739
+
740
+ Use cases:
741
+ - Loading images for LLM to process
742
+ - Retrieving image data for analysis or display
743
+ - Converting workspace image files to base64 format
744
+
745
+ How it works:
746
+ - Validates file existence and MIME type before reading
747
+ - Encodes file content as base64 string
748
+ - Returns image data with correct MIME type for proper handling
749
+ - Rejects unsupported formats with clear error messages
750
+
751
+ Supported formats:
752
+ - PNG (image/png)
753
+ - JPEG/JPG (image/jpeg)
754
+ - GIF (image/gif) - static only, animated not supported
755
+ - WebP (image/webp)
756
+
757
+ Notes:
758
+ - Maximum file size: 15MB (larger files will be rejected)
759
+ `,
760
+ inputSchema: inputSchema11.shape
761
+ },
762
+ async (input) => {
763
+ try {
764
+ return successToolResult(await readImageFile(input));
765
+ } catch (e) {
766
+ if (e instanceof Error) return errorToolResult(e);
767
+ throw e;
768
+ }
769
+ }
770
+ );
771
+ }
772
+ var MAX_PDF_SIZE = 30 * 1024 * 1024;
773
+ var inputSchema12 = z.object({
774
+ path: z.string()
775
+ });
776
+ async function readPdfFile(input) {
777
+ const { path: path2 } = input;
778
+ const validatedPath = await validatePath(path2);
779
+ const isFile = existsSync(validatedPath);
780
+ if (!isFile) {
781
+ throw new Error(`File ${path2} does not exist.`);
782
+ }
783
+ const mimeType = mime.lookup(validatedPath);
784
+ if (mimeType !== "application/pdf") {
785
+ throw new Error(`File ${path2} is not a PDF file.`);
786
+ }
787
+ const fileStats = await stat(validatedPath);
788
+ const fileSizeMB = fileStats.size / (1024 * 1024);
789
+ if (fileStats.size > MAX_PDF_SIZE) {
790
+ throw new Error(
791
+ `PDF file too large (${fileSizeMB.toFixed(1)}MB). Maximum supported size is ${MAX_PDF_SIZE / (1024 * 1024)}MB. Please use a smaller PDF file.`
792
+ );
793
+ }
794
+ return {
795
+ path: validatedPath,
796
+ mimeType,
797
+ size: fileStats.size
798
+ };
799
+ }
800
+ function registerReadPdfFile(server) {
801
+ server.registerTool(
802
+ "readPdfFile",
803
+ {
804
+ title: "Read PDF file",
805
+ description: dedent`
806
+ PDF file reader that converts documents to base64 encoded resources.
807
+
808
+ Use cases:
809
+ - Extracting content from PDF documents for analysis
810
+ - Loading PDF files for LLM processing
811
+ - Retrieving PDF data for conversion or manipulation
812
+
813
+ How it works:
814
+ - Validates file existence and MIME type (application/pdf)
815
+ - Encodes PDF content as base64 blob
816
+ - Returns as resource type with proper MIME type and URI
817
+ - Rejects non-PDF files with clear error messages
818
+
819
+ Notes:
820
+ - Returns entire PDF content, no page range support
821
+ - Maximum file size: 10MB (larger files will be rejected)
822
+ - Text extraction not performed, returns raw PDF data
823
+ `,
824
+ inputSchema: inputSchema12.shape
825
+ },
826
+ async (input) => {
827
+ try {
828
+ return successToolResult(await readPdfFile(input));
829
+ } catch (e) {
830
+ if (e instanceof Error) return errorToolResult(e);
831
+ throw e;
832
+ }
833
+ }
834
+ );
835
+ }
836
+ var inputSchema13 = z.object({
837
+ path: z.string(),
838
+ from: z.number().optional().describe("The line number to start reading from."),
839
+ to: z.number().optional().describe("The line number to stop reading at.")
840
+ });
841
+ async function readTextFile(input) {
842
+ const { path: path2, from, to } = input;
843
+ const validatedPath = await validatePath(path2);
844
+ const isFile = existsSync(validatedPath);
845
+ if (!isFile) {
846
+ throw new Error(`File ${path2} does not exist.`);
847
+ }
848
+ const fileContent = await readFile(validatedPath, "utf-8");
849
+ const lines = fileContent.split("\n");
850
+ const fromLine = from ?? 0;
851
+ const toLine = to ?? lines.length;
852
+ const selectedLines = lines.slice(fromLine, toLine);
853
+ const content = selectedLines.join("\n");
854
+ return {
855
+ path: path2,
856
+ content,
857
+ from: fromLine,
858
+ to: toLine
859
+ };
860
+ }
861
+ function registerReadTextFile(server) {
862
+ server.registerTool(
863
+ "readTextFile",
864
+ {
865
+ title: "Read text file",
866
+ description: dedent`
867
+ Text file reader with line range support for UTF-8 encoded files.
868
+
869
+ Use cases:
870
+ - Reading source code files for analysis
871
+ - Extracting specific sections from large text files
872
+ - Loading configuration or documentation files
873
+ - Viewing log files or data files
874
+
875
+ How it works:
876
+ - Reads files as UTF-8 encoded text without format validation
877
+ - Supports partial file reading via line number ranges
878
+ - Returns content wrapped in JSON with metadata
879
+ - WARNING: Binary files will cause errors or corrupted output
880
+
881
+ Common file types:
882
+ - Source code: .ts, .js, .py, .java, .cpp, etc.
883
+ - Documentation: .md, .txt, .rst
884
+ - Configuration: .json, .yaml, .toml, .ini
885
+ - Data files: .csv, .log, .sql
886
+ `,
887
+ inputSchema: inputSchema13.shape
888
+ },
889
+ async (input) => {
890
+ try {
891
+ return successToolResult(await readTextFile(input));
892
+ } catch (e) {
893
+ if (e instanceof Error) return errorToolResult(e);
894
+ throw e;
895
+ }
896
+ }
897
+ );
898
+ }
899
+ var inputSchema14 = z.object({
900
+ thought: z.string().describe("Your current thinking step"),
901
+ nextThoughtNeeded: z.boolean().optional().describe("true if you need more thinking, even if at what seemed like the end")
902
+ });
903
+ var Thought = class {
904
+ thoughtHistory = [];
905
+ branches = {};
906
+ processThought(input) {
907
+ const { nextThoughtNeeded } = input;
908
+ this.thoughtHistory.push(input);
909
+ return {
910
+ nextThoughtNeeded,
911
+ thoughtHistoryLength: this.thoughtHistory.length
912
+ };
913
+ }
914
+ };
915
+ var thought = new Thought();
916
+ async function think(input) {
917
+ return thought.processThought(input);
918
+ }
919
+ function registerThink(server) {
920
+ server.registerTool(
921
+ "think",
922
+ {
923
+ title: "think",
924
+ description: dedent`
925
+ Sequential thinking tool for step-by-step problem analysis and solution development.
926
+
927
+ Use cases:
928
+ - Breaking down complex problems into manageable steps
929
+ - Developing solutions through iterative reasoning
930
+ - Analyzing problems that require multiple perspectives
931
+ - Planning tasks with dependencies and considerations
932
+
933
+ How it works:
934
+ - Records each thinking step sequentially
935
+ - Maintains thought history for context
936
+ - Continues until solution is reached
937
+ - Returns thought count and continuation status
938
+
939
+ Parameters:
940
+ - thought: Current reasoning step or analysis
941
+ - nextThoughtNeeded: Whether additional thinking is required (optional)
942
+
943
+ Best practices:
944
+ - Use multiple calls for sophisticated reasoning chains
945
+ - Progress from high-level overview to detailed analysis (drill-down approach)
946
+ - Capture insights and eureka moments as they emerge
947
+ - Engage in reflective introspection and constructive self-critique
948
+ - Set nextThoughtNeeded to false only when fully satisfied with the solution
949
+ `,
950
+ inputSchema: inputSchema14.shape
951
+ },
952
+ async (input) => {
953
+ try {
954
+ return successToolResult(await think(input));
955
+ } catch (e) {
956
+ if (e instanceof Error) return errorToolResult(e);
957
+ throw e;
958
+ }
959
+ }
960
+ );
961
+ }
962
+ var todoInputSchema = z.object({
963
+ newTodos: z.array(z.string()).describe("New todos to add").optional(),
964
+ completedTodos: z.array(z.number()).describe("Todo ids that are completed").optional()
965
+ });
966
+ var clearTodoInputSchema = z.object({});
967
+ var Todo = class {
968
+ currentTodoId = 0;
969
+ todos = [];
970
+ processTodo(input) {
971
+ const { newTodos, completedTodos } = input;
972
+ if (newTodos) {
973
+ this.todos.push(
974
+ ...newTodos.map((title) => ({ id: this.currentTodoId++, title, completed: false }))
975
+ );
976
+ }
977
+ if (completedTodos) {
978
+ this.todos = this.todos.map((todo2) => ({
979
+ ...todo2,
980
+ completed: todo2.completed || completedTodos.includes(todo2.id)
981
+ }));
982
+ }
983
+ return {
984
+ todos: this.todos
985
+ };
986
+ }
987
+ clearTodo() {
988
+ this.todos = [];
989
+ this.currentTodoId = 0;
990
+ return {
991
+ todos: this.todos
992
+ };
993
+ }
994
+ };
995
+ var todoSingleton = new Todo();
996
+ async function todo(input) {
997
+ return todoSingleton.processTodo(input);
998
+ }
999
+ async function clearTodo(_input) {
1000
+ return todoSingleton.clearTodo();
1001
+ }
1002
+ function registerTodo(server) {
1003
+ server.registerTool(
1004
+ "todo",
1005
+ {
1006
+ title: "todo",
1007
+ description: dedent`
1008
+ Todo list manager that tracks tasks and their completion status.
1009
+
1010
+ Use cases:
1011
+ - Creating new tasks or action items
1012
+ - Marking tasks as completed
1013
+ - Viewing current task list and status
1014
+
1015
+ How it works:
1016
+ - Each todo gets a unique ID when created
1017
+ - Returns the full todo list after every operation
1018
+ - Maintains state across multiple calls
1019
+
1020
+ Parameters:
1021
+ - newTodos: Array of task descriptions to add
1022
+ - completedTodos: Array of todo IDs to mark as completed
1023
+ `,
1024
+ inputSchema: todoInputSchema.shape
1025
+ },
1026
+ async (input) => {
1027
+ try {
1028
+ return successToolResult(await todo(input));
1029
+ } catch (e) {
1030
+ if (e instanceof Error) return errorToolResult(e);
1031
+ throw e;
1032
+ }
1033
+ }
1034
+ );
1035
+ }
1036
+ function registerClearTodo(server) {
1037
+ server.registerTool(
1038
+ "clearTodo",
1039
+ {
1040
+ title: "clearTodo",
1041
+ description: dedent`
1042
+ Clears the todo list.
1043
+
1044
+ Use cases:
1045
+ - Resetting the todo list to an empty state
1046
+ - Starting fresh with a new task list
1047
+ - Clearing all tasks for a new day or project
1048
+
1049
+ How it works:
1050
+ - Resets the todo list to an empty state
1051
+ - Returns an empty todo list
1052
+ `,
1053
+ inputSchema: clearTodoInputSchema.shape
1054
+ },
1055
+ async (input) => {
1056
+ try {
1057
+ return successToolResult(await clearTodo(input));
1058
+ } catch (e) {
1059
+ if (e instanceof Error) return errorToolResult(e);
1060
+ throw e;
1061
+ }
1062
+ }
1063
+ );
1064
+ }
1065
+ var inputSchema15 = z.object({
1066
+ path: z.string().describe("Target file path (relative or absolute)."),
1067
+ text: z.string().max(1e4).describe("Text to write to the file. Max 10000 characters.")
1068
+ });
1069
+ async function writeTextFile(input) {
1070
+ const { path: path2, text } = input;
1071
+ const validatedPath = await validatePath(path2);
1072
+ if (existsSync(validatedPath)) {
1073
+ const stats = statSync(validatedPath);
1074
+ if (!(stats.mode & 128)) {
1075
+ throw new Error(`File ${path2} is not writable`);
1076
+ }
1077
+ }
1078
+ const dir = dirname(validatedPath);
1079
+ await mkdir(dir, { recursive: true });
1080
+ await writeFile(validatedPath, text, "utf-8");
1081
+ return {
1082
+ path: validatedPath,
1083
+ text
1084
+ };
1085
+ }
1086
+ function registerWriteTextFile(server) {
1087
+ server.registerTool(
1088
+ "writeTextFile",
1089
+ {
1090
+ title: "writeTextFile",
1091
+ description: dedent`
1092
+ Text file writer that creates or overwrites files with UTF-8 content.
1093
+
1094
+ Use cases:
1095
+ - Creating new configuration files
1096
+ - Writing generated code or documentation
1097
+ - Saving processed data or results
1098
+ - Creating log files or reports
1099
+
1100
+ How it works:
1101
+ - Writes content as UTF-8 encoded text
1102
+ - Returns success status with file path
1103
+
1104
+ Rules:
1105
+ - IF THE FILE ALREADY EXISTS, IT WILL BE OVERWRITTEN
1106
+ - YOU MUST PROVIDE A VALID UTF-8 STRING FOR THE TEXT
1107
+ - THERE IS A LIMIT ON THE NUMBER OF TOKENS THAT CAN BE GENERATED, SO DO NOT WRITE ALL THE CONTENT AT ONCE (IT WILL CAUSE AN ERROR)
1108
+ - IF YOU WANT TO WRITE MORE THAN 10,000 CHARACTERS, USE "appendTextFile" TOOL AFTER THIS ONE
1109
+ `,
1110
+ inputSchema: inputSchema15.shape
1111
+ },
1112
+ async (input) => {
1113
+ try {
1114
+ return successToolResult(await writeTextFile(input));
1115
+ } catch (e) {
1116
+ if (e instanceof Error) return errorToolResult(e);
1117
+ throw e;
1118
+ }
1119
+ }
1120
+ );
1121
+ }
1122
+
1123
+ export { appendTextFile, attemptCompletion, clearTodo, createDirectory, deleteDirectory, deleteFile, editTextFile, exec, getFileInfo, listDirectory, moveFile, readImageFile, readPdfFile, readTextFile, registerAppendTextFile, registerAttemptCompletion, registerClearTodo, registerCreateDirectory, registerDeleteDirectory, registerDeleteFile, registerEditTextFile, registerExec, registerGetFileInfo, registerListDirectory, registerMoveFile, registerReadImageFile, registerReadPdfFile, registerReadTextFile, registerThink, registerTodo, registerWriteTextFile, think, todo, writeTextFile };
1124
+ //# sourceMappingURL=chunk-CDWG4Z4N.js.map
1125
+ //# sourceMappingURL=chunk-CDWG4Z4N.js.map