junis 0.3.10 → 0.3.11

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
@@ -456,7 +456,10 @@ var toolPermissions = {
456
456
  desktop_hotkey: "confirm",
457
457
  desktop_scroll: "confirm",
458
458
  desktop_menu: "confirm",
459
+ desktop_paste: "confirm",
459
460
  desktop_screenshot: "confirm",
461
+ desktop_open_app: "auto",
462
+ desktop_open_url: "auto",
460
463
  cron_create: "confirm",
461
464
  cron_delete: "confirm",
462
465
  edit_block: "confirm",
@@ -487,6 +490,8 @@ var FilesystemTools = class {
487
490
  "ROUTING:",
488
491
  "- Use for system commands, package managers (npm, pip, brew), git, build tools, and scripting.",
489
492
  "- For reading files prefer read_file, for editing prefer edit_block, for searching prefer search_code.",
493
+ "- NOT for macOS app GUI interaction. When the user asks to interact with, control, or automate any application (clicking, typing, reading screen, navigating menus), use the desktop_* tools instead (desktop_open_app, desktop_see, desktop_click, desktop_type, desktop_paste, desktop_hotkey, desktop_scroll, desktop_menu, desktop_screenshot).",
494
+ "- The ONLY exception: opening System Preferences URLs for permissions (e.g. open 'x-apple.systempreferences:...').",
490
495
  "",
491
496
  "BEHAVIOR:",
492
497
  "- Execute commands directly when the user requests them. Do not ask for confirmation \u2014 the user has already decided.",
@@ -1626,7 +1631,11 @@ Cause: ${e.message}${hint}` }],
1626
1631
  "Start or stop screen recording. Captures the full screen as MP4 video.",
1627
1632
  "",
1628
1633
  "Use action='start' to begin, action='stop' to end and save. Only one recording can be active at a time.",
1629
- "Platform-specific: macOS (screencapture -v), Windows/Linux (ffmpeg)."
1634
+ "Platform-specific: macOS (screencapture -v), Windows/Linux (ffmpeg).",
1635
+ "",
1636
+ "PERMISSIONS (macOS): Screen Recording permission is needed. If denied, run via execute_command:",
1637
+ " open 'x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture'",
1638
+ "Toggle ON for 'screencapture' (or your terminal app), then retry."
1630
1639
  ].join("\n"),
1631
1640
  {
1632
1641
  action: z4.enum(["start", "stop"]).describe("'start': begin recording, 'stop': end recording and save the file"),
@@ -1716,10 +1725,28 @@ import { execFile as execFile2 } from "child_process";
1716
1725
  import { promisify as promisify4 } from "util";
1717
1726
  import { platform as platform2 } from "os";
1718
1727
  var execFileAsync2 = promisify4(execFile2);
1728
+ async function requestMacOSPermissions() {
1729
+ try {
1730
+ await execFileAsync2("swift", ["-e", `
1731
+ import CoreGraphics
1732
+ CGRequestScreenCaptureAccess()
1733
+ `], { timeout: 5e3 });
1734
+ } catch {
1735
+ }
1736
+ try {
1737
+ await execFileAsync2("swift", ["-e", `
1738
+ import ApplicationServices
1739
+ let opts = [kAXTrustedCheckOptionPrompt.takeUnretainedValue(): true] as CFDictionary
1740
+ AXIsProcessTrustedWithOptions(opts)
1741
+ `], { timeout: 5e3 });
1742
+ } catch {
1743
+ }
1744
+ }
1719
1745
  async function ensurePeekaboo() {
1720
1746
  if (platform2() !== "darwin") return false;
1721
1747
  try {
1722
1748
  await execFileAsync2("which", ["peekaboo"]);
1749
+ await requestMacOSPermissions();
1723
1750
  return true;
1724
1751
  } catch {
1725
1752
  console.log("\u23F3 peekaboo not found, installing via brew...");
@@ -1727,6 +1754,7 @@ async function ensurePeekaboo() {
1727
1754
  await execFileAsync2("brew", ["tap", "steipete/tap"], { timeout: 3e4 });
1728
1755
  await execFileAsync2("brew", ["install", "peekaboo"], { timeout: 12e4 });
1729
1756
  console.log("\u2705 peekaboo installed");
1757
+ await requestMacOSPermissions();
1730
1758
  return true;
1731
1759
  } catch (brewErr) {
1732
1760
  console.warn("\u26A0\uFE0F peekaboo install failed:", brewErr.message);
@@ -1756,6 +1784,10 @@ var PERM_FIX_HINT = [
1756
1784
  "3. Accessibility: open 'x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility'",
1757
1785
  "Toggle ON for 'peekaboo' in the opened panel, then retry."
1758
1786
  ].join("\n");
1787
+ function isPermissionError(msg) {
1788
+ const lower = msg.toLowerCase();
1789
+ return lower.includes("permission") || lower.includes("accessibility") || lower.includes("screen recording") || lower.includes("not trusted") || lower.includes("not allowed") || lower.includes("denied");
1790
+ }
1759
1791
  async function peekaboo(args) {
1760
1792
  try {
1761
1793
  const { stdout } = await execa("peekaboo", [...args, "--json-output"]);
@@ -1763,14 +1795,13 @@ async function peekaboo(args) {
1763
1795
  return JSON.parse(stdout);
1764
1796
  } catch (err) {
1765
1797
  consecutiveFailures++;
1766
- const msg = err.message?.toLowerCase() ?? "";
1767
- const isPermError = msg.includes("permission") || msg.includes("accessibility") || msg.includes("screen recording") || msg.includes("not trusted") || msg.includes("not allowed") || msg.includes("denied");
1768
- const hint = isPermError ? PERM_FIX_HINT : "";
1798
+ const msg = err.message ?? "";
1799
+ const hint = isPermissionError(msg) ? PERM_FIX_HINT : "";
1769
1800
  if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1770
1801
  consecutiveFailures = 0;
1771
- throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${err.message}${hint}`);
1802
+ throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${msg}${hint}`);
1772
1803
  }
1773
- throw new Error(`${err.message}${hint}`);
1804
+ throw new Error(`${msg}${hint}`);
1774
1805
  }
1775
1806
  }
1776
1807
  function checkBlacklist(app) {
@@ -1785,17 +1816,24 @@ var DesktopTools = class {
1785
1816
  [
1786
1817
  "Capture the macOS Accessibility Tree snapshot for a running application. Returns a structured element list with IDs, roles, labels, and positions.",
1787
1818
  "",
1788
- "WORKFLOW: List running apps \u2192 capture accessibility tree \u2192 find target element by role/label \u2192 interact using element ID or label (click, type, scroll).",
1789
- "Pass the returned snapshotId to subsequent interaction calls for 240x speed improvement (cached lookup vs. full re-scan).",
1819
+ "WHEN TO USE DESKTOP TOOLS:",
1820
+ "When the user asks to interact with, control, or automate ANY macOS application \u2014 use desktop_* tools, NOT execute_command.",
1821
+ "Workflow: desktop_open_app \u2192 desktop_see \u2192 desktop_click/type/paste \u2192 verify with desktop_see or desktop_screenshot.",
1822
+ "",
1823
+ "WORKFLOW TIPS:",
1824
+ "- If accessibility tree times out (complex UI apps like KakaoTalk): use desktop_screenshot + coordinate-based desktop_click instead.",
1825
+ "- For Korean/Japanese/Chinese text input: always use desktop_paste (NOT desktop_type).",
1826
+ "- For multi-window apps: use desktop_list_windows to find specific windows.",
1827
+ "- Pass snapshotId to subsequent calls for 240x speed improvement.",
1790
1828
  "",
1791
- "PERMISSIONS: Desktop tools require macOS Accessibility + Screen Recording permissions for 'peekaboo'.",
1792
- "If a tool fails with permission error, use execute_command to:",
1793
- " 1. peekaboo permissions --json-output (check which are missing)",
1829
+ "PERMISSIONS: Requires Accessibility + Screen Recording for 'peekaboo'.",
1830
+ "If denied, run via execute_command:",
1831
+ " 1. peekaboo permissions --json-output",
1794
1832
  " 2. open 'x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility'",
1795
1833
  " 3. open 'x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture'",
1796
- "Ask the user to toggle ON for 'peekaboo', then retry.",
1834
+ "Toggle ON for 'peekaboo', then retry.",
1797
1835
  "",
1798
- "SAFETY: Terminal, iTerm, and Finder are blocked. Two consecutive failures trigger an automatic safety stop."
1836
+ "SAFETY: Terminal, iTerm, and Finder are blocked. Two consecutive failures trigger automatic safety stop."
1799
1837
  ].join("\n"),
1800
1838
  {
1801
1839
  app: z5.string().optional().describe("App name to target (e.g. 'Safari', 'Notes', 'Google Chrome'). Omit for the frontmost app.")
@@ -1829,6 +1867,8 @@ var DesktopTools = class {
1829
1867
  "The 'on' parameter accepts: element label text (e.g. 'Save'), accessibility ID from a previous accessibility tree capture, or coordinates as 'x,y' string.",
1830
1868
  "For faster interaction, pass the snapshotId from a recent accessibility tree capture.",
1831
1869
  "",
1870
+ "PERMISSIONS: Requires macOS Accessibility permission for 'peekaboo'.",
1871
+ "",
1832
1872
  "SAFETY: Terminal, iTerm, and Finder are blocked. Two consecutive failures trigger automatic safety stop."
1833
1873
  ].join("\n"),
1834
1874
  {
@@ -1855,6 +1895,9 @@ var DesktopTools = class {
1855
1895
  "Type text into the currently focused UI element on macOS. The text is sent as keyboard input character-by-character.",
1856
1896
  "",
1857
1897
  "IMPORTANT: Always capture the accessibility tree first to verify the correct element is focused before typing.",
1898
+ "For Korean/Japanese/Chinese text or emoji, use desktop_paste instead \u2014 keyboard input does not support CJK characters.",
1899
+ "",
1900
+ "PERMISSIONS: Requires macOS Accessibility permission for 'peekaboo'.",
1858
1901
  "",
1859
1902
  "SAFETY: Terminal, iTerm, and Finder are blocked."
1860
1903
  ].join("\n"),
@@ -1879,6 +1922,8 @@ var DesktopTools = class {
1879
1922
  "",
1880
1923
  "Common shortcuts: 'cmd,c' (copy), 'cmd,v' (paste), 'cmd,z' (undo), 'cmd,s' (save), 'cmd,w' (close tab), 'cmd,q' (quit), 'cmd,shift,t' (reopen tab), 'cmd,tab' (switch app).",
1881
1924
  "",
1925
+ "PERMISSIONS: Requires macOS Accessibility permission for 'peekaboo'.",
1926
+ "",
1882
1927
  "SAFETY: Terminal, iTerm, and Finder are blocked."
1883
1928
  ].join("\n"),
1884
1929
  {
@@ -1902,6 +1947,8 @@ var DesktopTools = class {
1902
1947
  "",
1903
1948
  "Use 'ticks' to control scroll distance (default: 3, higher = more scrolling). Can target a specific element by label or ID from a previous accessibility tree capture.",
1904
1949
  "",
1950
+ "PERMISSIONS: Requires macOS Accessibility permission for 'peekaboo'.",
1951
+ "",
1905
1952
  "SAFETY: Terminal, iTerm, and Finder are blocked."
1906
1953
  ].join("\n"),
1907
1954
  {
@@ -1939,11 +1986,13 @@ var DesktopTools = class {
1939
1986
  };
1940
1987
  } catch (err) {
1941
1988
  consecutiveFailures++;
1989
+ const msg = err.message ?? "";
1990
+ const hint = isPermissionError(msg) ? PERM_FIX_HINT : "";
1942
1991
  if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1943
1992
  consecutiveFailures = 0;
1944
- throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${err.message}`);
1993
+ throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${msg}${hint}`);
1945
1994
  }
1946
- throw err;
1995
+ throw new Error(`${msg}${hint}`);
1947
1996
  }
1948
1997
  }
1949
1998
  );
@@ -1953,7 +2002,9 @@ var DesktopTools = class {
1953
2002
  "List all open windows on macOS, optionally filtered by app name. Returns window titles and metadata.",
1954
2003
  "",
1955
2004
  "If no app is specified, lists windows for the frontmost application.",
1956
- "Use this after identifying running apps to find specific windows before capturing the accessibility tree or taking a screenshot."
2005
+ "Use this after identifying running apps to find specific windows before capturing the accessibility tree or taking a screenshot.",
2006
+ "",
2007
+ "PERMISSIONS: Requires macOS Accessibility permission for 'peekaboo'."
1957
2008
  ].join("\n"),
1958
2009
  {
1959
2010
  app: z5.string().optional().describe("Filter by app name. Omit to query the frontmost app.")
@@ -1977,11 +2028,13 @@ var DesktopTools = class {
1977
2028
  };
1978
2029
  } catch (err) {
1979
2030
  consecutiveFailures++;
2031
+ const msg = err.message ?? "";
2032
+ const hint = isPermissionError(msg) ? PERM_FIX_HINT : "";
1980
2033
  if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1981
2034
  consecutiveFailures = 0;
1982
- throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${err.message}`);
2035
+ throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${msg}${hint}`);
1983
2036
  }
1984
- throw err;
2037
+ throw new Error(`${msg}${hint}`);
1985
2038
  }
1986
2039
  }
1987
2040
  );
@@ -1993,6 +2046,8 @@ var DesktopTools = class {
1993
2046
  "MODES: 'screen' captures the full display, 'window' captures a specific app window.",
1994
2047
  "TIP: Prefer the accessibility tree for understanding UI structure \u2014 use screenshots only when visual appearance matters (layouts, images, colors).",
1995
2048
  "",
2049
+ "PERMISSIONS: Requires macOS Screen Recording permission for 'peekaboo'.",
2050
+ "",
1996
2051
  "SAFETY: Terminal, iTerm, and Finder are blocked."
1997
2052
  ].join("\n"),
1998
2053
  {
@@ -2030,6 +2085,8 @@ var DesktopTools = class {
2030
2085
  "Examples: ['File', 'New Tab'], ['Edit', 'Find', 'Find...'], ['View', 'Enter Full Screen'].",
2031
2086
  "Omit the 'app' parameter to target the frontmost app. The target app must be running.",
2032
2087
  "",
2088
+ "PERMISSIONS: Requires macOS Accessibility permission for 'peekaboo'.",
2089
+ "",
2033
2090
  "SAFETY: Terminal, iTerm, and Finder are blocked."
2034
2091
  ].join("\n"),
2035
2092
  {
@@ -2048,14 +2105,82 @@ var DesktopTools = class {
2048
2105
  };
2049
2106
  } catch (err) {
2050
2107
  consecutiveFailures++;
2108
+ const msg = err.message ?? "";
2109
+ const hint = isPermissionError(msg) ? PERM_FIX_HINT : "";
2051
2110
  if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
2052
2111
  consecutiveFailures = 0;
2053
- throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${err.message}`);
2112
+ throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${msg}${hint}`);
2054
2113
  }
2055
- throw err;
2114
+ throw new Error(`${msg}${hint}`);
2056
2115
  }
2057
2116
  }
2058
2117
  );
2118
+ server.tool(
2119
+ "desktop_paste",
2120
+ [
2121
+ "Paste text via clipboard into the focused element. Use this for Korean, Japanese, Chinese, emoji, or any non-ASCII text.",
2122
+ "",
2123
+ "Unlike desktop_type (which sends keyboard input character-by-character), this uses the system clipboard to paste text, supporting all character sets including CJK and emoji.",
2124
+ "",
2125
+ "PERMISSIONS: Requires macOS Accessibility permission for 'peekaboo'.",
2126
+ "",
2127
+ "SAFETY: Terminal, iTerm, and Finder are blocked."
2128
+ ].join("\n"),
2129
+ {
2130
+ text: z5.string().describe("Text to paste into the focused element (supports Korean, Japanese, Chinese, emoji)"),
2131
+ app: z5.string().optional().describe("App name to focus before pasting")
2132
+ },
2133
+ async ({ text, app }) => {
2134
+ checkBlacklist(app);
2135
+ const args = ["type", "--paste", text];
2136
+ if (app) args.push("--app", app);
2137
+ const result = await peekaboo(args);
2138
+ return {
2139
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
2140
+ };
2141
+ }
2142
+ );
2143
+ server.tool(
2144
+ "desktop_open_app",
2145
+ [
2146
+ "Launch or bring to front a macOS application. Use this as the FIRST STEP when automating any app.",
2147
+ "",
2148
+ "This uses macOS native 'open -a' command. The app will be launched if not running, or brought to front if already running.",
2149
+ "After launching, wait briefly then use desktop_see to capture the accessibility tree.",
2150
+ "",
2151
+ "SAFETY: Terminal, iTerm, and Finder are blocked for automation safety."
2152
+ ].join("\n"),
2153
+ {
2154
+ app: z5.string().describe("Application name to launch (e.g. 'Safari', 'Notes', 'KakaoTalk', 'Google Chrome')")
2155
+ },
2156
+ async ({ app }) => {
2157
+ checkBlacklist(app);
2158
+ await execa("open", ["-a", app]);
2159
+ await new Promise((r) => setTimeout(r, 1500));
2160
+ return {
2161
+ content: [{ type: "text", text: `Launched ${app}` }]
2162
+ };
2163
+ }
2164
+ );
2165
+ server.tool(
2166
+ "desktop_open_url",
2167
+ [
2168
+ "Open a URL in the default browser or a specified app. Also works for file paths and custom URL schemes.",
2169
+ "",
2170
+ "Examples: 'https://google.com', 'file:///path/to/file.html', 'x-apple.systempreferences:...'"
2171
+ ].join("\n"),
2172
+ {
2173
+ url: z5.string().describe("URL to open (https://, file://, or custom scheme)"),
2174
+ app: z5.string().optional().describe("Specific app to open the URL with (e.g. 'Google Chrome', 'Firefox')")
2175
+ },
2176
+ async ({ url, app }) => {
2177
+ const args = app ? ["-a", app, url] : [url];
2178
+ await execa("open", args);
2179
+ return {
2180
+ content: [{ type: "text", text: `Opened: ${url}` }]
2181
+ };
2182
+ }
2183
+ );
2059
2184
  }
2060
2185
  };
2061
2186
 
@@ -42,7 +42,10 @@ var toolPermissions = {
42
42
  desktop_hotkey: "confirm",
43
43
  desktop_scroll: "confirm",
44
44
  desktop_menu: "confirm",
45
+ desktop_paste: "confirm",
45
46
  desktop_screenshot: "confirm",
47
+ desktop_open_app: "auto",
48
+ desktop_open_url: "auto",
46
49
  cron_create: "confirm",
47
50
  cron_delete: "confirm",
48
51
  edit_block: "confirm",
@@ -73,6 +76,8 @@ var FilesystemTools = class {
73
76
  "ROUTING:",
74
77
  "- Use for system commands, package managers (npm, pip, brew), git, build tools, and scripting.",
75
78
  "- For reading files prefer read_file, for editing prefer edit_block, for searching prefer search_code.",
79
+ "- NOT for macOS app GUI interaction. When the user asks to interact with, control, or automate any application (clicking, typing, reading screen, navigating menus), use the desktop_* tools instead (desktop_open_app, desktop_see, desktop_click, desktop_type, desktop_paste, desktop_hotkey, desktop_scroll, desktop_menu, desktop_screenshot).",
80
+ "- The ONLY exception: opening System Preferences URLs for permissions (e.g. open 'x-apple.systempreferences:...').",
76
81
  "",
77
82
  "BEHAVIOR:",
78
83
  "- Execute commands directly when the user requests them. Do not ask for confirmation \u2014 the user has already decided.",
@@ -1212,7 +1217,11 @@ Cause: ${e.message}${hint}` }],
1212
1217
  "Start or stop screen recording. Captures the full screen as MP4 video.",
