@perstack/base 0.0.5 → 0.0.7

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/index.js CHANGED
@@ -8,7 +8,7 @@ import { Command } from "commander";
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@perstack/base",
11
- version: "0.0.5",
11
+ version: "0.0.7",
12
12
  description: "Perstack base skills for agents.",
13
13
  author: "Wintermute Technologies, Inc.",
14
14
  type: "module",
@@ -46,56 +46,6 @@ var package_default = {
46
46
  }
47
47
  };
48
48
 
49
- // src/tools/append-text-file.ts
50
- import { existsSync, statSync } from "fs";
51
- import { appendFile } from "fs/promises";
52
- import { readFile } from "fs/promises";
53
- import { dedent } from "ts-dedent";
54
- import { z } from "zod";
55
-
56
- // src/lib/path.ts
57
- import { realpathSync } from "fs";
58
- import fs from "fs/promises";
59
- import os from "os";
60
- import path from "path";
61
- var workspacePath = realpathSync(expandHome(process.cwd()));
62
- function expandHome(filepath) {
63
- if (filepath.startsWith("~/") || filepath === "~") {
64
- return path.join(os.homedir(), filepath.slice(1));
65
- }
66
- return filepath;
67
- }
68
- async function validatePath(requestedPath) {
69
- const expandedPath = expandHome(requestedPath);
70
- const absolute = path.isAbsolute(expandedPath) ? path.resolve(expandedPath) : path.resolve(process.cwd(), expandedPath);
71
- if (absolute === `${workspacePath}/perstack`) {
72
- throw new Error("Access denied - perstack directory is not allowed");
73
- }
74
- try {
75
- const realAbsolute = await fs.realpath(absolute);
76
- if (!realAbsolute.startsWith(workspacePath)) {
77
- throw new Error("Access denied - symlink target outside allowed directories");
78
- }
79
- return realAbsolute;
80
- } catch (error) {
81
- const parentDir = path.dirname(absolute);
82
- try {
83
- const realParentPath = await fs.realpath(parentDir);
84
- if (!realParentPath.startsWith(workspacePath)) {
85
- throw new Error("Access denied - parent directory outside allowed directories");
86
- }
87
- return absolute;
88
- } catch {
89
- if (!absolute.startsWith(workspacePath)) {
90
- throw new Error(
91
- `Access denied - path outside allowed directories: ${absolute} not in ${workspacePath}`
92
- );
93
- }
94
- throw new Error(`Parent directory does not exist: ${parentDir}`);
95
- }
96
- }
97
- }
98
-
99
49
  // src/lib/workspace-api.ts
100
50
  import mime from "mime-types";
101
51
  var getWorkspaceApiConfig = () => {
@@ -250,98 +200,110 @@ var deleteWorkspaceItem = async (workspaceItemId) => {
250
200
  }
251
201
  };
252
202
 
