junis 0.3.3 → 0.3.5

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.
@@ -1,49 +1,16 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __export = (target, all) => {
10
- for (var name in all)
11
- __defProp(target, name, { get: all[name], enumerable: true });
12
- };
13
- var __copyProps = (to, from, except, desc) => {
14
- if (from && typeof from === "object" || typeof from === "function") {
15
- for (let key of __getOwnPropNames(from))
16
- if (!__hasOwnProp.call(to, key) && key !== except)
17
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
- }
19
- return to;
20
- };
21
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
- // If the importer is in node compatibility mode or this is not an ESM
23
- // file that has been converted to a CommonJS file using a Babel-
24
- // compatible transform (i.e. "__esModule" has not been set), then set
25
- // "default" to the CommonJS "module.exports" for node compatibility.
26
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
- mod
28
- ));
29
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
2
 
31
3
  // src/server/stdio.ts
32
- var stdio_exports = {};
33
- __export(stdio_exports, {
34
- startStdioServer: () => startStdioServer
35
- });
36
- module.exports = __toCommonJS(stdio_exports);
37
- var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
38
- var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
39
6
 
40
7
  // src/tools/filesystem.ts
41
- var import_child_process = require("child_process");
42
- var import_util = require("util");
43
- var import_promises = __toESM(require("fs/promises"));
44
- var import_path = __toESM(require("path"));
45
- var import_glob = require("glob");
46
- var import_zod = require("zod");
8
+ import { exec, execFile } from "child_process";
9
+ import { promisify } from "util";
10
+ import fs from "fs/promises";
11
+ import path from "path";
12
+ import { glob } from "glob";
13
+ import { z } from "zod";
47
14
 
48
15
  // src/server/permissions.ts