1213
1218
  "",
1214
1219
  "Use action='start' to begin, action='stop' to end and save. Only one recording can be active at a time.",
1215
- "Platform-specific: macOS (screencapture -v), Windows/Linux (ffmpeg)."
1220
+ "Platform-specific: macOS (screencapture -v), Windows/Linux (ffmpeg).",
1221
+ "",
1222
+ "PERMISSIONS (macOS): Screen Recording permission is needed. If denied, run via execute_command:",
1223
+ " open 'x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture'",
1224
+ "Toggle ON for 'screencapture' (or your terminal app), then retry."
1216
1225
  ].join("\n"),
1217
1226
  {
1218
1227
  action: z4.enum(["start", "stop"]).describe("'start': begin recording, 'stop': end recording and save the file"),
@@ -1302,10 +1311,28 @@ import { execFile as execFile2 } from "child_process";
1302
1311
  import { promisify as promisify4 } from "util";
1303
1312
  import { platform as platform2 } from "os";
1304
1313
  var execFileAsync2 = promisify4(execFile2);
1314
+ async function requestMacOSPermissions() {
1315
+ try {
1316
+ await execFileAsync2("swift", ["-e", `
1317
+ import CoreGraphics
1318
+ CGRequestScreenCaptureAccess()
1319
+ `], { timeout: 5e3 });
1320
+ } catch {
1321
+ }
1322
+ try {
1323
+ await execFileAsync2("swift", ["-e", `
1324
+ import ApplicationServices
1325
+ let opts = [kAXTrustedCheckOptionPrompt.takeUnretainedValue(): true] as CFDictionary
1326
+ AXIsProcessTrustedWithOptions(opts)
1327
+ `], { timeout: 5e3 });
1328
+ } catch {
1329
+ }
1330
+ }
1305
1331
  async function ensurePeekaboo() {
1306
1332
  if (platform2() !== "darwin") return false;
1307
1333
  try {
1308
1334
  await execFileAsync2("which", ["peekaboo"]);
1335
+ await requestMacOSPermissions();
1309
1336
  return true;
1310
1337
  } catch {
1311
1338
  console.log("\u23F3 peekaboo not found, installing via brew...");
@@ -1313,6 +1340,7 @@ async function ensurePeekaboo() {
1313
1340
  await execFileAsync2("brew", ["tap", "steipete/tap"], { timeout: 3e4 });
1314
1341
  await execFileAsync2("brew", ["install", "peekaboo"], { timeout: 12e4 });
1315
1342
  console.log("\u2705 peekaboo installed");
1343
+ await requestMacOSPermissions();
1316
1344
  return true;
1317
1345
  } catch (brewErr) {
1318
1346
  console.warn("\u26A0\uFE0F peekaboo install failed:", brewErr.message);
@@ -1342,6 +1370,10 @@ var PERM_FIX_HINT = [
1342
1370
  "3. Accessibility: open 'x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility'",
1343
1371
  "Toggle ON for 'peekaboo' in the opened panel, then retry."
1344
1372
  ].join("\n");
1373
+ function isPermissionError(msg) {
1374
+ const lower = msg.toLowerCase();
1375
+ return lower.includes("permission") || lower.includes("accessibility") || lower.includes("screen recording") || lower.includes("not trusted") || lower.includes("not allowed") || lower.includes("denied");
1376
+ }
1345
1377
  async function peekaboo(args) {
1346
1378
  try {
1347
1379
  const { stdout } = await execa("peekaboo", [...args, "--json-output"]);
@@ -1349,14 +1381,13 @@ async function peekaboo(args) {
1349
1381
  return JSON.parse(stdout);
1350
1382
  } catch (err) {
1351
1383
  consecutiveFailures++;
1352
- const msg = err.message?.toLowerCase() ?? "";
1353
- const isPermError = msg.includes("permission") || msg.includes("accessibility") || msg.includes("screen recording") || msg.includes("not trusted") || msg.includes("not allowed") || msg.includes("denied");
1354
- const hint = isPermError ? PERM_FIX_HINT : "";
1384
+ const msg = err.message ?? "";
1385
+ const hint = isPermissionError(msg) ? PERM_FIX_HINT : "";
1355
1386
  if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1356
1387
  consecutiveFailures = 0;
1357
- throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${err.message}${hint}`);
1388
+ throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${msg}${hint}`);
1358
1389
  }
1359
- throw new Error(`${err.message}${hint}`);
1390
+ throw new Error(`${msg}${hint}`);
1360
1391
  }
1361
1392
  }
1362
1393
  function checkBlacklist(app) {
@@ -1371,17 +1402,24 @@ var DesktopTools = class {
1371
1402
  [
1372
1403
  "Capture the macOS Accessibility Tree snapshot for a running application. Returns a structured element list with IDs, roles, labels, and positions.",
1373
1404
  "",
1374
- "WORKFLOW: List running apps \u2192 capture accessibility tree \u2192 find target element by role/label \u2192 interact using element ID or label (click, type, scroll).",
1375
- "Pass the returned snapshotId to subsequent interaction calls for 240x speed improvement (cached lookup vs. full re-scan).",
1405
+ "WHEN TO USE DESKTOP TOOLS:",
1406
+ "When the user asks to interact with, control, or automate ANY macOS application \u2014 use desktop_* tools, NOT execute_command.",
1407
+ "Workflow: desktop_open_app \u2192 desktop_see \u2192 desktop_click/type/paste \u2192 verify with desktop_see or desktop_screenshot.",
1408
+ "",
1409
+ "WORKFLOW TIPS:",
1410
+ "- If accessibility tree times out (complex UI apps like KakaoTalk): use desktop_screenshot + coordinate-based desktop_click instead.",
1411
+ "- For Korean/Japanese/Chinese text input: always use desktop_paste (NOT desktop_type).",
1412
+ "- For multi-window apps: use desktop_list_windows to find specific windows.",
1413
+ "- Pass snapshotId to subsequent calls for 240x speed improvement.",
1376
1414
  "",
1377
- "PERMISSIONS: Desktop tools require macOS Accessibility + Screen Recording permissions for 'peekaboo'.",
1378
- "If a tool fails with permission error, use execute_command to:",
1379
- " 1. peekaboo permissions --json-output (check which are missing)",
1415
+ "PERMISSIONS: Requires Accessibility + Screen Recording for 'peekaboo'.",
1416
+ "If denied, run via execute_command:",
1417
+ " 1. peekaboo permissions --json-output",
1380
1418
  " 2. open 'x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility'",
1381
1419
  " 3. open 'x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture'",
1382
- "Ask the user to toggle ON for 'peekaboo', then retry.",
1420
+ "Toggle ON for 'peekaboo', then retry.",
1383
1421
  "",
1384
- "SAFETY: Terminal, iTerm, and Finder are blocked. Two consecutive failures trigger an automatic safety stop."
1422
+ "SAFETY: Terminal, iTerm, and Finder are blocked. Two consecutive failures trigger automatic safety stop."
1385
1423
  ].join("\n"),
1386
1424
  {
1387
1425
  app: z5.string().optional().describe("App name to target (e.g. 'Safari', 'Notes', 'Google Chrome'). Omit for the frontmost app.")
@@ -1415,6 +1453,8 @@ var DesktopTools = class {
1415
1453
  "The 'on' parameter accepts: element label text (e.g. 'Save'), accessibility ID from a previous accessibility tree capture, or coordinates as 'x,y' string.",
1416
1454
  "For faster interaction, pass the snapshotId from a recent accessibility tree capture.",
1417
1455
  "",
1456
+ "PERMISSIONS: Requires macOS Accessibility permission for 'peekaboo'.",
1457
+ "",
1418
1458
  "SAFETY: Terminal, iTerm, and Finder are blocked. Two consecutive failures trigger automatic safety stop."
1419
1459
  ].join("\n"),
1420
1460
  {
@@ -1441,6 +1481,9 @@ var DesktopTools = class {
1441
1481
  "Type text into the currently focused UI element on macOS. The text is sent as keyboard input character-by-character.",
1442
1482
  "",
1443
1483
  "IMPORTANT: Always capture the accessibility tree first to verify the correct element is focused before typing.",
1484
+ "For Korean/Japanese/Chinese text or emoji, use desktop_paste instead \u2014 keyboard input does not support CJK characters.",
1485
+ "",
1486
+ "PERMISSIONS: Requires macOS Accessibility permission for 'peekaboo'.",
1444
1487
  "",
1445
1488
  "SAFETY: Terminal, iTerm, and Finder are blocked."
1446
1489
  ].join("\n"),
@@ -1465,6 +1508,8 @@ var DesktopTools = class {
1465
1508
  "",
1466
1509
  "Common shortcuts: 'cmd,c' (copy), 'cmd,v' (paste), 'cmd,z' (undo), 'cmd,s' (save), 'cmd,w' (close tab), 'cmd,q' (quit), 'cmd,shift,t' (reopen tab), 'cmd,tab' (switch app).",
1467
1510
  "",
1511
+ "PERMISSIONS: Requires macOS Accessibility permission for 'peekaboo'.",
1512
+ "",
1468
1513
  "SAFETY: Terminal, iTerm, and Finder are blocked."
1469
1514
  ].join("\n"),
1470
1515
  {
@@ -1488,6 +1533,8 @@ var DesktopTools = class {
1488
1533
  "",
1489
1534
  "Use 'ticks' to control scroll distance (default: 3, higher = more scrolling). Can target a specific element by label or ID from a previous accessibility tree capture.",
1490
1535
  "",
1536
+ "PERMISSIONS: Requires macOS Accessibility permission for 'peekaboo'.",
1537
+ "",
1491
1538
  "SAFETY: Terminal, iTerm, and Finder are blocked."
1492
1539
  ].join("\n"),
1493
1540
  {
@@ -1525,11 +1572,13 @@ var DesktopTools = class {
1525
1572
  };
1526
1573
  } catch (err) {
1527
1574
  consecutiveFailures++;
1575
+ const msg = err.message ?? "";
1576
+ const hint = isPermissionError(msg) ? PERM_FIX_HINT : "";
1528
1577
  if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1529
1578
  consecutiveFailures = 0;
1530
- throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${err.message}`);
1579
+ throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${msg}${hint}`);
1531
1580
  }
1532
- throw err;
1581
+ throw new Error(`${msg}${hint}`);
1533
1582
  }
1534
1583
  }
1535
1584
  );
@@ -1539,7 +1588,9 @@ var DesktopTools = class {
1539
1588
  "List all open windows on macOS, optionally filtered by app name. Returns window titles and metadata.",
1540
1589
  "",
1541
1590
  "If no app is specified, lists windows for the frontmost application.",
1542
- "Use this after identifying running apps to find specific windows before capturing the accessibility tree or taking a screenshot."
1591
+ "Use this after identifying running apps to find specific windows before capturing the accessibility tree or taking a screenshot.",
1592
+ "",
1593
+ "PERMISSIONS: Requires macOS Accessibility permission for 'peekaboo'."
1543
1594
  ].join("\n"),
1544
1595
  {
1545
1596
  app: z5.string().optional().describe("Filter by app name. Omit to query the frontmost app.")
@@ -1563,11 +1614,13 @@ var DesktopTools = class {
1563
1614
  };
1564
1615
  } catch (err) {
1565
1616
  consecutiveFailures++;
1617
+ const msg = err.message ?? "";
1618
+ const hint = isPermissionError(msg) ? PERM_FIX_HINT : "";
1566
1619
  if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1567
1620
  consecutiveFailures = 0;
1568
- throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${err.message}`);
1621
+ throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${msg}${hint}`);
1569
1622
  }
1570
- throw err;
1623
+ throw new Error(`${msg}${hint}`);
1571
1624
  }
1572
1625
  }
1573
1626
  );
