junis 0.3.1 → 0.3.3

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.
@@ -101,11 +101,26 @@ var FilesystemTools = class {
101
101
  register(server) {
102
102
  server.tool(
103
103
  "execute_command",
104
- "Execute terminal command",
104
+ [
105
+ "Execute a shell command on the user's local device.",
106
+ "",
107
+ "ROUTING:",
108
+ "- Use for system commands, package managers (npm, pip, brew), git, build tools, and scripting.",
109
+ "- For reading files prefer read_file, for editing prefer edit_block, for searching prefer search_code.",
110
+ "",
111
+ "BEHAVIOR:",
112
+ "- Safe, routine commands (ls, pwd, git status, echo): execute immediately without explanation.",
113
+ "- Destructive or irreversible commands (rm -rf, sudo, shutdown, mkfs): explain what will happen and get user confirmation first.",
114
+ "- If a command fails, analyze the error and suggest an alternative. Do not retry the identical command more than twice.",
115
+ "",
116
+ "SAFETY:",
117
+ "- Commands run with the user's full permissions. Never execute commands that could damage the system, expose credentials, or modify security settings without explicit user request.",
118
+ "- Avoid piping untrusted input into shells. Use absolute paths when possible. Quote paths containing spaces."
119
+ ].join("\n"),
105
120
  {
106
- command: import_zod.z.string().describe("Shell command to execute"),
107
- timeout_ms: import_zod.z.number().optional().default(3e4).describe("Timeout (ms)"),
108
- background: import_zod.z.boolean().optional().default(false).describe("Run in background")
121
+ command: import_zod.z.string().describe("The shell command to execute. Use absolute paths when possible. Quote paths containing spaces."),
122
+ timeout_ms: import_zod.z.number().optional().default(3e4).describe("Maximum execution time in milliseconds (default: 30000). Increase for long-running builds or downloads."),
123
+ background: import_zod.z.boolean().optional().default(false).describe("Run in background without waiting for completion. Use for servers or long-running processes.")
109
124
  },
110
125
  async ({ command, timeout_ms, background }) => {
111
126
  checkPermission("execute_command");
@@ -137,10 +152,15 @@ ${error.stderr ?? ""}`
137
152
  );
138
153
  server.tool(
139
154
  "read_file",
140
- "Read file",
155
+ [
156
+ "Read the contents of a file from the local filesystem.",
157
+ "",
158
+ "Returns file content as text (utf-8) or base64 for binary files. Supports any file type.",
159
+ "For searching within files, prefer search_code instead. For listing directory contents, use list_directory."
160
+ ].join("\n"),
141
161
  {
142
- path: import_zod.z.string().describe("File path"),
143
- encoding: import_zod.z.enum(["utf-8", "base64"]).optional().default("utf-8").describe("Encoding")
162
+ path: import_zod.z.string().describe("Absolute or relative file path to read"),
163
+ encoding: import_zod.z.enum(["utf-8", "base64"]).optional().default("utf-8").describe("'utf-8' for text files (default), 'base64' for binary files (images, PDFs, archives)")
144
164
  },
145
165
  async ({ path: filePath, encoding }) => {
146
166
  try {
@@ -157,10 +177,15 @@ ${error.stderr ?? ""}`
157
177
  );
158
178
  server.tool(
159
179
  "write_file",
160
- "Write/create file",
180
+ [
181
+ "Create a new file or completely overwrite an existing file. Parent directories are created automatically.",
182
+ "",
183
+ "WARNING: This replaces the entire file content. For partial modifications, use edit_block instead.",
184
+ "Prefer edit_block over write_file for existing files \u2014 it's safer and preserves unmodified content."
185
+ ].join("\n"),
161
186
  {
162
- path: import_zod.z.string().describe("File path"),
163
- content: import_zod.z.string().describe("File content")
187
+ path: import_zod.z.string().describe("File path to create or overwrite. Parent directories are auto-created."),
188
+ content: import_zod.z.string().describe("Complete file content. This replaces the entire file.")
164
189
  },
165
190
  async ({ path: filePath, content }) => {
166
191
  checkPermission("write_file");
@@ -171,9 +196,12 @@ ${error.stderr ?? ""}`
171
196
  );
172
197
  server.tool(
173
198
  "list_directory",
174
- "List directory contents",
199
+ [
200
+ "List files and subdirectories in the specified path. Returns entries with type indicators (\u{1F4C1} directory, \u{1F4C4} file).",
201
+ "Use this to explore project structure before reading or modifying files."
202
+ ].join("\n"),
175
203
  {
176
- path: import_zod.z.string().describe("Directory path")
204
+ path: import_zod.z.string().describe("Directory path to list")
177
205
  },
178
206
  async ({ path: dirPath }) => {
179
207
  try {
@@ -191,11 +219,16 @@ ${error.stderr ?? ""}`
191
219
  );
192
220
  server.tool(
193
221
  "search_code",
194
- "Search code/text",
222
+ [
223
+ "Search for text patterns across files using regex. Uses ripgrep for speed with glob fallback.",
224
+ "",
225
+ "Use this to find code definitions, function references, configuration values, or any text pattern.",
226
+ "Returns matching lines with file paths and line numbers for precise navigation."
227
+ ].join("\n"),
195
228
  {
196
- pattern: import_zod.z.string().describe("Search pattern (regex supported)"),
197
- directory: import_zod.z.string().optional().default(".").describe("Search directory"),
198
- file_pattern: import_zod.z.string().optional().default("**/*").describe("File pattern")
229
+ pattern: import_zod.z.string().describe("Search pattern with full regex support (e.g. 'function\\s+\\w+', 'import.*from', 'TODO')"),
230
+ directory: import_zod.z.string().optional().default(".").describe("Root directory to search from (default: current working directory)"),
231
+ file_pattern: import_zod.z.string().optional().default("**/*").describe("Glob pattern to filter files (e.g. '**/*.ts', '*.py', 'src/**/*.js')")
199
232
  },
200
233
  async ({ pattern, directory, file_pattern }) => {
201
234
  try {
@@ -233,7 +266,7 @@ ${error.stderr ?? ""}`
233
266
  );
234
267
  server.tool(
235
268
  "list_processes",
236
- "List running processes",
269
+ "List the top 30 running processes sorted by CPU usage. Use this to identify resource-heavy processes, find PIDs for kill_process, or diagnose performance issues.",
237
270
  {},
238
271
  async () => {
239
272
  const cmd = process.platform === "win32" ? "tasklist" : process.platform === "darwin" ? "ps aux | sort -rk 3 | head -30" : "ps aux --sort=-%cpu | head -30";
@@ -243,10 +276,14 @@ ${error.stderr ?? ""}`
243
276
  );
244
277
  server.tool(
245
278
  "kill_process",
246
- "Kill process (SIGTERM then 3s wait, auto SIGKILL if still alive)",
279
+ [
280
+ "Terminate a process by PID. Default: sends SIGTERM (graceful shutdown), waits 3 seconds, then auto-applies SIGKILL if still alive.",
281
+ "",
282
+ "SAFETY: Only kill processes the user explicitly identifies. Never kill system-critical processes (init, systemd, loginwindow, WindowServer) without explicit instruction."
283
+ ].join("\n"),
247
284
  {
248
- pid: import_zod.z.number().describe("PID of the process to kill"),
249
- signal: import_zod.z.enum(["SIGTERM", "SIGKILL"]).optional().default("SIGTERM").describe("Initial signal (default: SIGTERM). SIGKILL for immediate force kill)")
285
+ pid: import_zod.z.number().describe("PID of the process to terminate (use list_processes to find PIDs)"),
286
+ signal: import_zod.z.enum(["SIGTERM", "SIGKILL"]).optional().default("SIGTERM").describe("SIGTERM (default): graceful shutdown with 3s auto-SIGKILL fallback. SIGKILL: immediate force kill.")
250
287
  },
251
288
  async ({ pid, signal }) => {
252
289
  const isWindows = process.platform === "win32";
@@ -292,12 +329,20 @@ ${error.stderr ?? ""}`
292
329
  );
293
330
  server.tool(
294
331
  "edit_block",
295
- "Replace a specific text block in a file with new text (diff-based partial edit)",
332
+ [
333
+ "Replace a specific text block in a file with new text (diff-based partial edit).",
334
+ "",
335
+ "WORKFLOW: Always use read_file first to see current content, then use edit_block with the exact text to replace.",
336
+ "The old_string must match character-for-character including whitespace, indentation, and line breaks.",
337
+ "If multiple matches exist, include more surrounding context to make it unique, or set replace_all=true.",
338
+ "",
339
+ "Prefer this over write_file for modifying existing files \u2014 it only changes what you specify and preserves the rest."
340
+ ].join("\n"),
296
341
  {
297
- path: import_zod.z.string().describe("File path"),
298
- old_string: import_zod.z.string().describe("Existing text to replace (must match exactly)"),
299
- new_string: import_zod.z.string().describe("New text"),
300
- replace_all: import_zod.z.boolean().optional().default(false).describe("If true, replace all matches; if false, replace only the first")
342
+ path: import_zod.z.string().describe("Path to the file to edit. The file must already exist."),
343
+ old_string: import_zod.z.string().describe("The exact text to find and replace. Must match character-for-character including whitespace and newlines. Include enough context for uniqueness."),
344
+ new_string: import_zod.z.string().describe("The replacement text. Use empty string to delete the matched text."),
345
+ replace_all: import_zod.z.boolean().optional().default(false).describe("If true, replace ALL matches. If false (default), require exactly one match (errors on ambiguous multiple matches).")
301
346
  },
302
347
  async ({ path: filePath, old_string, new_string, replace_all }) => {
303
348
  const content = await import_promises.default.readFile(filePath, "utf-8");
@@ -332,11 +377,16 @@ ${error.stderr ?? ""}`
332
377
  );
333
378
  server.tool(
334
379
  "cron_create",
335
- "Create a recurring cron job. schedule uses cron syntax (e.g. '0 9 * * 1-5' = weekdays 9am).",
380
+ [
381
+ "Create a recurring scheduled task (cron job) using standard cron syntax.",
382
+ "",
383
+ "Common schedules: '*/5 * * * *' (every 5 min), '0 9 * * 1-5' (weekdays 9am), '0 0 * * *' (daily midnight), '0 */2 * * *' (every 2 hours).",
384
+ "Duplicate commands are automatically detected and rejected. Use cron_list to see existing jobs."
385
+ ].join("\n"),
336
386
  {
337
- schedule: import_zod.z.string().describe("Cron schedule expression (e.g. '*/5 * * * *' for every 5 min, '0 9 * * 1-5' for weekdays 9am)"),
338
- command: import_zod.z.string().describe("Shell command to execute"),
339
- label: import_zod.z.string().optional().describe("Optional label/comment for identification")
387
+ schedule: import_zod.z.string().describe("Cron schedule expression (5 fields: minute hour day month weekday). Examples: '*/5 * * * *' (every 5 min), '0 9 * * 1-5' (weekdays 9am)"),
388
+ command: import_zod.z.string().describe("Shell command to execute on schedule"),
389
+ label: import_zod.z.string().optional().describe("Human-readable label for identification (e.g. 'daily-backup', 'log-cleanup')")
340
390
  },
341
391
  async ({ schedule, command, label }) => {
342
392
  try {
@@ -378,7 +428,7 @@ ${error.stderr ?? ""}`
378
428
  );
379
429
  server.tool(
380
430
  "cron_list",
381
- "List all cron jobs in the current user's crontab",
431
+ "List all scheduled cron jobs with their IDs, labels, schedules, and commands. Use the returned ID numbers with cron_delete to remove specific jobs.",
382
432
  {},
383
433
  async () => {
384
434
  try {
@@ -425,10 +475,10 @@ ${error.stderr ?? ""}`
425
475
  );
426
476
  server.tool(
427
477
  "cron_delete",
428
- "Delete a cron job by its ID (from cron_list) or by matching command string",
478
+ "Delete a scheduled cron job by its ID (from cron_list output) or by matching command string. Associated comment labels are automatically cleaned up.",
429
479
  {
430
- id: import_zod.z.number().optional().describe("Cron job ID from cron_list output"),
431
- command: import_zod.z.string().optional().describe("Delete job matching this command string")
480
+ id: import_zod.z.number().optional().describe("Cron job ID from cron_list output (e.g. 1, 2, 3)"),
481
+ command: import_zod.z.string().optional().describe("Delete all jobs matching this command string")
432
482
  },
433
483
  async ({ id, command }) => {
434
484
  if (!id && !command) {
@@ -541,13 +591,22 @@ var BrowserTools = class {
541
591
  };
542
592
  server.tool(
543
593
  "browser_start",
544
- "Start browser (BrowserClaw). mode='managed'(default) launches new Chromium; mode='remote-cdp' connects to existing Chrome via CDP URL.",
594
+ [
595
+ "Launch or connect to a web browser for automation.",
596
+ "",
597
+ "MODES:",
598
+ "- 'managed' (default): Launches a new Chromium instance. Use 'headless' for background operation, 'profile' for persistent sessions (cookies, logins preserved).",
599
+ "- 'remote-cdp': Connects to an already-running Chrome via CDP URL (e.g. from chrome://inspect). Use this to automate an existing browser session.",
600
+ "",
601
+ "WORKFLOW: browser_start \u2192 browser_navigate \u2192 browser_snapshot \u2192 interact (click/type/fill) \u2192 browser_stop.",
602
+ "Always call browser_stop when done to release system resources."
603
+ ].join("\n"),
545
604
  {
546
- mode: import_zod2.z.enum(["managed", "remote-cdp"]).optional().default("managed").describe("'managed' = launch new browser, 'remote-cdp' = connect to existing Chrome"),
547
- headless: import_zod2.z.boolean().optional().default(false).describe("Run headless (managed mode only)"),
548
- cdpUrl: import_zod2.z.string().optional().describe("CDP URL for remote-cdp mode (e.g. http://localhost:9222)"),
549
- profile: import_zod2.z.string().optional().describe("Profile name (managed mode only)"),
550
- allowInternal: import_zod2.z.boolean().optional().default(false).describe("Allow localhost/internal URLs")
605
+ mode: import_zod2.z.enum(["managed", "remote-cdp"]).optional().default("managed").describe("'managed' = launch new browser, 'remote-cdp' = connect to existing Chrome via CDP"),
606
+ headless: import_zod2.z.boolean().optional().default(false).describe("Run without visible window (managed mode only). Use for background tasks."),
607
+ cdpUrl: import_zod2.z.string().optional().describe("Chrome DevTools Protocol URL for remote-cdp mode (e.g. http://localhost:9222)"),
608
+ profile: import_zod2.z.string().optional().describe("Browser profile name for persistent sessions \u2014 preserves cookies, logins, and history across restarts (managed mode only)"),
609
+ allowInternal: import_zod2.z.boolean().optional().default(false).describe("Allow navigation to localhost and internal network URLs")
551
610
  },
552
611
  ({ mode, headless, cdpUrl, profile, allowInternal }) => this.withLock(async () => {
553
612
  if (this.browser) {
@@ -568,7 +627,7 @@ var BrowserTools = class {
568
627
  );
569
628
  server.tool(
570
629
  "browser_stop",
571
- "Stop browser and release resources",
630
+ "Stop the browser and release all associated resources (memory, connections, processes). Always call this when browser automation is complete.",
572
631
  {},
573
632
  () => this.withLock(async () => {
574
633
  await this.cleanup();
@@ -577,9 +636,9 @@ var BrowserTools = class {
577
636
  );
578
637
  server.tool(
579
638
  "browser_navigate",
580
- "Navigate to URL. Opens new tab if browser started but no page yet.",
639
+ "Navigate the browser to a URL. Automatically opens a new tab if the browser is started but no page exists yet. Waits for the page to load before returning.",
581
640
  {
582
- url: import_zod2.z.string().describe("URL to navigate to")
641
+ url: import_zod2.z.string().describe("Full URL to navigate to (include https://)")
583
642
  },
584
643
  ({ url }) => this.withLock(async () => {
585
644
  if (!this.browser) throw new Error("Browser not started. Call browser_start first.");
@@ -594,10 +653,17 @@ var BrowserTools = class {
594
653
  );
595
654
  server.tool(
596
655
  "browser_snapshot",
597
- "Get Accessibility Tree snapshot with ref numbers. Use refs to interact with elements (e.g. browser_click with ref='e1').",
656
+ [
657
+ "Capture the page's Accessibility Tree with numbered ref IDs for each element. This is the primary way to 'see' and understand page content.",
658
+ "",
659
+ "WORKFLOW: Call browser_snapshot \u2192 find the target element's ref (e.g. 'e1', 'e5') \u2192 use that ref in browser_click, browser_type, or other interaction tools.",
660
+ "Refs change after page updates \u2014 always call browser_snapshot again after navigation or clicks that modify the page.",
661
+ "",
662
+ "Prefer this over browser_screenshot for understanding page structure \u2014 it's faster, structured, and machine-readable."
663
+ ].join("\n"),
598
664
  {
599
- interactive: import_zod2.z.boolean().optional().default(true).describe("Only include interactive elements"),
600
- compact: import_zod2.z.boolean().optional().default(true).describe("Remove empty containers")
665
+ interactive: import_zod2.z.boolean().optional().default(true).describe("true (default): only show clickable/typeable elements. false: show all elements including static text."),
666
+ compact: import_zod2.z.boolean().optional().default(true).describe("true (default): hide empty containers for cleaner output")
601
667
  },
602
668
  ({ interactive, compact }) => this.withLock(async () => {
603
669
  const result = await requirePage().snapshot({ interactive, compact });
@@ -617,11 +683,11 @@ ${refList}`
617
683
  );
618
684
  server.tool(
619
685
  "browser_click",
620
- "Click element by ref number from browser_snapshot",
686
+ "Click an element by its ref number from browser_snapshot. Always call browser_snapshot first to get current refs \u2014 they change after page updates.",
621
687
  {
622
- ref: import_zod2.z.string().describe("Ref number from snapshot (e.g. 'e1')"),
623
- doubleClick: import_zod2.z.boolean().optional().default(false),
624
- button: import_zod2.z.enum(["left", "right", "middle"]).optional().default("left")
688
+ ref: import_zod2.z.string().describe("Element ref from browser_snapshot (e.g. 'e1', 'e15'). Call browser_snapshot first to get current refs."),
689
+ doubleClick: import_zod2.z.boolean().optional().default(false).describe("Double-click instead of single click"),
690
+ button: import_zod2.z.enum(["left", "right", "middle"]).optional().default("left").describe("Mouse button to use")
625
691
  },
626
692
  ({ ref, doubleClick, button }) => this.withLock(async () => {
627
693
  await requirePage().click(ref, { doubleClick, button });
@@ -630,12 +696,12 @@ ${refList}`
630
696
  );
631
697
  server.tool(
632
698
  "browser_type",
633
- "Type text into element by ref number",
699
+ "Type text into an input element by ref number. Use 'submit=true' to press Enter after typing (e.g. for search forms). Use 'slowly=true' for sites requiring keystroke-by-keystroke input.",
634
700
  {
635
- ref: import_zod2.z.string().describe("Ref number from snapshot"),
636
- text: import_zod2.z.string().describe("Text to type"),
637
- submit: import_zod2.z.boolean().optional().default(false).describe("Press Enter after typing"),
638
- slowly: import_zod2.z.boolean().optional().default(false).describe("Type slowly (75ms per char)")
701
+ ref: import_zod2.z.string().describe("Element ref from browser_snapshot (e.g. 'e3')"),
702
+ text: import_zod2.z.string().describe("Text to type into the element"),
703
+ submit: import_zod2.z.boolean().optional().default(false).describe("Press Enter after typing (useful for search boxes and forms)"),
704
+ slowly: import_zod2.z.boolean().optional().default(false).describe("Type slowly (75ms per char) for sites that process each keystroke")
639
705
  },
640
706
  ({ ref, text, submit, slowly }) => this.withLock(async () => {
641
707
  await requirePage().type(ref, text, { submit, slowly });
@@ -644,13 +710,13 @@ ${refList}`
644
710
  );
645
711
  server.tool(
646
712
  "browser_fill",
647
- "Fill multiple form fields at once",
713
+ "Fill multiple form fields at once \u2014 more efficient than calling browser_type repeatedly. Each field needs a ref from browser_snapshot.",
648
714
  {
649
715
  fields: import_zod2.z.array(import_zod2.z.object({
650
716
  ref: import_zod2.z.string(),
651
717
  type: import_zod2.z.enum(["text", "checkbox", "radio"]),
652
718
  value: import_zod2.z.union([import_zod2.z.string(), import_zod2.z.boolean()])
653
- })).describe("Array of {ref, type, value}")
719
+ })).describe("Array of {ref, type, value}. type='text': value is string. type='checkbox'/'radio': value is boolean.")
654
720
  },
655
721
  ({ fields }) => this.withLock(async () => {
656
722
  await requirePage().fill(fields);
@@ -659,9 +725,9 @@ ${refList}`
659
725
  );
660
726
  server.tool(
661
727
  "browser_select",
662
- "Select dropdown option(s) by ref",
728
+ "Select one or more options from a dropdown/select element. Values should match the option value attributes, not display text.",
663
729
  {
664
- ref: import_zod2.z.string().describe("Ref number from snapshot"),
730
+ ref: import_zod2.z.string().describe("Ref of the <select> element from browser_snapshot"),
665
731
  values: import_zod2.z.array(import_zod2.z.string()).describe("Option value(s) to select")
666
732
  },
667
733
  ({ ref, values }) => this.withLock(async () => {
@@ -671,9 +737,9 @@ ${refList}`
671
737
  );
672
738
  server.tool(
673
739
  "browser_press",
674
- "Press keyboard key or combination (e.g. 'Enter', 'Control+a', 'Escape')",
740
+ "Press a keyboard key or key combination. Use for shortcuts (e.g. 'Control+a', 'Escape'), form submission ('Enter'), or navigation ('Tab'). Does not require a specific element ref.",
675
741
  {
676
- key: import_zod2.z.string().describe("Key combination (e.g. 'Enter', 'Control+a', 'Escape', 'Tab')")
742
+ key: import_zod2.z.string().describe("Key or combination: 'Enter', 'Escape', 'Tab', 'Control+a', 'Meta+c', 'ArrowDown', 'Backspace'")
677
743
  },
678
744
  ({ key }) => this.withLock(async () => {
679
745
  await requirePage().press(key);
@@ -682,9 +748,9 @@ ${refList}`
682
748
  );
683
749
  server.tool(
684
750
  "browser_hover",
685
- "Hover mouse over element by ref",
751
+ "Move the mouse cursor over an element by ref. Use to trigger hover menus, tooltips, or dropdown previews before clicking.",
686
752
  {
687
- ref: import_zod2.z.string().describe("Ref number from snapshot")
753
+ ref: import_zod2.z.string().describe("Element ref from browser_snapshot")
688
754
  },
689
755
  ({ ref }) => this.withLock(async () => {
690
756
  await requirePage().hover(ref);
@@ -693,10 +759,10 @@ ${refList}`
693
759
  );
694
760
  server.tool(
695
761
  "browser_drag",
696
- "Drag element from startRef to endRef",
762
+ "Drag an element from startRef to endRef. Both refs must come from a recent browser_snapshot. Use for drag-and-drop interfaces, sliders, or reorderable lists.",
697
763
  {
698
- startRef: import_zod2.z.string().describe("Source element ref"),
699
- endRef: import_zod2.z.string().describe("Target element ref")
764
+ startRef: import_zod2.z.string().describe("Source element ref to drag from"),
765
+ endRef: import_zod2.z.string().describe("Target element ref to drag to")
700
766
  },
701
767
  ({ startRef, endRef }) => this.withLock(async () => {
702
768
  await requirePage().drag(startRef, endRef);
@@ -705,10 +771,10 @@ ${refList}`
705
771
  );
706
772
  server.tool(
707
773
  "browser_upload",
708
- "Upload file(s) to file input element by ref",
774
+ "Upload local files to a file input element (<input type='file'>). The ref must point to a file input from browser_snapshot.",
709
775
  {
710
- ref: import_zod2.z.string().describe("Ref number of file input element"),
711
- paths: import_zod2.z.array(import_zod2.z.string()).describe("Absolute file path(s) to upload")
776
+ ref: import_zod2.z.string().describe("Ref of the file input element from browser_snapshot"),
777
+ paths: import_zod2.z.array(import_zod2.z.string()).describe("Absolute file path(s) on the local device to upload")
712
778
  },
713
779
  ({ ref, paths }) => this.withLock(async () => {
714
780
  await requirePage().uploadFile(ref, paths);
@@ -717,11 +783,16 @@ ${refList}`
717
783
  );
718
784
  server.tool(
719
785
  "browser_screenshot",
720
- "Take screenshot of current page",
786
+ [
787
+ "Capture a screenshot of the current page. Returns base64 image data (viewable by AI) or saves to a file.",
788
+ "",
789
+ "Prefer browser_snapshot (Accessibility Tree) for understanding page structure \u2014 it's faster and machine-readable.",
790
+ "Use browser_screenshot only when visual layout matters (charts, images, styling, visual verification)."
791
+ ].join("\n"),
721
792
  {
722
- path: import_zod2.z.string().optional().describe("Save path (if omitted, returns base64)"),
723
- fullPage: import_zod2.z.boolean().optional().default(false),
724
- ref: import_zod2.z.string().optional().describe("Capture specific element by ref")
793
+ path: import_zod2.z.string().optional().describe("Save path for the screenshot. If omitted, returns base64 image data directly."),
794
+ fullPage: import_zod2.z.boolean().optional().default(false).describe("Capture the full scrollable page, not just the visible viewport"),
795
+ ref: import_zod2.z.string().optional().describe("Capture only a specific element by its ref from browser_snapshot")
725
796
  },
726
797
  ({ path: path2, fullPage, ref }) => this.withLock(async () => {
727
798
  const buffer = await requirePage().screenshot({ fullPage, ref });
@@ -740,9 +811,9 @@ ${refList}`
740
811
  );
741
812
  server.tool(
742
813
  "browser_pdf",
743
- "Save current page as PDF",
814
+ "Save the current page as a PDF file. Renders the full page including below-the-fold content. Useful for archiving, sharing, or offline reading.",
744
815
  {
745
- path: import_zod2.z.string().describe("Save path (.pdf)")
816
+ path: import_zod2.z.string().describe("Output file path (.pdf)")
746
817
  },
747
818
  ({ path: path2 }) => this.withLock(async () => {
748
819
  const buffer = await requirePage().pdf();
@@ -752,9 +823,14 @@ ${refList}`
752
823
  );
753
824
  server.tool(
754
825
  "browser_evaluate",
755
- "Execute JavaScript in page context",
826
+ [
827
+ "Execute JavaScript code directly in the browser page context and return the result.",
828
+ "",
829
+ "Use for: extracting data not available in the Accessibility Tree, DOM manipulation, interacting with page APIs, or debugging.",
830
+ "Wrap complex logic in an IIFE: (function(){ ... })()"
831
+ ].join("\n"),
756
832
  {
757
- code: import_zod2.z.string().describe("JavaScript code to execute (wrap in function if needed)")
833
+ code: import_zod2.z.string().describe("JavaScript code to execute in the page context. Return values are automatically serialized.")
758
834
  },
759
835
  ({ code }) => this.withLock(async () => {
760
836
  try {
@@ -775,13 +851,17 @@ ${refList}`
775
851
  );
776
852
  server.tool(
777
853
  "browser_wait",
778
- "Wait for a condition: text appearance/disappearance, URL pattern, or fixed time",
854
+ [
855
+ "Wait for a specific condition before proceeding. Use between actions when the page needs time to update.",
856
+ "",
857
+ "OPTIONS (use one): 'text' (wait for text to appear), 'textGone' (wait for text to disappear), 'url' (URL matches glob), 'loadState' (page load state), 'timeMs' (fixed delay as last resort)."
858
+ ].join("\n"),
779
859
  {
780
- text: import_zod2.z.string().optional().describe("Wait until this text appears"),
781
- textGone: import_zod2.z.string().optional().describe("Wait until this text disappears"),
782
- url: import_zod2.z.string().optional().describe("Wait until URL matches (glob pattern, e.g. '**/dashboard')"),
783
- loadState: import_zod2.z.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Wait for load state"),
784
- timeMs: import_zod2.z.number().optional().describe("Wait fixed milliseconds")
860
+ text: import_zod2.z.string().optional().describe("Wait until this text appears on the page"),
861
+ textGone: import_zod2.z.string().optional().describe("Wait until this text disappears from the page"),
862
+ url: import_zod2.z.string().optional().describe("Wait until URL matches this glob pattern (e.g. '**/dashboard', '**/success')"),
863
+ loadState: import_zod2.z.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Wait for page load state: 'load' (full), 'domcontentloaded' (DOM ready), 'networkidle' (no pending requests)"),
864
+ timeMs: import_zod2.z.number().optional().describe("Fixed wait in milliseconds \u2014 use as last resort when other conditions don't apply")
785
865
  },
786
866
  ({ text, textGone, url, loadState, timeMs }) => this.withLock(async () => {
787
867
  const condition = {};
@@ -796,9 +876,9 @@ ${refList}`
796
876
  );
797
877
  server.tool(
798
878
  "browser_cookies",
799
- "Get, set, or clear cookies",
879
+ "Manage browser cookies: get all cookies, set a specific cookie, or clear all cookies. Useful for authentication state, session management, or testing.",
800
880
  {
801
- action: import_zod2.z.enum(["get", "set", "clear"]).describe("Action to perform"),
881
+ action: import_zod2.z.enum(["get", "set", "clear"]).describe("'get': retrieve all cookies, 'set': add/update a cookie, 'clear': remove all cookies"),
802
882
  cookie: import_zod2.z.object({
803
883
  name: import_zod2.z.string(),
804
884
  value: import_zod2.z.string(),
@@ -806,7 +886,7 @@ ${refList}`
806
886
  path: import_zod2.z.string().optional(),
807
887
  httpOnly: import_zod2.z.boolean().optional(),
808
888
  secure: import_zod2.z.boolean().optional()
809
- }).optional().describe("Cookie data (required for set action)")
889
+ }).optional().describe("Cookie data (required for 'set' action)")
810
890
  },
811
891
  ({ action, cookie }) => this.withLock(async () => {
812
892
  const page = requirePage();
@@ -825,12 +905,12 @@ ${refList}`
825
905
  );
826
906
  server.tool(
827
907
  "browser_storage",
828
- "Read/write/clear localStorage or sessionStorage",
908
+ "Read, write, or clear browser localStorage/sessionStorage. Useful for managing client-side state, authentication tokens, or application preferences.",
829
909
  {
830
- action: import_zod2.z.enum(["get", "set", "clear"]).describe("Action to perform"),
831
- kind: import_zod2.z.enum(["local", "session"]).optional().default("local").describe("Storage type"),
832
- key: import_zod2.z.string().optional().describe("Storage key (get/set)"),
833
- value: import_zod2.z.string().optional().describe("Value to set (set action)")
910
+ action: import_zod2.z.enum(["get", "set", "clear"]).describe("'get': read value(s), 'set': write a key-value pair, 'clear': remove all entries"),
911
+ kind: import_zod2.z.enum(["local", "session"]).optional().default("local").describe("'local' (persistent) or 'session' (cleared on tab close)"),
912
+ key: import_zod2.z.string().optional().describe("Storage key to get or set. Omit key with 'get' to retrieve all entries."),
913
+ value: import_zod2.z.string().optional().describe("Value to store (required for 'set' action)")
834
914
  },
835
915
  ({ action, kind, key, value }) => this.withLock(async () => {
836
916
  const page = requirePage();
@@ -850,13 +930,13 @@ ${refList}`
850
930
  server.tool(
851
931
  "browser_dialog",
852
932
  [
853
- "Handle JavaScript dialogs (alert/confirm/prompt).",
854
- "Two-step usage:",
855
- " 1. action='arm' \u2014 register a one-shot handler (returns immediately, does NOT block).",
933
+ "Handle JavaScript dialogs (alert, confirm, prompt). Two-step pattern:",
934
+ " 1. action='arm' \u2014 register a one-shot handler (returns immediately, does NOT block).",
856
935
  " 2. Trigger the dialog (e.g. browser_click on the button that calls confirm()).",
857
936
  " 3. action='wait' \u2014 await the handler to confirm the dialog was handled.",
937
+ "",
858
938
  "The 'accept' and 'promptText' params are only used with action='arm'."
859
- ].join(" "),
939
+ ].join("\n"),
860
940
  {
861
941
  action: import_zod2.z.enum(["arm", "wait"]).describe(
862
942
  "'arm' = register handler and return immediately; 'wait' = await the previously armed handler"
@@ -868,7 +948,7 @@ ${refList}`
868
948
  "Text to enter if the dialog is a prompt. Only used with action='arm'."
869
949
  ),
870
950
  timeoutMs: import_zod2.z.number().optional().describe(
871
- "Timeout in ms for 'wait' action. Default: 30000."
951
+ "Timeout in ms for 'wait' action (default: 30000). Increase for slow-loading dialogs."
872
952
  )
873
953
  },
874
954
  ({ action, accept, promptText, timeoutMs }) => this.withLock(async () => {
@@ -919,8 +999,8 @@ var NotebookTools = class {
919
999
  register(server) {
920
1000
  server.tool(
921
1001
  "notebook_read",
922
- "Read .ipynb notebook",
923
- { path: import_zod3.z.string().describe("Notebook file path") },
1002
+ "Read a Jupyter notebook (.ipynb) and return all cells with their types (code/markdown), source content, and output counts. Use this to understand notebook structure before making edits.",
1003
+ { path: import_zod3.z.string().describe("Path to the .ipynb notebook file") },
924
1004
  async ({ path: filePath }) => {
925
1005
  const nb = await readNotebook(filePath);
926
1006
  const cells = nb.cells.map((cell, i) => ({
@@ -936,11 +1016,11 @@ var NotebookTools = class {
936
1016
  );
937
1017
  server.tool(
938
1018
  "notebook_edit_cell",
939
- "Edit a specific notebook cell",
1019
+ "Replace the source code of a specific cell in a Jupyter notebook. Use notebook_read first to identify the correct cell index (0-based). Existing outputs for the cell are preserved \u2014 use notebook_execute to re-run.",
940
1020
  {
941
- path: import_zod3.z.string(),
942
- cell_index: import_zod3.z.number().describe("Cell index (0-based)"),
943
- source: import_zod3.z.string().describe("New source code")
1021
+ path: import_zod3.z.string().describe("Path to the .ipynb notebook file"),
1022
+ cell_index: import_zod3.z.number().describe("Cell index to edit (0-based). Use notebook_read to find the right index."),
1023
+ source: import_zod3.z.string().describe("New source code/content for the cell (replaces entire cell content)")
944
1024
  },
945
1025
  async ({ path: filePath, cell_index, source }) => {
946
1026
  const nb = await readNotebook(filePath);
@@ -956,10 +1036,15 @@ var NotebookTools = class {
956
1036
  );
957
1037
  server.tool(
958
1038
  "notebook_execute",
959
- "Execute notebook (nbconvert --execute)",
1039
+ [
1040
+ "Execute all cells in a Jupyter notebook using nbconvert. Results are saved in-place \u2014 the notebook file is updated with execution outputs.",
1041
+ "",
1042
+ "Requires Jupyter to be installed (pip install jupyter). The timeout applies per cell, not for the entire notebook.",
1043
+ "If execution fails on a cell, the error is captured in the cell output and subsequent cells may not execute."
1044
+ ].join("\n"),
960
1045
  {
961
- path: import_zod3.z.string().describe("Notebook file path"),
962
- timeout: import_zod3.z.number().optional().default(300).describe("Timeout per cell (seconds)")
1046
+ path: import_zod3.z.string().describe("Path to the .ipynb notebook file to execute"),
1047
+ timeout: import_zod3.z.number().optional().default(300).describe("Maximum execution time per cell in seconds (default: 300). Increase for cells with heavy computation.")
963
1048
  },
964
1049
  async ({ path: filePath, timeout }) => {
965
1050
  const nbconvertArgs = `nbconvert --to notebook --execute --inplace "${filePath}" --ExecutePreprocessor.timeout=${timeout}`;
@@ -988,12 +1073,12 @@ var NotebookTools = class {
988
1073
  );
989
1074
  server.tool(
990
1075
  "notebook_add_cell",
991
- "Add a new cell to notebook",
1076
+ "Insert a new cell into a Jupyter notebook. If position is omitted, the cell is appended at the end. Use cell_type='code' for executable Python cells, 'markdown' for documentation/text cells.",
992
1077
  {
993
- path: import_zod3.z.string().describe(".ipynb file path"),
994
- cell_type: import_zod3.z.enum(["code", "markdown"]).describe("Cell type"),
995
- source: import_zod3.z.string().describe("Cell source content"),
996
- position: import_zod3.z.number().optional().describe("Insert position (0-based). Appends to end if omitted")
1078
+ path: import_zod3.z.string().describe("Path to the .ipynb notebook file"),
1079
+ cell_type: import_zod3.z.enum(["code", "markdown"]).describe("'code' for executable cells, 'markdown' for text/documentation cells"),
1080
+ source: import_zod3.z.string().describe("Cell source content (Python code or Markdown text)"),
1081
+ position: import_zod3.z.number().optional().describe("Insert position (0-based index). Omit to append at the end. If position exceeds cell count, appends at end with a warning.")
997
1082
  },
998
1083
  async ({ path: filePath, cell_type: cellType, source, position }) => {
999
1084
  const nb = await readNotebook(filePath);
@@ -1024,10 +1109,10 @@ var NotebookTools = class {
1024
1109
  );
1025
1110
  server.tool(
1026
1111
  "notebook_delete_cell",
1027
- "Delete a specific notebook cell",
1112
+ "Delete a cell from a Jupyter notebook by its 0-based index. Use notebook_read first to verify the cell content before deletion. This action cannot be undone.",
1028
1113
  {
1029
- path: import_zod3.z.string().describe(".ipynb file path"),
1030
- cell_index: import_zod3.z.number().describe("Cell index to delete (0-based)")
1114
+ path: import_zod3.z.string().describe("Path to the .ipynb notebook file"),
1115
+ cell_index: import_zod3.z.number().describe("Cell index to delete (0-based). Use notebook_read first to verify content.")
1031
1116
  },
1032
1117
  async ({ path: filePath, cell_index }) => {
1033
1118
  const nb = await readNotebook(filePath);
@@ -1058,9 +1143,14 @@ var DeviceTools = class {
1058
1143
  register(server) {
1059
1144
  server.tool(
1060
1145
  "camera_capture",
1061
- "Camera photo capture",
1146
+ [
1147
+ "Capture a photo from the device's camera and return it as base64 image data.",
1148
+ "",
1149
+ "Platform-specific: macOS (imagesnap), Windows (ffmpeg/dshow), Linux (fswebcam).",
1150
+ "Requires a connected camera with OS permissions granted. If output_path is provided, the file is also saved to disk."
1151
+ ].join("\n"),
1062
1152
  {
1063
- output_path: import_zod4.z.string().optional()
1153
+ output_path: import_zod4.z.string().optional().describe("File path to save the captured photo. If omitted, returns image data only (temp file auto-cleaned).")
1064
1154
  },
1065
1155
  async ({ output_path }) => {
1066
1156
  const p = platform();
@@ -1096,10 +1186,10 @@ Please check if a camera is connected.` }],
1096
1186
  );
1097
1187
  server.tool(
1098
1188
  "notification_send",
1099
- "Send OS notification",
1189
+ "Send a native OS notification (banner/toast) to the user's desktop. Use for task completion alerts, reminders, or important status updates. The notification appears even when the terminal is not focused.",
1100
1190
  {
1101
- title: import_zod4.z.string().describe("Notification title"),
1102
- message: import_zod4.z.string().describe("Notification body")
1191
+ title: import_zod4.z.string().describe("Notification title (displayed prominently)"),
1192
+ message: import_zod4.z.string().describe("Notification body text")
1103
1193
  },
1104
1194
  async ({ title, message }) => {
1105
1195
  try {
@@ -1123,7 +1213,7 @@ Please check if a camera is connected.` }],
1123
1213
  );
1124
1214
  server.tool(
1125
1215
  "clipboard_read",
1126
- "Read clipboard",
1216
+ "Read the current contents of the system clipboard (text). Use to access content the user has copied. Platform-specific: macOS (pbpaste), Windows (PowerShell), Linux (xclip).",
1127
1217
  {},
1128
1218
  async () => {
1129
1219
  const p = platform();
@@ -1134,8 +1224,10 @@ Please check if a camera is connected.` }],
1134
1224
  );
1135
1225
  server.tool(
1136
1226
  "clipboard_write",
1137
- "Write to clipboard",
1138
- { text: import_zod4.z.string() },
1227
+ "Write text to the system clipboard, replacing its current contents. Use to prepare content for the user to paste elsewhere.",
1228
+ {
1229
+ text: import_zod4.z.string().describe("Text to copy to the clipboard")
1230
+ },
1139
1231
  async ({ text }) => {
1140
1232
  const p = platform();
1141
1233
  const cmd = {
@@ -1149,10 +1241,15 @@ Please check if a camera is connected.` }],
1149
1241
  );
1150
1242
  server.tool(
1151
1243
  "screen_record",
1152
- "Start/stop screen recording (macOS: screencapture -v, others: ffmpeg)",
1244
+ [
1245
+ "Start or stop screen recording. Captures the full screen as MP4 video.",
1246
+ "",
1247
+ "Use action='start' to begin, action='stop' to end and save. Only one recording can be active at a time.",
1248
+ "Platform-specific: macOS (screencapture -v), Windows/Linux (ffmpeg)."
1249
+ ].join("\n"),
1153
1250
  {
1154
- action: import_zod4.z.enum(["start", "stop"]).describe("start: begin recording, stop: end recording"),
1155
- output_path: import_zod4.z.string().optional().describe("Output path (used on start, default: /tmp/junis_record_<timestamp>.mp4)")
1251
+ action: import_zod4.z.enum(["start", "stop"]).describe("'start': begin recording, 'stop': end recording and save the file"),
1252
+ output_path: import_zod4.z.string().optional().describe("Output file path (used with 'start'). Default: /tmp/junis_record_<timestamp>.mp4")
1156
1253
  },
1157
1254
  async ({ action, output_path }) => {
1158
1255
  const p = platform();
@@ -1183,7 +1280,12 @@ Please check if a camera is connected.` }],
1183
1280
  );
1184
1281
  server.tool(
1185
1282
  "location_get",
1186
- "Get current location (macOS: CoreLocation CLI, others: IP-based fallback)",
1283
+ [
1284
+ "Get the device's current geographic location.",
1285
+ "",
1286
+ "macOS: Uses CoreLocation (GPS-accurate) with IP-based fallback. Other platforms: IP-based geolocation (city-level accuracy only).",
1287
+ "Returns latitude, longitude, and (when available) city and country."
1288
+ ].join("\n"),
1187
1289
  {},
1188
1290
  async () => {
1189
1291
  const p = platform();
@@ -1210,9 +1312,9 @@ Please check if a camera is connected.` }],
1210
1312
  );
1211
1313
  server.tool(
1212
1314
  "audio_play",
1213
- "Play audio file (macOS: afplay, others: ffplay)",
1315
+ "Play an audio file through the device's speakers. Supports MP3, WAV, AAC, and other common formats. Playback is synchronous \u2014 the tool returns after playback completes. Platform-specific: macOS (afplay), Windows/Linux (ffplay).",
1214
1316
  {
1215
- file_path: import_zod4.z.string().describe("Path to the audio file to play")
1317
+ file_path: import_zod4.z.string().describe("Absolute path to the audio file to play")
1216
1318
  },
1217
1319
  async ({ file_path }) => {
1218
1320
  const p = platform();