253
- // src/tools/append-text-file.ts
254
- var toolName = "appendTextFile";
255
- var toolDescription = dedent`
256
- Text file appender for adding content to the end of existing files.
203
+ // src/lib/register-tool.ts
204
+ function resolveToolByMode(localModeFunction, studioModeFunction) {
205
+ const mode = getWorkspaceMode();
206
+ const func = !studioModeFunction ? localModeFunction : mode === "local" /* LOCAL */ ? localModeFunction : studioModeFunction;
207
+ return async (input) => {
208
+ try {
209
+ const result = await func(input);
210
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
211
+ } catch (e) {
212
+ if (e instanceof Error) {
213
+ return {
214
+ content: [{ type: "text", text: JSON.stringify({ error: e.name, message: e.message }) }]
215
+ };
216
+ }
217
+ throw e;
218
+ }
219
+ };
220
+ }
257
221
 
258
- Use cases:
259
- - Adding entries to log files
260
- - Appending data to CSV or JSON files
261
- - Adding new sections to documentation
262
- - Extending configuration files
263
- - Building files incrementally
222
+ // src/tools/append-text-file.ts
223
+ import { existsSync, statSync } from "fs";
224
+ import { appendFile } from "fs/promises";
225
+ import { readFile } from "fs/promises";
226
+ import { dedent } from "ts-dedent";
227
+ import { z } from "zod";
264
228
 
265
- How it works:
266
- - Appends text to the end of an existing file
267
- - Does not modify existing content
268
- - Creates a new line before appending if needed
269
- - Returns the appended file path
229
+ // src/lib/path.ts
230
+ import { realpathSync } from "fs";
231
+ import fs from "fs/promises";
232
+ import os from "os";
233
+ import path from "path";
234
+ var workspacePath = realpathSync(expandHome(process.cwd()));
235
+ function expandHome(filepath) {
236
+ if (filepath.startsWith("~/") || filepath === "~") {
237
+ return path.join(os.homedir(), filepath.slice(1));
238
+ }
239
+ return filepath;
240
+ }
241
+ async function validatePath(requestedPath) {
242
+ const expandedPath = expandHome(requestedPath);
243
+ const absolute = path.isAbsolute(expandedPath) ? path.resolve(expandedPath) : path.resolve(process.cwd(), expandedPath);
244
+ if (absolute === `${workspacePath}/perstack`) {
245
+ throw new Error("Access denied - perstack directory is not allowed");
246
+ }
247
+ try {
248
+ const realAbsolute = await fs.realpath(absolute);
249
+ if (!realAbsolute.startsWith(workspacePath)) {
250
+ throw new Error("Access denied - symlink target outside allowed directories");
251
+ }
252
+ return realAbsolute;
253
+ } catch (error) {
254
+ const parentDir = path.dirname(absolute);
255
+ try {
256
+ const realParentPath = await fs.realpath(parentDir);
257
+ if (!realParentPath.startsWith(workspacePath)) {
258
+ throw new Error("Access denied - parent directory outside allowed directories");
259
+ }
260
+ return absolute;
261
+ } catch {
262
+ if (!absolute.startsWith(workspacePath)) {
263
+ throw new Error(
264
+ `Access denied - path outside allowed directories: ${absolute} not in ${workspacePath}`
265
+ );
266
+ }
267
+ throw new Error(`Parent directory does not exist: ${parentDir}`);
268
+ }
269
+ }
270
+ }
270
271
 
271
- Rules:
272
- - FILE MUST EXIST BEFORE APPENDING
273
- - YOU MUST PROVIDE A VALID UTF-8 STRING FOR THE TEXT
274
- - THERE IS A LIMIT ON THE NUMBER OF TOKENS THAT CAN BE GENERATED, SO DO NOT APPEND ALL THE CONTENT AT ONCE
275
- - IF YOU WANT TO APPEND MORE THAN 2000 CHARACTERS, USE THIS TOOL MULTIPLE TIMES
276
- `;
277
- var toolInputSchema = {
272
+ // src/tools/append-text-file.ts
273
+ var inputSchema = z.object({
278
274
  path: z.string().describe("Target file path to append to."),
279
275
  text: z.string().min(1).max(2e3).describe("Text to append to the file. Max 2000 characters.")
280
- };
281
- function addAppendTextFileTool(server) {
282
- const mode = getWorkspaceMode();
283
- switch (mode) {
284
- case "local" /* LOCAL */: {
285
- server.tool(
286
- toolName,
287
- `${toolDescription}
276
+ });
277
+ function appendTextFileConfig() {
278
+ return {
279
+ title: "Append text file",
280
+ description: dedent`
281
+ Adding content to the end of existing files.
288
282
 
289
- Mode: Local (Local filesystem)`,
290
- toolInputSchema,
291
- async ({ path: path2, text }) => {
292
- try {
293
- return {
294
- content: [
295
- {
296
- type: "text",
297
- text: JSON.stringify(await appendTextFileLocalMode(path2, text))
298
- }
299
- ]
300
- };
301
- } catch (e) {
302
- if (e instanceof Error) {
303
- return {
304
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
305
- };
306
- }
307
- throw e;
308
- }
309
- }
310
- );
311
- break;
312
- }
313
- case "studio" /* STUDIO */: {
314
- server.tool(
315
- toolName,
316
- `${toolDescription}
283
+ Use cases:
284
+ - Adding entries to log files
285
+ - Appending data to CSV or JSON files
286
+ - Adding new sections to documentation
287
+ - Extending configuration files
288
+ - Building files incrementally
317
289
 
318
- Mode: Studio (Workspace API)`,
319
- toolInputSchema,
320
- async ({ path: path2, text }) => {
321
- try {
322
- return {
323
- content: [
324
- {
325
- type: "text",
326
- text: JSON.stringify(await appendTextFileStudioMode(path2, text))
327
- }
328
- ]
329
- };
330
- } catch (e) {
331
- if (e instanceof Error) {
332
- return {
333
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
334
- };
335
- }
336
- throw e;
337
- }
338
- }
339
- );
340
- break;
341
- }
342
- }
290
+ How it works:
291
+ - Appends text to the end of an existing file
292
+ - Does not modify existing content
293
+ - Creates a new line before appending if needed
294
+ - Returns the appended file path
295
+
296
+ Rules:
297
+ - FILE MUST EXIST BEFORE APPENDING
298
+ - YOU MUST PROVIDE A VALID UTF-8 STRING FOR THE TEXT
299
+ - THERE IS A LIMIT ON THE NUMBER OF TOKENS THAT CAN BE GENERATED, SO DO NOT APPEND ALL THE CONTENT AT ONCE
300
+ - IF YOU WANT TO APPEND MORE THAN 2000 CHARACTERS, USE THIS TOOL MULTIPLE TIMES
301
+ `,
302
+ inputSchema: inputSchema.shape
303
+ };
343
304
  }
344
- async function appendTextFileLocalMode(path2, text) {
305
+ async function appendTextFileLocalMode(input) {
306
+ const { path: path2, text } = input;
345
307
  const validatedPath = await validatePath(path2);
346
308
  if (!existsSync(validatedPath)) {
347
309
  throw new Error(`File ${path2} does not exist.`);
@@ -351,12 +313,10 @@ async function appendTextFileLocalMode(path2, text) {
351
313
  throw new Error(`File ${path2} is not writable`);
352
314
  }
353
315
  await appendFile(validatedPath, text);
354
- return {
355
- path: validatedPath,
356
- text
357
- };
316
+ return { path: validatedPath, text };
358
317
  }
359
- async function appendTextFileStudioMode(path2, text) {
318
+ async function appendTextFileStudioMode(input) {
319
+ const { path: path2, text } = input;
360
320
  const validatedPath = await validatePath(path2);
361
321
  const relativePath = validatedPath.startsWith(workspacePath) ? validatedPath.slice(workspacePath.length + 1) : validatedPath;
362
322
  const existingItem = await findWorkspaceItem(relativePath);
@@ -366,7 +326,7 @@ async function appendTextFileStudioMode(path2, text) {
366
326
  if (existingItem.type === "workspaceItemDirectory") {
367
327
  throw new Error(`Path ${relativePath} is a directory.`);
368
328
  }
369
- await appendTextFileLocalMode(path2, text);
329
+ await appendTextFileLocalMode(input);
370
330
  await deleteWorkspaceItem(existingItem.id);
371
331
  const content = await readFile(validatedPath, "utf-8");
372
332
  await createWorkspaceItem(
@@ -379,45 +339,36 @@ async function appendTextFileStudioMode(path2, text) {
379
339
  },
380
340
  content
381
341
  );
382
- return {
383
- path: relativePath,
384
- text
385
- };
342
+ return { path: relativePath, text };
386
343
  }
387
344
 
388
345
  // src/tools/attempt-completion.ts
389
346
  import { dedent as dedent2 } from "ts-dedent";
390
- var addAttemptCompletionTool = (server) => {
391
- server.tool(
392
- "attemptCompletion",
393
- dedent2`
394
- Task completion signal that triggers immediate final report generation.
395
- Use cases:
396
- - Signaling task completion to Perstack runtime
397
- - Triggering final report generation
398
- - Ending the current expert's work cycle
399
- How it works:
400
- - Sends completion signal to Perstack runtime
401
- - Runtime immediately proceeds to final report generation
402
- - No confirmation or approval step required
403
- - No parameters needed for this signal
404
- Notes:
405
- - Triggers immediate transition to final report
406
- - Should only be used when task is fully complete
407
- - Cannot be reverted once called
408
- `,
409
- {},
410
- attemptCompletion
411
- );
412
- };
347
+ function attemptCompletionConfig() {
348
+ return {
349
+ title: "Attempt completion",
350
+ description: dedent2`
351
+ Task completion signal that triggers immediate final report generation.
352
+ Use cases:
353
+ - Signaling task completion to Perstack runtime
354
+ - Triggering final report generation
355
+ - Ending the current expert's work cycle
356
+ How it works:
357
+ - Sends completion signal to Perstack runtime
358
+ - Runtime immediately proceeds to final report generation
359
+ - No confirmation or approval step required
360
+ - No parameters needed for this signal
361
+ Notes:
362
+ - Triggers immediate transition to final report
363
+ - Should only be used when task is fully complete
364
+ - Cannot be reverted once called
365
+ `,
366
+ inputSchema: {}
367
+ };
368
+ }
413
369
  async function attemptCompletion() {
414
370
  return {
415
- content: [
416
- {
417
- type: "text",
418
- text: "End the agent loop and provide a final report"
419
- }
420
- ]
371
+ message: "End the agent loop and provide a final report"
421
372
  };
422
373
  }
423
374
 
@@ -427,92 +378,35 @@ import { mkdir } from "fs/promises";
427
378
  import { dirname } from "path";
428
379
  import { dedent as dedent3 } from "ts-dedent";
429
380
  import { z as z2 } from "zod";
430
- var toolName2 = "createDirectory";
431
- var toolDescription2 = dedent3`
432
- Directory creator for establishing folder structures in the workspace.
433
-
434
- Use cases:
435
- - Setting up project directory structure
436
- - Creating output folders for generated content
437
- - Organizing files into logical groups
438
- - Preparing directory hierarchies
439
-
440
- How it works:
441
- - Creates directories recursively
442
- - Handles existing directories gracefully
443
- - Creates parent directories as needed
444
- - Returns creation status
445
-
446
- Parameters:
447
- - path: Directory path to create
448
- `;
449
- var toolInputSchema2 = {
381
+ var inputSchema2 = z2.object({
450
382
  path: z2.string()
451
- };
452
- function addCreateDirectoryTool(server) {
453
- const mode = getWorkspaceMode();
454
- switch (mode) {
455
- case "local" /* LOCAL */: {
456
- server.tool(
457
- toolName2,
458
- `${toolDescription2}
383
+ });
384
+ function createDirectoryConfig() {
385
+ return {
386
+ title: "Create directory",
387
+ description: dedent3`
388
+ Directory creator for establishing folder structures in the workspace.
459
389
 
460
- Mode: Local (Local filesystem)`,
461
- toolInputSchema2,
462
- async ({ path: path2 }) => {
463
- try {
464
- return {
465
- content: [
466
- {
467
- type: "text",
468
- text: JSON.stringify(await createDirectoryLocalMode(path2))
469
- }
470
- ]
471
- };
472
- } catch (e) {
473
- if (e instanceof Error) {
474
- return {
475
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
476
- };
477
- }
478
- throw e;
479
- }
480
- }
481
- );
482
- break;
483
- }
484
- case "studio" /* STUDIO */: {
485
- server.tool(
486
- toolName2,
487
- `${toolDescription2}
390
+ Use cases:
391
+ - Setting up project directory structure
392
+ - Creating output folders for generated content
393
+ - Organizing files into logical groups
394
+ - Preparing directory hierarchies
488
395
 
489
- Mode: Studio (Workspace API)`,
490
- toolInputSchema2,
491
- async ({ path: path2 }) => {
492
- try {
493
- return {
494
- content: [
495
- {
496
- type: "text",
497
- text: JSON.stringify(await createDirectoryStudioMode(path2))
498
- }
499
- ]
500
- };
501
- } catch (e) {
502
- if (e instanceof Error) {
503
- return {
504
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
505
- };
506
- }
507
- throw e;
508
- }
509
- }
510
- );
511
- break;
512
- }
513
- }
396
+ How it works:
397
+ - Creates directories recursively
398
+ - Handles existing directories gracefully
399
+ - Creates parent directories as needed
400
+ - Returns creation status
401
+
402
+ Parameters:
403
+ - path: Directory path to create
404
+ `,
405
+ inputSchema: inputSchema2.shape
406
+ };
514
407
  }
515
- async function createDirectoryLocalMode(path2) {
408
+ async function createDirectoryLocalMode(input) {
409
+ const { path: path2 } = input;
516
410
  const validatedPath = await validatePath(path2);
517
411
  const exists = existsSync2(validatedPath);
518
412
  if (exists) {
@@ -530,7 +424,8 @@ async function createDirectoryLocalMode(path2) {
530
424
  path: validatedPath
531
425
  };
532
426
  }
533
- async function createDirectoryStudioMode(path2) {
427
+ async function createDirectoryStudioMode(input) {
428
+ const { path: path2 } = input;
534
429
  const validatedPath = await validatePath(path2);
535
430
  const relativePath = validatedPath.startsWith(workspacePath) ? validatedPath.slice(workspacePath.length + 1) : validatedPath;
536
431
  const existingItem = await findWorkspaceItem(relativePath);
@@ -552,7 +447,7 @@ async function createDirectoryStudioMode(path2) {
552
447
  });
553
448
  }
554
449
  }
555
- await createDirectoryLocalMode(path2);
450
+ await createDirectoryLocalMode(input);
556
451
  return {
557
452
  path: relativePath
558
453
  };
@@ -563,92 +458,35 @@ import { existsSync as existsSync3, statSync as statSync3 } from "fs";
563
458
  import { unlink } from "fs/promises";
564
459
  import { dedent as dedent4 } from "ts-dedent";
565
460
  import { z as z3 } from "zod";
566
- var toolName3 = "deleteFile";
567
- var toolDescription3 = dedent4`
568
- File deleter for removing files from the workspace.
569
-
570
- Use cases:
571
- - Removing temporary files
572
- - Cleaning up generated files
573
- - Deleting outdated configuration files
574
- - Removing unwanted artifacts
575
-
576
- How it works:
577
- - Validates file existence and permissions
578
- - Performs atomic delete operation
579
- - Removes file from both filesystem and workspace API
580
- - Returns deletion status
581
-
582
- Parameters:
583
- - path: File path to delete
584
- `;
585
- var toolInputSchema3 = {
461
+ var inputSchema3 = z3.object({
586
462
  path: z3.string()
587
- };
588
- function addDeleteFileTool(server) {
589
- const mode = getWorkspaceMode();
590
- switch (mode) {
591
- case "local" /* LOCAL */: {
592
- server.tool(
593
- toolName3,
594
- `${toolDescription3}
463
+ });
464
+ function deleteFileConfig() {
465
+ return {
466
+ title: "Delete file",
467
+ description: dedent4`
468
+ File deleter for removing files from the workspace.
595
469
 
596
- Mode: Local (Local filesystem)`,
597
- toolInputSchema3,
598
- async ({ path: path2 }) => {
599
- try {
600
- return {
601
- content: [
602
- {
603
- type: "text",
604
- text: JSON.stringify(await deleteFileLocalMode(path2))
605
- }
606
- ]
607
- };
608
- } catch (e) {
609
- if (e instanceof Error) {
610
- return {
611
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
612
- };
613
- }
614
- throw e;
615
- }
616
- }
617
- );
618
- break;
619
- }
620
- case "studio" /* STUDIO */: {
621
- server.tool(
622
- toolName3,
623
- `${toolDescription3}
470
+ Use cases:
471
+ - Removing temporary files
472
+ - Cleaning up generated files
473
+ - Deleting outdated configuration files
474
+ - Removing unwanted artifacts
624
475
 
625
- Mode: Studio (Workspace API)`,
626
- toolInputSchema3,
627
- async ({ path: path2 }) => {
628
- try {
629
- return {
630
- content: [
631
- {
632
- type: "text",
633
- text: JSON.stringify(await deleteFileStudioMode(path2))
634
- }
635
- ]
636
- };
637
- } catch (e) {
638
- if (e instanceof Error) {
639
- return {
640
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
641
- };
642
- }
643
- throw e;
644
- }
645
- }
646
- );
647
- break;
648
- }
649
- }
476
+ How it works:
477
+ - Validates file existence and permissions
478
+ - Performs atomic delete operation
479
+ - Removes file from both filesystem and workspace API
480
+ - Returns deletion status
481
+
482
+ Parameters:
483
+ - path: File path to delete
484
+ `,
485
+ inputSchema: inputSchema3.shape
486
+ };
650
487
  }
651
- async function deleteFileLocalMode(path2) {
488
+ async function deleteFileLocalMode(input) {
489
+ const { path: path2 } = input;
652
490
  const validatedPath = await validatePath(path2);
653
491
  if (!existsSync3(validatedPath)) {
654
492
  throw new Error(`File ${path2} does not exist.`);
@@ -665,7 +503,8 @@ async function deleteFileLocalMode(path2) {
665
503
  path: validatedPath
666
504
  };
667
505
  }
668
- async function deleteFileStudioMode(path2) {
506
+ async function deleteFileStudioMode(input) {
507
+ const { path: path2 } = input;
669
508
  const validatedPath = await validatePath(path2);
670
509
  const relativePath = validatedPath.startsWith(workspacePath) ? validatedPath.slice(workspacePath.length + 1) : validatedPath;
671
510
  const existingItem = await findWorkspaceItem(relativePath);
@@ -675,7 +514,7 @@ async function deleteFileStudioMode(path2) {
675
514
  if (existingItem.type === "workspaceItemDirectory") {
676
515
  throw new Error(`Path ${relativePath} is a directory. Use delete directory tool instead.`);
677
516
  }
678
- await deleteFileLocalMode(path2);
517
+ await deleteFileLocalMode(input);
679
518
  await deleteWorkspaceItem(existingItem.id);
680
519
  return {
681
520
  path: relativePath
@@ -687,98 +526,41 @@ import { existsSync as existsSync4, statSync as statSync4 } from "fs";
687
526
  import { readFile as readFile2, writeFile } from "fs/promises";
688
527
  import { dedent as dedent5 } from "ts-dedent";
689
528
  import { z as z4 } from "zod";
690
- var toolName4 = "editTextFile";
691
- var toolDescription4 = dedent5`
692
- Text file editor for modifying existing files with precise text replacement.
693
-
694
- Use cases:
695
- - Updating configuration values
696
- - Modifying code snippets
697
- - Replacing specific text blocks
698
- - Making targeted edits to files
699
-
700
- How it works:
701
- - Reads existing file content
702
- - Performs exact text replacement of oldText with newText
703
- - Normalizes line endings for consistent behavior
704
- - Returns summary of changes made
705
- - For appending text to files, use the appendTextFile tool instead
706
-
707
- Rules:
708
- - YOU MUST PROVIDE A VALID UTF-8 STRING FOR THE TEXT
709
- - 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)
710
- - IF YOU WANT TO EDIT MORE THAN 2000 CHARACTERS, USE THIS TOOL MULTIPLE TIMES
711
- - DO NOT USE THIS TOOL FOR APPENDING TEXT TO FILES - USE appendTextFile TOOL INSTEAD
712
- `;
713
- var toolInputSchema4 = {
529
+ var inputSchema4 = z4.object({
714
530
  path: z4.string().describe("Target file path to edit."),
715
531
  newText: z4.string().min(1).max(2e3).describe("Text to append to the file. Max 2000 characters."),
716
532
  oldText: z4.string().min(1).max(2e3).describe("Exact text to find and replace. Max 2000 characters.")
717
- };
718
- function addEditTextFileTool(server) {
719
- const mode = getWorkspaceMode();
720
- switch (mode) {
721
- case "local" /* LOCAL */: {
722
- server.tool(
723
- toolName4,
724
- `${toolDescription4}
533
+ });
534
+ function editTextFileConfig() {
535
+ return {
536
+ title: "Edit text file",
537
+ description: dedent5`
538
+ Text file editor for modifying existing files with precise text replacement.
725
539
 
726
- Mode: Local (Local filesystem)`,
727
- toolInputSchema4,
728
- async ({ path: path2, newText, oldText }) => {
729
- try {
730
- return {
731
- content: [
732
- {
733
- type: "text",
734
- text: JSON.stringify(await editTextFileLocalMode(path2, newText, oldText))
735
- }
736
- ]
737
- };
738
- } catch (e) {
739
- if (e instanceof Error) {
740
- return {
741
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
742
- };
743
- }
744
- throw e;
745
- }
746
- }
747
- );
748
- break;
749
- }
750
- case "studio" /* STUDIO */: {
751
- server.tool(
752
- toolName4,
753
- `${toolDescription4}
540
+ Use cases:
541
+ - Updating configuration values
542
+ - Modifying code snippets
543
+ - Replacing specific text blocks
544
+ - Making targeted edits to files
754
545
 
755
- Mode: Studio (Workspace API)`,
756
- toolInputSchema4,
757
- async ({ path: path2, oldText, newText }) => {
758
- try {
759
- return {
760
- content: [
761
- {
762
- type: "text",
763
- text: JSON.stringify(await editTextFileStudioMode(path2, newText, oldText))
764
- }
765
- ]
766
- };
767
- } catch (e) {
768
- if (e instanceof Error) {
769
- return {
770
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
771
- };
772
- }
773
- throw e;
774
- }
775
- }
776
- );
777
- break;
778
- }
779
- }
546
+ How it works:
547
+ - Reads existing file content
548
+ - Performs exact text replacement of oldText with newText
549
+ - Normalizes line endings for consistent behavior
550
+ - Returns summary of changes made
551
+ - For appending text to files, use the appendTextFile tool instead
552
+
553
+ Rules:
554
+ - YOU MUST PROVIDE A VALID UTF-8 STRING FOR THE TEXT
555
+ - 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)
556
+ - IF YOU WANT TO EDIT MORE THAN 2000 CHARACTERS, USE THIS TOOL MULTIPLE TIMES
557
+ - DO NOT USE THIS TOOL FOR APPENDING TEXT TO FILES - USE appendTextFile TOOL INSTEAD
558
+ `,
559
+ inputSchema: inputSchema4.shape
560
+ };
780
561
  }
781
- async function editTextFileLocalMode(path2, newText, oldText) {
562
+ async function editTextFileLocalMode(input) {
563
+ const { path: path2, newText, oldText } = input;
782
564
  const validatedPath = await validatePath(path2);
783
565
  if (!existsSync4(validatedPath)) {
784
566
  throw new Error(`File ${path2} does not exist.`);
@@ -794,7 +576,8 @@ async function editTextFileLocalMode(path2, newText, oldText) {
794
576
  oldText
795
577
  };
796
578
  }
797
- async function editTextFileStudioMode(path2, newText, oldText) {
579
+ async function editTextFileStudioMode(input) {
580
+ const { path: path2, newText, oldText } = input;
798
581
  const validatedPath = await validatePath(path2);
799
582
  const relativePath = validatedPath.startsWith(workspacePath) ? validatedPath.slice(workspacePath.length + 1) : validatedPath;
800
583
  const existingItem = await findWorkspaceItem(relativePath);
@@ -804,7 +587,7 @@ async function editTextFileStudioMode(path2, newText, oldText) {
804
587
  if (existingItem.type === "workspaceItemDirectory") {
805
588
  throw new Error(`Path ${relativePath} is a directory.`);
806
589
  }
807
- await editTextFileLocalMode(path2, newText, oldText);
590
+ await editTextFileLocalMode(input);
808
591
  await deleteWorkspaceItem(existingItem.id);
809
592
  const content = await readFile2(validatedPath, "utf-8");
810
593
  await createWorkspaceItem(
@@ -843,92 +626,35 @@ import { basename, dirname as dirname2, extname, resolve } from "path";
843
626
  import mime2 from "mime-types";
844
627
  import { dedent as dedent6 } from "ts-dedent";
845
628
  import { z as z5 } from "zod";
846
- var toolName5 = "getFileInfo";
847
- var toolDescription5 = dedent6`
848
- File information retriever for detailed metadata about files and directories.
849
-
850
- Use cases:
851
- - Checking file existence and type
852
- - Getting file size and timestamps
853
- - Determining MIME types
854
- - Validating file accessibility
855
-
856
- How it works:
857
- - Retrieves comprehensive file system metadata
858
- - Detects MIME type from file extension
859
- - Provides both absolute and relative paths
860
- - Returns human-readable file sizes
861
-
862
- Parameters:
863
- - path: File or directory path to inspect
864
- `;
865
- var toolInputSchema5 = {
629
+ var inputSchema5 = z5.object({
866
630
  path: z5.string()
867
- };
868
- function addGetFileInfoTool(server) {
869
- const mode = getWorkspaceMode();
870
- switch (mode) {
871
- case "local" /* LOCAL */: {
872
- server.tool(
873
- toolName5,
874
- `${toolDescription5}
631
+ });
632
+ function getFileInfoConfig() {
633
+ return {
634
+ title: "Get file info",
635
+ description: dedent6`
636
+ File information retriever for detailed metadata about files and directories.
875
637
 
876
- Mode: Local (Local filesystem)`,
877
- toolInputSchema5,
878
- async ({ path: path2 }) => {
879
- try {
880
- return {
881
- content: [
882
- {
883
- type: "text",
884
- text: JSON.stringify(await getFileInfoLocalMode(path2))
885
- }
886
- ]
887
- };
888
- } catch (e) {
889
- if (e instanceof Error) {
890
- return {
891
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
892
- };
893
- }
894
- throw e;
895
- }
896
- }
897
- );
898
- break;
899
- }
900
- case "studio" /* STUDIO */: {
901
- server.tool(
902
- toolName5,
903
- `${toolDescription5}
638
+ Use cases:
639
+ - Checking file existence and type
640
+ - Getting file size and timestamps
641
+ - Determining MIME types
642
+ - Validating file accessibility
904
643
 
905
- Mode: Studio (Workspace API + Local filesystem)`,
906
- toolInputSchema5,
907
- async ({ path: path2 }) => {
908
- try {
909
- return {
910
- content: [
911
- {
912
- type: "text",
913
- text: JSON.stringify(await getFileInfoStudioMode(path2))
914
- }
915
- ]
916
- };
917
- } catch (e) {
918
- if (e instanceof Error) {
919
- return {
920
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
921
- };
922
- }
923
- throw e;
924
- }
925
- }
926
- );
927
- break;
928
- }
929
- }
644
+ How it works:
645
+ - Retrieves comprehensive file system metadata
646
+ - Detects MIME type from file extension
647
+ - Provides both absolute and relative paths
648
+ - Returns human-readable file sizes
649
+
650
+ Parameters:
651
+ - path: File or directory path to inspect
652
+ `,
653
+ inputSchema: inputSchema5.shape
654
+ };
930
655
  }
931
- async function getFileInfoLocalMode(path2) {
656
+ async function getFileInfoLocalMode(input) {
657
+ const { path: path2 } = input;
932
658
  const validatedPath = await validatePath(path2);
933
659
  if (!existsSync5(validatedPath)) {
934
660
  throw new Error(`File or directory ${path2} does not exist`);
@@ -963,10 +689,11 @@ async function getFileInfoLocalMode(path2) {
963
689
  }
964
690
  };
965
691
  }
966
- async function getFileInfoStudioMode(path2) {
692
+ async function getFileInfoStudioMode(input) {
693
+ const { path: path2 } = input;
967
694
  const validatedPath = await validatePath(path2);
968
695
  const relativePath = validatedPath.startsWith(workspacePath) ? validatedPath.slice(workspacePath.length + 1) : validatedPath;
969
- const localInfo = await getFileInfoLocalMode(path2);
696
+ const localInfo = await getFileInfoLocalMode(input);
970
697
  const workspaceItem = await findWorkspaceItem(relativePath);
971
698
  return {
972
699
  ...localInfo,
@@ -994,92 +721,35 @@ import { readdir } from "fs/promises";
994
721
  import { join } from "path";
995
722
  import { dedent as dedent7 } from "ts-dedent";
996
723
  import { z as z6 } from "zod";
997
- var toolName6 = "listDirectory";
998
- var toolDescription6 = dedent7`
999
- Directory content lister with detailed file information.
1000
-
1001
- Use cases:
1002
- - Exploring project structure
1003
- - Finding files in a directory
1004
- - Checking directory contents before operations
1005
- - Understanding file organization
1006
-
1007
- How it works:
1008
- - Lists all files and subdirectories in specified directory only
1009
- - Provides file type, size, and modification time
1010
- - Sorts entries alphabetically
1011
- - Handles empty directories
1012
-
1013
- Parameters:
1014
- - path: Directory path to list (optional, defaults to workspace root)
1015
- `;
1016
- var toolInputSchema6 = {
1017
- path: z6.string().optional()
1018
- };
1019
- function addListDirectoryTool(server) {
1020
- const mode = getWorkspaceMode();
1021
- switch (mode) {
1022
- case "local" /* LOCAL */: {
1023
- server.tool(
1024
- toolName6,
1025
- `${toolDescription6}
724
+ var inputSchema6 = z6.object({
725
+ path: z6.string()
726
+ });
727
+ function listDirectoryConfig() {
728
+ return {
729
+ title: "List directory",
730
+ description: dedent7`
731
+ Directory content lister with detailed file information.
1026
732
 
1027
- Mode: Local (Local filesystem)`,
1028
- toolInputSchema6,
1029
- async ({ path: path2 = "." }) => {
1030
- try {
1031
- return {
1032
- content: [
1033
- {
1034
- type: "text",
1035
- text: JSON.stringify(await listDirectoryLocalMode(path2))
1036
- }
1037
- ]
1038
- };
1039
- } catch (e) {
1040
- if (e instanceof Error) {
1041
- return {
1042
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
1043
- };
1044
- }
1045
- throw e;
1046
- }
1047
- }
1048
- );
1049
- break;
1050
- }
1051
- case "studio" /* STUDIO */: {
1052
- server.tool(
1053
- toolName6,
1054
- `${toolDescription6}
733
+ Use cases:
734
+ - Exploring project structure
735
+ - Finding files in a directory
736
+ - Checking directory contents before operations
737
+ - Understanding file organization
1055
738
 
1056
- Mode: Studio (Workspace API)`,
1057
- toolInputSchema6,
1058
- async ({ path: path2 = "." }) => {
1059
- try {
1060
- return {
1061
- content: [
1062
- {
1063
- type: "text",
1064
- text: JSON.stringify(await listDirectoryStudioMode(path2))
1065
- }
1066
- ]
1067
- };
1068
- } catch (e) {
1069
- if (e instanceof Error) {
1070
- return {
1071
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
1072
- };
1073
- }
1074
- throw e;
1075
- }
1076
- }
1077
- );
1078
- break;
1079
- }
1080
- }
739
+ How it works:
740
+ - Lists all files and subdirectories in specified directory only
741
+ - Provides file type, size, and modification time
742
+ - Sorts entries alphabetically
743
+ - Handles empty directories
744
+
745
+ Parameters:
746
+ - path: Directory path to list (optional, defaults to workspace root)
747
+ `,
748
+ inputSchema: inputSchema6.shape
749
+ };
1081
750
  }
1082
- async function listDirectoryLocalMode(path2) {
751
+ async function listDirectoryLocalMode(input) {
752
+ const { path: path2 } = input;
1083
753
  const validatedPath = await validatePath(path2);
1084
754
  if (!existsSync6(validatedPath)) {
1085
755
  throw new Error(`Directory ${path2} does not exist.`);
@@ -1114,7 +784,8 @@ async function listDirectoryLocalMode(path2) {
1114
784
  items
1115
785
  };
1116
786
  }
1117
- async function listDirectoryStudioMode(path2) {
787
+ async function listDirectoryStudioMode(input) {
788
+ const { path: path2 } = input;
1118
789
  const validatedPath = await validatePath(path2);
1119
790
  const relativePath = validatedPath.startsWith(workspacePath) ? validatedPath.slice(workspacePath.length + 1) : validatedPath;
1120
791
  const workspaceItems = await getWorkspaceItems();
@@ -1143,94 +814,37 @@ import { mkdir as mkdir2, rename } from "fs/promises";
1143
814
  import { dirname as dirname3 } from "path";
1144
815
  import { dedent as dedent8 } from "ts-dedent";
1145
816
  import { z as z7 } from "zod";
1146
- var toolName7 = "moveFile";
1147
- var toolDescription7 = dedent8`
1148
- File mover for relocating or renaming files within the workspace.
1149
-
1150
- Use cases:
1151
- - Renaming files to follow naming conventions
1152
- - Moving files to different directories
1153
- - Organizing project structure
1154
- - Backing up files before modifications
1155
-
1156
- How it works:
1157
- - Validates source file existence
1158
- - Creates destination directory if needed
1159
- - Performs atomic move operation
1160
- - Preserves file permissions and timestamps
1161
-
1162
- Parameters:
1163
- - source: Current file path
1164
- - destination: Target file path
1165
- `;
1166
- var toolInputSchema7 = {
817
+ var inputSchema7 = z7.object({
1167
818
  source: z7.string(),
1168
819
  destination: z7.string()
1169
- };
1170
- function addMoveFileTool(server) {
1171
- const mode = getWorkspaceMode();
1172
- switch (mode) {
1173
- case "local" /* LOCAL */: {
1174
- server.tool(
1175
- toolName7,
1176
- `${toolDescription7}
820
+ });
821
+ function moveFileConfig() {
822
+ return {
823
+ title: "Move file",
824
+ description: dedent8`
825
+ File mover for relocating or renaming files within the workspace.
1177
826
 
1178
- Mode: Local (Local filesystem)`,
1179
- toolInputSchema7,
1180
- async ({ source, destination }) => {
1181
- try {
1182
- return {
1183
- content: [
1184
- {
1185
- type: "text",
1186
- text: JSON.stringify(await moveFileLocalMode(source, destination))
1187
- }
1188
- ]
1189
- };
1190
- } catch (e) {
1191
- if (e instanceof Error) {
1192
- return {
1193
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
1194
- };
1195
- }
1196
- throw e;
1197
- }
1198
- }
1199
- );
1200
- break;
1201
- }
1202
- case "studio" /* STUDIO */: {
1203
- server.tool(
1204
- toolName7,
1205
- `${toolDescription7}
827
+ Use cases:
828
+ - Renaming files to follow naming conventions
829
+ - Moving files to different directories
830
+ - Organizing project structure
831
+ - Backing up files before modifications
1206
832
 
1207
- Mode: Studio (Workspace API)`,
1208
- toolInputSchema7,
1209
- async ({ source, destination }) => {
1210
- try {
1211
- return {
1212
- content: [
1213
- {
1214
- type: "text",
1215
- text: JSON.stringify(await moveFileStudioMode(source, destination))
1216
- }
1217
- ]
1218
- };
1219
- } catch (e) {
1220
- if (e instanceof Error) {
1221
- return {
1222
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
1223
- };
1224
- }
1225
- throw e;
1226
- }
1227
- }
1228
- );
1229
- break;
1230
- }
1231
- }
833
+ How it works:
834
+ - Validates source file existence
835
+ - Creates destination directory if needed
836
+ - Performs atomic move operation
837
+ - Preserves file permissions and timestamps
838
+
839
+ Parameters:
840
+ - source: Current file path
841
+ - destination: Target file path
842
+ `,
843
+ inputSchema: inputSchema7.shape
844
+ };
1232
845
  }
1233
- async function moveFileLocalMode(source, destination) {
846
+ async function moveFileLocalMode(input) {
847
+ const { source, destination } = input;
1234
848
  const validatedSource = await validatePath(source);
1235
849
  const validatedDestination = await validatePath(destination);
1236
850
  if (!existsSync7(validatedSource)) {
@@ -1251,7 +865,8 @@ async function moveFileLocalMode(source, destination) {
1251
865
  destination: validatedDestination
1252
866
  };
1253
867
  }
1254
- async function moveFileStudioMode(source, destination) {
868
+ async function moveFileStudioMode(input) {
869
+ const { source, destination } = input;
1255
870
  const validatedSource = await validatePath(source);
1256
871
  const validatedDestination = await validatePath(destination);
1257
872
  const relativeSource = validatedSource.startsWith(workspacePath) ? validatedSource.slice(workspacePath.length + 1) : validatedSource;
@@ -1280,7 +895,7 @@ async function moveFileStudioMode(source, destination) {
1280
895
  await updateWorkspaceItem(sourceItem.id, {
1281
896
  path: relativeDestination
1282
897
  });
1283
- await moveFileLocalMode(source, destination);
898
+ await moveFileLocalMode(input);
1284
899
  return {
1285
900
  source: relativeSource,
1286
901
  destination: relativeDestination
@@ -1294,55 +909,40 @@ import mime3 from "mime-types";
1294
909
  import { dedent as dedent9 } from "ts-dedent";
1295
910
  import { z as z8 } from "zod";
1296
911
  var MAX_IMAGE_SIZE = 15 * 1024 * 1024;
1297
- var toolName8 = "readImageFile";
1298
- var toolDescription8 = dedent9`
1299
- Image file reader that converts images to base64 encoded strings with MIME type validation.
1300
-
1301
- Use cases:
1302
- - Loading images for LLM to process
1303
- - Retrieving image data for analysis or display
1304
- - Converting workspace image files to base64 format
912
+ var inputSchema8 = z8.object({
913
+ path: z8.string()
914
+ });
915
+ function readImageFileConfig() {
916
+ return {
917
+ title: "Read image file",
918
+ description: dedent9`
919
+ Image file reader that converts images to base64 encoded strings with MIME type validation.
1305
920
 
1306
- How it works:
1307
- - Validates file existence and MIME type before reading
1308
- - Encodes file content as base64 string
1309
- - Returns image data with correct MIME type for proper handling
1310
- - Rejects unsupported formats with clear error messages
921
+ Use cases:
922
+ - Loading images for LLM to process
923
+ - Retrieving image data for analysis or display
924
+ - Converting workspace image files to base64 format
1311
925
 
1312
- Supported formats:
1313
- - PNG (image/png)
1314
- - JPEG/JPG (image/jpeg)
1315
- - GIF (image/gif) - static only, animated not supported
1316
- - WebP (image/webp)
1317
-
1318
- Notes:
1319
- - Maximum file size: 15MB (larger files will be rejected)
1320
- `;
1321
- var toolInputSchema8 = {
1322
- path: z8.string()
1323
- };
1324
- function addReadImageFileTool(server) {
1325
- server.tool(toolName8, toolDescription8, toolInputSchema8, async ({ path: path2 }) => {
1326
- try {
1327
- return {
1328
- content: [
1329
- {
1330
- type: "text",
1331
- text: JSON.stringify(await readImageFile(path2))
1332
- }
1333
- ]
1334
- };
1335
- } catch (e) {
1336
- if (e instanceof Error) {
1337
- return {
1338
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
1339
- };
1340
- }
1341
- throw e;
1342
- }
1343
- });
926
+ How it works:
927
+ - Validates file existence and MIME type before reading
928
+ - Encodes file content as base64 string
929
+ - Returns image data with correct MIME type for proper handling
930
+ - Rejects unsupported formats with clear error messages
931
+
932
+ Supported formats:
933
+ - PNG (image/png)
934
+ - JPEG/JPG (image/jpeg)
935
+ - GIF (image/gif) - static only, animated not supported
936
+ - WebP (image/webp)
937
+
938
+ Notes:
939
+ - Maximum file size: 15MB (larger files will be rejected)
940
+ `,
941
+ inputSchema: inputSchema8.shape
942
+ };
1344
943
  }
1345
- async function readImageFile(path2) {
944
+ async function readImageFile(input) {
945
+ const { path: path2 } = input;
1346
946
  const validatedPath = await validatePath(path2);
1347
947
  const isFile = existsSync8(validatedPath);
1348
948
  if (!isFile) {
@@ -1373,51 +973,36 @@ import mime4 from "mime-types";
1373
973
  import { dedent as dedent10 } from "ts-dedent";
1374
974
  import { z as z9 } from "zod";
1375
975
  var MAX_PDF_SIZE = 30 * 1024 * 1024;
1376
- var toolName9 = "readPdfFile";
1377
- var toolDescription9 = dedent10`
1378
- PDF file reader that converts documents to base64 encoded resources.
1379
-
1380
- Use cases:
1381
- - Extracting content from PDF documents for analysis
1382
- - Loading PDF files for LLM processing
1383
- - Retrieving PDF data for conversion or manipulation
976
+ var inputSchema9 = z9.object({
977
+ path: z9.string()
978
+ });
979
+ function readPdfFileConfig() {
980
+ return {
981
+ title: "Read PDF file",
982
+ description: dedent10`
983
+ PDF file reader that converts documents to base64 encoded resources.
1384
984
 
1385
- How it works:
1386
- - Validates file existence and MIME type (application/pdf)
1387
- - Encodes PDF content as base64 blob
1388
- - Returns as resource type with proper MIME type and URI
1389
- - Rejects non-PDF files with clear error messages
985
+ Use cases:
986
+ - Extracting content from PDF documents for analysis
987
+ - Loading PDF files for LLM processing
988
+ - Retrieving PDF data for conversion or manipulation
1390
989
 
1391
- Notes:
1392
- - Returns entire PDF content, no page range support
1393
- - Maximum file size: 10MB (larger files will be rejected)
1394
- - Text extraction not performed, returns raw PDF data
1395
- `;
1396
- var toolInputSchema9 = {
1397
- path: z9.string()
1398
- };
1399
- function addReadPdfFileTool(server) {
1400
- server.tool(toolName9, toolDescription9, toolInputSchema9, async ({ path: path2 }) => {
1401
- try {
1402
- return {
1403
- content: [
1404
- {
1405
- type: "text",
1406
- text: JSON.stringify(await readPdfFile(path2))
1407
- }
1408
- ]
1409
- };
1410
- } catch (e) {
1411
- if (e instanceof Error) {
1412
- return {
1413
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
1414
- };
1415
- }
1416
- throw e;
1417
- }
1418
- });
990
+ How it works:
991
+ - Validates file existence and MIME type (application/pdf)
992
+ - Encodes PDF content as base64 blob
993
+ - Returns as resource type with proper MIME type and URI
994
+ - Rejects non-PDF files with clear error messages
995
+
996
+ Notes:
997
+ - Returns entire PDF content, no page range support
998
+ - Maximum file size: 10MB (larger files will be rejected)
999
+ - Text extraction not performed, returns raw PDF data
1000
+ `,
1001
+ inputSchema: inputSchema9.shape
1002
+ };
1419
1003
  }
1420
- async function readPdfFile(path2) {
1004
+ async function readPdfFile(input) {
1005
+ const { path: path2 } = input;
1421
1006
  const validatedPath = await validatePath(path2);
1422
1007
  const isFile = existsSync9(validatedPath);
1423
1008
  if (!isFile) {
@@ -1443,64 +1028,49 @@ async function readPdfFile(path2) {
1443
1028
 
1444
1029
  // src/tools/read-text-file.ts
1445
1030
  import { existsSync as existsSync10 } from "fs";
1446
- import { readFile as readFile4 } from "fs/promises";
1031
+ import { readFile as readFile3 } from "fs/promises";
1447
1032
  import { dedent as dedent11 } from "ts-dedent";
1448
1033
  import { z as z10 } from "zod";
1449
- var toolName10 = "readTextFile";
1450
- var toolDescription10 = dedent11`
1451
- Text file reader with line range support for UTF-8 encoded files.
1452
-
1453
- Use cases:
1454
- - Reading source code files for analysis
1455
- - Extracting specific sections from large text files
1456
- - Loading configuration or documentation files
1457
- - Viewing log files or data files
1458
-
1459
- How it works:
1460
- - Reads files as UTF-8 encoded text without format validation
1461
- - Supports partial file reading via line number ranges
1462
- - Returns content wrapped in JSON with metadata
1463
- - WARNING: Binary files will cause errors or corrupted output
1464
-
1465
- Common file types:
1466
- - Source code: .ts, .js, .py, .java, .cpp, etc.
1467
- - Documentation: .md, .txt, .rst
1468
- - Configuration: .json, .yaml, .toml, .ini
1469
- - Data files: .csv, .log, .sql
1470
- `;
1471
- var toolInputSchema10 = {
1034
+ var inputSchema10 = z10.object({
1472
1035
  path: z10.string(),
1473
1036
  from: z10.number().optional().describe("The line number to start reading from."),
1474
1037
  to: z10.number().optional().describe("The line number to stop reading at.")
1475
- };
1476
- function addReadTextFileTool(server) {
1477
- server.tool(toolName10, toolDescription10, toolInputSchema10, async ({ path: path2, from, to }) => {
1478
- try {
1479
- return {
1480
- content: [
1481
- {
1482
- type: "text",
1483
- text: JSON.stringify(await readTextFile(path2, from, to))
1484
- }
1485
- ]
1486
- };
1487
- } catch (e) {
1488
- if (e instanceof Error) {
1489
- return {
1490
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
1491
- };
1492
- }
1493
- throw e;
1494
- }
1495
- });
1038
+ });
1039
+ function readTextFileConfig() {
1040
+ return {
1041
+ title: "Read text file",
1042
+ description: dedent11`
1043
+ Text file reader with line range support for UTF-8 encoded files.
1044
+
1045
+ Use cases:
1046
+ - Reading source code files for analysis
1047
+ - Extracting specific sections from large text files
1048
+ - Loading configuration or documentation files
1049
+ - Viewing log files or data files
1050
+
1051
+ How it works:
1052
+ - Reads files as UTF-8 encoded text without format validation
1053
+ - Supports partial file reading via line number ranges
1054
+ - Returns content wrapped in JSON with metadata
1055
+ - WARNING: Binary files will cause errors or corrupted output
1056
+
1057
+ Common file types:
1058
+ - Source code: .ts, .js, .py, .java, .cpp, etc.
1059
+ - Documentation: .md, .txt, .rst
1060
+ - Configuration: .json, .yaml, .toml, .ini
1061
+ - Data files: .csv, .log, .sql
1062
+ `,
1063
+ inputSchema: inputSchema10.shape
1064
+ };
1496
1065
  }
1497
- async function readTextFile(path2, from, to) {
1066
+ async function readTextFile(input) {
1067
+ const { path: path2, from, to } = input;
1498
1068
  const validatedPath = await validatePath(path2);
1499
1069
  const isFile = existsSync10(validatedPath);
1500
1070
  if (!isFile) {
1501
1071
  throw new Error(`File ${path2} does not exist.`);
1502
1072
  }
1503
- const fileContent = await readFile4(validatedPath, "utf-8");
1073
+ const fileContent = await readFile3(validatedPath, "utf-8");
1504
1074
  const lines = fileContent.split("\n");
1505
1075
  const fromLine = from ?? 0;
1506
1076
  const toLine = to ?? lines.length;
@@ -1517,54 +1087,38 @@ async function readTextFile(path2, from, to) {
1517
1087
  // src/tools/test-url.ts
1518
1088
  import { dedent as dedent12 } from "ts-dedent";
1519
1089
  import { z as z11 } from "zod";
1520
- var toolName11 = "testUrl";
1521
- var toolDescription11 = dedent12`
1522
- URL tester that validates multiple URLs and extracts metadata.
1523
-
1524
- Use cases:
1525
- - Checking if URLs are accessible before web scraping
1526
- - Validating URL collections for RAG processes
1527
- - Extracting page metadata (title, description) for content analysis
1528
- - Batch URL validation for link checking
1090
+ var inputSchema11 = z11.object({
1091
+ urls: z11.array(z11.string()).min(1).max(10).describe("Array of URLs to test (max 10 URLs).")
1092
+ });
1093
+ function testUrlConfig() {
1094
+ return {
1095
+ title: "Test URL",
1096
+ description: dedent12`
1097
+ URL tester that validates multiple URLs and extracts metadata.
1529
1098
 
1530
- How it works:
1531
- - Performs HTTP GET requests to each URL
1532
- - Returns status code, title, and description for each URL
1533
- - Handles errors gracefully with timeout protection
1534
- - Processes URLs in parallel for performance
1099
+ Use cases:
1100
+ - Checking if URLs are accessible before web scraping
1101
+ - Validating URL collections for RAG processes
1102
+ - Extracting page metadata (title, description) for content analysis
1103
+ - Batch URL validation for link checking
1535
1104
 
1536
- Rules:
1537
- - URLs must be valid HTTP/HTTPS addresses
1538
- - Maximum 10 URLs per request to prevent abuse
1539
- - 10 second timeout per URL request
1540
- - Returns empty title/description if HTML parsing fails
1541
- `;
1542
- var toolInputSchema11 = {
1543
- urls: z11.array(z11.string()).min(1).max(10).describe("Array of URLs to test (max 10 URLs).")
1544
- };
1545
- function addTestUrlTool(server) {
1546
- server.tool(toolName11, toolDescription11, toolInputSchema11, async ({ urls }) => {
1547
- try {
1548
- const results = await testUrls(urls);
1549
- return {
1550
- content: [
1551
- {
1552
- type: "text",
1553
- text: JSON.stringify(results, null, 2)
1554
- }
1555
- ]
1556
- };
1557
- } catch (e) {
1558
- if (e instanceof Error) {
1559
- return {
1560
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
1561
- };
1562
- }
1563
- throw e;
1564
- }
1565
- });
1105
+ How it works:
1106
+ - Performs HTTP GET requests to each URL
1107
+ - Returns status code, title, and description for each URL
1108
+ - Handles errors gracefully with timeout protection
1109
+ - Processes URLs in parallel for performance
1110
+
1111
+ Rules:
1112
+ - URLs must be valid HTTP/HTTPS addresses
1113
+ - Maximum 10 URLs per request to prevent abuse
1114
+ - 10 second timeout per URL request
1115
+ - Returns empty title/description if HTML parsing fails
1116
+ `,
1117
+ inputSchema: inputSchema11.shape
1118
+ };
1566
1119
  }
1567
- async function testUrls(urls) {
1120
+ async function testUrls(input) {
1121
+ const { urls } = input;
1568
1122
  const results = await Promise.allSettled(
1569
1123
  urls.map(async (url) => {
1570
1124
  try {
@@ -1641,10 +1195,42 @@ function extractDescription(html) {
1641
1195
  // src/tools/think.ts
1642
1196
  import { dedent as dedent13 } from "ts-dedent";
1643
1197
  import { z as z12 } from "zod";
1644
- var thoughtInputSchema = z12.object({
1198
+ var inputSchema12 = z12.object({
1645
1199
  thought: z12.string().describe("Your current thinking step"),
1646
1200
  nextThoughtNeeded: z12.boolean().optional().describe("true if you need more thinking, even if at what seemed like the end")
1647
1201
  });
1202
+ function thinkConfig() {
1203
+ return {
1204
+ title: "think",
1205
+ description: dedent13`
1206
+ Sequential thinking tool for step-by-step problem analysis and solution development.
1207
+
1208
+ Use cases:
1209
+ - Breaking down complex problems into manageable steps
1210
+ - Developing solutions through iterative reasoning
1211
+ - Analyzing problems that require multiple perspectives
1212
+ - Planning tasks with dependencies and considerations
1213
+
1214
+ How it works:
1215
+ - Records each thinking step sequentially
1216
+ - Maintains thought history for context
1217
+ - Continues until solution is reached
1218
+ - Returns thought count and continuation status
1219
+
1220
+ Parameters:
1221
+ - thought: Current reasoning step or analysis
1222
+ - nextThoughtNeeded: Whether additional thinking is required (optional)
1223
+
1224
+ Best practices:
1225
+ - Use multiple calls for sophisticated reasoning chains
1226
+ - Progress from high-level overview to detailed analysis (drill-down approach)
1227
+ - Capture insights and eureka moments as they emerge
1228
+ - Engage in reflective introspection and constructive self-critique
1229
+ - Set nextThoughtNeeded to false only when fully satisfied with the solution
1230
+ `,
1231
+ inputSchema: inputSchema12.shape
1232
+ };
1233
+ }
1648
1234
  var Thought = class {
1649
1235
  thoughtHistory = [];
1650
1236
  branches = {};
@@ -1652,65 +1238,19 @@ var Thought = class {
1652
1238
  const { nextThoughtNeeded } = input;
1653
1239
  this.thoughtHistory.push(input);
1654
1240
  return {
1655
- content: [
1656
- {
1657
- type: "text",
1658
- text: JSON.stringify({
1659
- nextThoughtNeeded,
1660
- thoughtHistoryLength: this.thoughtHistory.length
1661
- })
1662
- }
1663
- ]
1241
+ nextThoughtNeeded,
1242
+ thoughtHistoryLength: this.thoughtHistory.length
1664
1243
  };
1665
1244
  }
1666
1245
  };
1667
- function addThinkTool(server) {
1668
- const thought = new Thought();
1669
- server.registerTool(
1670
- "think",
1671
- {
1672
- title: "think",
1673
- description: dedent13`
1674
- Sequential thinking tool for step-by-step problem analysis and solution development.
1675
-
1676
- Use cases:
1677
- - Breaking down complex problems into manageable steps
1678
- - Developing solutions through iterative reasoning
1679
- - Analyzing problems that require multiple perspectives
1680
- - Planning tasks with dependencies and considerations
1681
-
1682
- How it works:
1683
- - Records each thinking step sequentially
1684
- - Maintains thought history for context
1685
- - Continues until solution is reached
1686
- - Returns thought count and continuation status
1687
-
1688
- Parameters:
1689
- - thought: Current reasoning step or analysis
1690
- - nextThoughtNeeded: Whether additional thinking is required (optional)
1691
-
1692
- Best practices:
1693
- - Use multiple calls for sophisticated reasoning chains
1694
- - Progress from high-level overview to detailed analysis (drill-down approach)
1695
- - Capture insights and eureka moments as they emerge
1696
- - Engage in reflective introspection and constructive self-critique
1697
- - Set nextThoughtNeeded to false only when fully satisfied with the solution
1698
- `,
1699
- inputSchema: thoughtInputSchema.shape
1700
- },
1701
- async (input) => {
1702
- return thought.processThought(input);
1703
- }
1704
- );
1246
+ var thought = new Thought();
1247
+ async function think(input) {
1248
+ return thought.processThought(input);
1705
1249
  }
1706
1250
 
1707
1251
  // src/tools/todo.ts
1708
1252
  import { dedent as dedent14 } from "ts-dedent";
1709
1253
  import { z as z13 } from "zod";
1710
- var TodoInputSchema = z13.object({
1711
- newTodos: z13.array(z13.string()).describe("New todos to add").optional(),
1712
- completedTodos: z13.array(z13.number()).describe("Todo ids that are completed").optional()
1713
- });
1714
1254
  var Todo = class {
1715
1255
  currentTodoId = 0;
1716
1256
  todos = [];
@@ -1722,28 +1262,32 @@ var Todo = class {
1722
1262
  );
1723
1263
  }
1724
1264
  if (completedTodos) {
1725
- this.todos = this.todos.map((todo) => ({
1726
- ...todo,
1727
- completed: todo.completed || completedTodos.includes(todo.id)
1265
+ this.todos = this.todos.map((todo2) => ({
1266
+ ...todo2,
1267
+ completed: todo2.completed || completedTodos.includes(todo2.id)
1728
1268
  }));
1729
1269
  }
1730
1270
  return {
1731
- content: [
1732
- {
1733
- type: "text",
1734
- text: JSON.stringify({
1735
- todos: this.todos
1736
- })
1737
- }
1738
- ]
1271
+ todos: this.todos
1272
+ };
1273
+ }
1274
+ clearTodo() {
1275
+ this.todos = [];
1276
+ this.currentTodoId = 0;
1277
+ return {
1278
+ todos: this.todos
1739
1279
  };
1740
1280
  }
1741
1281
  };
1742
- function addTodoTool(server) {
1743
- const todo = new Todo();
1744
- server.tool(
1745
- "todo",
1746
- dedent14`
1282
+ var todoSingleton = new Todo();
1283
+ var todoInputSchema = z13.object({
1284
+ newTodos: z13.array(z13.string()).describe("New todos to add").optional(),
1285
+ completedTodos: z13.array(z13.number()).describe("Todo ids that are completed").optional()
1286
+ });
1287
+ function todoConfig() {
1288
+ return {
1289
+ title: "todo",
1290
+ description: dedent14`
1747
1291
  Todo list manager that tracks tasks and their completion status.
1748
1292
 
1749
1293
  Use cases:
@@ -1760,11 +1304,33 @@ function addTodoTool(server) {
1760
1304
  - newTodos: Array of task descriptions to add
1761
1305
  - completedTodos: Array of todo IDs to mark as completed
1762
1306
  `,
1763
- TodoInputSchema.shape,
1764
- async (input) => {
1765
- return todo.processTodo(input);
1766
- }
1767
- );
1307
+ inputSchema: todoInputSchema.shape
1308
+ };
1309
+ }
1310
+ async function todo(input) {
1311
+ return todoSingleton.processTodo(input);
1312
+ }
1313
+ var clearTodoInputSchema = z13.object({});
1314
+ function clearTodoConfig() {
1315
+ return {
1316
+ title: "clearTodo",
1317
+ description: dedent14`
1318
+ Clears the todo list.
1319
+
1320
+ Use cases:
1321
+ - Resetting the todo list to an empty state
1322
+ - Starting fresh with a new task list
1323
+ - Clearing all tasks for a new day or project
1324
+
1325
+ How it works:
1326
+ - Resets the todo list to an empty state
1327
+ - Returns an empty todo list
1328
+ `,
1329
+ inputSchema: clearTodoInputSchema.shape
1330
+ };
1331
+ }
1332
+ async function clearTodo(input) {
1333
+ return todoSingleton.clearTodo();
1768
1334
  }
1769
1335
 
1770
1336
  // src/tools/write-text-file.ts
@@ -1774,94 +1340,37 @@ import { mkdir as mkdir3 } from "fs/promises";
1774
1340
  import { dirname as dirname4 } from "path";
1775
1341
  import { dedent as dedent15 } from "ts-dedent";
1776
1342
  import { z as z14 } from "zod";
1777
- var toolName12 = "writeTextFile";
1778
- var toolDescription12 = dedent15`
1779
- Text file writer that creates or overwrites files with UTF-8 content.
1780
-
1781
- Use cases:
1782
- - Creating new configuration files
1783
- - Writing generated code or documentation
1784
- - Saving processed data or results
1785
- - Creating log files or reports
1786
-
1787
- How it works:
1788
- - Writes content as UTF-8 encoded text
1789
- - Returns success status with file path
1790
-
1791
- Rules:
1792
- - IF THE FILE ALREADY EXISTS, IT WILL BE OVERWRITTEN
1793
- - YOU MUST PROVIDE A VALID UTF-8 STRING FOR THE TEXT
1794
- - 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)
1795
- - IF YOU WANT TO WRITE MORE THAN 2000 CHARACTERS, USE "appendTextFile" TOOL AFTER THIS ONE
1796
- `;
1797
- var toolInputSchema12 = {
1343
+ var toolInputSchema = z14.object({
1798
1344
  path: z14.string().describe("Target file path (relative or absolute)."),
1799
- text: z14.string().min(1).max(2e3).describe("Text to write to the file. Max 2000 characters.")
1800
- };
1801
- function addWriteTextFileTool(server) {
1802
- const mode = getWorkspaceMode();
1803
- switch (mode) {
1804
- case "local" /* LOCAL */: {
1805
- server.tool(
1806
- toolName12,
1807
- `${toolDescription12}
1345
+ text: z14.string().min(1).max(1e4).describe("Text to write to the file. Max 10000 characters.")
1346
+ });
1347
+ function writeTextFileConfig() {
1348
+ return {
1349
+ title: "writeTextFile",
1350
+ description: dedent15`
1351
+ Text file writer that creates or overwrites files with UTF-8 content.
1808
1352
 
1809
- Mode: Local (Local filesystem)`,
1810
- toolInputSchema12,
1811
- async ({ path: path2, text }) => {
1812
- try {
1813
- return {
1814
- content: [
1815
- {
1816
- type: "text",
1817
- text: JSON.stringify(await writeTextFileLocalMode(path2, text))
1818
- }
1819
- ]
1820
- };
1821
- } catch (e) {
1822
- if (e instanceof Error) {
1823
- return {
1824
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
1825
- };
1826
- }
1827
- throw e;
1828
- }
1829
- }
1830
- );
1831
- break;
1832
- }
1833
- case "studio" /* STUDIO */: {
1834
- server.tool(
1835
- toolName12,
1836
- `${toolDescription12}
1353
+ Use cases:
1354
+ - Creating new configuration files
1355
+ - Writing generated code or documentation
1356
+ - Saving processed data or results
1357
+ - Creating log files or reports
1837
1358
 
1838
- Mode: Studio (Workspace API)`,
1839
- toolInputSchema12,
1840
- async ({ path: path2, text }) => {
1841
- try {
1842
- return {
1843
- content: [
1844
- {
1845
- type: "text",
1846
- text: JSON.stringify(await writeTextFileStudioMode(path2, text))
1847
- }
1848
- ]
1849
- };
1850
- } catch (e) {
1851
- if (e instanceof Error) {
1852
- return {
1853
- content: [{ type: "text", text: JSON.stringify({ error: e.message }) }]
1854
- };
1855
- }
1856
- throw e;
1857
- }
1858
- }
1859
- );
1860
- break;
1861
- }
1862
- }
1359
+ How it works:
1360
+ - Writes content as UTF-8 encoded text
1361
+ - Returns success status with file path
1362
+
1363
+ Rules:
1364
+ - IF THE FILE ALREADY EXISTS, IT WILL BE OVERWRITTEN
1365
+ - YOU MUST PROVIDE A VALID UTF-8 STRING FOR THE TEXT
1366
+ - 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)
1367
+ - IF YOU WANT TO WRITE MORE THAN 10,000 CHARACTERS, USE "appendTextFile" TOOL AFTER THIS ONE
1368
+ `,
1369
+ inputSchema: toolInputSchema.shape
1370
+ };
1863
1371
  }
1864
- async function writeTextFileLocalMode(path2, text) {
1372
+ async function writeTextFileLocalMode(input) {
1373
+ const { path: path2, text } = input;
1865
1374
  const validatedPath = await validatePath(path2);
1866
1375
  if (existsSync11(validatedPath)) {
1867
1376
  const stats = statSync8(validatedPath);
@@ -1877,7 +1386,8 @@ async function writeTextFileLocalMode(path2, text) {
1877
1386
  text
1878
1387
  };
1879
1388
  }
1880
- async function writeTextFileStudioMode(path2, text) {
1389
+ async function writeTextFileStudioMode(input) {
1390
+ const { path: path2, text } = input;
1881
1391
  const validatedPath = await validatePath(path2);
1882
1392
  const relativePath = validatedPath.startsWith(workspacePath) ? validatedPath.slice(workspacePath.length + 1) : validatedPath;
1883
1393
  const existingItem = await findWorkspaceItem(relativePath);
@@ -1912,7 +1422,7 @@ async function writeTextFileStudioMode(path2, text) {
1912
1422
  },
1913
1423
  text
1914
1424
  );
1915
- await writeTextFileLocalMode(path2, text);
1425
+ await writeTextFileLocalMode(input);
1916
1426
  return {
1917
1427
  path: relativePath,
1918
1428
  text
@@ -1934,21 +1444,58 @@ async function main() {
1934
1444
  }
1935
1445
  }
1936
1446
  );
1937
- addAttemptCompletionTool(server);
1938
- addThinkTool(server);
1939
- addTodoTool(server);
1940
- addReadImageFileTool(server);
1941
- addReadPdfFileTool(server);
1942
- addReadTextFileTool(server);
1943
- addEditTextFileTool(server);
1944
- addAppendTextFileTool(server);
1945
- addDeleteFileTool(server);
1946
- addMoveFileTool(server);
1947
- addGetFileInfoTool(server);
1948
- addWriteTextFileTool(server);
1949
- addCreateDirectoryTool(server);
1950
- addListDirectoryTool(server);
1951
- addTestUrlTool(server);
1447
+ server.registerTool(
1448
+ "attemptCompletion",
1449
+ attemptCompletionConfig(),
1450
+ resolveToolByMode(attemptCompletion)
1451
+ );
1452
+ server.registerTool("think", thinkConfig(), resolveToolByMode(think));
1453
+ server.registerTool("todo", todoConfig(), resolveToolByMode(todo));
1454
+ server.registerTool("clearTodo", clearTodoConfig(), resolveToolByMode(clearTodo));
1455
+ server.registerTool("testUrl", testUrlConfig(), resolveToolByMode(testUrls));
1456
+ server.registerTool(
1457
+ "getFileInfo",
1458
+ getFileInfoConfig(),
1459
+ resolveToolByMode(getFileInfoLocalMode, getFileInfoStudioMode)
1460
+ );
1461
+ server.registerTool("readTextFile", readTextFileConfig(), resolveToolByMode(readTextFile));
1462
+ server.registerTool("readImageFile", readImageFileConfig(), resolveToolByMode(readImageFile));
1463
+ server.registerTool("readPdfFile", readPdfFileConfig(), resolveToolByMode(readPdfFile));
1464
+ server.registerTool(
1465
+ "writeTextFile",
1466
+ writeTextFileConfig(),
1467
+ resolveToolByMode(writeTextFileLocalMode, writeTextFileStudioMode)
1468
+ );
1469
+ server.registerTool(
1470
+ "appendTextFile",
1471
+ appendTextFileConfig(),
1472
+ resolveToolByMode(appendTextFileLocalMode, appendTextFileStudioMode)
1473
+ );
1474
+ server.registerTool(
1475
+ "editTextFile",
1476
+ editTextFileConfig(),
1477
+ resolveToolByMode(editTextFileLocalMode, editTextFileStudioMode)
1478
+ );
1479
+ server.registerTool(
1480
+ "moveFile",
1481
+ moveFileConfig(),
1482
+ resolveToolByMode(moveFileLocalMode, moveFileStudioMode)
1483
+ );
1484
+ server.registerTool(
1485
+ "deleteFile",
1486
+ deleteFileConfig(),
1487
+ resolveToolByMode(deleteFileLocalMode, deleteFileStudioMode)
1488
+ );
1489
+ server.registerTool(
1490
+ "listDirectory",
1491
+ listDirectoryConfig(),
1492
+ resolveToolByMode(listDirectoryLocalMode, listDirectoryStudioMode)
1493
+ );
1494
+ server.registerTool(
1495
+ "createDirectory",
1496
+ createDirectoryConfig(),
1497
+ resolveToolByMode(createDirectoryLocalMode, createDirectoryStudioMode)
1498
+ );
1952
1499
  const transport = new StdioServerTransport();
1953
1500
  await server.connect(transport);
1954
1501
  });