@@ -1579,6 +1632,8 @@ var DesktopTools = class {
1579
1632
  "MODES: 'screen' captures the full display, 'window' captures a specific app window.",
1580
1633
  "TIP: Prefer the accessibility tree for understanding UI structure \u2014 use screenshots only when visual appearance matters (layouts, images, colors).",
1581
1634
  "",
1635
+ "PERMISSIONS: Requires macOS Screen Recording permission for 'peekaboo'.",
1636
+ "",
1582
1637
  "SAFETY: Terminal, iTerm, and Finder are blocked."
1583
1638
  ].join("\n"),
1584
1639
  {
@@ -1616,6 +1671,8 @@ var DesktopTools = class {
1616
1671
  "Examples: ['File', 'New Tab'], ['Edit', 'Find', 'Find...'], ['View', 'Enter Full Screen'].",
1617
1672
  "Omit the 'app' parameter to target the frontmost app. The target app must be running.",
1618
1673
  "",
1674
+ "PERMISSIONS: Requires macOS Accessibility permission for 'peekaboo'.",
1675
+ "",
1619
1676
  "SAFETY: Terminal, iTerm, and Finder are blocked."
1620
1677
  ].join("\n"),
1621
1678
  {
@@ -1634,14 +1691,82 @@ var DesktopTools = class {
1634
1691
  };
1635
1692
  } catch (err) {
1636
1693
  consecutiveFailures++;
1694
+ const msg = err.message ?? "";
1695
+ const hint = isPermissionError(msg) ? PERM_FIX_HINT : "";
1637
1696
  if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1638
1697
  consecutiveFailures = 0;
1639
- throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${err.message}`);
1698
+ throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${msg}${hint}`);
1640
1699
  }
