@projectservan8n/cnapse 0.5.8 → 0.6.1
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 +433 -5
- package/package.json +1 -1
- package/src/agents/computer.ts +17 -0
- package/src/agents/executor.ts +8 -0
- package/src/components/App.tsx +20 -1
- package/src/services/telegram.ts +184 -5
- package/src/tools/computer.ts +206 -0
package/dist/index.js
CHANGED
|
@@ -1250,6 +1250,20 @@ var execAsync3 = promisify3(exec3);
|
|
|
1250
1250
|
import { exec as exec4 } from "child_process";
|
|
1251
1251
|
import { promisify as promisify4 } from "util";
|
|
1252
1252
|
var execAsync4 = promisify4(exec4);
|
|
1253
|
+
async function moveMouse(x, y) {
|
|
1254
|
+
try {
|
|
1255
|
+
if (process.platform === "win32") {
|
|
1256
|
+
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" });
|
|
1257
|
+
} else if (process.platform === "darwin") {
|
|
1258
|
+
await execAsync4(`cliclick m:${x},${y}`);
|
|
1259
|
+
} else {
|
|
1260
|
+
await execAsync4(`xdotool mousemove ${x} ${y}`);
|
|
1261
|
+
}
|
|
1262
|
+
return ok(`Mouse moved to (${x}, ${y})`);
|
|
1263
|
+
} catch (error) {
|
|
1264
|
+
return err(`Failed to move mouse: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1253
1267
|
async function clickMouse(button = "left") {
|
|
1254
1268
|
try {
|
|
1255
1269
|
if (process.platform === "win32") {
|
|
@@ -1271,6 +1285,28 @@ ${button === "left" ? "[Win32.Mouse]::mouse_event(0x02, 0, 0, 0, 0); [Win32.Mous
|
|
|
1271
1285
|
return err(`Failed to click: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1272
1286
|
}
|
|
1273
1287
|
}
|
|
1288
|
+
async function doubleClick() {
|
|
1289
|
+
try {
|
|
1290
|
+
if (process.platform === "win32") {
|
|
1291
|
+
const script = `
|
|
1292
|
+
Add-Type -MemberDefinition @"
|
|
1293
|
+
[DllImport("user32.dll",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
|
|
1294
|
+
public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
|
|
1295
|
+
"@ -Name Mouse -Namespace Win32
|
|
1296
|
+
[Win32.Mouse]::mouse_event(0x02, 0, 0, 0, 0); [Win32.Mouse]::mouse_event(0x04, 0, 0, 0, 0)
|
|
1297
|
+
Start-Sleep -Milliseconds 50
|
|
1298
|
+
[Win32.Mouse]::mouse_event(0x02, 0, 0, 0, 0); [Win32.Mouse]::mouse_event(0x04, 0, 0, 0, 0)`;
|
|
1299
|
+
await execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
|
|
1300
|
+
} else if (process.platform === "darwin") {
|
|
1301
|
+
await execAsync4(`cliclick dc:.`);
|
|
1302
|
+
} else {
|
|
1303
|
+
await execAsync4(`xdotool click --repeat 2 --delay 50 1`);
|
|
1304
|
+
}
|
|
1305
|
+
return ok("Double clicked");
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
return err(`Failed to double click: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1274
1310
|
async function typeText(text) {
|
|
1275
1311
|
try {
|
|
1276
1312
|
if (process.platform === "win32") {
|
|
@@ -1403,6 +1439,54 @@ async function keyCombo(keys) {
|
|
|
1403
1439
|
return err(`Failed to press combo: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1404
1440
|
}
|
|
1405
1441
|
}
|
|
1442
|
+
async function getActiveWindow() {
|
|
1443
|
+
try {
|
|
1444
|
+
if (process.platform === "win32") {
|
|
1445
|
+
const script = `
|
|
1446
|
+
Add-Type @"
|
|
1447
|
+
using System;
|
|
1448
|
+
using System.Runtime.InteropServices;
|
|
1449
|
+
using System.Text;
|
|
1450
|
+
public class Win32 {
|
|
1451
|
+
[DllImport("user32.dll")]
|
|
1452
|
+
public static extern IntPtr GetForegroundWindow();
|
|
1453
|
+
[DllImport("user32.dll")]
|
|
1454
|
+
public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
|
|
1455
|
+
}
|
|
1456
|
+
"@
|
|
1457
|
+
$hwnd = [Win32]::GetForegroundWindow()
|
|
1458
|
+
$sb = New-Object System.Text.StringBuilder 256
|
|
1459
|
+
[Win32]::GetWindowText($hwnd, $sb, 256)
|
|
1460
|
+
$sb.ToString()`;
|
|
1461
|
+
const { stdout } = await execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
|
|
1462
|
+
return ok(stdout.trim() || "Unknown window");
|
|
1463
|
+
} else if (process.platform === "darwin") {
|
|
1464
|
+
const { stdout } = await execAsync4(`osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true'`);
|
|
1465
|
+
return ok(stdout.trim());
|
|
1466
|
+
} else {
|
|
1467
|
+
const { stdout } = await execAsync4(`xdotool getactivewindow getwindowname`);
|
|
1468
|
+
return ok(stdout.trim());
|
|
1469
|
+
}
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
return err(`Failed to get active window: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
async function listWindows() {
|
|
1475
|
+
try {
|
|
1476
|
+
if (process.platform === "win32") {
|
|
1477
|
+
const { stdout } = await execAsync4(`powershell -Command "Get-Process | Where-Object {$_.MainWindowTitle} | Select-Object ProcessName, MainWindowTitle | Format-Table -AutoSize"`, { shell: "cmd.exe" });
|
|
1478
|
+
return ok(stdout);
|
|
1479
|
+
} else if (process.platform === "darwin") {
|
|
1480
|
+
const { stdout } = await execAsync4(`osascript -e 'tell application "System Events" to get name of every application process whose visible is true'`);
|
|
1481
|
+
return ok(stdout);
|
|
1482
|
+
} else {
|
|
1483
|
+
const { stdout } = await execAsync4(`wmctrl -l`);
|
|
1484
|
+
return ok(stdout);
|
|
1485
|
+
}
|
|
1486
|
+
} catch (error) {
|
|
1487
|
+
return err(`Failed to list windows: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1406
1490
|
async function focusWindow(title) {
|
|
1407
1491
|
try {
|
|
1408
1492
|
if (process.platform === "win32") {
|
|
@@ -1418,6 +1502,209 @@ async function focusWindow(title) {
|
|
|
1418
1502
|
return err(`Failed to focus window: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1419
1503
|
}
|
|
1420
1504
|
}
|
|
1505
|
+
async function minimizeWindow(title) {
|
|
1506
|
+
try {
|
|
1507
|
+
if (process.platform === "win32") {
|
|
1508
|
+
if (title) {
|
|
1509
|
+
const escaped = title.replace(/'/g, "''");
|
|
1510
|
+
const script = `
|
|
1511
|
+
$proc = Get-Process | Where-Object { $_.MainWindowTitle -like '*${escaped}*' -and $_.MainWindowHandle -ne 0 } | Select-Object -First 1
|
|
1512
|
+
if ($proc) {
|
|
1513
|
+
Add-Type @"
|
|
1514
|
+
using System;
|
|
1515
|
+
using System.Runtime.InteropServices;
|
|
1516
|
+
public class Win32 {
|
|
1517
|
+
[DllImport("user32.dll")]
|
|
1518
|
+
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
1519
|
+
}
|
|
1520
|
+
"@
|
|
1521
|
+
[Win32]::ShowWindow($proc.MainWindowHandle, 6)
|
|
1522
|
+
Write-Output "Minimized: $($proc.MainWindowTitle)"
|
|
1523
|
+
} else {
|
|
1524
|
+
Write-Output "NOT_FOUND"
|
|
1525
|
+
}`;
|
|
1526
|
+
const { stdout } = await execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
|
|
1527
|
+
if (stdout.includes("NOT_FOUND")) {
|
|
1528
|
+
return err(`Window containing "${title}" not found`);
|
|
1529
|
+
}
|
|
1530
|
+
return ok(stdout.trim());
|
|
1531
|
+
} else {
|
|
1532
|
+
await execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('% n')"`, { shell: "cmd.exe" });
|
|
1533
|
+
return ok("Minimized active window");
|
|
1534
|
+
}
|
|
1535
|
+
} else if (process.platform === "darwin") {
|
|
1536
|
+
if (title) {
|
|
1537
|
+
await execAsync4(`osascript -e 'tell application "${title}" to set miniaturized of window 1 to true'`);
|
|
1538
|
+
} else {
|
|
1539
|
+
await execAsync4(`osascript -e 'tell application "System Events" to keystroke "m" using command down'`);
|
|
1540
|
+
}
|
|
1541
|
+
return ok(`Minimized window${title ? `: ${title}` : ""}`);
|
|
1542
|
+
} else {
|
|
1543
|
+
if (title) {
|
|
1544
|
+
await execAsync4(`wmctrl -r "${title}" -b add,hidden`);
|
|
1545
|
+
} else {
|
|
1546
|
+
await execAsync4(`xdotool getactivewindow windowminimize`);
|
|
1547
|
+
}
|
|
1548
|
+
return ok(`Minimized window${title ? `: ${title}` : ""}`);
|
|
1549
|
+
}
|
|
1550
|
+
} catch (error) {
|
|
1551
|
+
return err(`Failed to minimize window: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
async function maximizeWindow(title) {
|
|
1555
|
+
try {
|
|
1556
|
+
if (process.platform === "win32") {
|
|
1557
|
+
if (title) {
|
|
1558
|
+
const escaped = title.replace(/'/g, "''");
|
|
1559
|
+
const script = `
|
|
1560
|
+
$proc = Get-Process | Where-Object { $_.MainWindowTitle -like '*${escaped}*' -and $_.MainWindowHandle -ne 0 } | Select-Object -First 1
|
|
1561
|
+
if ($proc) {
|
|
1562
|
+
Add-Type @"
|
|
1563
|
+
using System;
|
|
1564
|
+
using System.Runtime.InteropServices;
|
|
1565
|
+
public class Win32 {
|
|
1566
|
+
[DllImport("user32.dll")]
|
|
1567
|
+
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
1568
|
+
}
|
|
1569
|
+
"@
|
|
1570
|
+
[Win32]::ShowWindow($proc.MainWindowHandle, 3)
|
|
1571
|
+
Write-Output "Maximized: $($proc.MainWindowTitle)"
|
|
1572
|
+
} else {
|
|
1573
|
+
Write-Output "NOT_FOUND"
|
|
1574
|
+
}`;
|
|
1575
|
+
const { stdout } = await execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
|
|
1576
|
+
if (stdout.includes("NOT_FOUND")) {
|
|
1577
|
+
return err(`Window containing "${title}" not found`);
|
|
1578
|
+
}
|
|
1579
|
+
return ok(stdout.trim());
|
|
1580
|
+
} else {
|
|
1581
|
+
await execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('% x')"`, { shell: "cmd.exe" });
|
|
1582
|
+
return ok("Maximized active window");
|
|
1583
|
+
}
|
|
1584
|
+
} else if (process.platform === "darwin") {
|
|
1585
|
+
if (title) {
|
|
1586
|
+
await execAsync4(`osascript -e 'tell application "${title}" to set zoomed of window 1 to true'`);
|
|
1587
|
+
} else {
|
|
1588
|
+
await execAsync4(`osascript -e 'tell application "System Events" to keystroke "f" using {control down, command down}'`);
|
|
1589
|
+
}
|
|
1590
|
+
return ok(`Maximized window${title ? `: ${title}` : ""}`);
|
|
1591
|
+
} else {
|
|
1592
|
+
if (title) {
|
|
1593
|
+
await execAsync4(`wmctrl -r "${title}" -b add,maximized_vert,maximized_horz`);
|
|
1594
|
+
} else {
|
|
1595
|
+
await execAsync4(`wmctrl -r :ACTIVE: -b add,maximized_vert,maximized_horz`);
|
|
1596
|
+
}
|
|
1597
|
+
return ok(`Maximized window${title ? `: ${title}` : ""}`);
|
|
1598
|
+
}
|
|
1599
|
+
} catch (error) {
|
|
1600
|
+
return err(`Failed to maximize window: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
async function closeWindow(title) {
|
|
1604
|
+
try {
|
|
1605
|
+
if (process.platform === "win32") {
|
|
1606
|
+
if (title) {
|
|
1607
|
+
const escaped = title.replace(/'/g, "''");
|
|
1608
|
+
await execAsync4(`powershell -Command "Get-Process | Where-Object { $_.MainWindowTitle -like '*${escaped}*' } | ForEach-Object { $_.CloseMainWindow() }"`, { shell: "cmd.exe" });
|
|
1609
|
+
return ok(`Closed window: ${title}`);
|
|
1610
|
+
} else {
|
|
1611
|
+
await execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('%{F4}')"`, { shell: "cmd.exe" });
|
|
1612
|
+
return ok("Closed active window");
|
|
1613
|
+
}
|
|
1614
|
+
} else if (process.platform === "darwin") {
|
|
1615
|
+
if (title) {
|
|
1616
|
+
await execAsync4(`osascript -e 'tell application "${title}" to close window 1'`);
|
|
1617
|
+
} else {
|
|
1618
|
+
await execAsync4(`osascript -e 'tell application "System Events" to keystroke "w" using command down'`);
|
|
1619
|
+
}
|
|
1620
|
+
return ok(`Closed window${title ? `: ${title}` : ""}`);
|
|
1621
|
+
} else {
|
|
1622
|
+
if (title) {
|
|
1623
|
+
await execAsync4(`wmctrl -c "${title}"`);
|
|
1624
|
+
} else {
|
|
1625
|
+
await execAsync4(`xdotool getactivewindow windowclose`);
|
|
1626
|
+
}
|
|
1627
|
+
return ok(`Closed window${title ? `: ${title}` : ""}`);
|
|
1628
|
+
}
|
|
1629
|
+
} catch (error) {
|
|
1630
|
+
return err(`Failed to close window: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
async function restoreWindow(title) {
|
|
1634
|
+
try {
|
|
1635
|
+
if (process.platform === "win32") {
|
|
1636
|
+
const escaped = title.replace(/'/g, "''");
|
|
1637
|
+
const script = `
|
|
1638
|
+
$proc = Get-Process | Where-Object { $_.MainWindowTitle -like '*${escaped}*' -and $_.MainWindowHandle -ne 0 } | Select-Object -First 1
|
|
1639
|
+
if ($proc) {
|
|
1640
|
+
Add-Type @"
|
|
1641
|
+
using System;
|
|
1642
|
+
using System.Runtime.InteropServices;
|
|
1643
|
+
public class Win32 {
|
|
1644
|
+
[DllImport("user32.dll")]
|
|
1645
|
+
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
1646
|
+
}
|
|
1647
|
+
"@
|
|
1648
|
+
[Win32]::ShowWindow($proc.MainWindowHandle, 9)
|
|
1649
|
+
Write-Output "Restored: $($proc.MainWindowTitle)"
|
|
1650
|
+
} else {
|
|
1651
|
+
Write-Output "NOT_FOUND"
|
|
1652
|
+
}`;
|
|
1653
|
+
const { stdout } = await execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
|
|
1654
|
+
if (stdout.includes("NOT_FOUND")) {
|
|
1655
|
+
return err(`Window containing "${title}" not found`);
|
|
1656
|
+
}
|
|
1657
|
+
return ok(stdout.trim());
|
|
1658
|
+
} else if (process.platform === "darwin") {
|
|
1659
|
+
await execAsync4(`osascript -e 'tell application "${title}" to set miniaturized of window 1 to false'`);
|
|
1660
|
+
return ok(`Restored window: ${title}`);
|
|
1661
|
+
} else {
|
|
1662
|
+
await execAsync4(`wmctrl -r "${title}" -b remove,hidden`);
|
|
1663
|
+
return ok(`Restored window: ${title}`);
|
|
1664
|
+
}
|
|
1665
|
+
} catch (error) {
|
|
1666
|
+
return err(`Failed to restore window: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
async function scrollMouse(amount) {
|
|
1670
|
+
try {
|
|
1671
|
+
if (process.platform === "win32") {
|
|
1672
|
+
const direction = amount > 0 ? 120 * Math.abs(amount) : -120 * Math.abs(amount);
|
|
1673
|
+
const script = `
|
|
1674
|
+
Add-Type -MemberDefinition @"
|
|
1675
|
+
[DllImport("user32.dll",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
|
|
1676
|
+
public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
|
|
1677
|
+
"@ -Name Mouse -Namespace Win32
|
|
1678
|
+
[Win32.Mouse]::mouse_event(0x0800, 0, 0, ${direction}, 0)`;
|
|
1679
|
+
await execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
|
|
1680
|
+
} else if (process.platform === "darwin") {
|
|
1681
|
+
const dir = amount > 0 ? "u" : "d";
|
|
1682
|
+
await execAsync4(`cliclick -r ${dir}:${Math.abs(amount)}`);
|
|
1683
|
+
} else {
|
|
1684
|
+
const btn = amount > 0 ? "4" : "5";
|
|
1685
|
+
await execAsync4(`xdotool click --repeat ${Math.abs(amount)} ${btn}`);
|
|
1686
|
+
}
|
|
1687
|
+
return ok(`Scrolled ${amount > 0 ? "up" : "down"} by ${Math.abs(amount)}`);
|
|
1688
|
+
} catch (error) {
|
|
1689
|
+
return err(`Failed to scroll: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
async function getMousePosition() {
|
|
1693
|
+
try {
|
|
1694
|
+
if (process.platform === "win32") {
|
|
1695
|
+
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" });
|
|
1696
|
+
return ok(`Mouse position: ${stdout.trim()}`);
|
|
1697
|
+
} else if (process.platform === "darwin") {
|
|
1698
|
+
const { stdout } = await execAsync4(`cliclick p`);
|
|
1699
|
+
return ok(`Mouse position: ${stdout.trim()}`);
|
|
1700
|
+
} else {
|
|
1701
|
+
const { stdout } = await execAsync4(`xdotool getmouselocation --shell`);
|
|
1702
|
+
return ok(stdout);
|
|
1703
|
+
}
|
|
1704
|
+
} catch (error) {
|
|
1705
|
+
return err(`Failed to get mouse position: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1421
1708
|
|
|
1422
1709
|
// src/tools/index.ts
|
|
1423
1710
|
function ok(output) {
|
|
@@ -1465,6 +1752,7 @@ var TelegramBotService = class extends EventEmitter {
|
|
|
1465
1752
|
bot = null;
|
|
1466
1753
|
isRunning = false;
|
|
1467
1754
|
allowedChatIds = /* @__PURE__ */ new Set();
|
|
1755
|
+
chatHistory = /* @__PURE__ */ new Map();
|
|
1468
1756
|
constructor() {
|
|
1469
1757
|
super();
|
|
1470
1758
|
}
|
|
@@ -1622,13 +1910,58 @@ ${result.error}
|
|
|
1622
1910
|
if (ctx.message.text.startsWith("/")) {
|
|
1623
1911
|
return;
|
|
1624
1912
|
}
|
|
1913
|
+
const chatId = ctx.chat.id;
|
|
1914
|
+
const userText = ctx.message.text;
|
|
1915
|
+
const from = ctx.from.username || ctx.from.first_name || "User";
|
|
1625
1916
|
const message = {
|
|
1626
|
-
chatId
|
|
1627
|
-
text:
|
|
1628
|
-
from
|
|
1917
|
+
chatId,
|
|
1918
|
+
text: userText,
|
|
1919
|
+
from
|
|
1629
1920
|
};
|
|
1630
1921
|
this.emit("message", message);
|
|
1631
|
-
this.
|
|
1922
|
+
if (!this.chatHistory.has(chatId)) {
|
|
1923
|
+
this.chatHistory.set(chatId, []);
|
|
1924
|
+
}
|
|
1925
|
+
const history = this.chatHistory.get(chatId);
|
|
1926
|
+
history.push({ role: "user", content: userText });
|
|
1927
|
+
if (history.length > 10) {
|
|
1928
|
+
history.splice(0, history.length - 10);
|
|
1929
|
+
}
|
|
1930
|
+
try {
|
|
1931
|
+
await ctx.sendChatAction("typing");
|
|
1932
|
+
const computerControlResult = await this.tryComputerControl(userText);
|
|
1933
|
+
if (computerControlResult) {
|
|
1934
|
+
await ctx.reply(computerControlResult);
|
|
1935
|
+
history.push({ role: "assistant", content: computerControlResult });
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
const isVisionRequest = /screen|see|look|what('?s| is) (on|visible)|show me|screenshot/i.test(userText);
|
|
1939
|
+
let response;
|
|
1940
|
+
if (isVisionRequest) {
|
|
1941
|
+
const screenshot = await captureScreenshot();
|
|
1942
|
+
if (screenshot) {
|
|
1943
|
+
response = await chatWithVision(history, screenshot);
|
|
1944
|
+
} else {
|
|
1945
|
+
response = await chat(history);
|
|
1946
|
+
}
|
|
1947
|
+
} else {
|
|
1948
|
+
response = await chat(history);
|
|
1949
|
+
}
|
|
1950
|
+
history.push({ role: "assistant", content: response.content });
|
|
1951
|
+
const responseText = response.content || "(no response)";
|
|
1952
|
+
if (responseText.length > 4e3) {
|
|
1953
|
+
const chunks = responseText.match(/.{1,4000}/gs) || [responseText];
|
|
1954
|
+
for (const chunk of chunks) {
|
|
1955
|
+
await ctx.reply(chunk);
|
|
1956
|
+
}
|
|
1957
|
+
} else {
|
|
1958
|
+
await ctx.reply(responseText);
|
|
1959
|
+
}
|
|
1960
|
+
} catch (error) {
|
|
1961
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
1962
|
+
await ctx.reply(`\u274C Error: ${errorMsg}`);
|
|
1963
|
+
this.emit("error", new Error(errorMsg));
|
|
1964
|
+
}
|
|
1632
1965
|
});
|
|
1633
1966
|
this.bot.catch((err2) => {
|
|
1634
1967
|
this.emit("error", err2);
|
|
@@ -1643,6 +1976,85 @@ ${result.error}
|
|
|
1643
1976
|
}
|
|
1644
1977
|
return this.allowedChatIds.has(chatId);
|
|
1645
1978
|
}
|
|
1979
|
+
/**
|
|
1980
|
+
* Try to execute computer control commands directly
|
|
1981
|
+
* Returns response string if handled, null if not a computer command
|
|
1982
|
+
*/
|
|
1983
|
+
async tryComputerControl(text) {
|
|
1984
|
+
const lower = text.toLowerCase();
|
|
1985
|
+
let match = lower.match(/minimize\s+(?:the\s+)?(.+)/i);
|
|
1986
|
+
if (match) {
|
|
1987
|
+
const result = await minimizeWindow(match[1].trim());
|
|
1988
|
+
return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
|
|
1989
|
+
}
|
|
1990
|
+
match = lower.match(/maximize\s+(?:the\s+)?(.+)/i);
|
|
1991
|
+
if (match) {
|
|
1992
|
+
const result = await maximizeWindow(match[1].trim());
|
|
1993
|
+
return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
|
|
1994
|
+
}
|
|
1995
|
+
match = lower.match(/close\s+(?:the\s+)?(.+)/i);
|
|
1996
|
+
if (match) {
|
|
1997
|
+
const result = await closeWindow(match[1].trim());
|
|
1998
|
+
return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
|
|
1999
|
+
}
|
|
2000
|
+
match = lower.match(/restore\s+(?:the\s+)?(.+)/i);
|
|
2001
|
+
if (match) {
|
|
2002
|
+
const result = await restoreWindow(match[1].trim());
|
|
2003
|
+
return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
|
|
2004
|
+
}
|
|
2005
|
+
match = lower.match(/(?:focus|open|switch to)\s+(?:the\s+)?(.+)/i);
|
|
2006
|
+
if (match) {
|
|
2007
|
+
const result = await focusWindow(match[1].trim());
|
|
2008
|
+
return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
|
|
2009
|
+
}
|
|
2010
|
+
match = text.match(/type\s+["'](.+)["']/i);
|
|
2011
|
+
if (match) {
|
|
2012
|
+
const result = await typeText(match[1]);
|
|
2013
|
+
return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
|
|
2014
|
+
}
|
|
2015
|
+
match = lower.match(/press\s+(?:the\s+)?(\w+)/i);
|
|
2016
|
+
if (match) {
|
|
2017
|
+
const result = await pressKey(match[1]);
|
|
2018
|
+
return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
|
|
2019
|
+
}
|
|
2020
|
+
if (/^click$/i.test(lower) || /click\s+(?:the\s+)?mouse/i.test(lower)) {
|
|
2021
|
+
const result = await clickMouse("left");
|
|
2022
|
+
return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
|
|
2023
|
+
}
|
|
2024
|
+
if (/right\s*click/i.test(lower)) {
|
|
2025
|
+
const result = await clickMouse("right");
|
|
2026
|
+
return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
|
|
2027
|
+
}
|
|
2028
|
+
if (/double\s*click/i.test(lower)) {
|
|
2029
|
+
const result = await doubleClick();
|
|
2030
|
+
return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
|
|
2031
|
+
}
|
|
2032
|
+
match = lower.match(/move\s+(?:the\s+)?mouse\s+(?:to\s+)?(\d+)[,\s]+(\d+)/i);
|
|
2033
|
+
if (match) {
|
|
2034
|
+
const result = await moveMouse(parseInt(match[1]), parseInt(match[2]));
|
|
2035
|
+
return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
|
|
2036
|
+
}
|
|
2037
|
+
match = lower.match(/scroll\s+(up|down)(?:\s+(\d+))?/i);
|
|
2038
|
+
if (match) {
|
|
2039
|
+
const amount = match[1] === "up" ? parseInt(match[2]) || 3 : -(parseInt(match[2]) || 3);
|
|
2040
|
+
const result = await scrollMouse(amount);
|
|
2041
|
+
return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
|
|
2042
|
+
}
|
|
2043
|
+
if (/list\s+(?:all\s+)?windows/i.test(lower) || /what\s+windows/i.test(lower)) {
|
|
2044
|
+
const result = await listWindows();
|
|
2045
|
+
return result.success ? `\u{1F4CB} Open Windows:
|
|
2046
|
+
${result.output}` : `\u274C ${result.error}`;
|
|
2047
|
+
}
|
|
2048
|
+
if (/(?:active|current|focused)\s+window/i.test(lower) || /what\s+(?:window|app)/i.test(lower)) {
|
|
2049
|
+
const result = await getActiveWindow();
|
|
2050
|
+
return result.success ? `\u{1FA9F} Active: ${result.output}` : `\u274C ${result.error}`;
|
|
2051
|
+
}
|
|
2052
|
+
if (/mouse\s+position/i.test(lower) || /where.*mouse/i.test(lower)) {
|
|
2053
|
+
const result = await getMousePosition();
|
|
2054
|
+
return result.success ? `\u{1F5B1}\uFE0F ${result.output}` : `\u274C ${result.error}`;
|
|
2055
|
+
}
|
|
2056
|
+
return null;
|
|
2057
|
+
}
|
|
1646
2058
|
/**
|
|
1647
2059
|
* Send a message to a specific chat
|
|
1648
2060
|
*/
|
|
@@ -2271,17 +2683,33 @@ ${tasks.format(task)}`);
|
|
|
2271
2683
|
setStatus("Ready");
|
|
2272
2684
|
}
|
|
2273
2685
|
}, [chat2, tasks]);
|
|
2686
|
+
const isComputerControlRequest = useCallback5((text) => {
|
|
2687
|
+
const lower = text.toLowerCase();
|
|
2688
|
+
const patterns = [
|
|
2689
|
+
/^(can you |please |)?(open|close|minimize|maximize|restore|focus|click|type|press|scroll|move|drag)/i,
|
|
2690
|
+
/^(can you |please |)?move (the |my |)mouse/i,
|
|
2691
|
+
/^(can you |please |)?(start|launch|run) [a-z]/i,
|
|
2692
|
+
/(open|close|minimize|maximize) (the |my |)?[a-z]/i,
|
|
2693
|
+
/click (on |the |)/i,
|
|
2694
|
+
/type ["'].+["']/i,
|
|
2695
|
+
/press (enter|escape|tab|ctrl|alt|shift|space|backspace|delete|f\d+)/i
|
|
2696
|
+
];
|
|
2697
|
+
return patterns.some((p) => p.test(lower));
|
|
2698
|
+
}, []);
|
|
2274
2699
|
const handleSubmit = useCallback5(async (value) => {
|
|
2275
2700
|
if (!value.trim()) return;
|
|
2276
2701
|
setInputValue("");
|
|
2277
2702
|
if (value.startsWith("/")) {
|
|
2278
2703
|
await handleCommand(value);
|
|
2704
|
+
} else if (isComputerControlRequest(value)) {
|
|
2705
|
+
chat2.addSystemMessage(`\u{1F916} Executing: ${value}`);
|
|
2706
|
+
await handleTaskCommand(value);
|
|
2279
2707
|
} else {
|
|
2280
2708
|
setStatus("Thinking...");
|
|
2281
2709
|
await chat2.sendMessage(value);
|
|
2282
2710
|
setStatus("Ready");
|
|
2283
2711
|
}
|
|
2284
|
-
}, [chat2, handleCommand]);
|
|
2712
|
+
}, [chat2, handleCommand, handleTaskCommand, isComputerControlRequest]);
|
|
2285
2713
|
const handleProviderSelect = useCallback5((provider, model) => {
|
|
2286
2714
|
chat2.addSystemMessage(`\u2705 Updated: ${provider} / ${model}`);
|
|
2287
2715
|
}, [chat2]);
|
package/package.json
CHANGED
package/src/agents/computer.ts
CHANGED
|
@@ -16,7 +16,13 @@ Available tools:
|
|
|
16
16
|
- getActiveWindow() - Get info about the currently focused window
|
|
17
17
|
- listWindows() - List all open windows
|
|
18
18
|
- focusWindow(title) - Focus a window by title (partial match)
|
|
19
|
+
- minimizeWindow(title?) - Minimize a window by title, or active window if no title
|
|
20
|
+
- maximizeWindow(title?) - Maximize a window by title, or active window if no title
|
|
21
|
+
- closeWindow(title?) - Close a window by title, or active window if no title
|
|
22
|
+
- restoreWindow(title) - Restore a minimized window by title
|
|
19
23
|
- scrollMouse(amount) - Scroll mouse wheel (positive=up, negative=down)
|
|
24
|
+
- dragMouse(startX, startY, endX, endY) - Drag mouse from one point to another
|
|
25
|
+
- getMousePosition() - Get current mouse position
|
|
20
26
|
|
|
21
27
|
Guidelines:
|
|
22
28
|
1. Always confirm dangerous actions (like closing windows with unsaved work)
|
|
@@ -25,6 +31,13 @@ Guidelines:
|
|
|
25
31
|
4. Report what you see/do at each step
|
|
26
32
|
5. If something fails, try alternative approaches
|
|
27
33
|
|
|
34
|
+
Window control examples:
|
|
35
|
+
- minimizeWindow("Visual Studio Code") - Minimize VS Code
|
|
36
|
+
- minimizeWindow() - Minimize the currently active window
|
|
37
|
+
- maximizeWindow("Chrome") - Maximize Chrome
|
|
38
|
+
- closeWindow("Notepad") - Close Notepad
|
|
39
|
+
- restoreWindow("Discord") - Restore minimized Discord
|
|
40
|
+
|
|
28
41
|
Common keyboard shortcuts:
|
|
29
42
|
- Copy: control+c
|
|
30
43
|
- Paste: control+v
|
|
@@ -52,6 +65,10 @@ When asked to open an application:
|
|
|
52
65
|
'getActiveWindow',
|
|
53
66
|
'listWindows',
|
|
54
67
|
'focusWindow',
|
|
68
|
+
'minimizeWindow',
|
|
69
|
+
'maximizeWindow',
|
|
70
|
+
'closeWindow',
|
|
71
|
+
'restoreWindow',
|
|
55
72
|
'scrollMouse',
|
|
56
73
|
'dragMouse',
|
|
57
74
|
'getMousePosition',
|
package/src/agents/executor.ts
CHANGED
|
@@ -114,6 +114,14 @@ export async function executeTool(call: ToolCall): Promise<ToolResult> {
|
|
|
114
114
|
return await computer.listWindows();
|
|
115
115
|
case 'focusWindow':
|
|
116
116
|
return await computer.focusWindow(args.title as string);
|
|
117
|
+
case 'minimizeWindow':
|
|
118
|
+
return await computer.minimizeWindow(args.title as string | undefined);
|
|
119
|
+
case 'maximizeWindow':
|
|
120
|
+
return await computer.maximizeWindow(args.title as string | undefined);
|
|
121
|
+
case 'closeWindow':
|
|
122
|
+
return await computer.closeWindow(args.title as string | undefined);
|
|
123
|
+
case 'restoreWindow':
|
|
124
|
+
return await computer.restoreWindow(args.title as string);
|
|
117
125
|
case 'scrollMouse':
|
|
118
126
|
return await computer.scrollMouse(args.amount as number);
|
|
119
127
|
case 'dragMouse':
|
package/src/components/App.tsx
CHANGED
|
@@ -202,6 +202,21 @@ export function App() {
|
|
|
202
202
|
}
|
|
203
203
|
}, [chat, tasks]);
|
|
204
204
|
|
|
205
|
+
// Check if message looks like a computer control request
|
|
206
|
+
const isComputerControlRequest = useCallback((text: string): boolean => {
|
|
207
|
+
const lower = text.toLowerCase();
|
|
208
|
+
const patterns = [
|
|
209
|
+
/^(can you |please |)?(open|close|minimize|maximize|restore|focus|click|type|press|scroll|move|drag)/i,
|
|
210
|
+
/^(can you |please |)?move (the |my |)mouse/i,
|
|
211
|
+
/^(can you |please |)?(start|launch|run) [a-z]/i,
|
|
212
|
+
/(open|close|minimize|maximize) (the |my |)?[a-z]/i,
|
|
213
|
+
/click (on |the |)/i,
|
|
214
|
+
/type ["'].+["']/i,
|
|
215
|
+
/press (enter|escape|tab|ctrl|alt|shift|space|backspace|delete|f\d+)/i,
|
|
216
|
+
];
|
|
217
|
+
return patterns.some(p => p.test(lower));
|
|
218
|
+
}, []);
|
|
219
|
+
|
|
205
220
|
// Submit handler
|
|
206
221
|
const handleSubmit = useCallback(async (value: string) => {
|
|
207
222
|
if (!value.trim()) return;
|
|
@@ -209,12 +224,16 @@ export function App() {
|
|
|
209
224
|
|
|
210
225
|
if (value.startsWith('/')) {
|
|
211
226
|
await handleCommand(value);
|
|
227
|
+
} else if (isComputerControlRequest(value)) {
|
|
228
|
+
// Auto-route to task system for computer control
|
|
229
|
+
chat.addSystemMessage(`🤖 Executing: ${value}`);
|
|
230
|
+
await handleTaskCommand(value);
|
|
212
231
|
} else {
|
|
213
232
|
setStatus('Thinking...');
|
|
214
233
|
await chat.sendMessage(value);
|
|
215
234
|
setStatus('Ready');
|
|
216
235
|
}
|
|
217
|
-
}, [chat, handleCommand]);
|
|
236
|
+
}, [chat, handleCommand, handleTaskCommand, isComputerControlRequest]);
|
|
218
237
|
|
|
219
238
|
// Provider selection callback
|
|
220
239
|
const handleProviderSelect = useCallback((provider: string, model: string) => {
|
package/src/services/telegram.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { EventEmitter } from 'events';
|
|
|
6
6
|
import { getConfig, getApiKey } from '../lib/config.js';
|
|
7
7
|
import { describeScreen, captureScreenshot } from '../lib/vision.js';
|
|
8
8
|
import { runCommand } from '../tools/shell.js';
|
|
9
|
+
import { chat as chatWithAI, chatWithVision, Message } from '../lib/api.js';
|
|
10
|
+
import * as computer from '../tools/computer.js';
|
|
9
11
|
|
|
10
12
|
export interface TelegramMessage {
|
|
11
13
|
chatId: number;
|
|
@@ -25,6 +27,7 @@ export class TelegramBotService extends EventEmitter {
|
|
|
25
27
|
private bot: any = null;
|
|
26
28
|
private isRunning = false;
|
|
27
29
|
private allowedChatIds: Set<number> = new Set();
|
|
30
|
+
private chatHistory: Map<number, Message[]> = new Map();
|
|
28
31
|
|
|
29
32
|
constructor() {
|
|
30
33
|
super();
|
|
@@ -205,7 +208,7 @@ export class TelegramBotService extends EventEmitter {
|
|
|
205
208
|
await ctx.reply(status);
|
|
206
209
|
});
|
|
207
210
|
|
|
208
|
-
// Handle text messages - forward to AI
|
|
211
|
+
// Handle text messages - forward to AI and respond
|
|
209
212
|
this.bot.on('text', async (ctx: any) => {
|
|
210
213
|
if (!this.isAllowed(ctx.chat.id)) {
|
|
211
214
|
return;
|
|
@@ -216,14 +219,79 @@ export class TelegramBotService extends EventEmitter {
|
|
|
216
219
|
return;
|
|
217
220
|
}
|
|
218
221
|
|
|
222
|
+
const chatId = ctx.chat.id;
|
|
223
|
+
const userText = ctx.message.text;
|
|
224
|
+
const from = ctx.from.username || ctx.from.first_name || 'User';
|
|
225
|
+
|
|
219
226
|
const message: TelegramMessage = {
|
|
220
|
-
chatId
|
|
221
|
-
text:
|
|
222
|
-
from
|
|
227
|
+
chatId,
|
|
228
|
+
text: userText,
|
|
229
|
+
from,
|
|
223
230
|
};
|
|
224
231
|
|
|
225
232
|
this.emit('message', message);
|
|
226
|
-
|
|
233
|
+
|
|
234
|
+
// Get or initialize chat history for this user
|
|
235
|
+
if (!this.chatHistory.has(chatId)) {
|
|
236
|
+
this.chatHistory.set(chatId, []);
|
|
237
|
+
}
|
|
238
|
+
const history = this.chatHistory.get(chatId)!;
|
|
239
|
+
|
|
240
|
+
// Add user message to history
|
|
241
|
+
history.push({ role: 'user', content: userText });
|
|
242
|
+
|
|
243
|
+
// Keep only last 10 messages for context
|
|
244
|
+
if (history.length > 10) {
|
|
245
|
+
history.splice(0, history.length - 10);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
// Send typing indicator
|
|
250
|
+
await ctx.sendChatAction('typing');
|
|
251
|
+
|
|
252
|
+
// Check if this looks like a computer control request
|
|
253
|
+
const computerControlResult = await this.tryComputerControl(userText);
|
|
254
|
+
if (computerControlResult) {
|
|
255
|
+
await ctx.reply(computerControlResult);
|
|
256
|
+
history.push({ role: 'assistant', content: computerControlResult });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Check if this looks like a screen/vision request
|
|
261
|
+
const isVisionRequest = /screen|see|look|what('?s| is) (on|visible)|show me|screenshot/i.test(userText);
|
|
262
|
+
|
|
263
|
+
let response;
|
|
264
|
+
if (isVisionRequest) {
|
|
265
|
+
// Capture screenshot and use vision
|
|
266
|
+
const screenshot = await captureScreenshot();
|
|
267
|
+
if (screenshot) {
|
|
268
|
+
response = await chatWithVision(history, screenshot);
|
|
269
|
+
} else {
|
|
270
|
+
response = await chatWithAI(history);
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
response = await chatWithAI(history);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Add assistant response to history
|
|
277
|
+
history.push({ role: 'assistant', content: response.content });
|
|
278
|
+
|
|
279
|
+
// Send response (split if too long for Telegram)
|
|
280
|
+
const responseText = response.content || '(no response)';
|
|
281
|
+
if (responseText.length > 4000) {
|
|
282
|
+
// Split into chunks
|
|
283
|
+
const chunks = responseText.match(/.{1,4000}/gs) || [responseText];
|
|
284
|
+
for (const chunk of chunks) {
|
|
285
|
+
await ctx.reply(chunk);
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
await ctx.reply(responseText);
|
|
289
|
+
}
|
|
290
|
+
} catch (error) {
|
|
291
|
+
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
292
|
+
await ctx.reply(`❌ Error: ${errorMsg}`);
|
|
293
|
+
this.emit('error', new Error(errorMsg));
|
|
294
|
+
}
|
|
227
295
|
});
|
|
228
296
|
|
|
229
297
|
// Error handling
|
|
@@ -243,6 +311,117 @@ export class TelegramBotService extends EventEmitter {
|
|
|
243
311
|
return this.allowedChatIds.has(chatId);
|
|
244
312
|
}
|
|
245
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Try to execute computer control commands directly
|
|
316
|
+
* Returns response string if handled, null if not a computer command
|
|
317
|
+
*/
|
|
318
|
+
private async tryComputerControl(text: string): Promise<string | null> {
|
|
319
|
+
const lower = text.toLowerCase();
|
|
320
|
+
|
|
321
|
+
// Minimize window
|
|
322
|
+
let match = lower.match(/minimize\s+(?:the\s+)?(.+)/i);
|
|
323
|
+
if (match) {
|
|
324
|
+
const result = await computer.minimizeWindow(match[1].trim());
|
|
325
|
+
return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Maximize window
|
|
329
|
+
match = lower.match(/maximize\s+(?:the\s+)?(.+)/i);
|
|
330
|
+
if (match) {
|
|
331
|
+
const result = await computer.maximizeWindow(match[1].trim());
|
|
332
|
+
return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Close window
|
|
336
|
+
match = lower.match(/close\s+(?:the\s+)?(.+)/i);
|
|
337
|
+
if (match) {
|
|
338
|
+
const result = await computer.closeWindow(match[1].trim());
|
|
339
|
+
return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Restore window
|
|
343
|
+
match = lower.match(/restore\s+(?:the\s+)?(.+)/i);
|
|
344
|
+
if (match) {
|
|
345
|
+
const result = await computer.restoreWindow(match[1].trim());
|
|
346
|
+
return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Focus/open window
|
|
350
|
+
match = lower.match(/(?:focus|open|switch to)\s+(?:the\s+)?(.+)/i);
|
|
351
|
+
if (match) {
|
|
352
|
+
const result = await computer.focusWindow(match[1].trim());
|
|
353
|
+
return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Type text
|
|
357
|
+
match = text.match(/type\s+["'](.+)["']/i);
|
|
358
|
+
if (match) {
|
|
359
|
+
const result = await computer.typeText(match[1]);
|
|
360
|
+
return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Press key
|
|
364
|
+
match = lower.match(/press\s+(?:the\s+)?(\w+)/i);
|
|
365
|
+
if (match) {
|
|
366
|
+
const result = await computer.pressKey(match[1]);
|
|
367
|
+
return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Click
|
|
371
|
+
if (/^click$/i.test(lower) || /click\s+(?:the\s+)?mouse/i.test(lower)) {
|
|
372
|
+
const result = await computer.clickMouse('left');
|
|
373
|
+
return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Right click
|
|
377
|
+
if (/right\s*click/i.test(lower)) {
|
|
378
|
+
const result = await computer.clickMouse('right');
|
|
379
|
+
return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Double click
|
|
383
|
+
if (/double\s*click/i.test(lower)) {
|
|
384
|
+
const result = await computer.doubleClick();
|
|
385
|
+
return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Move mouse to coordinates
|
|
389
|
+
match = lower.match(/move\s+(?:the\s+)?mouse\s+(?:to\s+)?(\d+)[,\s]+(\d+)/i);
|
|
390
|
+
if (match) {
|
|
391
|
+
const result = await computer.moveMouse(parseInt(match[1]), parseInt(match[2]));
|
|
392
|
+
return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Scroll
|
|
396
|
+
match = lower.match(/scroll\s+(up|down)(?:\s+(\d+))?/i);
|
|
397
|
+
if (match) {
|
|
398
|
+
const amount = match[1] === 'up' ? (parseInt(match[2]) || 3) : -(parseInt(match[2]) || 3);
|
|
399
|
+
const result = await computer.scrollMouse(amount);
|
|
400
|
+
return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// List windows
|
|
404
|
+
if (/list\s+(?:all\s+)?windows/i.test(lower) || /what\s+windows/i.test(lower)) {
|
|
405
|
+
const result = await computer.listWindows();
|
|
406
|
+
return result.success ? `📋 Open Windows:\n${result.output}` : `❌ ${result.error}`;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Get active window
|
|
410
|
+
if (/(?:active|current|focused)\s+window/i.test(lower) || /what\s+(?:window|app)/i.test(lower)) {
|
|
411
|
+
const result = await computer.getActiveWindow();
|
|
412
|
+
return result.success ? `🪟 Active: ${result.output}` : `❌ ${result.error}`;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Mouse position
|
|
416
|
+
if (/mouse\s+position/i.test(lower) || /where.*mouse/i.test(lower)) {
|
|
417
|
+
const result = await computer.getMousePosition();
|
|
418
|
+
return result.success ? `🖱️ ${result.output}` : `❌ ${result.error}`;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Not a computer control command
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
|
|
246
425
|
/**
|
|
247
426
|
* Send a message to a specific chat
|
|
248
427
|
*/
|
package/src/tools/computer.ts
CHANGED
|
@@ -288,6 +288,188 @@ export async function focusWindow(title: string): Promise<ToolResult> {
|
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Minimize a window by title (or active window if no title)
|
|
293
|
+
*/
|
|
294
|
+
export async function minimizeWindow(title?: string): Promise<ToolResult> {
|
|
295
|
+
try {
|
|
296
|
+
if (process.platform === 'win32') {
|
|
297
|
+
if (title) {
|
|
298
|
+
const escaped = title.replace(/'/g, "''");
|
|
299
|
+
const script = `
|
|
300
|
+
$proc = Get-Process | Where-Object { $_.MainWindowTitle -like '*${escaped}*' -and $_.MainWindowHandle -ne 0 } | Select-Object -First 1
|
|
301
|
+
if ($proc) {
|
|
302
|
+
Add-Type @"
|
|
303
|
+
using System;
|
|
304
|
+
using System.Runtime.InteropServices;
|
|
305
|
+
public class Win32 {
|
|
306
|
+
[DllImport("user32.dll")]
|
|
307
|
+
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
308
|
+
}
|
|
309
|
+
"@
|
|
310
|
+
[Win32]::ShowWindow($proc.MainWindowHandle, 6)
|
|
311
|
+
Write-Output "Minimized: $($proc.MainWindowTitle)"
|
|
312
|
+
} else {
|
|
313
|
+
Write-Output "NOT_FOUND"
|
|
314
|
+
}`;
|
|
315
|
+
const { stdout } = await execAsync(`powershell -Command "${script.replace(/\n/g, ' ')}"`, { shell: 'cmd.exe' });
|
|
316
|
+
if (stdout.includes('NOT_FOUND')) {
|
|
317
|
+
return err(`Window containing "${title}" not found`);
|
|
318
|
+
}
|
|
319
|
+
return ok(stdout.trim());
|
|
320
|
+
} else {
|
|
321
|
+
// Minimize active window using Alt+Space, N
|
|
322
|
+
await execAsync(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('% n')"`, { shell: 'cmd.exe' });
|
|
323
|
+
return ok('Minimized active window');
|
|
324
|
+
}
|
|
325
|
+
} else if (process.platform === 'darwin') {
|
|
326
|
+
if (title) {
|
|
327
|
+
await execAsync(`osascript -e 'tell application "${title}" to set miniaturized of window 1 to true'`);
|
|
328
|
+
} else {
|
|
329
|
+
await execAsync(`osascript -e 'tell application "System Events" to keystroke "m" using command down'`);
|
|
330
|
+
}
|
|
331
|
+
return ok(`Minimized window${title ? `: ${title}` : ''}`);
|
|
332
|
+
} else {
|
|
333
|
+
if (title) {
|
|
334
|
+
await execAsync(`wmctrl -r "${title}" -b add,hidden`);
|
|
335
|
+
} else {
|
|
336
|
+
await execAsync(`xdotool getactivewindow windowminimize`);
|
|
337
|
+
}
|
|
338
|
+
return ok(`Minimized window${title ? `: ${title}` : ''}`);
|
|
339
|
+
}
|
|
340
|
+
} catch (error) {
|
|
341
|
+
return err(`Failed to minimize window: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Maximize a window by title (or active window if no title)
|
|
347
|
+
*/
|
|
348
|
+
export async function maximizeWindow(title?: string): Promise<ToolResult> {
|
|
349
|
+
try {
|
|
350
|
+
if (process.platform === 'win32') {
|
|
351
|
+
if (title) {
|
|
352
|
+
const escaped = title.replace(/'/g, "''");
|
|
353
|
+
const script = `
|
|
354
|
+
$proc = Get-Process | Where-Object { $_.MainWindowTitle -like '*${escaped}*' -and $_.MainWindowHandle -ne 0 } | Select-Object -First 1
|
|
355
|
+
if ($proc) {
|
|
356
|
+
Add-Type @"
|
|
357
|
+
using System;
|
|
358
|
+
using System.Runtime.InteropServices;
|
|
359
|
+
public class Win32 {
|
|
360
|
+
[DllImport("user32.dll")]
|
|
361
|
+
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
362
|
+
}
|
|
363
|
+
"@
|
|
364
|
+
[Win32]::ShowWindow($proc.MainWindowHandle, 3)
|
|
365
|
+
Write-Output "Maximized: $($proc.MainWindowTitle)"
|
|
366
|
+
} else {
|
|
367
|
+
Write-Output "NOT_FOUND"
|
|
368
|
+
}`;
|
|
369
|
+
const { stdout } = await execAsync(`powershell -Command "${script.replace(/\n/g, ' ')}"`, { shell: 'cmd.exe' });
|
|
370
|
+
if (stdout.includes('NOT_FOUND')) {
|
|
371
|
+
return err(`Window containing "${title}" not found`);
|
|
372
|
+
}
|
|
373
|
+
return ok(stdout.trim());
|
|
374
|
+
} else {
|
|
375
|
+
// Maximize active window using Alt+Space, X
|
|
376
|
+
await execAsync(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('% x')"`, { shell: 'cmd.exe' });
|
|
377
|
+
return ok('Maximized active window');
|
|
378
|
+
}
|
|
379
|
+
} else if (process.platform === 'darwin') {
|
|
380
|
+
if (title) {
|
|
381
|
+
await execAsync(`osascript -e 'tell application "${title}" to set zoomed of window 1 to true'`);
|
|
382
|
+
} else {
|
|
383
|
+
await execAsync(`osascript -e 'tell application "System Events" to keystroke "f" using {control down, command down}'`);
|
|
384
|
+
}
|
|
385
|
+
return ok(`Maximized window${title ? `: ${title}` : ''}`);
|
|
386
|
+
} else {
|
|
387
|
+
if (title) {
|
|
388
|
+
await execAsync(`wmctrl -r "${title}" -b add,maximized_vert,maximized_horz`);
|
|
389
|
+
} else {
|
|
390
|
+
await execAsync(`wmctrl -r :ACTIVE: -b add,maximized_vert,maximized_horz`);
|
|
391
|
+
}
|
|
392
|
+
return ok(`Maximized window${title ? `: ${title}` : ''}`);
|
|
393
|
+
}
|
|
394
|
+
} catch (error) {
|
|
395
|
+
return err(`Failed to maximize window: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Close a window by title (or active window if no title)
|
|
401
|
+
*/
|
|
402
|
+
export async function closeWindow(title?: string): Promise<ToolResult> {
|
|
403
|
+
try {
|
|
404
|
+
if (process.platform === 'win32') {
|
|
405
|
+
if (title) {
|
|
406
|
+
const escaped = title.replace(/'/g, "''");
|
|
407
|
+
await execAsync(`powershell -Command "Get-Process | Where-Object { $_.MainWindowTitle -like '*${escaped}*' } | ForEach-Object { $_.CloseMainWindow() }"`, { shell: 'cmd.exe' });
|
|
408
|
+
return ok(`Closed window: ${title}`);
|
|
409
|
+
} else {
|
|
410
|
+
await execAsync(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('%{F4}')"`, { shell: 'cmd.exe' });
|
|
411
|
+
return ok('Closed active window');
|
|
412
|
+
}
|
|
413
|
+
} else if (process.platform === 'darwin') {
|
|
414
|
+
if (title) {
|
|
415
|
+
await execAsync(`osascript -e 'tell application "${title}" to close window 1'`);
|
|
416
|
+
} else {
|
|
417
|
+
await execAsync(`osascript -e 'tell application "System Events" to keystroke "w" using command down'`);
|
|
418
|
+
}
|
|
419
|
+
return ok(`Closed window${title ? `: ${title}` : ''}`);
|
|
420
|
+
} else {
|
|
421
|
+
if (title) {
|
|
422
|
+
await execAsync(`wmctrl -c "${title}"`);
|
|
423
|
+
} else {
|
|
424
|
+
await execAsync(`xdotool getactivewindow windowclose`);
|
|
425
|
+
}
|
|
426
|
+
return ok(`Closed window${title ? `: ${title}` : ''}`);
|
|
427
|
+
}
|
|
428
|
+
} catch (error) {
|
|
429
|
+
return err(`Failed to close window: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Restore a minimized window by title
|
|
435
|
+
*/
|
|
436
|
+
export async function restoreWindow(title: string): Promise<ToolResult> {
|
|
437
|
+
try {
|
|
438
|
+
if (process.platform === 'win32') {
|
|
439
|
+
const escaped = title.replace(/'/g, "''");
|
|
440
|
+
const script = `
|
|
441
|
+
$proc = Get-Process | Where-Object { $_.MainWindowTitle -like '*${escaped}*' -and $_.MainWindowHandle -ne 0 } | Select-Object -First 1
|
|
442
|
+
if ($proc) {
|
|
443
|
+
Add-Type @"
|
|
444
|
+
using System;
|
|
445
|
+
using System.Runtime.InteropServices;
|
|
446
|
+
public class Win32 {
|
|
447
|
+
[DllImport("user32.dll")]
|
|
448
|
+
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
449
|
+
}
|
|
450
|
+
"@
|
|
451
|
+
[Win32]::ShowWindow($proc.MainWindowHandle, 9)
|
|
452
|
+
Write-Output "Restored: $($proc.MainWindowTitle)"
|
|
453
|
+
} else {
|
|
454
|
+
Write-Output "NOT_FOUND"
|
|
455
|
+
}`;
|
|
456
|
+
const { stdout } = await execAsync(`powershell -Command "${script.replace(/\n/g, ' ')}"`, { shell: 'cmd.exe' });
|
|
457
|
+
if (stdout.includes('NOT_FOUND')) {
|
|
458
|
+
return err(`Window containing "${title}" not found`);
|
|
459
|
+
}
|
|
460
|
+
return ok(stdout.trim());
|
|
461
|
+
} else if (process.platform === 'darwin') {
|
|
462
|
+
await execAsync(`osascript -e 'tell application "${title}" to set miniaturized of window 1 to false'`);
|
|
463
|
+
return ok(`Restored window: ${title}`);
|
|
464
|
+
} else {
|
|
465
|
+
await execAsync(`wmctrl -r "${title}" -b remove,hidden`);
|
|
466
|
+
return ok(`Restored window: ${title}`);
|
|
467
|
+
}
|
|
468
|
+
} catch (error) {
|
|
469
|
+
return err(`Failed to restore window: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
291
473
|
/**
|
|
292
474
|
* Scroll mouse wheel
|
|
293
475
|
*/
|
|
@@ -383,6 +565,10 @@ export function getComputerTools() {
|
|
|
383
565
|
getActiveWindow,
|
|
384
566
|
listWindows,
|
|
385
567
|
focusWindow,
|
|
568
|
+
minimizeWindow,
|
|
569
|
+
maximizeWindow,
|
|
570
|
+
closeWindow,
|
|
571
|
+
restoreWindow,
|
|
386
572
|
scrollMouse,
|
|
387
573
|
dragMouse,
|
|
388
574
|
getMousePosition,
|
|
@@ -436,6 +622,26 @@ export const computerTools = [
|
|
|
436
622
|
description: 'Focus a window by title',
|
|
437
623
|
parameters: { type: 'object', properties: { title: { type: 'string' } }, required: ['title'] },
|
|
438
624
|
},
|
|
625
|
+
{
|
|
626
|
+
name: 'minimizeWindow',
|
|
627
|
+
description: 'Minimize a window by title (or active window if no title given)',
|
|
628
|
+
parameters: { type: 'object', properties: { title: { type: 'string', description: 'Window title to minimize (partial match). Leave empty for active window.' } } },
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
name: 'maximizeWindow',
|
|
632
|
+
description: 'Maximize a window by title (or active window if no title given)',
|
|
633
|
+
parameters: { type: 'object', properties: { title: { type: 'string', description: 'Window title to maximize (partial match). Leave empty for active window.' } } },
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
name: 'closeWindow',
|
|
637
|
+
description: 'Close a window by title (or active window if no title given)',
|
|
638
|
+
parameters: { type: 'object', properties: { title: { type: 'string', description: 'Window title to close (partial match). Leave empty for active window.' } } },
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
name: 'restoreWindow',
|
|
642
|
+
description: 'Restore a minimized window by title',
|
|
643
|
+
parameters: { type: 'object', properties: { title: { type: 'string', description: 'Window title to restore (partial match)' } }, required: ['title'] },
|
|
644
|
+
},
|
|
439
645
|
{
|
|
440
646
|
name: 'scrollMouse',
|
|
441
647
|
description: 'Scroll mouse wheel (positive=up, negative=down)',
|