junis 0.3.7 → 0.3.8

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
@@ -1626,7 +1626,6 @@ var APP_BLACKLIST = /* @__PURE__ */ new Set([
1626
1626
  var consecutiveFailures = 0;
1627
1627
  var MAX_CONSECUTIVE_FAILURES = 2;
1628
1628
  async function peekaboo(args) {
1629
- consecutiveFailures = 0;
1630
1629
  try {
1631
1630
  const { stdout } = await execa("peekaboo", [...args, "--json-output"]);
1632
1631
  consecutiveFailures = 0;
@@ -1650,10 +1649,10 @@ var DesktopTools = class {
1650
1649
  server.tool(
1651
1650
  "desktop_see",
1652
1651
  [
1653
- "Capture the macOS Accessibility Tree snapshot for a running application. Returns structured element list with IDs, roles, labels, and positions.",
1652
+ "Capture the macOS Accessibility Tree snapshot for a running application. Returns a structured element list with IDs, roles, labels, and positions.",
1654
1653
  "",
1655
- "WORKFLOW: Call desktop_see \u2192 find target element \u2192 use its ID in desktop_click or desktop_type.",
1656
- "Pass the returned snapshotId to subsequent calls for 240x speed improvement (cached lookup vs. full re-scan).",
1654
+ "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).",
1655
+ "Pass the returned snapshotId to subsequent interaction calls for 240x speed improvement (cached lookup vs. full re-scan).",
1657
1656
  "",
1658
1657
  "SAFETY: Terminal, iTerm, and Finder are blocked. Two consecutive failures trigger an automatic safety stop."
1659
1658
  ].join("\n"),
@@ -1686,15 +1685,15 @@ var DesktopTools = class {
1686
1685
  [
1687
1686
  "Click a macOS UI element by its accessibility label, ID, or x,y coordinates.",
1688
1687
  "",
1689
- "The 'on' parameter accepts: element label text (e.g. 'Save'), accessibility ID from desktop_see, or coordinates as 'x,y' string.",
1690
- "For faster interaction, pass the snapshotId from a recent desktop_see call.",
1688
+ "The 'on' parameter accepts: element label text (e.g. 'Save'), accessibility ID from a previous accessibility tree capture, or coordinates as 'x,y' string.",
1689
+ "For faster interaction, pass the snapshotId from a recent accessibility tree capture.",
1691
1690
  "",
1692
1691
  "SAFETY: Terminal, iTerm, and Finder are blocked. Two consecutive failures trigger automatic safety stop."
1693
1692
  ].join("\n"),
1694
1693
  {
1695
1694
  on: z5.string().describe("Element label, accessibility ID, or 'x,y' coordinates to click"),
1696
1695
  app: z5.string().optional().describe("App name to target (e.g. 'Safari')"),
1697
- snapshot: z5.string().optional().describe("snapshotId from desktop_see for cached interaction (240x faster)"),
1696
+ snapshot: z5.string().optional().describe("snapshotId from a previous accessibility tree capture for cached interaction (240x faster)"),
1698
1697
  doubleClick: z5.boolean().optional().default(false).describe("Double-click instead of single click")
1699
1698
  },
1700
1699
  async ({ on, app, snapshot, doubleClick }) => {
@@ -1714,7 +1713,9 @@ var DesktopTools = class {
1714
1713
  [
1715
1714
  "Type text into the currently focused UI element on macOS. The text is sent as keyboard input character-by-character.",
1716
1715
  "",
1717
- "SAFETY: Terminal, iTerm, and Finder are blocked. Use desktop_see first to verify the correct element is focused."
1716
+ "IMPORTANT: Always capture the accessibility tree first to verify the correct element is focused before typing.",
1717
+ "",
1718
+ "SAFETY: Terminal, iTerm, and Finder are blocked."
1718
1719
  ].join("\n"),
1719
1720
  {
1720
1721
  text: z5.string().describe("Text to type into the focused element"),
@@ -1755,11 +1756,17 @@ var DesktopTools = class {
1755
1756
  );
1756
1757
  server.tool(
1757
1758
  "desktop_scroll",
1758
- "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.",
1759
+ [
1760
+ "Scroll within a macOS application or specific UI element.",
1761
+ "",
1762
+ "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.",
1763
+ "",
1764
+ "SAFETY: Terminal, iTerm, and Finder are blocked."
1765
+ ].join("\n"),
1759
1766
  {
1760
1767
  direction: z5.enum(["up", "down", "left", "right"]).describe("Scroll direction"),
1761
1768
  ticks: z5.number().optional().default(3).describe("Number of scroll ticks (default: 3). Higher = more scrolling."),
1762
- on: z5.string().optional().describe("Element label or ID to scroll within (from desktop_see). Omit to scroll the active area."),
1769
+ on: z5.string().optional().describe("Element label or ID to scroll within (from a previous accessibility tree capture). Omit to scroll the active area."),
1763
1770
  app: z5.string().optional().describe("App name to target")
1764
1771
  },
1765
1772
  async ({ direction, ticks, on, app }) => {
@@ -1775,23 +1782,38 @@ var DesktopTools = class {
1775
1782
  );
1776
1783
  server.tool(
1777
1784
  "desktop_list_apps",
1778
- "List all currently running applications on macOS. Returns app names that can be used as the 'app' parameter in other desktop tools (desktop_see, desktop_click, desktop_type, etc.).",
1785
+ [
1786
+ "List all currently running applications on macOS. Returns app names usable as the 'app' parameter in all other desktop tools.",
1787
+ "",
1788
+ "WORKFLOW: This is typically the first step \u2014 identify which apps are running before interacting with their UI.",
1789
+ "The returned names are exact strings to pass as 'app' (e.g. 'Safari', 'Google Chrome', 'Notes')."
1790
+ ].join("\n"),
1779
1791
  {},
1780
1792
  async () => {
1781
1793
  try {
1782
1794
  const { stdout } = await execa("peekaboo", ["list", "apps", "--json"]);
1795
+ consecutiveFailures = 0;
1783
1796
  return {
1784
1797
  content: [{ type: "text", text: stdout }]
1785
1798
  };
1786
1799
  } catch (err) {
1787
1800
  consecutiveFailures++;
1801
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1802
+ consecutiveFailures = 0;
1803
+ throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${err.message}`);
1804
+ }
1788
1805
  throw err;
1789
1806
  }
1790
1807
  }
1791
1808
  );
1792
1809
  server.tool(
1793
1810
  "desktop_list_windows",
1794
- "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.",
1811
+ [
1812
+ "List all open windows on macOS, optionally filtered by app name. Returns window titles and metadata.",
1813
+ "",
1814
+ "If no app is specified, lists windows for the frontmost application.",
1815
+ "Use this after identifying running apps to find specific windows before capturing the accessibility tree or taking a screenshot."
1816
+ ].join("\n"),
1795
1817
  {
1796
1818
  app: z5.string().optional().describe("Filter by app name. Omit to query the frontmost app.")
1797
1819
  },
@@ -1808,11 +1830,16 @@ var DesktopTools = class {
1808
1830
  }
1809
1831
  const args = ["list", "windows", "--app", targetApp, "--json"];
1810
1832
  const { stdout } = await execa("peekaboo", args);
1833
+ consecutiveFailures = 0;
1811
1834
  return {
1812
1835
  content: [{ type: "text", text: stdout }]
1813
1836
  };
1814
1837
  } catch (err) {
1815
1838
  consecutiveFailures++;
1839
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1840
+ consecutiveFailures = 0;
1841
+ throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${err.message}`);
1842
+ }
1816
1843
  throw err;
1817
1844
  }
1818
1845
  }
@@ -1820,10 +1847,12 @@ var DesktopTools = class {
1820
1847
  server.tool(
1821
1848
  "desktop_screenshot",
1822
1849
  [
1823
- "Take a high-quality macOS screenshot using Peekaboo (Retina display support). Returns base64 image data.",
1850
+ "Take a high-quality macOS screenshot (Retina display support). Returns base64 image data.",
1824
1851
  "",
1825
1852
  "MODES: 'screen' captures the full display, 'window' captures a specific app window.",
1826
- "Prefer desktop_see (Accessibility Tree) for understanding UI structure \u2014 use screenshot only when visual appearance matters (layouts, images, colors)."
1853
+ "TIP: Prefer the accessibility tree for understanding UI structure \u2014 use screenshots only when visual appearance matters (layouts, images, colors).",
1854
+ "",
1855
+ "SAFETY: Terminal, iTerm, and Finder are blocked."
1827
1856
  ].join("\n"),
1828
1857
  {
1829
1858
  app: z5.string().optional().describe("Capture a specific app's window (by name)"),
@@ -1855,10 +1884,12 @@ var DesktopTools = class {
1855
1884
  server.tool(
1856
1885
  "desktop_menu",
1857
1886
  [
1858
- "Click a menu bar item in a macOS application. Navigate nested menus by adding path segments.",
1887
+ "Click a menu bar item in a macOS application. Navigate nested menus by providing path segments.",
1859
1888
  "",
1860
1889
  "Examples: ['File', 'New Tab'], ['Edit', 'Find', 'Find...'], ['View', 'Enter Full Screen'].",
1861
- "The target app must be running and accessible."
1890
+ "Omit the 'app' parameter to target the frontmost app. The target app must be running.",
1891
+ "",
1892
+ "SAFETY: Terminal, iTerm, and Finder are blocked."
1862
1893
  ].join("\n"),
1863
1894
  {
1864
1895
  path: z5.array(z5.string()).describe("Menu path as array (e.g. ['File', 'Save'], ['Edit', 'Find', 'Find...'])"),
@@ -1870,11 +1901,16 @@ var DesktopTools = class {
1870
1901
  if (app) args.push("--app", app);
1871
1902
  try {
1872
1903
  const { stdout } = await execa("peekaboo", args);
1904
+ consecutiveFailures = 0;
1873
1905
  return {
1874
1906
  content: [{ type: "text", text: stdout || "Menu click executed" }]
1875
1907
  };
1876
1908
  } catch (err) {
1877
1909
  consecutiveFailures++;
1910
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1911
+ consecutiveFailures = 0;
1912
+ throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${err.message}`);
1913
+ }
1878
1914
  throw err;
1879
1915
  }
1880
1916
  }
@@ -2613,6 +2649,8 @@ program.command("start", { isDefault: true }).description("Start Junis agent con
2613
2649
  console.log("");
2614
2650
  }
2615
2651
  } else {
2652
+ const oldDeviceKey = config2.device_key;
2653
+ const oldToken = config2.token;
2616
2654
  const checked = await checkEnsureTeam(config2, "junis");
2617
2655
  if (!checked) {
2618
2656
  let waitingPrinted = false;
@@ -2633,7 +2671,10 @@ program.command("start", { isDefault: true }).description("Start Junis agent con
2633
2671
  } else {
2634
2672
  process.stdout.write("\xB7");
2635
2673
  }
2636
- }
2674
+ },
2675
+ oldDeviceKey,
2676
+ oldToken,
2677
+ oldDeviceKey
2637
2678
  );
2638
2679
  console.log("");
2639
2680
  console.log(` \u2705 Authenticated as ${authResult.email ?? "your account"}`);
@@ -2799,6 +2840,8 @@ program.command("start", { isDefault: true }).description("Start Junis agent con
2799
2840
  console.log("");
2800
2841
  }
2801
2842
  } else {
2843
+ const oldDeviceKey = config.device_key;
2844
+ const oldToken = config.token;
2802
2845
  const checked = await checkEnsureTeam(config, "junis");
2803
2846
  if (!checked) {
2804
2847
  let waitingPrinted = false;
@@ -2819,7 +2862,10 @@ program.command("start", { isDefault: true }).description("Start Junis agent con
2819
2862
  } else {
2820
2863
  process.stdout.write("\xB7");
2821
2864
  }
2822
- }
2865
+ },
2866
+ oldDeviceKey,
2867
+ oldToken,
2868
+ oldDeviceKey
2823
2869
  );
2824
2870
  console.log("");
2825
2871
  console.log(` \u2705 Authenticated as ${authResult.email ?? "your account"}`);
@@ -1335,7 +1335,6 @@ var APP_BLACKLIST = /* @__PURE__ */ new Set([
1335
1335
  var consecutiveFailures = 0;
1336
1336
  var MAX_CONSECUTIVE_FAILURES = 2;
1337
1337
  async function peekaboo(args) {
1338
- consecutiveFailures = 0;
1339
1338
  try {
1340
1339
  const { stdout } = await execa("peekaboo", [...args, "--json-output"]);
1341
1340
  consecutiveFailures = 0;
@@ -1359,10 +1358,10 @@ var DesktopTools = class {
1359
1358
  server.tool(
1360
1359
  "desktop_see",
1361
1360
  [
1362
- "Capture the macOS Accessibility Tree snapshot for a running application. Returns structured element list with IDs, roles, labels, and positions.",
1361
+ "Capture the macOS Accessibility Tree snapshot for a running application. Returns a structured element list with IDs, roles, labels, and positions.",
1363
1362
  "",
1364
- "WORKFLOW: Call desktop_see \u2192 find target element \u2192 use its ID in desktop_click or desktop_type.",
1365
- "Pass the returned snapshotId to subsequent calls for 240x speed improvement (cached lookup vs. full re-scan).",
1363
+ "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).",
1364
+ "Pass the returned snapshotId to subsequent interaction calls for 240x speed improvement (cached lookup vs. full re-scan).",
1366
1365
  "",
1367
1366
  "SAFETY: Terminal, iTerm, and Finder are blocked. Two consecutive failures trigger an automatic safety stop."
1368
1367
  ].join("\n"),
@@ -1395,15 +1394,15 @@ var DesktopTools = class {
1395
1394
  [
1396
1395
  "Click a macOS UI element by its accessibility label, ID, or x,y coordinates.",
1397
1396
  "",
1398
- "The 'on' parameter accepts: element label text (e.g. 'Save'), accessibility ID from desktop_see, or coordinates as 'x,y' string.",
1399
- "For faster interaction, pass the snapshotId from a recent desktop_see call.",
1397
+ "The 'on' parameter accepts: element label text (e.g. 'Save'), accessibility ID from a previous accessibility tree capture, or coordinates as 'x,y' string.",
1398
+ "For faster interaction, pass the snapshotId from a recent accessibility tree capture.",
1400
1399
  "",
1401
1400
  "SAFETY: Terminal, iTerm, and Finder are blocked. Two consecutive failures trigger automatic safety stop."
1402
1401
  ].join("\n"),
1403
1402
  {
1404
1403
  on: z5.string().describe("Element label, accessibility ID, or 'x,y' coordinates to click"),
1405
1404
  app: z5.string().optional().describe("App name to target (e.g. 'Safari')"),
1406
- snapshot: z5.string().optional().describe("snapshotId from desktop_see for cached interaction (240x faster)"),
1405
+ snapshot: z5.string().optional().describe("snapshotId from a previous accessibility tree capture for cached interaction (240x faster)"),
1407
1406
  doubleClick: z5.boolean().optional().default(false).describe("Double-click instead of single click")
1408
1407
  },
1409
1408
  async ({ on, app, snapshot, doubleClick }) => {
@@ -1423,7 +1422,9 @@ var DesktopTools = class {
1423
1422
  [
1424
1423
  "Type text into the currently focused UI element on macOS. The text is sent as keyboard input character-by-character.",
1425
1424
  "",
1426
- "SAFETY: Terminal, iTerm, and Finder are blocked. Use desktop_see first to verify the correct element is focused."
1425
+ "IMPORTANT: Always capture the accessibility tree first to verify the correct element is focused before typing.",
1426
+ "",
1427
+ "SAFETY: Terminal, iTerm, and Finder are blocked."
1427
1428
  ].join("\n"),
1428
1429
  {
1429
1430
  text: z5.string().describe("Text to type into the focused element"),
@@ -1464,11 +1465,17 @@ var DesktopTools = class {
1464
1465
  );
1465
1466
  server.tool(
1466
1467
  "desktop_scroll",
1467
- "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.",
1468
+ [
1469
+ "Scroll within a macOS application or specific UI element.",
1470
+ "",
1471
+ "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.",
1472
+ "",
1473
+ "SAFETY: Terminal, iTerm, and Finder are blocked."
1474
+ ].join("\n"),
1468
1475
  {
1469
1476
  direction: z5.enum(["up", "down", "left", "right"]).describe("Scroll direction"),
1470
1477
  ticks: z5.number().optional().default(3).describe("Number of scroll ticks (default: 3). Higher = more scrolling."),
1471
- on: z5.string().optional().describe("Element label or ID to scroll within (from desktop_see). Omit to scroll the active area."),
1478
+ on: z5.string().optional().describe("Element label or ID to scroll within (from a previous accessibility tree capture). Omit to scroll the active area."),
1472
1479
  app: z5.string().optional().describe("App name to target")
1473
1480
  },
1474
1481
  async ({ direction, ticks, on, app }) => {
@@ -1484,23 +1491,38 @@ var DesktopTools = class {
1484
1491
  );
1485
1492
  server.tool(
1486
1493
  "desktop_list_apps",
1487
- "List all currently running applications on macOS. Returns app names that can be used as the 'app' parameter in other desktop tools (desktop_see, desktop_click, desktop_type, etc.).",
1494
+ [
1495
+ "List all currently running applications on macOS. Returns app names usable as the 'app' parameter in all other desktop tools.",
1496
+ "",
1497
+ "WORKFLOW: This is typically the first step \u2014 identify which apps are running before interacting with their UI.",
1498
+ "The returned names are exact strings to pass as 'app' (e.g. 'Safari', 'Google Chrome', 'Notes')."
1499
+ ].join("\n"),
1488
1500
  {},
1489
1501
  async () => {
1490
1502
  try {
1491
1503
  const { stdout } = await execa("peekaboo", ["list", "apps", "--json"]);
1504
+ consecutiveFailures = 0;
1492
1505
  return {
1493
1506
  content: [{ type: "text", text: stdout }]
1494
1507
  };
1495
1508
  } catch (err) {
1496
1509
  consecutiveFailures++;
1510
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1511
+ consecutiveFailures = 0;
1512
+ throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${err.message}`);
1513
+ }
1497
1514
  throw err;
1498
1515
  }
1499
1516
  }
1500
1517
  );
1501
1518
  server.tool(
1502
1519
  "desktop_list_windows",
1503
- "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.",
1520
+ [
1521
+ "List all open windows on macOS, optionally filtered by app name. Returns window titles and metadata.",
1522
+ "",
1523
+ "If no app is specified, lists windows for the frontmost application.",
1524
+ "Use this after identifying running apps to find specific windows before capturing the accessibility tree or taking a screenshot."
1525
+ ].join("\n"),
1504
1526
  {
1505
1527
  app: z5.string().optional().describe("Filter by app name. Omit to query the frontmost app.")
1506
1528
  },
@@ -1517,11 +1539,16 @@ var DesktopTools = class {
1517
1539
  }
1518
1540
  const args = ["list", "windows", "--app", targetApp, "--json"];
1519
1541
  const { stdout } = await execa("peekaboo", args);
1542
+ consecutiveFailures = 0;
1520
1543
  return {
1521
1544
  content: [{ type: "text", text: stdout }]
1522
1545
  };
1523
1546
  } catch (err) {
1524
1547
  consecutiveFailures++;
1548
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1549
+ consecutiveFailures = 0;
1550
+ throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${err.message}`);
1551
+ }
1525
1552
  throw err;
1526
1553
  }
1527
1554
  }
@@ -1529,10 +1556,12 @@ var DesktopTools = class {
1529
1556
  server.tool(
1530
1557
  "desktop_screenshot",
1531
1558
  [
1532
- "Take a high-quality macOS screenshot using Peekaboo (Retina display support). Returns base64 image data.",
1559
+ "Take a high-quality macOS screenshot (Retina display support). Returns base64 image data.",
1533
1560
  "",
1534
1561
  "MODES: 'screen' captures the full display, 'window' captures a specific app window.",
1535
- "Prefer desktop_see (Accessibility Tree) for understanding UI structure \u2014 use screenshot only when visual appearance matters (layouts, images, colors)."
1562
+ "TIP: Prefer the accessibility tree for understanding UI structure \u2014 use screenshots only when visual appearance matters (layouts, images, colors).",
1563
+ "",
1564
+ "SAFETY: Terminal, iTerm, and Finder are blocked."
1536
1565
  ].join("\n"),
1537
1566
  {
1538
1567
  app: z5.string().optional().describe("Capture a specific app's window (by name)"),
@@ -1564,10 +1593,12 @@ var DesktopTools = class {
1564
1593
  server.tool(
1565
1594
  "desktop_menu",
1566
1595
  [
1567
- "Click a menu bar item in a macOS application. Navigate nested menus by adding path segments.",
1596
+ "Click a menu bar item in a macOS application. Navigate nested menus by providing path segments.",
1568
1597
  "",
1569
1598
  "Examples: ['File', 'New Tab'], ['Edit', 'Find', 'Find...'], ['View', 'Enter Full Screen'].",
1570
- "The target app must be running and accessible."
1599
+ "Omit the 'app' parameter to target the frontmost app. The target app must be running.",
1600
+ "",
1601
+ "SAFETY: Terminal, iTerm, and Finder are blocked."
1571
1602
  ].join("\n"),
1572
1603
  {
1573
1604
  path: z5.array(z5.string()).describe("Menu path as array (e.g. ['File', 'Save'], ['Edit', 'Find', 'Find...'])"),
@@ -1579,11 +1610,16 @@ var DesktopTools = class {
1579
1610
  if (app) args.push("--app", app);
1580
1611
  try {
1581
1612
  const { stdout } = await execa("peekaboo", args);
1613
+ consecutiveFailures = 0;
1582
1614
  return {
1583
1615
  content: [{ type: "text", text: stdout || "Menu click executed" }]
1584
1616
  };
1585
1617
  } catch (err) {
1586
1618
  consecutiveFailures++;
1619
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1620
+ consecutiveFailures = 0;
1621
+ throw new Error(`peekaboo failed ${MAX_CONSECUTIVE_FAILURES} times in a row. Auto-stopped for safety. Last error: ${err.message}`);
1622
+ }
1587
1623
  throw err;
1588
1624
  }
1589
1625
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "junis",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "One-line device control for AI agents",
5
5
  "type": "module",
6
6
  "bin": {