1641
- throw err;
1700
+ throw new Error(`${msg}${hint}`);
1642
1701
  }
1643
1702
  }
1644
1703
  );
1704
+ server.tool(
1705
+ "desktop_paste",
1706
+ [
1707
+ "Paste text via clipboard into the focused element. Use this for Korean, Japanese, Chinese, emoji, or any non-ASCII text.",
1708
+ "",
1709
+ "Unlike desktop_type (which sends keyboard input character-by-character), this uses the system clipboard to paste text, supporting all character sets including CJK and emoji.",
1710
+ "",
1711
+ "PERMISSIONS: Requires macOS Accessibility permission for 'peekaboo'.",
1712
+ "",
1713
+ "SAFETY: Terminal, iTerm, and Finder are blocked."
1714
+ ].join("\n"),
1715
+ {
1716
+ text: z5.string().describe("Text to paste into the focused element (supports Korean, Japanese, Chinese, emoji)"),
1717
+ app: z5.string().optional().describe("App name to focus before pasting")
1718
+ },
1719
+ async ({ text, app }) => {
1720
+ checkBlacklist(app);
1721
+ const args = ["type", "--paste", text];
1722
+ if (app) args.push("--app", app);
1723
+ const result = await peekaboo(args);
1724
+ return {
1725
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
1726
+ };
1727
+ }
1728
+ );
1729
+ server.tool(
1730
+ "desktop_open_app",
1731
+ [
1732
+ "Launch or bring to front a macOS application. Use this as the FIRST STEP when automating any app.",
1733
+ "",
1734
+ "This uses macOS native 'open -a' command. The app will be launched if not running, or brought to front if already running.",
1735
+ "After launching, wait briefly then use desktop_see to capture the accessibility tree.",
1736
+ "",
1737
+ "SAFETY: Terminal, iTerm, and Finder are blocked for automation safety."
1738
+ ].join("\n"),
1739
+ {
1740
+ app: z5.string().describe("Application name to launch (e.g. 'Safari', 'Notes', 'KakaoTalk', 'Google Chrome')")
1741
+ },
1742
+ async ({ app }) => {
1743
+ checkBlacklist(app);
1744
+ await execa("open", ["-a", app]);
1745
+ await new Promise((r) => setTimeout(r, 1500));
1746
+ return {
1747
+ content: [{ type: "text", text: `Launched ${app}` }]
1748
+ };
1749
+ }
1750
+ );
1751
+ server.tool(
1752
+ "desktop_open_url",
1753
+ [
1754
+ "Open a URL in the default browser or a specified app. Also works for file paths and custom URL schemes.",
1755
+ "",
1756
+ "Examples: 'https://google.com', 'file:///path/to/file.html', 'x-apple.systempreferences:...'"
1757
+ ].join("\n"),
1758
+ {
1759
+ url: z5.string().describe("URL to open (https://, file://, or custom scheme)"),
1760
+ app: z5.string().optional().describe("Specific app to open the URL with (e.g. 'Google Chrome', 'Firefox')")
1761
+ },
1762
+ async ({ url, app }) => {
1763
+ const args = app ? ["-a", app, url] : [url];
1764
+ await execa("open", args);
1765
+ return {
1766
+ content: [{ type: "text", text: `Opened: ${url}` }]
1767
+ };
1768
+ }
1769
+ );
1645
1770
  }
