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.
package/dist/cli/index.js CHANGED
@@ -1,92 +1,19 @@
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 __commonJS = (cb, mod) => function __require() {
10
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
-
29
- // package.json
30
- var require_package = __commonJS({
31
- "package.json"(exports2, module2) {
32
- module2.exports = {
33
- name: "junis",
34
- version: "0.3.3",
35
- description: "One-line device control for AI agents",
36
- bin: {
37
- junis: "dist/cli/index.js"
38
- },
39
- scripts: {
40
- build: "tsup src/cli/index.ts --format cjs --dts --out-dir dist/cli && tsup src/server/mcp.ts --format cjs --out-dir dist/server && tsup src/server/stdio.ts --format cjs --out-dir dist/server",
41
- dev: "tsx src/cli/index.ts",
42
- "type-check": "tsc --noEmit",
43
- prepublishOnly: "npm run build"
44
- },
45
- dependencies: {
46
- "@inquirer/prompts": "^8.2.1",
47
- "@modelcontextprotocol/sdk": "^1.0.0",
48
- browserclaw: "^0.2.7",
49
- commander: "^12.0.0",
50
- execa: "^8.0.0",
51
- glob: "^11.0.0",
52
- "node-notifier": "^10.0.1",
53
- open: "^10.1.0",
54
- "playwright-core": ">=1.50.0",
55
- ws: "^8.18.0",
56
- zod: "^4.3.6"
57
- },
58
- devDependencies: {
59
- "@types/node": "^20.0.0",
60
- "@types/node-notifier": "^8.0.5",
61
- "@types/ws": "^8.5.0",
62
- tsup: "^8.0.0",
63
- tsx: "^4.0.0",
64
- typescript: "^5.0.0"
65
- },
66
- engines: {
67
- node: ">=18.0.0"
68
- },
69
- files: [
70
- "dist/"
71
- ]
72
- };
73
- }
74
- });
75
2
 
76
3
  // src/cli/index.ts
77
- var import_commander = require("commander");
78
- var import_prompts = require("@inquirer/prompts");
4
+ import { program } from "commander";
5
+ import { select } from "@inquirer/prompts";
79
6
 
80
7
  // src/cli/config.ts
81
- var import_fs = __toESM(require("fs"));
82
- var import_path = __toESM(require("path"));
83
- var import_os = __toESM(require("os"));
84
- var CONFIG_DIR = import_path.default.join(import_os.default.homedir(), ".junis");
85
- var CONFIG_FILE = import_path.default.join(CONFIG_DIR, "config.json");
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import os from "os";
11
+ var CONFIG_DIR = path.join(os.homedir(), ".junis");
12
+ var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
86
13
  function loadConfig() {
87
14
  try {
88
- if (!import_fs.default.existsSync(CONFIG_FILE)) return null;
89
- const raw = import_fs.default.readFileSync(CONFIG_FILE, "utf-8");
15
+ if (!fs.existsSync(CONFIG_FILE)) return null;
16
+ const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
90
17
  return JSON.parse(raw);
91
18
  } catch {
92
19
  return null;
@@ -94,9 +21,9 @@ function loadConfig() {
94
21
  }
95
22
  function saveConfig(config) {
96
23
  try {
97
- import_fs.default.mkdirSync(CONFIG_DIR, { recursive: true });
98
- import_fs.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
99
- import_fs.default.chmodSync(CONFIG_FILE, 384);
24
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
25
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
26
+ fs.chmodSync(CONFIG_FILE, 384);
100
27
  } catch (err) {
101
28
  console.error(`
102
29
  \u274C Failed to save config file: ${err.message}`);
@@ -105,13 +32,13 @@ function saveConfig(config) {
105
32
  }
106
33
  }
107
34
  function clearConfig() {
108
- if (import_fs.default.existsSync(CONFIG_FILE)) {
109
- import_fs.default.unlinkSync(CONFIG_FILE);
35
+ if (fs.existsSync(CONFIG_FILE)) {
36
+ fs.unlinkSync(CONFIG_FILE);
110
37
  }
111
38
  }
112
39
 
113
40
  // src/cli/auth.ts
114
- var import_open = __toESM(require("open"));
41
+ import open from "open";
115
42
  var JUNIS_API = process.env.JUNIS_API_URL ?? "https://junis.ai";
116
43
  var JUNIS_WEB = (() => {
117
44
  if (process.env.JUNIS_WEB_URL) return process.env.JUNIS_WEB_URL;
@@ -157,7 +84,7 @@ async function authenticate(deviceName, platform3, onBrowserOpen, onWaiting, exi
157
84
  const verificationUri = JUNIS_WEB ? startData.verification_uri.replace(/^https?:\/\/[^/]+/, JUNIS_WEB) : startData.verification_uri;
158
85
  onBrowserOpen?.(verificationUri);
159
86
  try {
160
- await (0, import_open.default)(verificationUri);
87
+ await open(verificationUri);
161
88
  } catch {
162
89
  console.warn(`
163
90
  \u26A0\uFE0F Could not open browser automatically. Please open the following URL manually:
@@ -204,7 +131,7 @@ function sleep(ms) {
204
131
  }
205
132
 
206
133
  // src/relay/client.ts
207
- var import_ws = __toESM(require("ws"));
134
+ import WebSocket from "ws";
208
135
  var JUNIS_WS = (() => {
209
136
  if (process.env.JUNIS_WS_URL) return process.env.JUNIS_WS_URL;
210
137
  const apiUrl = process.env.JUNIS_API_URL ?? "https://junis.ai";
@@ -230,7 +157,7 @@ var RelayClient = class {
230
157
  if (this.destroyed) return;
231
158
  const url = `${JUNIS_WS}/ws/devices/${this.config.device_key}`;
232
159
  console.log(`\u{1F517} Connecting to relay server...`);
233
- const ws = new import_ws.default(url, {
160
+ const ws = new WebSocket(url, {
234
161
  headers: { Authorization: `Bearer ${this.config.token}` }
235
162
  });
236
163
  this.ws = ws;
@@ -295,7 +222,7 @@ var RelayClient = class {
295
222
  this.connect();
296
223
  }
297
224
  send(data) {
298
- if (this.ws?.readyState === import_ws.default.OPEN) {
225
+ if (this.ws?.readyState === WebSocket.OPEN) {
299
226
  this.ws.send(JSON.stringify(data));
300
227
  }
301
228
  }
@@ -323,17 +250,17 @@ var RelayClient = class {
323
250
  };
324
251
 
325
252
  // src/server/mcp.ts
326
- var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
327
- var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
328
- var import_http = require("http");
253
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
254
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
255
+ import { createServer } from "http";
329
256
 
330
257
  // src/tools/filesystem.ts
331
- var import_child_process = require("child_process");
332
- var import_util = require("util");
333
- var import_promises = __toESM(require("fs/promises"));
334
- var import_path2 = __toESM(require("path"));
335
- var import_glob = require("glob");
336
- var import_zod = require("zod");
258
+ import { exec, execFile } from "child_process";
259
+ import { promisify } from "util";
260
+ import fs2 from "fs/promises";
261
+ import path2 from "path";
262
+ import { glob } from "glob";
263
+ import { z } from "zod";
337
264
 
338
265
  // src/server/permissions.ts
339
266
  var toolPermissions = {
@@ -385,8 +312,8 @@ function checkPermission(toolName) {
385
312
  }
386
313
 
387
314
  // src/tools/filesystem.ts
388
- var execAsync = (0, import_util.promisify)(import_child_process.exec);
389
- var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
315
+ var execAsync = promisify(exec);
316
+ var execFileAsync = promisify(execFile);
390
317
  var FilesystemTools = class {
391
318
  register(server) {
392
319
  server.tool(
@@ -408,14 +335,14 @@ var FilesystemTools = class {
408
335
  "- Avoid piping untrusted input into shells. Use absolute paths when possible. Quote paths containing spaces."
409
336
  ].join("\n"),
410
337
  {
411
- command: import_zod.z.string().describe("The shell command to execute. Use absolute paths when possible. Quote paths containing spaces."),
412
- timeout_ms: import_zod.z.number().optional().default(3e4).describe("Maximum execution time in milliseconds (default: 30000). Increase for long-running builds or downloads."),
413
- background: import_zod.z.boolean().optional().default(false).describe("Run in background without waiting for completion. Use for servers or long-running processes.")
338
+ command: z.string().describe("The shell command to execute. Use absolute paths when possible. Quote paths containing spaces."),
339
+ timeout_ms: z.number().optional().default(3e4).describe("Maximum execution time in milliseconds (default: 30000). Increase for long-running builds or downloads."),
340
+ background: z.boolean().optional().default(false).describe("Run in background without waiting for completion. Use for servers or long-running processes.")
414
341
  },
415
342
  async ({ command, timeout_ms, background }) => {
416
343
  checkPermission("execute_command");
417
344
  if (background) {
418
- (0, import_child_process.exec)(command);
345
+ exec(command);
419
346
  return { content: [{ type: "text", text: "Background execution started" }] };
420
347
  }
421
348
  try {
@@ -449,12 +376,12 @@ ${error.stderr ?? ""}`
449
376
  "For searching within files, prefer search_code instead. For listing directory contents, use list_directory."
450
377
  ].join("\n"),
451
378
  {
452
- path: import_zod.z.string().describe("Absolute or relative file path to read"),
453
- 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)")
379
+ path: z.string().describe("Absolute or relative file path to read"),
380
+ encoding: z.enum(["utf-8", "base64"]).optional().default("utf-8").describe("'utf-8' for text files (default), 'base64' for binary files (images, PDFs, archives)")
454
381
  },
455
382
  async ({ path: filePath, encoding }) => {
456
383
  try {
457
- const content = await import_promises.default.readFile(filePath, encoding);
384
+ const content = await fs2.readFile(filePath, encoding);
458
385
  return { content: [{ type: "text", text: content }] };
459
386
  } catch (err) {
460
387
  const e = err;
@@ -474,13 +401,13 @@ ${error.stderr ?? ""}`
474
401
  "Prefer edit_block over write_file for existing files \u2014 it's safer and preserves unmodified content."
475
402
  ].join("\n"),
476
403
  {
477
- path: import_zod.z.string().describe("File path to create or overwrite. Parent directories are auto-created."),
478
- content: import_zod.z.string().describe("Complete file content. This replaces the entire file.")
404
+ path: z.string().describe("File path to create or overwrite. Parent directories are auto-created."),
405
+ content: z.string().describe("Complete file content. This replaces the entire file.")
479
406
  },
480
407
  async ({ path: filePath, content }) => {
481
408
  checkPermission("write_file");
482
- await import_promises.default.mkdir(import_path2.default.dirname(filePath), { recursive: true });
483
- await import_promises.default.writeFile(filePath, content, "utf-8");
409
+ await fs2.mkdir(path2.dirname(filePath), { recursive: true });
410
+ await fs2.writeFile(filePath, content, "utf-8");
484
411
  return { content: [{ type: "text", text: "File saved" }] };
485
412
  }
486
413
  );
@@ -491,11 +418,11 @@ ${error.stderr ?? ""}`
491
418
  "Use this to explore project structure before reading or modifying files."
492
419
  ].join("\n"),
493
420
  {
494
- path: import_zod.z.string().describe("Directory path to list")
421
+ path: z.string().describe("Directory path to list")
495
422
  },
496
423
  async ({ path: dirPath }) => {
497
424
  try {
498
- const entries = await import_promises.default.readdir(dirPath, { withFileTypes: true });
425
+ const entries = await fs2.readdir(dirPath, { withFileTypes: true });
499
426
  const lines = entries.map((e) => `${e.isDirectory() ? "\u{1F4C1}" : "\u{1F4C4}"} ${e.name}`);
500
427
  return { content: [{ type: "text", text: lines.join("\n") }] };
501
428
  } catch (err) {
@@ -516,9 +443,9 @@ ${error.stderr ?? ""}`
516
443
  "Returns matching lines with file paths and line numbers for precise navigation."
517
444
  ].join("\n"),
518
445
  {
519
- pattern: import_zod.z.string().describe("Search pattern with full regex support (e.g. 'function\\s+\\w+', 'import.*from', 'TODO')"),
520
- directory: import_zod.z.string().optional().default(".").describe("Root directory to search from (default: current working directory)"),
521
- file_pattern: import_zod.z.string().optional().default("**/*").describe("Glob pattern to filter files (e.g. '**/*.ts', '*.py', 'src/**/*.js')")
446
+ pattern: z.string().describe("Search pattern with full regex support (e.g. 'function\\s+\\w+', 'import.*from', 'TODO')"),
447
+ directory: z.string().optional().default(".").describe("Root directory to search from (default: current working directory)"),
448
+ file_pattern: z.string().optional().default("**/*").describe("Glob pattern to filter files (e.g. '**/*.ts', '*.py', 'src/**/*.js')")
522
449
  },
523
450
  async ({ pattern, directory, file_pattern }) => {
524
451
  try {
@@ -529,13 +456,13 @@ ${error.stderr ?? ""}`
529
456
  );
530
457
  return { content: [{ type: "text", text: stdout || "No results" }] };
531
458
  } catch {
532
- const safeDirectory = import_path2.default.resolve(directory);
533
- const files = await (0, import_glob.glob)(file_pattern, { cwd: safeDirectory });
459
+ const safeDirectory = path2.resolve(directory);
460
+ const files = await glob(file_pattern, { cwd: safeDirectory });
534
461
  const results = [];
535
462
  for (const file of files.slice(0, 100)) {
536
463
  try {
537
- const content = await import_promises.default.readFile(
538
- import_path2.default.join(safeDirectory, file),
464
+ const content = await fs2.readFile(
465
+ path2.join(safeDirectory, file),
539
466
  "utf-8"
540
467
  );
541
468
  const lines = content.split("\n");
@@ -572,8 +499,8 @@ ${error.stderr ?? ""}`
572
499
  "SAFETY: Only kill processes the user explicitly identifies. Never kill system-critical processes (init, systemd, loginwindow, WindowServer) without explicit instruction."
573
500
  ].join("\n"),
574
501
  {
575
- pid: import_zod.z.number().describe("PID of the process to terminate (use list_processes to find PIDs)"),
576
- signal: import_zod.z.enum(["SIGTERM", "SIGKILL"]).optional().default("SIGTERM").describe("SIGTERM (default): graceful shutdown with 3s auto-SIGKILL fallback. SIGKILL: immediate force kill.")
502
+ pid: z.number().describe("PID of the process to terminate (use list_processes to find PIDs)"),
503
+ signal: z.enum(["SIGTERM", "SIGKILL"]).optional().default("SIGTERM").describe("SIGTERM (default): graceful shutdown with 3s auto-SIGKILL fallback. SIGKILL: immediate force kill.")
577
504
  },
578
505
  async ({ pid, signal }) => {
579
506
  const isWindows = process.platform === "win32";
@@ -629,13 +556,13 @@ ${error.stderr ?? ""}`
629
556
  "Prefer this over write_file for modifying existing files \u2014 it only changes what you specify and preserves the rest."
630
557
  ].join("\n"),
631
558
  {
632
- path: import_zod.z.string().describe("Path to the file to edit. The file must already exist."),
633
- 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."),
634
- new_string: import_zod.z.string().describe("The replacement text. Use empty string to delete the matched text."),
635
- 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).")
559
+ path: z.string().describe("Path to the file to edit. The file must already exist."),
560
+ 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."),
561
+ new_string: z.string().describe("The replacement text. Use empty string to delete the matched text."),
562
+ 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).")
636
563
  },
637
564
  async ({ path: filePath, old_string, new_string, replace_all }) => {
638
- const content = await import_promises.default.readFile(filePath, "utf-8");
565
+ const content = await fs2.readFile(filePath, "utf-8");
639
566
  if (!content.includes(old_string)) {
640
567
  throw new Error(`old_string not found in file: ${filePath}`);
641
568
  }
@@ -659,7 +586,7 @@ ${error.stderr ?? ""}`
659
586
  result = content.replace(old_string, new_string);
660
587
  replaced = 1;
661
588
  }
662
- await import_promises.default.writeFile(filePath, result, "utf-8");
589
+ await fs2.writeFile(filePath, result, "utf-8");
663
590
  return {
664
591
  content: [{ type: "text", text: `Replaced (${replaced} occurrence(s) changed)` }]
665
592
  };
@@ -674,9 +601,9 @@ ${error.stderr ?? ""}`
674
601
  "Duplicate commands are automatically detected and rejected. Use cron_list to see existing jobs."
675
602
  ].join("\n"),
676
603
  {
677
- 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)"),
678
- command: import_zod.z.string().describe("Shell command to execute on schedule"),
679
- label: import_zod.z.string().optional().describe("Human-readable label for identification (e.g. 'daily-backup', 'log-cleanup')")
604
+ 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)"),
605
+ command: z.string().describe("Shell command to execute on schedule"),
606
+ label: z.string().optional().describe("Human-readable label for identification (e.g. 'daily-backup', 'log-cleanup')")
680
607
  },
681
608
  async ({ schedule, command, label }) => {
682
609
  try {
@@ -698,9 +625,9 @@ ${error.stderr ?? ""}`
698
625
  `;
699
626
  const updated = existing.trimEnd() + "\n" + newEntry;
700
627
  const tmpFile = `/tmp/junis_crontab_${Date.now()}.txt`;
701
- await import_promises.default.writeFile(tmpFile, updated, "utf-8");
628
+ await fs2.writeFile(tmpFile, updated, "utf-8");
702
629
  await execAsync(`crontab ${tmpFile}`);
703
- await import_promises.default.unlink(tmpFile).catch(() => {
630
+ await fs2.unlink(tmpFile).catch(() => {
704
631
  });
705
632
  return {
706
633
  content: [{ type: "text", text: `\u2705 Cron job created:
@@ -767,8 +694,8 @@ ${error.stderr ?? ""}`
767
694
  "cron_delete",
768
695
  "Delete a scheduled cron job by its ID (from cron_list output) or by matching command string. Associated comment labels are automatically cleaned up.",
769
696
  {
770
- id: import_zod.z.number().optional().describe("Cron job ID from cron_list output (e.g. 1, 2, 3)"),
771
- command: import_zod.z.string().optional().describe("Delete all jobs matching this command string")
697
+ id: z.number().optional().describe("Cron job ID from cron_list output (e.g. 1, 2, 3)"),
698
+ command: z.string().optional().describe("Delete all jobs matching this command string")
772
699
  },
773
700
  async ({ id, command }) => {
774
701
  if (!id && !command) {
@@ -805,9 +732,9 @@ ${error.stderr ?? ""}`
805
732
  }
806
733
  const updated2 = filtered2.join("\n");
807
734
  const tmpFile2 = `/tmp/junis_crontab_${Date.now()}.txt`;
808
- await import_promises.default.writeFile(tmpFile2, updated2, "utf-8");
735
+ await fs2.writeFile(tmpFile2, updated2, "utf-8");
809
736
  await execAsync(`crontab ${tmpFile2}`);
810
- await import_promises.default.unlink(tmpFile2).catch(() => {
737
+ await fs2.unlink(tmpFile2).catch(() => {
811
738
  });
812
739
  return { content: [{ type: "text", text: `\u2705 Deleted cron job matching: ${command}` }] };
813
740
  }
@@ -832,9 +759,9 @@ ${error.stderr ?? ""}`
832
759
  const filtered = lines.filter((_, i) => i < target.lineStart || i > target.lineEnd);
833
760
  const updated = filtered.join("\n");
834
761
  const tmpFile = `/tmp/junis_crontab_${Date.now()}.txt`;
835
- await import_promises.default.writeFile(tmpFile, updated, "utf-8");
762
+ await fs2.writeFile(tmpFile, updated, "utf-8");
836
763
  await execAsync(`crontab ${tmpFile}`);
837
- await import_promises.default.unlink(tmpFile).catch(() => {
764
+ await fs2.unlink(tmpFile).catch(() => {
838
765
  });
839
766
  return { content: [{ type: "text", text: `\u2705 Deleted cron job #${id}` }] };
840
767
  } catch (err) {
@@ -849,9 +776,9 @@ ${error.stderr ?? ""}`
849
776
  };
850
777
 
851
778
  // src/tools/browser.ts
852
- var import_browserclaw = require("browserclaw");
853
- var import_promises2 = __toESM(require("fs/promises"));
854
- var import_zod2 = require("zod");
779
+ import { BrowserClaw } from "browserclaw";
780
+ import fs3 from "fs/promises";
781
+ import { z as z2 } from "zod";
855
782
  var BrowserTools = class {
856
783
  browser = null;
857
784
  page = null;
@@ -892,11 +819,11 @@ var BrowserTools = class {
892
819
  "Always call browser_stop when done to release system resources."
893
820
  ].join("\n"),
894
821
  {
895
- mode: import_zod2.z.enum(["managed", "remote-cdp"]).optional().default("managed").describe("'managed' = launch new browser, 'remote-cdp' = connect to existing Chrome via CDP"),
896
- headless: import_zod2.z.boolean().optional().default(false).describe("Run without visible window (managed mode only). Use for background tasks."),
897
- cdpUrl: import_zod2.z.string().optional().describe("Chrome DevTools Protocol URL for remote-cdp mode (e.g. http://localhost:9222)"),
898
- profile: import_zod2.z.string().optional().describe("Browser profile name for persistent sessions \u2014 preserves cookies, logins, and history across restarts (managed mode only)"),
899
- allowInternal: import_zod2.z.boolean().optional().default(false).describe("Allow navigation to localhost and internal network URLs")
822
+ mode: z2.enum(["managed", "remote-cdp"]).optional().default("managed").describe("'managed' = launch new browser, 'remote-cdp' = connect to existing Chrome via CDP"),
823
+ headless: z2.boolean().optional().default(false).describe("Run without visible window (managed mode only). Use for background tasks."),
824
+ cdpUrl: z2.string().optional().describe("Chrome DevTools Protocol URL for remote-cdp mode (e.g. http://localhost:9222)"),
825
+ profile: z2.string().optional().describe("Browser profile name for persistent sessions \u2014 preserves cookies, logins, and history across restarts (managed mode only)"),
826
+ allowInternal: z2.boolean().optional().default(false).describe("Allow navigation to localhost and internal network URLs")
900
827
  },
901
828
  ({ mode, headless, cdpUrl, profile, allowInternal }) => this.withLock(async () => {
902
829
  if (this.browser) {
@@ -904,9 +831,9 @@ var BrowserTools = class {
904
831
  }
905
832
  if (mode === "remote-cdp") {
906
833
  if (!cdpUrl) throw new Error("cdpUrl is required for remote-cdp mode");
907
- this.browser = await import_browserclaw.BrowserClaw.connect(cdpUrl, { allowInternal });
834
+ this.browser = await BrowserClaw.connect(cdpUrl, { allowInternal });
908
835
  } else {
909
- this.browser = await import_browserclaw.BrowserClaw.launch({
836
+ this.browser = await BrowserClaw.launch({
910
837
  headless,
911
838
  profileName: profile,
912
839
  allowInternal
@@ -928,7 +855,7 @@ var BrowserTools = class {
928
855
  "browser_navigate",
929
856
  "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.",
930
857
  {
931
- url: import_zod2.z.string().describe("Full URL to navigate to (include https://)")
858
+ url: z2.string().describe("Full URL to navigate to (include https://)")
932
859
  },
933
860
  ({ url }) => this.withLock(async () => {
934
861
  if (!this.browser) throw new Error("Browser not started. Call browser_start first.");
@@ -952,8 +879,8 @@ var BrowserTools = class {
952
879
  "Prefer this over browser_screenshot for understanding page structure \u2014 it's faster, structured, and machine-readable."
953
880
  ].join("\n"),
954
881
  {
955
- interactive: import_zod2.z.boolean().optional().default(true).describe("true (default): only show clickable/typeable elements. false: show all elements including static text."),
956
- compact: import_zod2.z.boolean().optional().default(true).describe("true (default): hide empty containers for cleaner output")
882
+ interactive: z2.boolean().optional().default(true).describe("true (default): only show clickable/typeable elements. false: show all elements including static text."),
883
+ compact: z2.boolean().optional().default(true).describe("true (default): hide empty containers for cleaner output")
957
884
  },
958
885
  ({ interactive, compact }) => this.withLock(async () => {
959
886
  const result = await requirePage().snapshot({ interactive, compact });
@@ -975,9 +902,9 @@ ${refList}`
975
902
  "browser_click",
976
903
  "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.",
977
904
  {
978
- ref: import_zod2.z.string().describe("Element ref from browser_snapshot (e.g. 'e1', 'e15'). Call browser_snapshot first to get current refs."),
979
- doubleClick: import_zod2.z.boolean().optional().default(false).describe("Double-click instead of single click"),
980
- button: import_zod2.z.enum(["left", "right", "middle"]).optional().default("left").describe("Mouse button to use")
905
+ ref: z2.string().describe("Element ref from browser_snapshot (e.g. 'e1', 'e15'). Call browser_snapshot first to get current refs."),
906
+ doubleClick: z2.boolean().optional().default(false).describe("Double-click instead of single click"),
907
+ button: z2.enum(["left", "right", "middle"]).optional().default("left").describe("Mouse button to use")
981
908
  },
982
909
  ({ ref, doubleClick, button }) => this.withLock(async () => {
983
910
  await requirePage().click(ref, { doubleClick, button });
@@ -988,10 +915,10 @@ ${refList}`
988
915
  "browser_type",
989
916
  "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.",
990
917
  {
991
- ref: import_zod2.z.string().describe("Element ref from browser_snapshot (e.g. 'e3')"),
992
- text: import_zod2.z.string().describe("Text to type into the element"),
993
- submit: import_zod2.z.boolean().optional().default(false).describe("Press Enter after typing (useful for search boxes and forms)"),
994
- slowly: import_zod2.z.boolean().optional().default(false).describe("Type slowly (75ms per char) for sites that process each keystroke")
918
+ ref: z2.string().describe("Element ref from browser_snapshot (e.g. 'e3')"),
919
+ text: z2.string().describe("Text to type into the element"),
920
+ submit: z2.boolean().optional().default(false).describe("Press Enter after typing (useful for search boxes and forms)"),
921
+ slowly: z2.boolean().optional().default(false).describe("Type slowly (75ms per char) for sites that process each keystroke")
995
922
  },
996
923
  ({ ref, text, submit, slowly }) => this.withLock(async () => {
997
924
  await requirePage().type(ref, text, { submit, slowly });
@@ -1002,10 +929,10 @@ ${refList}`
1002
929
  "browser_fill",
1003
930
  "Fill multiple form fields at once \u2014 more efficient than calling browser_type repeatedly. Each field needs a ref from browser_snapshot.",
1004
931
  {
1005
- fields: import_zod2.z.array(import_zod2.z.object({
1006
- ref: import_zod2.z.string(),
1007
- type: import_zod2.z.enum(["text", "checkbox", "radio"]),
1008
- value: import_zod2.z.union([import_zod2.z.string(), import_zod2.z.boolean()])
932
+ fields: z2.array(z2.object({
933
+ ref: z2.string(),
934
+ type: z2.enum(["text", "checkbox", "radio"]),
935
+ value: z2.union([z2.string(), z2.boolean()])
1009
936
  })).describe("Array of {ref, type, value}. type='text': value is string. type='checkbox'/'radio': value is boolean.")
1010
937
  },
1011
938
  ({ fields }) => this.withLock(async () => {
@@ -1017,8 +944,8 @@ ${refList}`
1017
944
  "browser_select",
1018
945
  "Select one or more options from a dropdown/select element. Values should match the option value attributes, not display text.",
1019
946
  {
1020
- ref: import_zod2.z.string().describe("Ref of the <select> element from browser_snapshot"),
1021
- values: import_zod2.z.array(import_zod2.z.string()).describe("Option value(s) to select")
947
+ ref: z2.string().describe("Ref of the <select> element from browser_snapshot"),
948
+ values: z2.array(z2.string()).describe("Option value(s) to select")
1022
949
  },
1023
950
  ({ ref, values }) => this.withLock(async () => {
1024
951
  await requirePage().select(ref, ...values);
@@ -1029,7 +956,7 @@ ${refList}`
1029
956
  "browser_press",
1030
957
  "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.",
1031
958
  {
1032
- key: import_zod2.z.string().describe("Key or combination: 'Enter', 'Escape', 'Tab', 'Control+a', 'Meta+c', 'ArrowDown', 'Backspace'")
959
+ key: z2.string().describe("Key or combination: 'Enter', 'Escape', 'Tab', 'Control+a', 'Meta+c', 'ArrowDown', 'Backspace'")
1033
960
  },
1034
961
  ({ key }) => this.withLock(async () => {
1035
962
  await requirePage().press(key);
@@ -1040,7 +967,7 @@ ${refList}`
1040
967
  "browser_hover",
1041
968
  "Move the mouse cursor over an element by ref. Use to trigger hover menus, tooltips, or dropdown previews before clicking.",
1042
969
  {
1043
- ref: import_zod2.z.string().describe("Element ref from browser_snapshot")
970
+ ref: z2.string().describe("Element ref from browser_snapshot")
1044
971
  },
1045
972
  ({ ref }) => this.withLock(async () => {
1046
973
  await requirePage().hover(ref);
@@ -1051,8 +978,8 @@ ${refList}`
1051
978
  "browser_drag",
1052
979
  "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.",
1053
980
  {
1054
- startRef: import_zod2.z.string().describe("Source element ref to drag from"),
1055
- endRef: import_zod2.z.string().describe("Target element ref to drag to")
981
+ startRef: z2.string().describe("Source element ref to drag from"),
982
+ endRef: z2.string().describe("Target element ref to drag to")
1056
983
  },
1057
984
  ({ startRef, endRef }) => this.withLock(async () => {
1058
985
  await requirePage().drag(startRef, endRef);
@@ -1063,8 +990,8 @@ ${refList}`
1063
990
  "browser_upload",
1064
991
  "Upload local files to a file input element (<input type='file'>). The ref must point to a file input from browser_snapshot.",
1065
992
  {
1066
- ref: import_zod2.z.string().describe("Ref of the file input element from browser_snapshot"),
1067
- paths: import_zod2.z.array(import_zod2.z.string()).describe("Absolute file path(s) on the local device to upload")
993
+ ref: z2.string().describe("Ref of the file input element from browser_snapshot"),
994
+ paths: z2.array(z2.string()).describe("Absolute file path(s) on the local device to upload")
1068
995
  },
1069
996
  ({ ref, paths }) => this.withLock(async () => {
1070
997
  await requirePage().uploadFile(ref, paths);
@@ -1080,14 +1007,14 @@ ${refList}`
1080
1007
  "Use browser_screenshot only when visual layout matters (charts, images, styling, visual verification)."
1081
1008
  ].join("\n"),
1082
1009
  {
1083
- path: import_zod2.z.string().optional().describe("Save path for the screenshot. If omitted, returns base64 image data directly."),
1084
- fullPage: import_zod2.z.boolean().optional().default(false).describe("Capture the full scrollable page, not just the visible viewport"),
1085
- ref: import_zod2.z.string().optional().describe("Capture only a specific element by its ref from browser_snapshot")
1010
+ path: z2.string().optional().describe("Save path for the screenshot. If omitted, returns base64 image data directly."),
1011
+ fullPage: z2.boolean().optional().default(false).describe("Capture the full scrollable page, not just the visible viewport"),
1012
+ ref: z2.string().optional().describe("Capture only a specific element by its ref from browser_snapshot")
1086
1013
  },
1087
1014
  ({ path: path4, fullPage, ref }) => this.withLock(async () => {
1088
1015
  const buffer = await requirePage().screenshot({ fullPage, ref });
1089
1016
  if (path4) {
1090
- await import_promises2.default.writeFile(path4, buffer);
1017
+ await fs3.writeFile(path4, buffer);
1091
1018
  return { content: [{ type: "text", text: `Screenshot saved: ${path4}` }] };
1092
1019
  }
1093
1020
  return {
@@ -1103,11 +1030,11 @@ ${refList}`
1103
1030
  "browser_pdf",
1104
1031
  "Save the current page as a PDF file. Renders the full page including below-the-fold content. Useful for archiving, sharing, or offline reading.",
1105
1032
  {
1106
- path: import_zod2.z.string().describe("Output file path (.pdf)")
1033
+ path: z2.string().describe("Output file path (.pdf)")
1107
1034
  },
1108
1035
  ({ path: path4 }) => this.withLock(async () => {
1109
1036
  const buffer = await requirePage().pdf();
1110
- await import_promises2.default.writeFile(path4, buffer);
1037
+ await fs3.writeFile(path4, buffer);
1111
1038
  return { content: [{ type: "text", text: `PDF saved: ${path4}` }] };
1112
1039
  })
1113
1040
  );
@@ -1120,7 +1047,7 @@ ${refList}`
1120
1047
  "Wrap complex logic in an IIFE: (function(){ ... })()"
1121
1048
  ].join("\n"),
1122
1049
  {
1123
- code: import_zod2.z.string().describe("JavaScript code to execute in the page context. Return values are automatically serialized.")
1050
+ code: z2.string().describe("JavaScript code to execute in the page context. Return values are automatically serialized.")
1124
1051
  },
1125
1052
  ({ code }) => this.withLock(async () => {
1126
1053
  try {
@@ -1147,11 +1074,11 @@ ${refList}`
1147
1074
  "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)."
1148
1075
  ].join("\n"),
1149
1076
  {
1150
- text: import_zod2.z.string().optional().describe("Wait until this text appears on the page"),
1151
- textGone: import_zod2.z.string().optional().describe("Wait until this text disappears from the page"),
1152
- url: import_zod2.z.string().optional().describe("Wait until URL matches this glob pattern (e.g. '**/dashboard', '**/success')"),
1153
- loadState: import_zod2.z.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Wait for page load state: 'load' (full), 'domcontentloaded' (DOM ready), 'networkidle' (no pending requests)"),
1154
- timeMs: import_zod2.z.number().optional().describe("Fixed wait in milliseconds \u2014 use as last resort when other conditions don't apply")
1077
+ text: z2.string().optional().describe("Wait until this text appears on the page"),
1078
+ textGone: z2.string().optional().describe("Wait until this text disappears from the page"),
1079
+ url: z2.string().optional().describe("Wait until URL matches this glob pattern (e.g. '**/dashboard', '**/success')"),
1080
+ loadState: z2.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Wait for page load state: 'load' (full), 'domcontentloaded' (DOM ready), 'networkidle' (no pending requests)"),
1081
+ timeMs: z2.number().optional().describe("Fixed wait in milliseconds \u2014 use as last resort when other conditions don't apply")
1155
1082
  },
1156
1083
  ({ text, textGone, url, loadState, timeMs }) => this.withLock(async () => {
1157
1084
  const condition = {};
@@ -1168,14 +1095,14 @@ ${refList}`
1168
1095
  "browser_cookies",
1169
1096
  "Manage browser cookies: get all cookies, set a specific cookie, or clear all cookies. Useful for authentication state, session management, or testing.",
1170
1097
  {
1171
- action: import_zod2.z.enum(["get", "set", "clear"]).describe("'get': retrieve all cookies, 'set': add/update a cookie, 'clear': remove all cookies"),
1172
- cookie: import_zod2.z.object({
1173
- name: import_zod2.z.string(),
1174
- value: import_zod2.z.string(),
1175
- domain: import_zod2.z.string().optional(),
1176
- path: import_zod2.z.string().optional(),
1177
- httpOnly: import_zod2.z.boolean().optional(),
1178
- secure: import_zod2.z.boolean().optional()
1098
+ action: z2.enum(["get", "set", "clear"]).describe("'get': retrieve all cookies, 'set': add/update a cookie, 'clear': remove all cookies"),
1099
+ cookie: z2.object({
1100
+ name: z2.string(),
1101
+ value: z2.string(),
1102
+ domain: z2.string().optional(),
1103
+ path: z2.string().optional(),
1104
+ httpOnly: z2.boolean().optional(),
1105
+ secure: z2.boolean().optional()
1179
1106
  }).optional().describe("Cookie data (required for 'set' action)")
1180
1107
  },
1181
1108
  ({ action, cookie }) => this.withLock(async () => {
@@ -1197,10 +1124,10 @@ ${refList}`
1197
1124
  "browser_storage",
1198
1125
  "Read, write, or clear browser localStorage/sessionStorage. Useful for managing client-side state, authentication tokens, or application preferences.",
1199
1126
  {
1200
- action: import_zod2.z.enum(["get", "set", "clear"]).describe("'get': read value(s), 'set': write a key-value pair, 'clear': remove all entries"),
1201
- kind: import_zod2.z.enum(["local", "session"]).optional().default("local").describe("'local' (persistent) or 'session' (cleared on tab close)"),
1202
- key: import_zod2.z.string().optional().describe("Storage key to get or set. Omit key with 'get' to retrieve all entries."),
1203
- value: import_zod2.z.string().optional().describe("Value to store (required for 'set' action)")
1127
+ action: z2.enum(["get", "set", "clear"]).describe("'get': read value(s), 'set': write a key-value pair, 'clear': remove all entries"),
1128
+ kind: z2.enum(["local", "session"]).optional().default("local").describe("'local' (persistent) or 'session' (cleared on tab close)"),
1129
+ key: z2.string().optional().describe("Storage key to get or set. Omit key with 'get' to retrieve all entries."),
1130
+ value: z2.string().optional().describe("Value to store (required for 'set' action)")
1204
1131
  },
1205
1132
  ({ action, kind, key, value }) => this.withLock(async () => {
1206
1133
  const page = requirePage();
@@ -1228,16 +1155,16 @@ ${refList}`
1228
1155
  "The 'accept' and 'promptText' params are only used with action='arm'."
1229
1156
  ].join("\n"),
1230
1157
  {
1231
- action: import_zod2.z.enum(["arm", "wait"]).describe(
1158
+ action: z2.enum(["arm", "wait"]).describe(
1232
1159
  "'arm' = register handler and return immediately; 'wait' = await the previously armed handler"
1233
1160
  ),
1234
- accept: import_zod2.z.boolean().optional().default(true).describe(
1161
+ accept: z2.boolean().optional().default(true).describe(
1235
1162
  "Accept (true) or dismiss (false) the dialog. Only used with action='arm'."
1236
1163
  ),
1237
- promptText: import_zod2.z.string().optional().describe(
1164
+ promptText: z2.string().optional().describe(
1238
1165
  "Text to enter if the dialog is a prompt. Only used with action='arm'."
1239
1166
  ),
1240
- timeoutMs: import_zod2.z.number().optional().describe(
1167
+ timeoutMs: z2.number().optional().describe(
1241
1168
  "Timeout in ms for 'wait' action (default: 30000). Increase for slow-loading dialogs."
1242
1169
  )
1243
1170
  },
@@ -1269,13 +1196,13 @@ ${refList}`
1269
1196
  };
1270
1197
 
1271
1198
  // src/tools/notebook.ts
1272
- var import_zod3 = require("zod");
1273
- var import_promises3 = __toESM(require("fs/promises"));
1274
- var import_child_process2 = require("child_process");
1275
- var import_util2 = require("util");
1276
- var execAsync2 = (0, import_util2.promisify)(import_child_process2.exec);
1199
+ import { z as z3 } from "zod";
1200
+ import fs4 from "fs/promises";
1201
+ import { exec as exec2 } from "child_process";
1202
+ import { promisify as promisify2 } from "util";
1203
+ var execAsync2 = promisify2(exec2);
1277
1204
  async function readNotebook(filePath) {
1278
- const raw = await import_promises3.default.readFile(filePath, "utf-8");
1205
+ const raw = await fs4.readFile(filePath, "utf-8");
1279
1206
  try {
1280
1207
  return JSON.parse(raw);
1281
1208
  } catch {
@@ -1283,14 +1210,14 @@ async function readNotebook(filePath) {
1283
1210
  }
1284
1211
  }
1285
1212
  async function writeNotebook(filePath, nb) {
1286
- await import_promises3.default.writeFile(filePath, JSON.stringify(nb, null, 1), "utf-8");
1213
+ await fs4.writeFile(filePath, JSON.stringify(nb, null, 1), "utf-8");
1287
1214
  }
1288
1215
  var NotebookTools = class {
1289
1216
  register(server) {
1290
1217
  server.tool(
1291
1218
  "notebook_read",
1292
1219
  "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.",
1293
- { path: import_zod3.z.string().describe("Path to the .ipynb notebook file") },
1220
+ { path: z3.string().describe("Path to the .ipynb notebook file") },
1294
1221
  async ({ path: filePath }) => {
1295
1222
  const nb = await readNotebook(filePath);
1296
1223
  const cells = nb.cells.map((cell, i) => ({
@@ -1308,9 +1235,9 @@ var NotebookTools = class {
1308
1235
  "notebook_edit_cell",
1309
1236
  "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.",
1310
1237
  {
1311
- path: import_zod3.z.string().describe("Path to the .ipynb notebook file"),
1312
- cell_index: import_zod3.z.number().describe("Cell index to edit (0-based). Use notebook_read to find the right index."),
1313
- source: import_zod3.z.string().describe("New source code/content for the cell (replaces entire cell content)")
1238
+ path: z3.string().describe("Path to the .ipynb notebook file"),
1239
+ cell_index: z3.number().describe("Cell index to edit (0-based). Use notebook_read to find the right index."),
1240
+ source: z3.string().describe("New source code/content for the cell (replaces entire cell content)")
1314
1241
  },
1315
1242
  async ({ path: filePath, cell_index, source }) => {
1316
1243
  const nb = await readNotebook(filePath);
@@ -1333,8 +1260,8 @@ var NotebookTools = class {
1333
1260
  "If execution fails on a cell, the error is captured in the cell output and subsequent cells may not execute."
1334
1261
  ].join("\n"),
1335
1262
  {
1336
- path: import_zod3.z.string().describe("Path to the .ipynb notebook file to execute"),
1337
- timeout: import_zod3.z.number().optional().default(300).describe("Maximum execution time per cell in seconds (default: 300). Increase for cells with heavy computation.")
1263
+ path: z3.string().describe("Path to the .ipynb notebook file to execute"),
1264
+ timeout: z3.number().optional().default(300).describe("Maximum execution time per cell in seconds (default: 300). Increase for cells with heavy computation.")
1338
1265
  },
1339
1266
  async ({ path: filePath, timeout }) => {
1340
1267
  const nbconvertArgs = `nbconvert --to notebook --execute --inplace "${filePath}" --ExecutePreprocessor.timeout=${timeout}`;
@@ -1365,10 +1292,10 @@ var NotebookTools = class {
1365
1292
  "notebook_add_cell",
1366
1293
  "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.",
1367
1294
  {
1368
- path: import_zod3.z.string().describe("Path to the .ipynb notebook file"),
1369
- cell_type: import_zod3.z.enum(["code", "markdown"]).describe("'code' for executable cells, 'markdown' for text/documentation cells"),
1370
- source: import_zod3.z.string().describe("Cell source content (Python code or Markdown text)"),
1371
- 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.")
1295
+ path: z3.string().describe("Path to the .ipynb notebook file"),
1296
+ cell_type: z3.enum(["code", "markdown"]).describe("'code' for executable cells, 'markdown' for text/documentation cells"),
1297
+ source: z3.string().describe("Cell source content (Python code or Markdown text)"),
1298
+ 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.")
1372
1299
  },
1373
1300
  async ({ path: filePath, cell_type: cellType, source, position }) => {
1374
1301
  const nb = await readNotebook(filePath);
@@ -1401,8 +1328,8 @@ var NotebookTools = class {
1401
1328
  "notebook_delete_cell",
1402
1329
  "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.",
1403
1330
  {
1404
- path: import_zod3.z.string().describe("Path to the .ipynb notebook file"),
1405
- cell_index: import_zod3.z.number().describe("Cell index to delete (0-based). Use notebook_read first to verify content.")
1331
+ path: z3.string().describe("Path to the .ipynb notebook file"),
1332
+ cell_index: z3.number().describe("Cell index to delete (0-based). Use notebook_read first to verify content.")
1406
1333
  },
1407
1334
  async ({ path: filePath, cell_index }) => {
1408
1335
  const nb = await readNotebook(filePath);
@@ -1418,11 +1345,11 @@ var NotebookTools = class {
1418
1345
  };
1419
1346
 
1420
1347
  // src/tools/device.ts
1421
- var import_child_process3 = require("child_process");
1422
- var import_util3 = require("util");
1423
- var import_zod4 = require("zod");
1424
- var import_node_notifier = __toESM(require("node-notifier"));
1425
- var execAsync3 = (0, import_util3.promisify)(import_child_process3.exec);
1348
+ import { exec as exec3 } from "child_process";
1349
+ import { promisify as promisify3 } from "util";
1350
+ import { z as z4 } from "zod";
1351
+ import notifier from "node-notifier";
1352
+ var execAsync3 = promisify3(exec3);
1426
1353
  var screenRecordPid = null;
1427
1354
  function platform() {
1428
1355
  if (process.platform === "darwin") return "mac";
@@ -1440,7 +1367,7 @@ var DeviceTools = class {
1440
1367
  "Requires a connected camera with OS permissions granted. If output_path is provided, the file is also saved to disk."
1441
1368
  ].join("\n"),
1442
1369
  {
1443
- 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).")
1370
+ output_path: z4.string().optional().describe("File path to save the captured photo. If omitted, returns image data only (temp file auto-cleaned).")
1444
1371
  },
1445
1372
  async ({ output_path }) => {
1446
1373
  const p = platform();
@@ -1478,13 +1405,13 @@ Please check if a camera is connected.` }],
1478
1405
  "notification_send",
1479
1406
  "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.",
1480
1407
  {
1481
- title: import_zod4.z.string().describe("Notification title (displayed prominently)"),
1482
- message: import_zod4.z.string().describe("Notification body text")
1408
+ title: z4.string().describe("Notification title (displayed prominently)"),
1409
+ message: z4.string().describe("Notification body text")
1483
1410
  },
1484
1411
  async ({ title, message }) => {
1485
1412
  try {
1486
1413
  await new Promise((resolve, reject) => {
1487
- import_node_notifier.default.notify(
1414
+ notifier.notify(
1488
1415
  { title, message },
1489
1416
  (err) => {
1490
1417
  if (err) reject(err);
@@ -1516,7 +1443,7 @@ Please check if a camera is connected.` }],
1516
1443
  "clipboard_write",
1517
1444
  "Write text to the system clipboard, replacing its current contents. Use to prepare content for the user to paste elsewhere.",
1518
1445
  {
1519
- text: import_zod4.z.string().describe("Text to copy to the clipboard")
1446
+ text: z4.string().describe("Text to copy to the clipboard")
1520
1447
  },
1521
1448
  async ({ text }) => {
1522
1449
  const p = platform();
@@ -1538,8 +1465,8 @@ Please check if a camera is connected.` }],
1538
1465
  "Platform-specific: macOS (screencapture -v), Windows/Linux (ffmpeg)."
1539
1466
  ].join("\n"),
1540
1467
  {
1541
- action: import_zod4.z.enum(["start", "stop"]).describe("'start': begin recording, 'stop': end recording and save the file"),
1542
- output_path: import_zod4.z.string().optional().describe("Output file path (used with 'start'). Default: /tmp/junis_record_<timestamp>.mp4")
1468
+ action: z4.enum(["start", "stop"]).describe("'start': begin recording, 'stop': end recording and save the file"),
1469
+ output_path: z4.string().optional().describe("Output file path (used with 'start'). Default: /tmp/junis_record_<timestamp>.mp4")
1543
1470
  },
1544
1471
  async ({ action, output_path }) => {
1545
1472
  const p = platform();
@@ -1604,7 +1531,7 @@ Please check if a camera is connected.` }],
1604
1531
  "audio_play",
1605
1532
  "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).",
1606
1533
  {
1607
- file_path: import_zod4.z.string().describe("Absolute path to the audio file to play")
1534
+ file_path: z4.string().describe("Absolute path to the audio file to play")
1608
1535
  },
1609
1536
  async ({ file_path }) => {
1610
1537
  const p = platform();
@@ -1621,12 +1548,12 @@ Please check if a camera is connected.` }],
1621
1548
  };
1622
1549
 
1623
1550
  // src/setup/peekaboo-installer.ts
1624
- var import_child_process4 = require("child_process");
1625
- var import_util4 = require("util");
1626
- var import_os2 = require("os");
1627
- var execFileAsync2 = (0, import_util4.promisify)(import_child_process4.execFile);
1551
+ import { execFile as execFile2 } from "child_process";
1552
+ import { promisify as promisify4 } from "util";
1553
+ import { platform as platform2 } from "os";
1554
+ var execFileAsync2 = promisify4(execFile2);
1628
1555
  async function ensurePeekaboo() {
1629
- if ((0, import_os2.platform)() !== "darwin") return false;
1556
+ if (platform2() !== "darwin") return false;
1630
1557
  try {
1631
1558
  await execFileAsync2("which", ["peekaboo"]);
1632
1559
  return true;
@@ -1646,9 +1573,9 @@ async function ensurePeekaboo() {
1646
1573
  }
1647
1574
 
1648
1575
  // src/tools/desktop.ts
1649
- var import_execa = require("execa");
1650
- var import_zod5 = require("zod");
1651
- var import_fs2 = __toESM(require("fs"));
1576
+ import { execa } from "execa";
1577
+ import { z as z5 } from "zod";
1578
+ import fs5 from "fs";
1652
1579
  var APP_BLACKLIST = /* @__PURE__ */ new Set([
1653
1580
  "Terminal",
1654
1581
  "iTerm2",
@@ -1661,7 +1588,7 @@ var MAX_CONSECUTIVE_FAILURES = 2;
1661
1588
  async function peekaboo(args) {
1662
1589
  consecutiveFailures = 0;
1663
1590
  try {
1664
- const { stdout } = await (0, import_execa.execa)("peekaboo", [...args, "--json-output"]);
1591
+ const { stdout } = await execa("peekaboo", [...args, "--json-output"]);
1665
1592
  consecutiveFailures = 0;
1666
1593
  return JSON.parse(stdout);
1667
1594
  } catch (err) {
@@ -1691,7 +1618,7 @@ var DesktopTools = class {
1691
1618
  "SAFETY: Terminal, iTerm, and Finder are blocked. Two consecutive failures trigger an automatic safety stop."
1692
1619
  ].join("\n"),
1693
1620
  {
1694
- app: import_zod5.z.string().optional().describe("App name to target (e.g. 'Safari', 'Notes', 'Google Chrome'). Omit for the frontmost app.")
1621
+ app: z5.string().optional().describe("App name to target (e.g. 'Safari', 'Notes', 'Google Chrome'). Omit for the frontmost app.")
1695
1622
  },
1696
1623
  async ({ app }) => {
1697
1624
  checkBlacklist(app);
@@ -1725,10 +1652,10 @@ var DesktopTools = class {
1725
1652
  "SAFETY: Terminal, iTerm, and Finder are blocked. Two consecutive failures trigger automatic safety stop."
1726
1653
  ].join("\n"),
1727
1654
  {
1728
- on: import_zod5.z.string().describe("Element label, accessibility ID, or 'x,y' coordinates to click"),
1729
- app: import_zod5.z.string().optional().describe("App name to target (e.g. 'Safari')"),
1730
- snapshot: import_zod5.z.string().optional().describe("snapshotId from desktop_see for cached interaction (240x faster)"),
1731
- doubleClick: import_zod5.z.boolean().optional().default(false).describe("Double-click instead of single click")
1655
+ on: z5.string().describe("Element label, accessibility ID, or 'x,y' coordinates to click"),
1656
+ app: z5.string().optional().describe("App name to target (e.g. 'Safari')"),
1657
+ snapshot: z5.string().optional().describe("snapshotId from desktop_see for cached interaction (240x faster)"),
1658
+ doubleClick: z5.boolean().optional().default(false).describe("Double-click instead of single click")
1732
1659
  },
1733
1660
  async ({ on, app, snapshot, doubleClick }) => {
1734
1661
  checkBlacklist(app);
@@ -1750,8 +1677,8 @@ var DesktopTools = class {
1750
1677
  "SAFETY: Terminal, iTerm, and Finder are blocked. Use desktop_see first to verify the correct element is focused."
1751
1678
  ].join("\n"),
1752
1679
  {
1753
- text: import_zod5.z.string().describe("Text to type into the focused element"),
1754
- app: import_zod5.z.string().optional().describe("App name to focus before typing")
1680
+ text: z5.string().describe("Text to type into the focused element"),
1681
+ app: z5.string().optional().describe("App name to focus before typing")
1755
1682
  },
1756
1683
  async ({ text, app }) => {
1757
1684
  checkBlacklist(app);
@@ -1773,8 +1700,8 @@ var DesktopTools = class {
1773
1700
  "SAFETY: Terminal, iTerm, and Finder are blocked."
1774
1701
  ].join("\n"),
1775
1702
  {
1776
- keys: import_zod5.z.string().describe("Comma-separated key combination (e.g. 'cmd,c', 'cmd,shift,t', 'escape', 'cmd,option,i')"),
1777
- app: import_zod5.z.string().optional().describe("App name to target")
1703
+ keys: z5.string().describe("Comma-separated key combination (e.g. 'cmd,c', 'cmd,shift,t', 'escape', 'cmd,option,i')"),
1704
+ app: z5.string().optional().describe("App name to target")
1778
1705
  },
1779
1706
  async ({ keys, app }) => {
1780
1707
  checkBlacklist(app);
@@ -1790,10 +1717,10 @@ var DesktopTools = class {
1790
1717
  "desktop_scroll",
1791
1718
  "Scroll within a macOS application or specific UI element. Use 'ticks' to control scroll distance (default: 3). Can target a specific element by label or ID with the 'on' parameter.",
1792
1719
  {
1793
- direction: import_zod5.z.enum(["up", "down", "left", "right"]).describe("Scroll direction"),
1794
- ticks: import_zod5.z.number().optional().default(3).describe("Number of scroll ticks (default: 3). Higher = more scrolling."),
1795
- on: import_zod5.z.string().optional().describe("Element label or ID to scroll within (from desktop_see). Omit to scroll the active area."),
1796
- app: import_zod5.z.string().optional().describe("App name to target")
1720
+ direction: z5.enum(["up", "down", "left", "right"]).describe("Scroll direction"),
1721
+ ticks: z5.number().optional().default(3).describe("Number of scroll ticks (default: 3). Higher = more scrolling."),
1722
+ on: z5.string().optional().describe("Element label or ID to scroll within (from desktop_see). Omit to scroll the active area."),
1723
+ app: z5.string().optional().describe("App name to target")
1797
1724
  },
1798
1725
  async ({ direction, ticks, on, app }) => {
1799
1726
  checkBlacklist(app);
@@ -1812,7 +1739,7 @@ var DesktopTools = class {
1812
1739
  {},
1813
1740
  async () => {
1814
1741
  try {
1815
- const { stdout } = await (0, import_execa.execa)("peekaboo", ["list", "apps", "--json"]);
1742
+ const { stdout } = await execa("peekaboo", ["list", "apps", "--json"]);
1816
1743
  return {
1817
1744
  content: [{ type: "text", text: stdout }]
1818
1745
  };
@@ -1826,21 +1753,21 @@ var DesktopTools = class {
1826
1753
  "desktop_list_windows",
1827
1754
  "List all open windows on macOS, optionally filtered by app name. If no app is specified, lists windows for the frontmost application. Useful for identifying which windows are available for automation.",
1828
1755
  {
1829
- app: import_zod5.z.string().optional().describe("Filter by app name. Omit to query the frontmost app.")
1756
+ app: z5.string().optional().describe("Filter by app name. Omit to query the frontmost app.")
1830
1757
  },
1831
1758
  async ({ app }) => {
1832
1759
  checkBlacklist(app);
1833
1760
  try {
1834
1761
  let targetApp = app;
1835
1762
  if (!targetApp) {
1836
- const { stdout: stdout2 } = await (0, import_execa.execa)("osascript", [
1763
+ const { stdout: stdout2 } = await execa("osascript", [
1837
1764
  "-e",
1838
1765
  'tell application "System Events" to get name of first application process whose frontmost is true'
1839
1766
  ]);
1840
1767
  targetApp = stdout2.trim();
1841
1768
  }
1842
1769
  const args = ["list", "windows", "--app", targetApp, "--json"];
1843
- const { stdout } = await (0, import_execa.execa)("peekaboo", args);
1770
+ const { stdout } = await execa("peekaboo", args);
1844
1771
  return {
1845
1772
  content: [{ type: "text", text: stdout }]
1846
1773
  };
@@ -1859,8 +1786,8 @@ var DesktopTools = class {
1859
1786
  "Prefer desktop_see (Accessibility Tree) for understanding UI structure \u2014 use screenshot only when visual appearance matters (layouts, images, colors)."
1860
1787
  ].join("\n"),
1861
1788
  {
1862
- app: import_zod5.z.string().optional().describe("Capture a specific app's window (by name)"),
1863
- mode: import_zod5.z.enum(["screen", "window"]).optional().default("screen").describe("'screen': full display capture, 'window': specific app window only")
1789
+ app: z5.string().optional().describe("Capture a specific app's window (by name)"),
1790
+ mode: z5.enum(["screen", "window"]).optional().default("screen").describe("'screen': full display capture, 'window': specific app window only")
1864
1791
  },
1865
1792
  async ({ app, mode }) => {
1866
1793
  checkBlacklist(app);
@@ -1871,7 +1798,7 @@ var DesktopTools = class {
1871
1798
  const files = data?.files;
1872
1799
  const filePath = files?.[0]?.path;
1873
1800
  if (filePath) {
1874
- const imageBuffer = import_fs2.default.readFileSync(filePath);
1801
+ const imageBuffer = fs5.readFileSync(filePath);
1875
1802
  return {
1876
1803
  content: [{
1877
1804
  type: "image",
@@ -1894,15 +1821,15 @@ var DesktopTools = class {
1894
1821
  "The target app must be running and accessible."
1895
1822
  ].join("\n"),
1896
1823
  {
1897
- path: import_zod5.z.array(import_zod5.z.string()).describe("Menu path as array (e.g. ['File', 'Save'], ['Edit', 'Find', 'Find...'])"),
1898
- app: import_zod5.z.string().optional().describe("App name to target. Omit for the frontmost app.")
1824
+ path: z5.array(z5.string()).describe("Menu path as array (e.g. ['File', 'Save'], ['Edit', 'Find', 'Find...'])"),
1825
+ app: z5.string().optional().describe("App name to target. Omit for the frontmost app.")
1899
1826
  },
1900
1827
  async ({ path: path4, app }) => {
1901
1828
  checkBlacklist(app);
1902
1829
  const args = ["menu", "click", "--path", path4.join(" > ")];
1903
1830
  if (app) args.push("--app", app);
1904
1831
  try {
1905
- const { stdout } = await (0, import_execa.execa)("peekaboo", args);
1832
+ const { stdout } = await execa("peekaboo", args);
1906
1833
  return {
1907
1834
  content: [{ type: "text", text: stdout || "Menu click executed" }]
1908
1835
  };
@@ -1920,7 +1847,7 @@ var mcpPort = 3e3;
1920
1847
  var globalBrowserTools = null;
1921
1848
  var desktopToolsEnabled = false;
1922
1849
  function createMcpServer() {
1923
- const server = new import_mcp.McpServer({
1850
+ const server = new McpServer({
1924
1851
  name: "junis",
1925
1852
  version: "0.1.0"
1926
1853
  });
@@ -2050,7 +1977,7 @@ async function startMCPServer(port) {
2050
1977
  console.log("\u2705 Peekaboo available \u2014 desktop tools enabled");
2051
1978
  }
2052
1979
  let resolvedPort = port;
2053
- const httpServer = (0, import_http.createServer)(
1980
+ const httpServer = createServer(
2054
1981
  async (req, res) => {
2055
1982
  try {
2056
1983
  if (req.method === "OPTIONS") {
@@ -2064,7 +1991,7 @@ async function startMCPServer(port) {
2064
1991
  if (url === "/mcp") {
2065
1992
  if (req.method === "POST") {
2066
1993
  const mcpServer = createMcpServer();
2067
- const transport = new import_streamableHttp.StreamableHTTPServerTransport({
1994
+ const transport = new StreamableHTTPServerTransport({
2068
1995
  sessionIdGenerator: void 0
2069
1996
  // stateless
2070
1997
  });
@@ -2215,10 +2142,11 @@ async function handleMCPRequest(id, payload) {
2215
2142
  }
2216
2143
 
2217
2144
  // src/server/stdio.ts
2218
- var import_mcp2 = require("@modelcontextprotocol/sdk/server/mcp.js");
2219
- var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
2145
+ import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
2146
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2147
+ import { fileURLToPath } from "url";
2220
2148
  async function startStdioServer() {
2221
- const server = new import_mcp2.McpServer({ name: "junis", version: "0.1.0" });
2149
+ const server = new McpServer2({ name: "junis", version: "0.1.0" });
2222
2150
  const fsTools = new FilesystemTools();
2223
2151
  fsTools.register(server);
2224
2152
  const browserTools = new BrowserTools();
@@ -2228,60 +2156,64 @@ async function startStdioServer() {
2228
2156
  notebookTools.register(server);
2229
2157
  const deviceTools = new DeviceTools();
2230
2158
  deviceTools.register(server);
2231
- const transport = new import_stdio.StdioServerTransport();
2159
+ const transport = new StdioServerTransport();
2232
2160
  await server.connect(transport);
2233
2161
  process.on("SIGINT", async () => {
2234
2162
  await browserTools.cleanup();
2235
2163
  process.exit(0);
2236
2164
  });
2237
2165
  }
2238
- if (require.main === module) {
2166
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
2239
2167
  startStdioServer().catch(console.error);
2240
2168
  }
2241
2169
 
2170
+ // src/cli/index.ts
2171
+ import { execSync as execSync2 } from "child_process";
2172
+ import { createRequire } from "module";
2173
+
2242
2174
  // src/cli/daemon.ts
2243
- var import_fs3 = __toESM(require("fs"));
2244
- var import_path3 = __toESM(require("path"));
2245
- var import_os3 = __toESM(require("os"));
2246
- var import_child_process5 = require("child_process");
2247
- var CONFIG_DIR2 = import_path3.default.join(import_os3.default.homedir(), ".junis");
2248
- var PID_FILE = import_path3.default.join(CONFIG_DIR2, "junis.pid");
2249
- var LOG_DIR = import_path3.default.join(CONFIG_DIR2, "logs");
2250
- var LOG_FILE = import_path3.default.join(LOG_DIR, "junis.log");
2251
- var PLIST_PATH = import_path3.default.join(
2252
- import_os3.default.homedir(),
2175
+ import fs6 from "fs";
2176
+ import path3 from "path";
2177
+ import os2 from "os";
2178
+ import { execSync, spawn } from "child_process";
2179
+ var CONFIG_DIR2 = path3.join(os2.homedir(), ".junis");
2180
+ var PID_FILE = path3.join(CONFIG_DIR2, "junis.pid");
2181
+ var LOG_DIR = path3.join(CONFIG_DIR2, "logs");
2182
+ var LOG_FILE = path3.join(LOG_DIR, "junis.log");
2183
+ var PLIST_PATH = path3.join(
2184
+ os2.homedir(),
2253
2185
  "Library/LaunchAgents/ai.junis.plist"
2254
2186
  );
2255
- var SYSTEMD_PATH = import_path3.default.join(
2256
- import_os3.default.homedir(),
2187
+ var SYSTEMD_PATH = path3.join(
2188
+ os2.homedir(),
2257
2189
  ".config/systemd/user/junis.service"
2258
2190
  );
2259
2191
  function isRunning() {
2260
2192
  try {
2261
- if (!import_fs3.default.existsSync(PID_FILE)) return { running: false };
2262
- const pid = parseInt(import_fs3.default.readFileSync(PID_FILE, "utf-8").trim(), 10);
2193
+ if (!fs6.existsSync(PID_FILE)) return { running: false };
2194
+ const pid = parseInt(fs6.readFileSync(PID_FILE, "utf-8").trim(), 10);
2263
2195
  if (isNaN(pid)) return { running: false };
2264
2196
  process.kill(pid, 0);
2265
2197
  return { running: true, pid };
2266
2198
  } catch {
2267
2199
  try {
2268
- import_fs3.default.unlinkSync(PID_FILE);
2200
+ fs6.unlinkSync(PID_FILE);
2269
2201
  } catch {
2270
2202
  }
2271
2203
  return { running: false };
2272
2204
  }
2273
2205
  }
2274
2206
  function writePid(pid) {
2275
- import_fs3.default.mkdirSync(CONFIG_DIR2, { recursive: true });
2276
- import_fs3.default.writeFileSync(PID_FILE, String(pid), "utf-8");
2207
+ fs6.mkdirSync(CONFIG_DIR2, { recursive: true });
2208
+ fs6.writeFileSync(PID_FILE, String(pid), "utf-8");
2277
2209
  }
2278
2210
  function startDaemon(port) {
2279
- import_fs3.default.mkdirSync(LOG_DIR, { recursive: true });
2211
+ fs6.mkdirSync(LOG_DIR, { recursive: true });
2280
2212
  const nodePath = process.execPath;
2281
2213
  const scriptPath = process.argv[1];
2282
- const out = import_fs3.default.openSync(LOG_FILE, "a");
2283
- const err = import_fs3.default.openSync(LOG_FILE, "a");
2284
- const child = (0, import_child_process5.spawn)(nodePath, [scriptPath, "start", "--daemon", "--port", String(port)], {
2214
+ const out = fs6.openSync(LOG_FILE, "a");
2215
+ const err = fs6.openSync(LOG_FILE, "a");
2216
+ const child = spawn(nodePath, [scriptPath, "start", "--daemon", "--port", String(port)], {
2285
2217
  detached: true,
2286
2218
  stdio: ["ignore", out, err],
2287
2219
  env: { ...process.env }
@@ -2297,7 +2229,7 @@ function stopDaemon() {
2297
2229
  try {
2298
2230
  process.kill(pid, "SIGTERM");
2299
2231
  try {
2300
- import_fs3.default.unlinkSync(PID_FILE);
2232
+ fs6.unlinkSync(PID_FILE);
2301
2233
  } catch {
2302
2234
  }
2303
2235
  return true;
@@ -2331,7 +2263,7 @@ var ServiceManager = class {
2331
2263
  <key>EnvironmentVariables</key>
2332
2264
  <dict>
2333
2265
  <key>HOME</key>
2334
- <string>${import_os3.default.homedir()}</string>
2266
+ <string>${os2.homedir()}</string>
2335
2267
  <key>PATH</key>
2336
2268
  <string>${process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin"}</string>
2337
2269
  ${process.env.JUNIS_API_URL ? `<key>JUNIS_API_URL</key>
@@ -2351,12 +2283,12 @@ var ServiceManager = class {
2351
2283
  <string>${LOG_FILE}</string>
2352
2284
  </dict>
2353
2285
  </plist>`;
2354
- import_fs3.default.mkdirSync(import_path3.default.dirname(PLIST_PATH), { recursive: true });
2355
- import_fs3.default.mkdirSync(LOG_DIR, { recursive: true });
2356
- import_fs3.default.writeFileSync(PLIST_PATH, plist, "utf-8");
2286
+ fs6.mkdirSync(path3.dirname(PLIST_PATH), { recursive: true });
2287
+ fs6.mkdirSync(LOG_DIR, { recursive: true });
2288
+ fs6.writeFileSync(PLIST_PATH, plist, "utf-8");
2357
2289
  try {
2358
- (0, import_child_process5.execSync)(`launchctl unload "${PLIST_PATH}" 2>/dev/null || true`);
2359
- (0, import_child_process5.execSync)(`launchctl load "${PLIST_PATH}"`);
2290
+ execSync(`launchctl unload "${PLIST_PATH}" 2>/dev/null || true`);
2291
+ execSync(`launchctl load "${PLIST_PATH}"`);
2360
2292
  } catch (e) {
2361
2293
  throw new Error(`launchctl load failed: ${e.message}`);
2362
2294
  }
@@ -2369,7 +2301,7 @@ After=network.target
2369
2301
  ExecStart=${nodePath} ${scriptPath} start --daemon
2370
2302
  Restart=always
2371
2303
  RestartSec=5
2372
- Environment=HOME=${import_os3.default.homedir()}
2304
+ Environment=HOME=${os2.homedir()}
2373
2305
  Environment=PATH=${process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin"}
2374
2306
  ${process.env.JUNIS_API_URL ? `Environment=JUNIS_API_URL=${process.env.JUNIS_API_URL}` : ""}
2375
2307
  ${process.env.JUNIS_WS_URL ? `Environment=JUNIS_WS_URL=${process.env.JUNIS_WS_URL}` : ""}
@@ -2379,14 +2311,14 @@ StandardError=append:${LOG_FILE}
2379
2311
 
2380
2312
  [Install]
2381
2313
  WantedBy=default.target`;
2382
- import_fs3.default.mkdirSync(import_path3.default.dirname(SYSTEMD_PATH), { recursive: true });
2383
- import_fs3.default.mkdirSync(LOG_DIR, { recursive: true });
2384
- import_fs3.default.writeFileSync(SYSTEMD_PATH, unit, "utf-8");
2385
- (0, import_child_process5.execSync)("systemctl --user daemon-reload");
2386
- (0, import_child_process5.execSync)("systemctl --user enable junis");
2387
- (0, import_child_process5.execSync)("systemctl --user start junis");
2314
+ fs6.mkdirSync(path3.dirname(SYSTEMD_PATH), { recursive: true });
2315
+ fs6.mkdirSync(LOG_DIR, { recursive: true });
2316
+ fs6.writeFileSync(SYSTEMD_PATH, unit, "utf-8");
2317
+ execSync("systemctl --user daemon-reload");
2318
+ execSync("systemctl --user enable junis");
2319
+ execSync("systemctl --user start junis");
2388
2320
  } else {
2389
- (0, import_child_process5.execSync)(
2321
+ execSync(
2390
2322
  `schtasks /Create /F /TN "Junis" /TR "${nodePath} ${scriptPath} start --daemon" /SC ONLOGON /RL HIGHEST`
2391
2323
  );
2392
2324
  }
@@ -2394,21 +2326,21 @@ WantedBy=default.target`;
2394
2326
  async uninstall() {
2395
2327
  if (this.platform === "mac") {
2396
2328
  try {
2397
- (0, import_child_process5.execSync)(`launchctl unload "${PLIST_PATH}" 2>/dev/null || true`);
2398
- if (import_fs3.default.existsSync(PLIST_PATH)) import_fs3.default.unlinkSync(PLIST_PATH);
2329
+ execSync(`launchctl unload "${PLIST_PATH}" 2>/dev/null || true`);
2330
+ if (fs6.existsSync(PLIST_PATH)) fs6.unlinkSync(PLIST_PATH);
2399
2331
  } catch {
2400
2332
  }
2401
2333
  } else if (this.platform === "linux") {
2402
2334
  try {
2403
- (0, import_child_process5.execSync)("systemctl --user stop junis 2>/dev/null || true");
2404
- (0, import_child_process5.execSync)("systemctl --user disable junis 2>/dev/null || true");
2405
- if (import_fs3.default.existsSync(SYSTEMD_PATH)) import_fs3.default.unlinkSync(SYSTEMD_PATH);
2406
- (0, import_child_process5.execSync)("systemctl --user daemon-reload 2>/dev/null || true");
2335
+ execSync("systemctl --user stop junis 2>/dev/null || true");
2336
+ execSync("systemctl --user disable junis 2>/dev/null || true");
2337
+ if (fs6.existsSync(SYSTEMD_PATH)) fs6.unlinkSync(SYSTEMD_PATH);
2338
+ execSync("systemctl --user daemon-reload 2>/dev/null || true");
2407
2339
  } catch {
2408
2340
  }
2409
2341
  } else {
2410
2342
  try {
2411
- (0, import_child_process5.execSync)('schtasks /Delete /F /TN "Junis" 2>nul || true');
2343
+ execSync('schtasks /Delete /F /TN "Junis" 2>nul || true');
2412
2344
  } catch {
2413
2345
  }
2414
2346
  }
@@ -2416,13 +2348,13 @@ WantedBy=default.target`;
2416
2348
  };
2417
2349
 
2418
2350
  // src/cli/index.ts
2419
- var { version } = require_package();
2420
- import_commander.program.name("junis").description("MCP server for full device control by AI").version(version);
2351
+ var _require = createRequire(import.meta.url);
2352
+ var { version } = _require("../../package.json");
2353
+ program.name("junis").description("MCP server for full device control by AI").version(version);
2421
2354
  function getSystemInfo() {
2422
2355
  const platform3 = process.platform;
2423
2356
  if (platform3 === "darwin") {
2424
2357
  try {
2425
- const { execSync: execSync2 } = require("child_process");
2426
2358
  const sw = execSync2("sw_vers -productVersion", { encoding: "utf8" }).trim();
2427
2359
  const hw = execSync2("sysctl -n machdep.cpu.brand_string", { encoding: "utf8" }).trim();
2428
2360
  return `macOS ${sw} (${hw})`;
@@ -2553,7 +2485,7 @@ async function runBackground(config, port) {
2553
2485
  console.log("");
2554
2486
  process.exit(0);
2555
2487
  }
2556
- import_commander.program.command("start", { isDefault: true }).description("Start Junis agent connection").option("--local", "Run local MCP server only (no cloud connection)").option("--port <number>", "Port number", "3000").option("--reset", "Clear existing credentials and re-login").option("--daemon", "Run in daemon mode (internal, used by launchd/systemd)").option("--foreground", "Run in foreground mode (no prompt)").option("--stdio", "Run as stdio transport (for MCP client integration)").action(async (options) => {
2488
+ program.command("start", { isDefault: true }).description("Start Junis agent connection").option("--local", "Run local MCP server only (no cloud connection)").option("--port <number>", "Port number", "3000").option("--reset", "Clear existing credentials and re-login").option("--daemon", "Run in daemon mode (internal, used by launchd/systemd)").option("--foreground", "Run in foreground mode (no prompt)").option("--stdio", "Run as stdio transport (for MCP client integration)").action(async (options) => {
2557
2489
  const port = parseInt(options.port, 10);
2558
2490
  if (options.stdio) {
2559
2491
  await startStdioServer();
@@ -2693,7 +2625,7 @@ import_commander.program.command("start", { isDefault: true }).description("Star
2693
2625
  console.log(" To stop: npx junis stop");
2694
2626
  return;
2695
2627
  }
2696
- const mode = await (0, import_prompts.select)({
2628
+ const mode = await select({
2697
2629
  message: "Select run mode:",
2698
2630
  choices: [
2699
2631
  {
@@ -2782,7 +2714,7 @@ import_commander.program.command("start", { isDefault: true }).description("Star
2782
2714
  await runBackground(config, port);
2783
2715
  }
2784
2716
  });
2785
- import_commander.program.command("stop").description("Stop background service and disable auto-start").action(async () => {
2717
+ program.command("stop").description("Stop background service and disable auto-start").action(async () => {
2786
2718
  const stopped = stopDaemon();
2787
2719
  const svc = new ServiceManager();
2788
2720
  let serviceUninstalled = false;
@@ -2798,11 +2730,11 @@ import_commander.program.command("stop").description("Stop background service an
2798
2730
  console.log("\u2139\uFE0F No running Junis process found.");
2799
2731
  }
2800
2732
  });
2801
- import_commander.program.command("logout").description("Clear authentication credentials").action(() => {
2733
+ program.command("logout").description("Clear authentication credentials").action(() => {
2802
2734
  clearConfig();
2803
2735
  console.log("\u2705 Authentication credentials cleared");
2804
2736
  });
2805
- import_commander.program.command("status").description("Check current status").action(() => {
2737
+ program.command("status").description("Check current status").action(() => {
2806
2738
  const config = loadConfig();
2807
2739
  const { running, pid } = isRunning();
2808
2740
  if (!config) {
@@ -2817,7 +2749,7 @@ import_commander.program.command("status").description("Check current status").a
2817
2749
  console.log(" To start: npx junis");
2818
2750
  }
2819
2751
  });
2820
- import_commander.program.addHelpText("after", `
2752
+ program.addHelpText("after", `
2821
2753
  Examples:
2822
2754
  npx junis Interactive mode (foreground/background)
2823
2755
  npx junis --local Local MCP server only (no cloud)
@@ -2835,4 +2767,4 @@ MCP Client Config (Claude Code, Claude Desktop, Codex, etc.):
2835
2767
  }
2836
2768
  }
2837
2769
  `);
2838
- import_commander.program.parse();
2770
+ program.parse();