@projectservan8n/cnapse 0.6.0 → 0.6.2

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/index.js CHANGED
@@ -925,10 +925,10 @@ async function captureScreenFallback() {
925
925
  const { exec: exec6 } = await import("child_process");
926
926
  const { promisify: promisify6 } = await import("util");
927
927
  const { tmpdir } = await import("os");
928
- const { join: join2 } = await import("path");
929
- const { readFile, unlink } = await import("fs/promises");
928
+ const { join: join3 } = await import("path");
929
+ const { readFile: readFile2, unlink } = await import("fs/promises");
930
930
  const execAsync6 = promisify6(exec6);
931
- const tempFile = join2(tmpdir(), `cnapse-screen-${Date.now()}.png`);
931
+ const tempFile = join3(tmpdir(), `cnapse-screen-${Date.now()}.png`);
932
932
  try {
933
933
  const platform = process.platform;
934
934
  if (platform === "win32") {
@@ -947,7 +947,7 @@ async function captureScreenFallback() {
947
947
  } else {
948
948
  await execAsync6(`gnome-screenshot -f "${tempFile}" 2>/dev/null || scrot "${tempFile}" 2>/dev/null || import -window root "${tempFile}"`);
949
949
  }
950
- const imageBuffer = await readFile(tempFile);
950
+ const imageBuffer = await readFile2(tempFile);
951
951
  await unlink(tempFile).catch(() => {
952
952
  });
953
953
  return imageBuffer.toString("base64");
@@ -1238,6 +1238,56 @@ import { EventEmitter } from "events";
1238
1238
  import { exec as exec5 } from "child_process";
1239
1239
  import { promisify as promisify5 } from "util";
1240
1240
 
1241
+ // src/tools/filesystem.ts
1242
+ import { promises as fs } from "fs";
1243
+ import { join, dirname } from "path";
1244
+ async function readFile(path2) {
1245
+ try {
1246
+ const content = await fs.readFile(path2, "utf-8");
1247
+ return ok(content);
1248
+ } catch (error) {
1249
+ return err(`Failed to read file: ${error.message}`);
1250
+ }
1251
+ }
1252
+ async function writeFile(path2, content) {
1253
+ try {
1254
+ const dir = dirname(path2);
1255
+ await fs.mkdir(dir, { recursive: true });
1256
+ await fs.writeFile(path2, content, "utf-8");
1257
+ return ok(`Written ${content.length} bytes to ${path2}`);
1258
+ } catch (error) {
1259
+ return err(`Failed to write file: ${error.message}`);
1260
+ }
1261
+ }
1262
+ async function listDir(path2, recursive = false) {
1263
+ try {
1264
+ const stat = await fs.stat(path2);
1265
+ if (!stat.isDirectory()) {
1266
+ return err(`Not a directory: ${path2}`);
1267
+ }
1268
+ const entries = [];
1269
+ async function walkDir(dir, prefix) {
1270
+ const items = await fs.readdir(dir, { withFileTypes: true });
1271
+ for (const item of items) {
1272
+ const displayPath = prefix ? `${prefix}/${item.name}` : item.name;
1273
+ if (item.isDirectory()) {
1274
+ entries.push(`${displayPath}/`);
1275
+ if (recursive) {
1276
+ await walkDir(join(dir, item.name), displayPath);
1277
+ }
1278
+ } else {
1279
+ entries.push(displayPath);
1280
+ }
1281
+ }
1282
+ }
1283
+ await walkDir(path2, "");
1284
+ entries.sort();
1285
+ return ok(entries.join("\n"));
1286
+ } catch (error) {
1287
+ return err(`Failed to list directory: ${error.message}`);
1288
+ }
1289
+ }
1290
+
1241
1291
  // src/tools/clipboard.ts
1242
1292
  import clipboardy from "clipboardy";
1243
1293
 
@@ -1250,6 +1300,20 @@ var execAsync3 = promisify3(exec3);
1250
1300
  import { exec as exec4 } from "child_process";
1251
1301
  import { promisify as promisify4 } from "util";
1252
1302
  var execAsync4 = promisify4(exec4);
1303
+ async function moveMouse(x, y) {
1304
+ try {
1305
+ if (process.platform === "win32") {
1306
+ await execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(${x}, ${y})"`, { shell: "cmd.exe" });
1307
+ } else if (process.platform === "darwin") {
1308
+ await execAsync4(`cliclick m:${x},${y}`);
1309
+ } else {
1310
+ await execAsync4(`xdotool mousemove ${x} ${y}`);
1311
+ }
1312
+ return ok(`Mouse moved to (${x}, ${y})`);
1313
+ } catch (error) {
1314
+ return err(`Failed to move mouse: ${error instanceof Error ? error.message : "Unknown error"}`);
1315
+ }
1316
+ }
1253
1317
  async function clickMouse(button = "left") {
1254
1318
  try {
1255
1319
  if (process.platform === "win32") {
@@ -1271,6 +1335,28 @@ ${button === "left" ? "[Win32.Mouse]::mouse_event(0x02, 0, 0, 0, 0); [Win32.Mous
1271
1335
  return err(`Failed to click: ${error instanceof Error ? error.message : "Unknown error"}`);
1272
1336
  }
1273
1337
  }
1338
+ async function doubleClick() {
1339
+ try {
1340
+ if (process.platform === "win32") {
1341
+ const script = `
1342
+ Add-Type -MemberDefinition @"
1343
+ [DllImport("user32.dll",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
1344
+ public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
1345
+ "@ -Name Mouse -Namespace Win32
1346
+ [Win32.Mouse]::mouse_event(0x02, 0, 0, 0, 0); [Win32.Mouse]::mouse_event(0x04, 0, 0, 0, 0)
1347
+ Start-Sleep -Milliseconds 50
1348
+ [Win32.Mouse]::mouse_event(0x02, 0, 0, 0, 0); [Win32.Mouse]::mouse_event(0x04, 0, 0, 0, 0)`;
1349
+ await execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
1350
+ } else if (process.platform === "darwin") {
1351
+ await execAsync4(`cliclick dc:.`);
1352
+ } else {
1353
+ await execAsync4(`xdotool click --repeat 2 --delay 50 1`);
1354
+ }
1355
+ return ok("Double clicked");
1356
+ } catch (error) {
1357
+ return err(`Failed to double click: ${error instanceof Error ? error.message : "Unknown error"}`);
1358
+ }
1359
+ }
1274
1360
  async function typeText(text) {
1275
1361
  try {
1276
1362
  if (process.platform === "win32") {
@@ -1403,6 +1489,54 @@ async function keyCombo(keys) {
1403
1489
  return err(`Failed to press combo: ${error instanceof Error ? error.message : "Unknown error"}`);
1404
1490
  }
1405
1491
  }
1492
+ async function getActiveWindow() {
1493
+ try {
1494
+ if (process.platform === "win32") {
1495
+ const script = `
1496
+ Add-Type @"
1497
+ using System;
1498
+ using System.Runtime.InteropServices;
1499
+ using System.Text;
1500
+ public class Win32 {
1501
+ [DllImport("user32.dll")]
1502
+ public static extern IntPtr GetForegroundWindow();
1503
+ [DllImport("user32.dll")]
1504
+ public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
1505
+ }
1506
+ "@
1507
+ $hwnd = [Win32]::GetForegroundWindow()
1508
+ $sb = New-Object System.Text.StringBuilder 256
1509
+ [Win32]::GetWindowText($hwnd, $sb, 256)
1510
+ $sb.ToString()`;
1511
+ const { stdout } = await execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
1512
+ return ok(stdout.trim() || "Unknown window");
1513
+ } else if (process.platform === "darwin") {
1514
+ const { stdout } = await execAsync4(`osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true'`);
1515
+ return ok(stdout.trim());
1516
+ } else {
1517
+ const { stdout } = await execAsync4(`xdotool getactivewindow getwindowname`);
1518
+ return ok(stdout.trim());
1519
+ }
1520
+ } catch (error) {
1521
+ return err(`Failed to get active window: ${error instanceof Error ? error.message : "Unknown error"}`);
1522
+ }
1523
+ }
1524
+ async function listWindows() {
1525
+ try {
1526
+ if (process.platform === "win32") {
1527
+ const { stdout } = await execAsync4(`powershell -Command "Get-Process | Where-Object {$_.MainWindowTitle} | Select-Object ProcessName, MainWindowTitle | Format-Table -AutoSize"`, { shell: "cmd.exe" });
1528
+ return ok(stdout);
1529
+ } else if (process.platform === "darwin") {
1530
+ const { stdout } = await execAsync4(`osascript -e 'tell application "System Events" to get name of every application process whose visible is true'`);
1531
+ return ok(stdout);
1532
+ } else {
1533
+ const { stdout } = await execAsync4(`wmctrl -l`);
1534
+ return ok(stdout);
1535
+ }
1536
+ } catch (error) {
1537
+ return err(`Failed to list windows: ${error instanceof Error ? error.message : "Unknown error"}`);
1538
+ }
1539
+ }
1406
1540
  async function focusWindow(title) {
1407
1541
  try {
1408
1542
  if (process.platform === "win32") {
@@ -1418,6 +1552,209 @@ async function focusWindow(title) {
1418
1552
  return err(`Failed to focus window: ${error instanceof Error ? error.message : "Unknown error"}`);
1419
1553
  }
1420
1554
  }
1555
+ async function minimizeWindow(title) {
1556
+ try {
1557
+ if (process.platform === "win32") {
1558
+ if (title) {
1559
+ const escaped = title.replace(/'/g, "''");
1560
+ const script = `
1561
+ $proc = Get-Process | Where-Object { $_.MainWindowTitle -like '*${escaped}*' -and $_.MainWindowHandle -ne 0 } | Select-Object -First 1
1562
+ if ($proc) {
1563
+ Add-Type @"
1564
+ using System;
1565
+ using System.Runtime.InteropServices;
1566
+ public class Win32 {
1567
+ [DllImport("user32.dll")]
1568
+ public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
1569
+ }
1570
+ "@
1571
+ [Win32]::ShowWindow($proc.MainWindowHandle, 6)
1572
+ Write-Output "Minimized: $($proc.MainWindowTitle)"
1573
+ } else {
1574
+ Write-Output "NOT_FOUND"
1575
+ }`;
1576
+ const { stdout } = await execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
1577
+ if (stdout.includes("NOT_FOUND")) {
1578
+ return err(`Window containing "${title}" not found`);
1579
+ }
1580
+ return ok(stdout.trim());
1581
+ } else {
1582
+ await execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('% n')"`, { shell: "cmd.exe" });
1583
+ return ok("Minimized active window");
1584
+ }
1585
+ } else if (process.platform === "darwin") {
1586
+ if (title) {
1587
+ await execAsync4(`osascript -e 'tell application "${title}" to set miniaturized of window 1 to true'`);
1588
+ } else {
1589
+ await execAsync4(`osascript -e 'tell application "System Events" to keystroke "m" using command down'`);
1590
+ }
1591
+ return ok(`Minimized window${title ? `: ${title}` : ""}`);
1592
+ } else {
1593
+ if (title) {
1594
+ await execAsync4(`wmctrl -r "${title}" -b add,hidden`);
1595
+ } else {
1596
+ await execAsync4(`xdotool getactivewindow windowminimize`);
1597
+ }
1598
+ return ok(`Minimized window${title ? `: ${title}` : ""}`);
1599
+ }
1600
+ } catch (error) {
1601
+ return err(`Failed to minimize window: ${error instanceof Error ? error.message : "Unknown error"}`);
1602
+ }
1603
+ }
1604
+ async function maximizeWindow(title) {
1605
+ try {
1606
+ if (process.platform === "win32") {
1607
+ if (title) {
1608
+ const escaped = title.replace(/'/g, "''");
1609
+ const script = `
1610
+ $proc = Get-Process | Where-Object { $_.MainWindowTitle -like '*${escaped}*' -and $_.MainWindowHandle -ne 0 } | Select-Object -First 1
1611
+ if ($proc) {
1612
+ Add-Type @"
1613
+ using System;
1614
+ using System.Runtime.InteropServices;
1615
+ public class Win32 {
1616
+ [DllImport("user32.dll")]
1617
+ public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
1618
+ }
1619
+ "@
1620
+ [Win32]::ShowWindow($proc.MainWindowHandle, 3)
1621
+ Write-Output "Maximized: $($proc.MainWindowTitle)"
1622
+ } else {
1623
+ Write-Output "NOT_FOUND"
1624
+ }`;
1625
+ const { stdout } = await execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
1626
+ if (stdout.includes("NOT_FOUND")) {
1627
+ return err(`Window containing "${title}" not found`);
1628
+ }
1629
+ return ok(stdout.trim());
1630
+ } else {
1631
+ await execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('% x')"`, { shell: "cmd.exe" });
1632
+ return ok("Maximized active window");
1633
+ }
1634
+ } else if (process.platform === "darwin") {
1635
+ if (title) {
1636
+ await execAsync4(`osascript -e 'tell application "${title}" to set zoomed of window 1 to true'`);
1637
+ } else {
1638
+ await execAsync4(`osascript -e 'tell application "System Events" to keystroke "f" using {control down, command down}'`);
1639
+ }
1640
+ return ok(`Maximized window${title ? `: ${title}` : ""}`);
1641
+ } else {
1642
+ if (title) {
1643
+ await execAsync4(`wmctrl -r "${title}" -b add,maximized_vert,maximized_horz`);
1644
+ } else {
1645
+ await execAsync4(`wmctrl -r :ACTIVE: -b add,maximized_vert,maximized_horz`);
1646
+ }
1647
+ return ok(`Maximized window${title ? `: ${title}` : ""}`);
1648
+ }
1649
+ } catch (error) {
1650
+ return err(`Failed to maximize window: ${error instanceof Error ? error.message : "Unknown error"}`);
1651
+ }
1652
+ }
1653
+ async function closeWindow(title) {
1654
+ try {
1655
+ if (process.platform === "win32") {
1656
+ if (title) {
1657
+ const escaped = title.replace(/'/g, "''");
1658
+ await execAsync4(`powershell -Command "Get-Process | Where-Object { $_.MainWindowTitle -like '*${escaped}*' } | ForEach-Object { $_.CloseMainWindow() }"`, { shell: "cmd.exe" });
1659
+ return ok(`Closed window: ${title}`);
1660
+ } else {
1661
+ await execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('%{F4}')"`, { shell: "cmd.exe" });
1662
+ return ok("Closed active window");
1663
+ }
1664
+ } else if (process.platform === "darwin") {
1665
+ if (title) {
1666
+ await execAsync4(`osascript -e 'tell application "${title}" to close window 1'`);
1667
+ } else {
1668
+ await execAsync4(`osascript -e 'tell application "System Events" to keystroke "w" using command down'`);
1669
+ }
1670
+ return ok(`Closed window${title ? `: ${title}` : ""}`);
1671
+ } else {
1672
+ if (title) {
1673
+ await execAsync4(`wmctrl -c "${title}"`);
1674
+ } else {
1675
+ await execAsync4(`xdotool getactivewindow windowclose`);
1676
+ }
1677
+ return ok(`Closed window${title ? `: ${title}` : ""}`);
1678
+ }
1679
+ } catch (error) {
1680
+ return err(`Failed to close window: ${error instanceof Error ? error.message : "Unknown error"}`);
1681
+ }
1682
+ }
1683
+ async function restoreWindow(title) {
1684
+ try {
1685
+ if (process.platform === "win32") {
1686
+ const escaped = title.replace(/'/g, "''");
1687
+ const script = `
1688
+ $proc = Get-Process | Where-Object { $_.MainWindowTitle -like '*${escaped}*' -and $_.MainWindowHandle -ne 0 } | Select-Object -First 1
1689
+ if ($proc) {
1690
+ Add-Type @"
1691
+ using System;
1692
+ using System.Runtime.InteropServices;
1693
+ public class Win32 {
1694
+ [DllImport("user32.dll")]
1695
+ public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
1696
+ }
1697
+ "@
1698
+ [Win32]::ShowWindow($proc.MainWindowHandle, 9)
1699
+ Write-Output "Restored: $($proc.MainWindowTitle)"
1700
+ } else {
1701
+ Write-Output "NOT_FOUND"
1702
+ }`;
1703
+ const { stdout } = await execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
1704
+ if (stdout.includes("NOT_FOUND")) {
1705
+ return err(`Window containing "${title}" not found`);
1706
+ }
1707
+ return ok(stdout.trim());
1708
+ } else if (process.platform === "darwin") {
1709
+ await execAsync4(`osascript -e 'tell application "${title}" to set miniaturized of window 1 to false'`);
1710
+ return ok(`Restored window: ${title}`);
1711
+ } else {
1712
+ await execAsync4(`wmctrl -r "${title}" -b remove,hidden`);
1713
+ return ok(`Restored window: ${title}`);
1714
+ }
1715
+ } catch (error) {
1716
+ return err(`Failed to restore window: ${error instanceof Error ? error.message : "Unknown error"}`);
1717
+ }
1718
+ }
1719
+ async function scrollMouse(amount) {
1720
+ try {
1721
+ if (process.platform === "win32") {
1722
+ const direction = amount > 0 ? 120 * Math.abs(amount) : -120 * Math.abs(amount);
1723
+ const script = `
1724
+ Add-Type -MemberDefinition @"
1725
+ [DllImport("user32.dll",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
1726
+ public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
1727
+ "@ -Name Mouse -Namespace Win32
1728
+ [Win32.Mouse]::mouse_event(0x0800, 0, 0, ${direction}, 0)`;
1729
+ await execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
1730
+ } else if (process.platform === "darwin") {
1731
+ const dir = amount > 0 ? "u" : "d";
1732
+ await execAsync4(`cliclick -r ${dir}:${Math.abs(amount)}`);
1733
+ } else {
1734
+ const btn = amount > 0 ? "4" : "5";
1735
+ await execAsync4(`xdotool click --repeat ${Math.abs(amount)} ${btn}`);
1736
+ }
1737
+ return ok(`Scrolled ${amount > 0 ? "up" : "down"} by ${Math.abs(amount)}`);
1738
+ } catch (error) {
1739
+ return err(`Failed to scroll: ${error instanceof Error ? error.message : "Unknown error"}`);
1740
+ }
1741
+ }
1742
+ async function getMousePosition() {
1743
+ try {
1744
+ if (process.platform === "win32") {
1745
+ const { stdout } = await execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; $p = [System.Windows.Forms.Cursor]::Position; Write-Output \\"$($p.X),$($p.Y)\\""`, { shell: "cmd.exe" });
1746
+ return ok(`Mouse position: ${stdout.trim()}`);
1747
+ } else if (process.platform === "darwin") {
1748
+ const { stdout } = await execAsync4(`cliclick p`);
1749
+ return ok(`Mouse position: ${stdout.trim()}`);
1750
+ } else {
1751
+ const { stdout } = await execAsync4(`xdotool getmouselocation --shell`);
1752
+ return ok(stdout);
1753
+ }
1754
+ } catch (error) {
1755
+ return err(`Failed to get mouse position: ${error instanceof Error ? error.message : "Unknown error"}`);
1756
+ }
1757
+ }
1421
1758
 
1422
1759
  // src/tools/index.ts
1423
1760
  function ok(output) {
@@ -1461,6 +1798,65 @@ ${stderr}`
1461
1798
  }
1462
1799
 
1463
1800
  // src/services/telegram.ts
1801
+ function formatForTelegram(text) {
1802
+ const hasMarkdown = /[*_`\[\]()]/.test(text);
1803
+ if (!hasMarkdown) {
1804
+ return { text, parseMode: void 0 };
1805
+ }
1806
+ try {
1807
+ let formatted = text;
1808
+ const escapeChars = ["\\", "_", "*", "[", "]", "(", ")", "~", "`", ">", "#", "+", "-", "=", "|", "{", "}", ".", "!"];
1809
+ const placeholders = [];
1810
+ let placeholderIndex = 0;
1811
+ formatted = formatted.replace(/```([\s\S]*?)```/g, (match, code) => {
1812
+ const placeholder = `__CODEBLOCK_${placeholderIndex++}__`;
1813
+ placeholders.push({ placeholder, original: "```" + code.replace(/\\/g, "\\\\") + "```" });
1814
+ return placeholder;
1815
+ });
1816
+ formatted = formatted.replace(/`([^`]+)`/g, (match, code) => {
1817
+ const placeholder = `__INLINECODE_${placeholderIndex++}__`;
1818
+ placeholders.push({ placeholder, original: "`" + code.replace(/\\/g, "\\\\") + "`" });
1819
+ return placeholder;
1820
+ });
1821
+ formatted = formatted.replace(/\*\*(.+?)\*\*/g, (match, text2) => {
1822
+ const placeholder = `__BOLD_${placeholderIndex++}__`;
1823
+ placeholders.push({ placeholder, original: "*" + text2 + "*" });
1824
+ return placeholder;
1825
+ });
1826
+ formatted = formatted.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, (match, text2) => {
1827
+ const placeholder = `__ITALIC_${placeholderIndex++}__`;
1828
+ placeholders.push({ placeholder, original: "_" + text2 + "_" });
1829
+ return placeholder;
1830
+ });
1831
+ formatted = formatted.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text2, url) => {
1832
+ const placeholder = `__LINK_${placeholderIndex++}__`;
1833
+ placeholders.push({ placeholder, original: "[" + text2 + "](" + url + ")" });
1834
+ return placeholder;
1835
+ });
1836
+ for (const char of escapeChars) {
1837
+ if (char === "\\") continue;
1838
+ formatted = formatted.split(char).join("\\" + char);
1839
+ }
1840
+ for (const { placeholder, original } of placeholders) {
1841
+ formatted = formatted.replace(placeholder, original);
1842
+ }
1843
+ return { text: formatted, parseMode: "MarkdownV2" };
1844
+ } catch {
1845
+ return { text, parseMode: void 0 };
1846
+ }
1847
+ }
1848
+ async function sendFormattedMessage(ctx, text) {
1849
+ const { text: formatted, parseMode } = formatForTelegram(text);
1850
+ try {
1851
+ if (parseMode) {
1852
+ await ctx.reply(formatted, { parse_mode: parseMode });
1853
+ } else {
1854
+ await ctx.reply(text);
1855
+ }
1856
+ } catch {
1857
+ await ctx.reply(text);
1858
+ }
1859
+ }
1464
1860
  var TelegramBotService = class extends EventEmitter {
1465
1861
  bot = null;
1466
1862
  isRunning = false;
@@ -1642,6 +2038,12 @@ ${result.error}
1642
2038
  }
1643
2039
  try {
1644
2040
  await ctx.sendChatAction("typing");
2041
+ const computerControlResult = await this.tryComputerControl(userText);
2042
+ if (computerControlResult) {
2043
+ await sendFormattedMessage(ctx, computerControlResult);
2044
+ history.push({ role: "assistant", content: computerControlResult });
2045
+ return;
2046
+ }
1645
2047
  const isVisionRequest = /screen|see|look|what('?s| is) (on|visible)|show me|screenshot/i.test(userText);
1646
2048
  let response;
1647
2049
  if (isVisionRequest) {
@@ -1659,10 +2061,10 @@ ${result.error}
1659
2061
  if (responseText.length > 4e3) {
1660
2062
  const chunks = responseText.match(/.{1,4000}/gs) || [responseText];
1661
2063
  for (const chunk of chunks) {
1662
- await ctx.reply(chunk);
2064
+ await sendFormattedMessage(ctx, chunk);
1663
2065
  }
1664
2066
  } else {
1665
- await ctx.reply(responseText);
2067
+ await sendFormattedMessage(ctx, responseText);
1666
2068
  }
1667
2069
  } catch (error) {
1668
2070
  const errorMsg = error instanceof Error ? error.message : "Unknown error";
@@ -1683,6 +2085,85 @@ ${result.error}
1683
2085
  }
1684
2086
  return this.allowedChatIds.has(chatId);
1685
2087
  }
2088
+ /**
2089
+ * Try to execute computer control commands directly
2090
+ * Returns response string if handled, null if not a computer command
2091
+ */
2092
+ async tryComputerControl(text) {
2093
+ const lower = text.toLowerCase();
2094
+ let match = lower.match(/minimize\s+(?:the\s+)?(.+)/i);
2095
+ if (match) {
2096
+ const result = await minimizeWindow(match[1].trim());
2097
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
2098
+ }
2099
+ match = lower.match(/maximize\s+(?:the\s+)?(.+)/i);
2100
+ if (match) {
2101
+ const result = await maximizeWindow(match[1].trim());
2102
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
2103
+ }
2104
+ match = lower.match(/close\s+(?:the\s+)?(.+)/i);
2105
+ if (match) {
2106
+ const result = await closeWindow(match[1].trim());
2107
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
2108
+ }
2109
+ match = lower.match(/restore\s+(?:the\s+)?(.+)/i);
2110
+ if (match) {
2111
+ const result = await restoreWindow(match[1].trim());
2112
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
2113
+ }
2114
+ match = lower.match(/(?:focus|open|switch to)\s+(?:the\s+)?(.+)/i);
2115
+ if (match) {
2116
+ const result = await focusWindow(match[1].trim());
2117
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
2118
+ }
2119
+ match = text.match(/type\s+["'](.+)["']/i);
2120
+ if (match) {
2121
+ const result = await typeText(match[1]);
2122
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
2123
+ }
2124
+ match = lower.match(/press\s+(?:the\s+)?(\w+)/i);
2125
+ if (match) {
2126
+ const result = await pressKey(match[1]);
2127
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
2128
+ }
2129
+ if (/^click$/i.test(lower) || /click\s+(?:the\s+)?mouse/i.test(lower)) {
2130
+ const result = await clickMouse("left");
2131
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
2132
+ }
2133
+ if (/right\s*click/i.test(lower)) {
2134
+ const result = await clickMouse("right");
2135
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
2136
+ }
2137
+ if (/double\s*click/i.test(lower)) {
2138
+ const result = await doubleClick();
2139
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
2140
+ }
2141
+ match = lower.match(/move\s+(?:the\s+)?mouse\s+(?:to\s+)?(\d+)[,\s]+(\d+)/i);
2142
+ if (match) {
2143
+ const result = await moveMouse(parseInt(match[1]), parseInt(match[2]));
2144
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
2145
+ }
2146
+ match = lower.match(/scroll\s+(up|down)(?:\s+(\d+))?/i);
2147
+ if (match) {
2148
+ const amount = match[1] === "up" ? parseInt(match[2]) || 3 : -(parseInt(match[2]) || 3);
2149
+ const result = await scrollMouse(amount);
2150
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
2151
+ }
2152
+ if (/list\s+(?:all\s+)?windows/i.test(lower) || /what\s+windows/i.test(lower)) {
2153
+ const result = await listWindows();
2154
+ return result.success ? `\u{1F4CB} Open Windows:
2155
+ ${result.output}` : `\u274C ${result.error}`;
2156
+ }
2157
+ if (/(?:active|current|focused)\s+window/i.test(lower) || /what\s+(?:window|app)/i.test(lower)) {
2158
+ const result = await getActiveWindow();
2159
+ return result.success ? `\u{1FA9F} Active: ${result.output}` : `\u274C ${result.error}`;
2160
+ }
2161
+ if (/mouse\s+position/i.test(lower) || /where.*mouse/i.test(lower)) {
2162
+ const result = await getMousePosition();
2163
+ return result.success ? `\u{1F5B1}\uFE0F ${result.output}` : `\u274C ${result.error}`;
2164
+ }
2165
+ return null;
2166
+ }
1686
2167
  /**
1687
2168
  * Send a message to a specific chat
1688
2169
  */
@@ -1778,14 +2259,14 @@ function useTelegram(onMessage) {
1778
2259
  import { useState as useState6, useCallback as useCallback4 } from "react";
1779
2260
 
1780
2261
  // src/lib/tasks.ts
1781
- import * as fs from "fs";
2262
+ import * as fs2 from "fs";
1782
2263
  import * as path from "path";
1783
2264
  import * as os2 from "os";
1784
2265
  var TASK_MEMORY_FILE = path.join(os2.homedir(), ".cnapse", "task-memory.json");
1785
2266
  function loadTaskMemory() {
1786
2267
  try {
1787
- if (fs.existsSync(TASK_MEMORY_FILE)) {
1788
- const data = fs.readFileSync(TASK_MEMORY_FILE, "utf-8");
2268
+ if (fs2.existsSync(TASK_MEMORY_FILE)) {
2269
+ const data = fs2.readFileSync(TASK_MEMORY_FILE, "utf-8");
1789
2270
  return JSON.parse(data);
1790
2271
  }
1791
2272
  } catch {
@@ -1812,10 +2293,10 @@ function saveTaskPattern(input, steps) {
1812
2293
  }
1813
2294
  memory.patterns = memory.patterns.sort((a, b) => b.successCount - a.successCount).slice(0, 100);
1814
2295
  const dir = path.dirname(TASK_MEMORY_FILE);
1815
- if (!fs.existsSync(dir)) {
1816
- fs.mkdirSync(dir, { recursive: true });
2296
+ if (!fs2.existsSync(dir)) {
2297
+ fs2.mkdirSync(dir, { recursive: true });
1817
2298
  }
1818
- fs.writeFileSync(TASK_MEMORY_FILE, JSON.stringify(memory, null, 2));
2299
+ fs2.writeFileSync(TASK_MEMORY_FILE, JSON.stringify(memory, null, 2));
1819
2300
  } catch {
1820
2301
  }
1821
2302
  }
@@ -1875,14 +2356,36 @@ Before outputting steps, THINK through these questions:
1875
2356
  - Typing too fast -> add small waits
1876
2357
 
1877
2358
  ## AVAILABLE ACTIONS
2359
+
2360
+ ### App Control
1878
2361
  - open_app: Open app via Run dialog (e.g., "open_app:notepad", "open_app:code", "open_app:chrome")
2362
+ - open_folder: Open VS Code with folder (e.g., "open_folder:E:/MyProject")
2363
+ - focus_window: Focus by title (e.g., "focus_window:Notepad")
2364
+
2365
+ ### Input
1879
2366
  - type_text: Type text string (e.g., "type_text:Hello World")
1880
2367
  - press_key: Single key (e.g., "press_key:enter", "press_key:escape", "press_key:tab")
1881
2368
  - key_combo: Key combination (e.g., "key_combo:control+s", "key_combo:alt+f4", "key_combo:meta+r")
1882
2369
  - click: Mouse click (e.g., "click:left", "click:right")
2370
+
2371
+ ### File Operations
2372
+ - read_file: Read file contents (e.g., "read_file:E:/test/index.html")
2373
+ - write_file: Write content to file (e.g., "write_file:E:/test/output.txt|Hello World")
2374
+ - list_files: List files in directory (e.g., "list_files:E:/test")
2375
+
2376
+ ### AI Coding
2377
+ - generate_code: AI generates code based on description (e.g., "generate_code:E:/test/index.html|create an HTML page with input on left, output on right")
2378
+ - edit_code: AI modifies existing code (e.g., "edit_code:E:/test/app.js|add error handling to the fetch calls")
2379
+
2380
+ ### Web Browsing
2381
+ - open_url: Open URL in default browser (e.g., "open_url:https://perplexity.ai")
2382
+ - browse_and_ask: Open AI website, type question, wait for response (e.g., "browse_and_ask:perplexity|What is the capital of France?")
2383
+ - browse_and_ask: Supports: perplexity, chatgpt, claude, google
2384
+
2385
+ ### Utility
1883
2386
  - wait: Wait N seconds (e.g., "wait:2" - use 1-3s for app loads)
1884
- - focus_window: Focus by title (e.g., "focus_window:Notepad")
1885
2387
  - screenshot: Capture and describe screen
2388
+ - shell: Run shell command (e.g., "shell:npm install")
1886
2389
  ${learnedExamples}
1887
2390
  ## EXAMPLES WITH REASONING
1888
2391
 
@@ -1925,6 +2428,59 @@ Output:
1925
2428
  { "description": "Close active window with Alt+F4", "action": "key_combo:alt+f4" }
1926
2429
  ]
1927
2430
 
2431
+ ### Example 4: "open folder E:/Test in vscode and create an HTML editor"
2432
+ Thinking:
2433
+ - Goal: Open VS Code with folder, then create/edit HTML file to be an editor
2434
+ - How: Use open_folder to launch VS Code with the folder, then use AI to generate code
2435
+ - Sequence: Open folder -> List files to see what exists -> Generate/edit the HTML
2436
+ - Edge case: File might not exist yet
2437
+
2438
+ Output:
2439
+ [
2440
+ { "description": "Open VS Code with the Test folder", "action": "open_folder:E:/Test" },
2441
+ { "description": "Wait for VS Code to load", "action": "wait:3" },
2442
+ { "description": "List files in the folder", "action": "list_files:E:/Test" },
2443
+ { "description": "Generate HTML editor code", "action": "generate_code:E:/Test/editor.html|Create an HTML page with a code editor layout: textarea input on the left side, live preview output on the right side. Include basic CSS for split layout and JavaScript to update preview on input." }
2444
+ ]
2445
+
2446
+ ### Example 5: "read the config.json and add a new setting"
2447
+ Thinking:
2448
+ - Goal: Read existing file, understand it, modify it
2449
+ - How: read_file to get contents, then edit_code to modify
2450
+ - Sequence: Read first, then edit
2451
+
2452
+ Output:
2453
+ [
2454
+ { "description": "Read the config file", "action": "read_file:config.json" },
2455
+ { "description": "Add new setting to config", "action": "edit_code:config.json|add a new setting called 'darkMode' with value true" }
2456
+ ]
2457
+
2458
+ ### Example 6: "ask perplexity what is the best programming language"
2459
+ Thinking:
2460
+ - Goal: Open Perplexity AI in browser and ask a question
2461
+ - How: Use browse_and_ask with perplexity target
2462
+ - Sequence: Open site -> type question -> wait for response -> screenshot result
2463
+
2464
+ Output:
2465
+ [
2466
+ { "description": "Ask Perplexity the question", "action": "browse_and_ask:perplexity|what is the best programming language" },
2467
+ { "description": "Wait for response to generate", "action": "wait:5" },
2468
+ { "description": "Capture the response", "action": "screenshot" }
2469
+ ]
2470
+
2471
+ ### Example 7: "search google for weather today"
2472
+ Thinking:
2473
+ - Goal: Open Google and search for something
2474
+ - How: Use browse_and_ask with google target
2475
+ - Sequence: Open Google, search, capture results
2476
+
2477
+ Output:
2478
+ [
2479
+ { "description": "Search Google", "action": "browse_and_ask:google|weather today" },
2480
+ { "description": "Wait for results", "action": "wait:2" },
2481
+ { "description": "Capture search results", "action": "screenshot" }
2482
+ ]
2483
+
1928
2484
  ## YOUR TASK
1929
2485
  Now parse this request: "${input}"
1930
2486
 
@@ -2014,6 +2570,128 @@ async function executeStep(step) {
2014
2570
  await focusWindow(params);
2015
2571
  step.result = `Focused window: ${params}`;
2016
2572
  break;
2573
+ case "open_folder":
2574
+ await runCommand(`code "${params}"`, 1e4);
2575
+ step.result = `Opened VS Code with folder: ${params}`;
2576
+ break;
2577
+ case "read_file": {
2578
+ const readResult = await readFile(params);
2579
+ if (readResult.success) {
2580
+ step.result = readResult.output;
2581
+ } else {
2582
+ throw new Error(readResult.error || "Failed to read file");
2583
+ }
2584
+ break;
2585
+ }
2586
+ case "write_file": {
2587
+ const [filePath, ...contentParts] = params.split("|");
2588
+ const content = contentParts.join("|");
2589
+ const writeResult = await writeFile(filePath, content);
2590
+ if (writeResult.success) {
2591
+ step.result = `Written to ${filePath}`;
2592
+ } else {
2593
+ throw new Error(writeResult.error || "Failed to write file");
2594
+ }
2595
+ break;
2596
+ }
2597
+ case "list_files": {
2598
+ const listResult = await listDir(params, false);
2599
+ if (listResult.success) {
2600
+ step.result = listResult.output;
2601
+ } else {
2602
+ throw new Error(listResult.error || "Failed to list files");
2603
+ }
2604
+ break;
2605
+ }
2606
+ case "generate_code": {
2607
+ const [codePath, ...descParts] = params.split("|");
2608
+ const codeDescription = descParts.join("|");
2609
+ const codePrompt = `Generate complete, working code for this request. Output ONLY the code, no explanations or markdown:
2610
+
2611
+ Request: ${codeDescription}
2612
+
2613
+ File: ${codePath}`;
2614
+ const codeResponse = await chat([{ role: "user", content: codePrompt }]);
2615
+ let generatedCode = codeResponse.content;
2616
+ generatedCode = generatedCode.replace(/^```[\w]*\n?/gm, "").replace(/\n?```$/gm, "").trim();
2617
+ const genResult = await writeFile(codePath, generatedCode);
2618
+ if (genResult.success) {
2619
+ step.result = `Generated and saved code to ${codePath}`;
2620
+ } else {
2621
+ throw new Error(genResult.error || "Failed to write generated code");
2622
+ }
2623
+ break;
2624
+ }
2625
+ case "edit_code": {
2626
+ const [editPath, ...instrParts] = params.split("|");
2627
+ const instructions = instrParts.join("|");
2628
+ const existingResult = await readFile(editPath);
2629
+ if (!existingResult.success) {
2630
+ throw new Error(`Cannot read file: ${existingResult.error}`);
2631
+ }
2632
+ const editPrompt = `Edit this code according to the instructions. Output ONLY the complete modified code, no explanations or markdown:
2633
+
2634
+ Instructions: ${instructions}
2635
+
2636
+ Current code:
2637
+ ${existingResult.output}`;
2638
+ const editResponse = await chat([{ role: "user", content: editPrompt }]);
2639
+ let editedCode = editResponse.content;
2640
+ editedCode = editedCode.replace(/^```[\w]*\n?/gm, "").replace(/\n?```$/gm, "").trim();
2641
+ const editWriteResult = await writeFile(editPath, editedCode);
2642
+ if (editWriteResult.success) {
2643
+ step.result = `Edited and saved ${editPath}`;
2644
+ } else {
2645
+ throw new Error(editWriteResult.error || "Failed to write edited code");
2646
+ }
2647
+ break;
2648
+ }
2649
+ case "shell": {
2650
+ const shellResult = await runCommand(params, 3e4);
2651
+ if (shellResult.success) {
2652
+ step.result = shellResult.output || "Command completed";
2653
+ } else {
2654
+ throw new Error(shellResult.error || "Command failed");
2655
+ }
2656
+ break;
2657
+ }
2658
+ case "open_url": {
2659
+ const url = params.startsWith("http") ? params : `https://${params}`;
2660
+ if (process.platform === "win32") {
2661
+ await runCommand(`start "" "${url}"`, 5e3);
2662
+ } else if (process.platform === "darwin") {
2663
+ await runCommand(`open "${url}"`, 5e3);
2664
+ } else {
2665
+ await runCommand(`xdg-open "${url}"`, 5e3);
2666
+ }
2667
+ step.result = `Opened ${url} in browser`;
2668
+ break;
2669
+ }
2670
+ case "browse_and_ask": {
2671
+ const [site, ...questionParts] = params.split("|");
2672
+ const question = questionParts.join("|");
2673
+ const sites = {
2674
+ perplexity: { url: "https://www.perplexity.ai", waitTime: 3 },
2675
+ chatgpt: { url: "https://chat.openai.com", waitTime: 4 },
2676
+ claude: { url: "https://claude.ai", waitTime: 4 },
2677
+ google: { url: "https://www.google.com", waitTime: 2 },
2678
+ bing: { url: "https://www.bing.com", waitTime: 2 }
2679
+ };
2680
+ const siteConfig = sites[site.toLowerCase()] || { url: `https://${site}`, waitTime: 3 };
2681
+ if (process.platform === "win32") {
2682
+ await runCommand(`start "" "${siteConfig.url}"`, 5e3);
2683
+ } else if (process.platform === "darwin") {
2684
+ await runCommand(`open "${siteConfig.url}"`, 5e3);
2685
+ } else {
2686
+ await runCommand(`xdg-open "${siteConfig.url}"`, 5e3);
2687
+ }
2688
+ await sleep(siteConfig.waitTime * 1e3);
2689
+ await typeText(question);
2690
+ await sleep(300);
2691
+ await pressKey("Return");
2692
+ step.result = `Asked ${site}: "${question}"`;
2693
+ break;
2694
+ }
2017
2695
  case "screenshot":
2018
2696
  const vision = await describeScreen();
2019
2697
  step.result = vision.description;
@@ -2070,8 +2748,8 @@ function getTaskMemoryStats() {
2070
2748
  }
2071
2749
  function clearTaskMemory() {
2072
2750
  try {
2073
- if (fs.existsSync(TASK_MEMORY_FILE)) {
2074
- fs.unlinkSync(TASK_MEMORY_FILE);
2751
+ if (fs2.existsSync(TASK_MEMORY_FILE)) {
2752
+ fs2.unlinkSync(TASK_MEMORY_FILE);
2075
2753
  }
2076
2754
  } catch {
2077
2755
  }
@@ -2311,17 +2989,33 @@ ${tasks.format(task)}`);
2311
2989
  setStatus("Ready");
2312
2990
  }
2313
2991
  }, [chat2, tasks]);
2992
+ const isComputerControlRequest = useCallback5((text) => {
2993
+ const lower = text.toLowerCase();
2994
+ const patterns = [
2995
+ /^(can you |please |)?(open|close|minimize|maximize|restore|focus|click|type|press|scroll|move|drag)/i,
2996
+ /^(can you |please |)?move (the |my |)mouse/i,
2997
+ /^(can you |please |)?(start|launch|run) [a-z]/i,
2998
+ /(open|close|minimize|maximize) (the |my |)?[a-z]/i,
2999
+ /click (on |the |)/i,
3000
+ /type ["'].+["']/i,
3001
+ /press (enter|escape|tab|ctrl|alt|shift|space|backspace|delete|f\d+)/i
3002
+ ];
3003
+ return patterns.some((p) => p.test(lower));
3004
+ }, []);
2314
3005
  const handleSubmit = useCallback5(async (value) => {
2315
3006
  if (!value.trim()) return;
2316
3007
  setInputValue("");
2317
3008
  if (value.startsWith("/")) {
2318
3009
  await handleCommand(value);
3010
+ } else if (isComputerControlRequest(value)) {
3011
+ chat2.addSystemMessage(`\u{1F916} Executing: ${value}`);
3012
+ await handleTaskCommand(value);
2319
3013
  } else {
2320
3014
  setStatus("Thinking...");
2321
3015
  await chat2.sendMessage(value);
2322
3016
  setStatus("Ready");
2323
3017
  }
2324
- }, [chat2, handleCommand]);
3018
+ }, [chat2, handleCommand, handleTaskCommand, isComputerControlRequest]);
2325
3019
  const handleProviderSelect = useCallback5((provider, model) => {
2326
3020
  chat2.addSystemMessage(`\u2705 Updated: ${provider} / ${model}`);
2327
3021
  }, [chat2]);