1646
1771
  };
1647
1772
 
@@ -43,7 +43,10 @@ var toolPermissions = {
43
43
  desktop_hotkey: "confirm",
44
44
  desktop_scroll: "confirm",
45
45
  desktop_menu: "confirm",
46
+ desktop_paste: "confirm",
46
47
  desktop_screenshot: "confirm",
48
+ desktop_open_app: "auto",
49
+ desktop_open_url: "auto",
47
50
  cron_create: "confirm",
48
51
  cron_delete: "confirm",
49
52
  edit_block: "confirm",
@@ -74,6 +77,8 @@ var FilesystemTools = class {
74
77
  "ROUTING:",
75
78
  "- Use for system commands, package managers (npm, pip, brew), git, build tools, and scripting.",
76
79
  "- For reading files prefer read_file, for editing prefer edit_block, for searching prefer search_code.",
80
+ "- NOT for macOS app GUI interaction. When the user asks to interact with, control, or automate any application (clicking, typing, reading screen, navigating menus), use the desktop_* tools instead (desktop_open_app, desktop_see, desktop_click, desktop_type, desktop_paste, desktop_hotkey, desktop_scroll, desktop_menu, desktop_screenshot).",
81
+ "- The ONLY exception: opening System Preferences URLs for permissions (e.g. open 'x-apple.systempreferences:...').",
77
82
  "",
78
83
  "BEHAVIOR:",
79
84
  "- Execute commands directly when the user requests them. Do not ask for confirmation \u2014 the user has already decided.",
@@ -1213,7 +1218,11 @@ Cause: ${e.message}${hint}` }],
1213
1218
  "Start or stop screen recording. Captures the full screen as MP4 video.",
1214
1219
  "",
1215
1220
  "Use action='start' to begin, action='stop' to end and save. Only one recording can be active at a time.",
1216
- "Platform-specific: macOS (screencapture -v), Windows/Linux (ffmpeg)."
1221
+ "Platform-specific: macOS (screencapture -v), Windows/Linux (ffmpeg).",
1222
+ "",
1223
+ "PERMISSIONS (macOS): Screen Recording permission is needed. If denied, run via execute_command:",
1224
+ " open 'x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture'",
1225
+ "Toggle ON for 'screencapture' (or your terminal app), then retry."
1217
1226
  ].join("\n"),
1218
1227
  {
1219
1228
  action: z4.enum(["start", "stop"]).describe("'start': begin recording, 'stop': end recording and save the file"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "junis",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "One-line device control for AI agents",
5
5
  "type": "module",
6
6
  "bin": {