@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 +710 -16
- package/package.json +1 -1
- package/src/components/App.tsx +20 -1
- package/src/lib/tasks.ts +243 -1
- package/src/services/telegram.ts +218 -3
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:
|
|
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 =
|
|
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
|
|
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
|
|
2064
|
+
await sendFormattedMessage(ctx, chunk);
|
|
1663
2065
|
}
|
|
1664
2066
|
} else {
|
|
1665
|
-
await ctx
|
|
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
|
|
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 (
|
|
1788
|
-
const data =
|
|
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 (!
|
|
1816
|
-
|
|
2296
|
+
if (!fs2.existsSync(dir)) {
|
|
2297
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1817
2298
|
}
|
|
1818
|
-
|
|
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 (
|
|
2074
|
-
|
|
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]);
|