49
16
  var toolPermissions = {
@@ -95,8 +62,8 @@ function checkPermission(toolName) {
95
62
  }
96
63
 
97
64
  // src/tools/filesystem.ts
98
- var execAsync = (0, import_util.promisify)(import_child_process.exec);
99
- var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
65
+ var execAsync = promisify(exec);
66
+ var execFileAsync = promisify(execFile);
100
67
  var FilesystemTools = class {
101
68
  register(server) {
102
69
  server.tool(
@@ -118,14 +85,14 @@ var FilesystemTools = class {
118
85
  "- Avoid piping untrusted input into shells. Use absolute paths when possible. Quote paths containing spaces."
119
86
  ].join("\n"),
120
87
  {
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.")
88
+ command: z.string().describe("The shell command to execute. Use absolute paths when possible. Quote paths containing spaces."),
89
+ timeout_ms: z.number().optional().default(3e4).describe("Maximum execution time in milliseconds (default: 30000). Increase for long-running builds or downloads."),
90
+ background: z.boolean().optional().default(false).describe("Run in background without waiting for completion. Use for servers or long-running processes.")
124
91
  },
125
92
  async ({ command, timeout_ms, background }) => {
126
93
  checkPermission("execute_command");
127
94
  if (background) {
128
- (0, import_child_process.exec)(command);
95
+ exec(command);
129
96
  return { content: [{ type: "text", text: "Background execution started" }] };
130
97
  }
131
98
  try {
@@ -159,12 +126,12 @@ ${error.stderr ?? ""}`
159
126
  "For searching within files, prefer search_code instead. For listing directory contents, use list_directory."
160
127
  ].join("\n"),
161
128
  {
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)")
129
+ path: z.string().describe("Absolute or relative file path to read"),
130
+ encoding: z.enum(["utf-8", "base64"]).optional().default("utf-8").describe("'utf-8' for text files (default), 'base64' for binary files (images, PDFs, archives)")
164
131
  },
165
132
  async ({ path: filePath, encoding }) => {
166
133
  try {
167
- const content = await import_promises.default.readFile(filePath, encoding);
134
+ const content = await fs.readFile(filePath, encoding);
168
135
  return { content: [{ type: "text", text: content }] };
169
136
  } catch (err) {
170
137
  const e = err;
@@ -184,13 +151,13 @@ ${error.stderr ?? ""}`
184
151
  "Prefer edit_block over write_file for existing files \u2014 it's safer and preserves unmodified content."
185
152
  ].join("\n"),
186
153
  {
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.")
154
+ path: z.string().describe("File path to create or overwrite. Parent directories are auto-created."),
155
+ content: z.string().describe("Complete file content. This replaces the entire file.")
189
156
  },
190
157
  async ({ path: filePath, content }) => {
191
158
  checkPermission("write_file");
192
- await import_promises.default.mkdir(import_path.default.dirname(filePath), { recursive: true });
193
- await import_promises.default.writeFile(filePath, content, "utf-8");
159
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
160
+ await fs.writeFile(filePath, content, "utf-8");
194
161
  return { content: [{ type: "text", text: "File saved" }] };
195
162
  }
196
163
  );
@@ -201,11 +168,11 @@ ${error.stderr ?? ""}`
201
168
  "Use this to explore project structure before reading or modifying files."
202
169
  ].join("\n"),
203
170
  {
204
- path: import_zod.z.string().describe("Directory path to list")
171
+ path: z.string().describe("Directory path to list")
205
172
  },
206
173
  async ({ path: dirPath }) => {
207
174
  try {
208
- const entries = await import_promises.default.readdir(dirPath, { withFileTypes: true });
175
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
209
176
  const lines = entries.map((e) => `${e.isDirectory() ? "\u{1F4C1}" : "\u{1F4C4}"} ${e.name}`);
210
177
  return { content: [{ type: "text", text: lines.join("\n") }] };
211
178
  } catch (err) {
@@ -226,9 +193,9 @@ ${error.stderr ?? ""}`
226
193
  "Returns matching lines with file paths and line numbers for precise navigation."
227
194
  ].join("\n"),
228
195
  {
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')")
196
+ pattern: z.string().describe("Search pattern with full regex support (e.g. 'function\\s+\\w+', 'import.*from', 'TODO')"),
197
+ directory: z.string().optional().default(".").describe("Root directory to search from (default: current working directory)"),
198
+ file_pattern: z.string().optional().default("**/*").describe("Glob pattern to filter files (e.g. '**/*.ts', '*.py', 'src/**/*.js')")
232
199
  },
233
200
  async ({ pattern, directory, file_pattern }) => {
234
201
  try {
@@ -239,13 +206,13 @@ ${error.stderr ?? ""}`
239
206
  );
240
207
  return { content: [{ type: "text", text: stdout || "No results" }] };
241
208
  } catch {
242
- const safeDirectory = import_path.default.resolve(directory);
243
- const files = await (0, import_glob.glob)(file_pattern, { cwd: safeDirectory });
209
+ const safeDirectory = path.resolve(directory);
210
+ const files = await glob(file_pattern, { cwd: safeDirectory });
244
211
  const results = [];
245
212
  for (const file of files.slice(0, 100)) {
246
213
  try {
247
- const content = await import_promises.default.readFile(
248
- import_path.default.join(safeDirectory, file),
214
+ const content = await fs.readFile(
215
+ path.join(safeDirectory, file),
249
216
  "utf-8"
250
217
  );
251
218
  const lines = content.split("\n");
@@ -282,8 +249,8 @@ ${error.stderr ?? ""}`
282
249
  "SAFETY: Only kill processes the user explicitly identifies. Never kill system-critical processes (init, systemd, loginwindow, WindowServer) without explicit instruction."
283
250
  ].join("\n"),
284
251
  {
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.")
252
+ pid: z.number().describe("PID of the process to terminate (use list_processes to find PIDs)"),
253
+ signal: z.enum(["SIGTERM", "SIGKILL"]).optional().default("SIGTERM").describe("SIGTERM (default): graceful shutdown with 3s auto-SIGKILL fallback. SIGKILL: immediate force kill.")
287
254
  },
288
255
  async ({ pid, signal }) => {
289
256
  const isWindows = process.platform === "win32";
@@ -339,13 +306,13 @@ ${error.stderr ?? ""}`
339
306
  "Prefer this over write_file for modifying existing files \u2014 it only changes what you specify and preserves the rest."
340
307
  ].join("\n"),
341
308
  {
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).")
309
+ path: z.string().describe("Path to the file to edit. The file must already exist."),
310
+ old_string: z.string().describe("The exact text to find and replace. Must match character-for-character including whitespace and newlines. Include enough context for uniqueness."),
311
+ new_string: z.string().describe("The replacement text. Use empty string to delete the matched text."),
312
+ replace_all: z.boolean().optional().default(false).describe("If true, replace ALL matches. If false (default), require exactly one match (errors on ambiguous multiple matches).")
346
313
  },
347
314
  async ({ path: filePath, old_string, new_string, replace_all }) => {
348
- const content = await import_promises.default.readFile(filePath, "utf-8");
315
+ const content = await fs.readFile(filePath, "utf-8");
349
316
  if (!content.includes(old_string)) {
350
317
  throw new Error(`old_string not found in file: ${filePath}`);
351
318
  }
@@ -369,7 +336,7 @@ ${error.stderr ?? ""}`
369
336
  result = content.replace(old_string, new_string);
370
337
  replaced = 1;
371
338
  }
372
- await import_promises.default.writeFile(filePath, result, "utf-8");
339
+ await fs.writeFile(filePath, result, "utf-8");
373
340
  return {
374
341
  content: [{ type: "text", text: `Replaced (${replaced} occurrence(s) changed)` }]
375
342
  };
@@ -384,9 +351,9 @@ ${error.stderr ?? ""}`
384
351
  "Duplicate commands are automatically detected and rejected. Use cron_list to see existing jobs."
385
352
  ].join("\n"),
386
353
  {
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')")
354
+ schedule: z.string().describe("Cron schedule expression (5 fields: minute hour day month weekday). Examples: '*/5 * * * *' (every 5 min), '0 9 * * 1-5' (weekdays 9am)"),
355
+ command: z.string().describe("Shell command to execute on schedule"),
356
+ label: z.string().optional().describe("Human-readable label for identification (e.g. 'daily-backup', 'log-cleanup')")
390
357
  },
391
358
  async ({ schedule, command, label }) => {
392
359
  try {
@@ -408,9 +375,9 @@ ${error.stderr ?? ""}`
408
375
  `;
409
376
  const updated = existing.trimEnd() + "\n" + newEntry;
410
377
  const tmpFile = `/tmp/junis_crontab_${Date.now()}.txt`;
411
- await import_promises.default.writeFile(tmpFile, updated, "utf-8");
378
+ await fs.writeFile(tmpFile, updated, "utf-8");
412
379
  await execAsync(`crontab ${tmpFile}`);
413
- await import_promises.default.unlink(tmpFile).catch(() => {
380
+ await fs.unlink(tmpFile).catch(() => {
414
381
  });
415
382
  return {
416
383
  content: [{ type: "text", text: `\u2705 Cron job created:
@@ -477,8 +444,8 @@ ${error.stderr ?? ""}`
477
444
  "cron_delete",
478
445
  "Delete a scheduled cron job by its ID (from cron_list output) or by matching command string. Associated comment labels are automatically cleaned up.",
479
446
  {
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")
447
+ id: z.number().optional().describe("Cron job ID from cron_list output (e.g. 1, 2, 3)"),
448
+ command: z.string().optional().describe("Delete all jobs matching this command string")
482
449
  },
483
450
  async ({ id, command }) => {
484
451
  if (!id && !command) {
@@ -515,9 +482,9 @@ ${error.stderr ?? ""}`
515
482
  }
516
483
  const updated2 = filtered2.join("\n");
517
484
  const tmpFile2 = `/tmp/junis_crontab_${Date.now()}.txt`;
518
- await import_promises.default.writeFile(tmpFile2, updated2, "utf-8");
485
+ await fs.writeFile(tmpFile2, updated2, "utf-8");
519
486
  await execAsync(`crontab ${tmpFile2}`);
520
- await import_promises.default.unlink(tmpFile2).catch(() => {
487
+ await fs.unlink(tmpFile2).catch(() => {
521
488
  });
522
489
  return { content: [{ type: "text", text: `\u2705 Deleted cron job matching: ${command}` }] };
523
490
  }
@@ -542,9 +509,9 @@ ${error.stderr ?? ""}`
542
509
  const filtered = lines.filter((_, i) => i < target.lineStart || i > target.lineEnd);
543
510
  const updated = filtered.join("\n");
544
511
  const tmpFile = `/tmp/junis_crontab_${Date.now()}.txt`;
545
- await import_promises.default.writeFile(tmpFile, updated, "utf-8");
512
+ await fs.writeFile(tmpFile, updated, "utf-8");
546
513
  await execAsync(`crontab ${tmpFile}`);
547
- await import_promises.default.unlink(tmpFile).catch(() => {
514
+ await fs.unlink(tmpFile).catch(() => {
548
515
  });
549
516
  return { content: [{ type: "text", text: `\u2705 Deleted cron job #${id}` }] };
550
517
  } catch (err) {
@@ -559,9 +526,9 @@ ${error.stderr ?? ""}`
559
526
  };
560
527
 
561
528
  // src/tools/browser.ts
562
- var import_browserclaw = require("browserclaw");
563
- var import_promises2 = __toESM(require("fs/promises"));
564
- var import_zod2 = require("zod");
529
+ import { BrowserClaw } from "browserclaw";
530
+ import fs2 from "fs/promises";
531
+ import { z as z2 } from "zod";
565
532
  var BrowserTools = class {
566
533
  browser = null;
567
534
  page = null;
@@ -602,11 +569,11 @@ var BrowserTools = class {
602
569
  "Always call browser_stop when done to release system resources."
603
570
  ].join("\n"),
604
571
  {
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")
572
+ mode: z2.enum(["managed", "remote-cdp"]).optional().default("managed").describe("'managed' = launch new browser, 'remote-cdp' = connect to existing Chrome via CDP"),
573
+ headless: z2.boolean().optional().default(false).describe("Run without visible window (managed mode only). Use for background tasks."),
574
+ cdpUrl: z2.string().optional().describe("Chrome DevTools Protocol URL for remote-cdp mode (e.g. http://localhost:9222)"),
575
+ profile: z2.string().optional().describe("Browser profile name for persistent sessions \u2014 preserves cookies, logins, and history across restarts (managed mode only)"),
576
+ allowInternal: z2.boolean().optional().default(false).describe("Allow navigation to localhost and internal network URLs")
610
577
  },
611
578
  ({ mode, headless, cdpUrl, profile, allowInternal }) => this.withLock(async () => {
612
579
  if (this.browser) {
@@ -614,9 +581,9 @@ var BrowserTools = class {
614
581
  }
615
582
  if (mode === "remote-cdp") {
616
583
  if (!cdpUrl) throw new Error("cdpUrl is required for remote-cdp mode");
617
- this.browser = await import_browserclaw.BrowserClaw.connect(cdpUrl, { allowInternal });
584
+ this.browser = await BrowserClaw.connect(cdpUrl, { allowInternal });
618
585
  } else {
619
- this.browser = await import_browserclaw.BrowserClaw.launch({
586
+ this.browser = await BrowserClaw.launch({
620
587
  headless,
621
588
  profileName: profile,
622
589
  allowInternal
@@ -638,7 +605,7 @@ var BrowserTools = class {
638
605
  "browser_navigate",
639
606
  "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.",
640
607
  {
641
- url: import_zod2.z.string().describe("Full URL to navigate to (include https://)")
608
+ url: z2.string().describe("Full URL to navigate to (include https://)")
642
609
  },
643
610
  ({ url }) => this.withLock(async () => {
644
611
  if (!this.browser) throw new Error("Browser not started. Call browser_start first.");
@@ -662,8 +629,8 @@ var BrowserTools = class {
662
629
  "Prefer this over browser_screenshot for understanding page structure \u2014 it's faster, structured, and machine-readable."
663
630
  ].join("\n"),
664
631
  {
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")
632
+ interactive: z2.boolean().optional().default(true).describe("true (default): only show clickable/typeable elements. false: show all elements including static text."),
633
+ compact: z2.boolean().optional().default(true).describe("true (default): hide empty containers for cleaner output")
667
634
  },
668
635
  ({ interactive, compact }) => this.withLock(async () => {
669
636
  const result = await requirePage().snapshot({ interactive, compact });
@@ -685,9 +652,9 @@ ${refList}`
685
652
  "browser_click",
686
653
  "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.",
687
654
  {
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")
655
+ ref: z2.string().describe("Element ref from browser_snapshot (e.g. 'e1', 'e15'). Call browser_snapshot first to get current refs."),
656
+ doubleClick: z2.boolean().optional().default(false).describe("Double-click instead of single click"),
657
+ button: z2.enum(["left", "right", "middle"]).optional().default("left").describe("Mouse button to use")
691
658
  },
692
659
  ({ ref, doubleClick, button }) => this.withLock(async () => {
693
660
  await requirePage().click(ref, { doubleClick, button });
@@ -698,10 +665,10 @@ ${refList}`
698
665
  "browser_type",
699
666
  "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.",
700
667
  {
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")
668
+ ref: z2.string().describe("Element ref from browser_snapshot (e.g. 'e3')"),
669
+ text: z2.string().describe("Text to type into the element"),
670
+ submit: z2.boolean().optional().default(false).describe("Press Enter after typing (useful for search boxes and forms)"),
671
+ slowly: z2.boolean().optional().default(false).describe("Type slowly (75ms per char) for sites that process each keystroke")
705
672
  },
706
673
  ({ ref, text, submit, slowly }) => this.withLock(async () => {
707
674
  await requirePage().type(ref, text, { submit, slowly });
@@ -712,10 +679,10 @@ ${refList}`
712
679
  "browser_fill",
713
680
  "Fill multiple form fields at once \u2014 more efficient than calling browser_type repeatedly. Each field needs a ref from browser_snapshot.",
714
681
  {
715
- fields: import_zod2.z.array(import_zod2.z.object({
716
- ref: import_zod2.z.string(),
717
- type: import_zod2.z.enum(["text", "checkbox", "radio"]),
718
- value: import_zod2.z.union([import_zod2.z.string(), import_zod2.z.boolean()])
682
+ fields: z2.array(z2.object({
683
+ ref: z2.string(),
684
+ type: z2.enum(["text", "checkbox", "radio"]),
685
+ value: z2.union([z2.string(), z2.boolean()])
719
686
  })).describe("Array of {ref, type, value}. type='text': value is string. type='checkbox'/'radio': value is boolean.")
720
687
  },
721
688
  ({ fields }) => this.withLock(async () => {
@@ -727,8 +694,8 @@ ${refList}`
727
694
  "browser_select",
728
695
  "Select one or more options from a dropdown/select element. Values should match the option value attributes, not display text.",
729
696
  {
730
- ref: import_zod2.z.string().describe("Ref of the <select> element from browser_snapshot"),
731
- values: import_zod2.z.array(import_zod2.z.string()).describe("Option value(s) to select")
697
+ ref: z2.string().describe("Ref of the <select> element from browser_snapshot"),
698
+ values: z2.array(z2.string()).describe("Option value(s) to select")
732
699
  },
733
700
  ({ ref, values }) => this.withLock(async () => {
734
701
  await requirePage().select(ref, ...values);
@@ -739,7 +706,7 @@ ${refList}`
739
706
  "browser_press",
740
707
  "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.",
741
708
  {
742
- key: import_zod2.z.string().describe("Key or combination: 'Enter', 'Escape', 'Tab', 'Control+a', 'Meta+c', 'ArrowDown', 'Backspace'")
709
+ key: z2.string().describe("Key or combination: 'Enter', 'Escape', 'Tab', 'Control+a', 'Meta+c', 'ArrowDown', 'Backspace'")
743
710
  },
744
711
  ({ key }) => this.withLock(async () => {
745
712
  await requirePage().press(key);
@@ -750,7 +717,7 @@ ${refList}`
750
717
  "browser_hover",
751
718
  "Move the mouse cursor over an element by ref. Use to trigger hover menus, tooltips, or dropdown previews before clicking.",
752
719
  {
753
- ref: import_zod2.z.string().describe("Element ref from browser_snapshot")
720
+ ref: z2.string().describe("Element ref from browser_snapshot")
754
721
  },
755
722
  ({ ref }) => this.withLock(async () => {
756
723
  await requirePage().hover(ref);
@@ -761,8 +728,8 @@ ${refList}`
761
728
  "browser_drag",
762
729
  "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.",
763
730
  {
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")
731
+ startRef: z2.string().describe("Source element ref to drag from"),
732
+ endRef: z2.string().describe("Target element ref to drag to")
766
733
  },
767
734
  ({ startRef, endRef }) => this.withLock(async () => {
768
735
  await requirePage().drag(startRef, endRef);
@@ -773,8 +740,8 @@ ${refList}`
773
740
  "browser_upload",
774
741
  "Upload local files to a file input element (<input type='file'>). The ref must point to a file input from browser_snapshot.",
775
742
  {
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")
743
+ ref: z2.string().describe("Ref of the file input element from browser_snapshot"),
744
+ paths: z2.array(z2.string()).describe("Absolute file path(s) on the local device to upload")
778
745
  },
779
746
  ({ ref, paths }) => this.withLock(async () => {
780
747
  await requirePage().uploadFile(ref, paths);
@@ -790,14 +757,14 @@ ${refList}`
790
757
  "Use browser_screenshot only when visual layout matters (charts, images, styling, visual verification)."
791
758
  ].join("\n"),
792
759
  {
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")
760
+ path: z2.string().optional().describe("Save path for the screenshot. If omitted, returns base64 image data directly."),
761
+ fullPage: z2.boolean().optional().default(false).describe("Capture the full scrollable page, not just the visible viewport"),
762
+ ref: z2.string().optional().describe("Capture only a specific element by its ref from browser_snapshot")
796
763
  },
797
764
  ({ path: path2, fullPage, ref }) => this.withLock(async () => {
798
765
  const buffer = await requirePage().screenshot({ fullPage, ref });
799
766
  if (path2) {
800
- await import_promises2.default.writeFile(path2, buffer);
767
+ await fs2.writeFile(path2, buffer);
801
768
  return { content: [{ type: "text", text: `Screenshot saved: ${path2}` }] };
802
769
  }
803
770
  return {
@@ -813,11 +780,11 @@ ${refList}`
813
780
  "browser_pdf",
814
781
  "Save the current page as a PDF file. Renders the full page including below-the-fold content. Useful for archiving, sharing, or offline reading.",
815
782
  {
816
- path: import_zod2.z.string().describe("Output file path (.pdf)")
783
+ path: z2.string().describe("Output file path (.pdf)")
817
784
  },
818
785
  ({ path: path2 }) => this.withLock(async () => {
819
786
  const buffer = await requirePage().pdf();
820
- await import_promises2.default.writeFile(path2, buffer);
787
+ await fs2.writeFile(path2, buffer);
821
788
  return { content: [{ type: "text", text: `PDF saved: ${path2}` }] };
822
789
  })
823
790
  );
@@ -830,7 +797,7 @@ ${refList}`
830
797
  "Wrap complex logic in an IIFE: (function(){ ... })()"
831
798
  ].join("\n"),
832
799
  {
833
- code: import_zod2.z.string().describe("JavaScript code to execute in the page context. Return values are automatically serialized.")
800
+ code: z2.string().describe("JavaScript code to execute in the page context. Return values are automatically serialized.")
834
801
  },
835
802
  ({ code }) => this.withLock(async () => {
836
803
  try {
@@ -857,11 +824,11 @@ ${refList}`
857
824
  "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
825
  ].join("\n"),
859
826
  {
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")
827
+ text: z2.string().optional().describe("Wait until this text appears on the page"),
828
+ textGone: z2.string().optional().describe("Wait until this text disappears from the page"),
829
+ url: z2.string().optional().describe("Wait until URL matches this glob pattern (e.g. '**/dashboard', '**/success')"),
830
+ loadState: z2.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Wait for page load state: 'load' (full), 'domcontentloaded' (DOM ready), 'networkidle' (no pending requests)"),
831
+ timeMs: z2.number().optional().describe("Fixed wait in milliseconds \u2014 use as last resort when other conditions don't apply")
865
832
  },
866
833
  ({ text, textGone, url, loadState, timeMs }) => this.withLock(async () => {
867
834
  const condition = {};
@@ -878,14 +845,14 @@ ${refList}`
878
845
  "browser_cookies",
879
846
  "Manage browser cookies: get all cookies, set a specific cookie, or clear all cookies. Useful for authentication state, session management, or testing.",
880
847
  {
881
- action: import_zod2.z.enum(["get", "set", "clear"]).describe("'get': retrieve all cookies, 'set': add/update a cookie, 'clear': remove all cookies"),
882
- cookie: import_zod2.z.object({
883
- name: import_zod2.z.string(),
884
- value: import_zod2.z.string(),
885
- domain: import_zod2.z.string().optional(),
886
- path: import_zod2.z.string().optional(),
887
- httpOnly: import_zod2.z.boolean().optional(),
888
- secure: import_zod2.z.boolean().optional()
848
+ action: z2.enum(["get", "set", "clear"]).describe("'get': retrieve all cookies, 'set': add/update a cookie, 'clear': remove all cookies"),
849
+ cookie: z2.object({
850
+ name: z2.string(),
851
+ value: z2.string(),
852
+ domain: z2.string().optional(),
853
+ path: z2.string().optional(),
854
+ httpOnly: z2.boolean().optional(),
855
+ secure: z2.boolean().optional()
889
856
  }).optional().describe("Cookie data (required for 'set' action)")
890
857
  },
891
858
  ({ action, cookie }) => this.withLock(async () => {
@@ -907,10 +874,10 @@ ${refList}`
907
874
  "browser_storage",
908
875
  "Read, write, or clear browser localStorage/sessionStorage. Useful for managing client-side state, authentication tokens, or application preferences.",
909
876
  {
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)")
877
+ action: z2.enum(["get", "set", "clear"]).describe("'get': read value(s), 'set': write a key-value pair, 'clear': remove all entries"),
878
+ kind: z2.enum(["local", "session"]).optional().default("local").describe("'local' (persistent) or 'session' (cleared on tab close)"),
879
+ key: z2.string().optional().describe("Storage key to get or set. Omit key with 'get' to retrieve all entries."),
880
+ value: z2.string().optional().describe("Value to store (required for 'set' action)")
914
881
  },
915
882
  ({ action, kind, key, value }) => this.withLock(async () => {
916
883
  const page = requirePage();
@@ -938,16 +905,16 @@ ${refList}`
938
905
  "The 'accept' and 'promptText' params are only used with action='arm'."
939
906
  ].join("\n"),
940
907
  {
941
- action: import_zod2.z.enum(["arm", "wait"]).describe(
908
+ action: z2.enum(["arm", "wait"]).describe(
942
909
  "'arm' = register handler and return immediately; 'wait' = await the previously armed handler"
943
910
  ),
944
- accept: import_zod2.z.boolean().optional().default(true).describe(
911
+ accept: z2.boolean().optional().default(true).describe(
945
912
  "Accept (true) or dismiss (false) the dialog. Only used with action='arm'."
946
913
  ),
947
- promptText: import_zod2.z.string().optional().describe(
914
+ promptText: z2.string().optional().describe(
948
915
  "Text to enter if the dialog is a prompt. Only used with action='arm'."
949
916
  ),
950
- timeoutMs: import_zod2.z.number().optional().describe(
917
+ timeoutMs: z2.number().optional().describe(
951
918
  "Timeout in ms for 'wait' action (default: 30000). Increase for slow-loading dialogs."
952
919
  )
953
920
  },
@@ -979,13 +946,13 @@ ${refList}`
979
946
  };
980
947
 
981
948
  // src/tools/notebook.ts
982
- var import_zod3 = require("zod");
983
- var import_promises3 = __toESM(require("fs/promises"));
984
- var import_child_process2 = require("child_process");
985
- var import_util2 = require("util");
986
- var execAsync2 = (0, import_util2.promisify)(import_child_process2.exec);
949
+ import { z as z3 } from "zod";
950
+ import fs3 from "fs/promises";
951
+ import { exec as exec2 } from "child_process";
952
+ import { promisify as promisify2 } from "util";
953
+ var execAsync2 = promisify2(exec2);
987
954
  async function readNotebook(filePath) {
988
- const raw = await import_promises3.default.readFile(filePath, "utf-8");
955
+ const raw = await fs3.readFile(filePath, "utf-8");
989
956
  try {
990
957
  return JSON.parse(raw);
991
958
  } catch {
@@ -993,14 +960,14 @@ async function readNotebook(filePath) {
993
960
  }
994
961
  }
995
962
  async function writeNotebook(filePath, nb) {
996
- await import_promises3.default.writeFile(filePath, JSON.stringify(nb, null, 1), "utf-8");
963
+ await fs3.writeFile(filePath, JSON.stringify(nb, null, 1), "utf-8");
997
964
  }
998
965
  var NotebookTools = class {
999
966
  register(server) {
1000
967
  server.tool(
1001
968
  "notebook_read",
1002
969
  "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") },
970
+ { path: z3.string().describe("Path to the .ipynb notebook file") },
1004
971
  async ({ path: filePath }) => {
1005
972
  const nb = await readNotebook(filePath);
1006
973
  const cells = nb.cells.map((cell, i) => ({
@@ -1018,9 +985,9 @@ var NotebookTools = class {
1018
985
  "notebook_edit_cell",
1019
986
  "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.",
1020
987
  {
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)")
988
+ path: z3.string().describe("Path to the .ipynb notebook file"),
989
+ cell_index: z3.number().describe("Cell index to edit (0-based). Use notebook_read to find the right index."),
990
+ source: z3.string().describe("New source code/content for the cell (replaces entire cell content)")
1024
991
  },
1025
992
  async ({ path: filePath, cell_index, source }) => {
1026
993
  const nb = await readNotebook(filePath);
@@ -1043,8 +1010,8 @@ var NotebookTools = class {
1043
1010
  "If execution fails on a cell, the error is captured in the cell output and subsequent cells may not execute."
1044
1011
  ].join("\n"),
1045
1012
  {
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.")
1013
+ path: z3.string().describe("Path to the .ipynb notebook file to execute"),
1014
+ timeout: z3.number().optional().default(300).describe("Maximum execution time per cell in seconds (default: 300). Increase for cells with heavy computation.")
1048
1015
  },
1049
1016
  async ({ path: filePath, timeout }) => {
1050
1017
  const nbconvertArgs = `nbconvert --to notebook --execute --inplace "${filePath}" --ExecutePreprocessor.timeout=${timeout}`;
@@ -1075,10 +1042,10 @@ var NotebookTools = class {
1075
1042
  "notebook_add_cell",
1076
1043
  "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.",
1077
1044
  {
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.")
1045
+ path: z3.string().describe("Path to the .ipynb notebook file"),
1046
+ cell_type: z3.enum(["code", "markdown"]).describe("'code' for executable cells, 'markdown' for text/documentation cells"),
1047
+ source: z3.string().describe("Cell source content (Python code or Markdown text)"),
1048
+ position: z3.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.")
1082
1049
  },
1083
1050
  async ({ path: filePath, cell_type: cellType, source, position }) => {
1084
1051
  const nb = await readNotebook(filePath);
@@ -1111,8 +1078,8 @@ var NotebookTools = class {
1111
1078
  "notebook_delete_cell",
1112
1079
  "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.",
1113
1080
  {
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.")
1081
+ path: z3.string().describe("Path to the .ipynb notebook file"),
1082
+ cell_index: z3.number().describe("Cell index to delete (0-based). Use notebook_read first to verify content.")
1116
1083
  },
1117
1084
  async ({ path: filePath, cell_index }) => {
1118
1085
  const nb = await readNotebook(filePath);
@@ -1128,11 +1095,11 @@ var NotebookTools = class {
1128
1095
  };
1129
1096
 
1130
1097
  // src/tools/device.ts
1131
- var import_child_process3 = require("child_process");
1132
- var import_util3 = require("util");
1133
- var import_zod4 = require("zod");
1134
- var import_node_notifier = __toESM(require("node-notifier"));
1135
- var execAsync3 = (0, import_util3.promisify)(import_child_process3.exec);
1098
+ import { exec as exec3 } from "child_process";
1099
+ import { promisify as promisify3 } from "util";
1100
+ import { z as z4 } from "zod";
1101
+ import notifier from "node-notifier";
1102
+ var execAsync3 = promisify3(exec3);
1136
1103
  var screenRecordPid = null;
1137
1104
  function platform() {
1138
1105
  if (process.platform === "darwin") return "mac";
@@ -1150,7 +1117,7 @@ var DeviceTools = class {
1150
1117
  "Requires a connected camera with OS permissions granted. If output_path is provided, the file is also saved to disk."
1151
1118
  ].join("\n"),
1152
1119
  {
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).")
1120
+ output_path: z4.string().optional().describe("File path to save the captured photo. If omitted, returns image data only (temp file auto-cleaned).")
1154
1121
  },
1155
1122
  async ({ output_path }) => {
1156
1123
  const p = platform();
@@ -1188,13 +1155,13 @@ Please check if a camera is connected.` }],
1188
1155
  "notification_send",
1189
1156
  "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.",
1190
1157
  {
1191
- title: import_zod4.z.string().describe("Notification title (displayed prominently)"),
1192
- message: import_zod4.z.string().describe("Notification body text")
1158
+ title: z4.string().describe("Notification title (displayed prominently)"),
1159
+ message: z4.string().describe("Notification body text")
1193
1160
  },
1194
1161
  async ({ title, message }) => {
1195
1162
  try {
1196
1163
  await new Promise((resolve, reject) => {
1197
- import_node_notifier.default.notify(
1164
+ notifier.notify(
1198
1165
  { title, message },
1199
1166
  (err) => {
1200
1167
  if (err) reject(err);
@@ -1226,7 +1193,7 @@ Please check if a camera is connected.` }],
1226
1193
  "clipboard_write",
1227
1194
  "Write text to the system clipboard, replacing its current contents. Use to prepare content for the user to paste elsewhere.",
1228
1195
  {
1229
- text: import_zod4.z.string().describe("Text to copy to the clipboard")
1196
+ text: z4.string().describe("Text to copy to the clipboard")
1230
1197
  },
1231
1198
  async ({ text }) => {
1232
1199
  const p = platform();
@@ -1248,8 +1215,8 @@ Please check if a camera is connected.` }],
1248
1215
  "Platform-specific: macOS (screencapture -v), Windows/Linux (ffmpeg)."
1249
1216
  ].join("\n"),
1250
1217
  {
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")
1218
+ action: z4.enum(["start", "stop"]).describe("'start': begin recording, 'stop': end recording and save the file"),
1219
+ output_path: z4.string().optional().describe("Output file path (used with 'start'). Default: /tmp/junis_record_<timestamp>.mp4")
1253
1220
  },
1254
1221
  async ({ action, output_path }) => {
1255
1222
  const p = platform();
@@ -1314,7 +1281,7 @@ Please check if a camera is connected.` }],
1314
1281
  "audio_play",
1315
1282
  "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).",
1316
1283
  {
1317
- file_path: import_zod4.z.string().describe("Absolute path to the audio file to play")
1284
+ file_path: z4.string().describe("Absolute path to the audio file to play")
1318
1285
  },
1319
1286
  async ({ file_path }) => {
1320
1287
  const p = platform();
@@ -1331,8 +1298,9 @@ Please check if a camera is connected.` }],
1331
1298
  };
1332
1299
 
1333
1300
  // src/server/stdio.ts
1301
+ import { fileURLToPath } from "url";
1334
1302
  async function startStdioServer() {
1335
- const server = new import_mcp.McpServer({ name: "junis", version: "0.1.0" });
1303
+ const server = new McpServer({ name: "junis", version: "0.1.0" });
1336
1304
  const fsTools = new FilesystemTools();
1337
1305
  fsTools.register(server);
1338
1306
  const browserTools = new BrowserTools();
@@ -1342,17 +1310,16 @@ async function startStdioServer() {
1342
1310
  notebookTools.register(server);
1343
1311
  const deviceTools = new DeviceTools();
1344
1312
  deviceTools.register(server);
1345
- const transport = new import_stdio.StdioServerTransport();
1313
+ const transport = new StdioServerTransport();
1346
1314
  await server.connect(transport);
1347
1315
  process.on("SIGINT", async () => {
1348
1316
  await browserTools.cleanup();
1349
1317
  process.exit(0);
1350
1318
  });
1351
1319
  }
1352
- if (require.main === module) {
1320
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
1353
1321
  startStdioServer().catch(console.error);
1354
1322
  }
1355
- // Annotate the CommonJS export names for ESM import in node:
1356
- 0 && (module.exports = {
1323
+ export {
1357
1324
  startStdioServer
1358
- });
1325
+ };