@projectservan8n/cnapse 0.8.0 → 0.8.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 CHANGED
@@ -1458,648 +1458,187 @@ ${stderr}`
1458
1458
  }
1459
1459
  }
1460
1460
 
1461
- // src/services/telegram.ts
1462
- function formatForTelegram(text) {
1463
- const hasMarkdown = /[*_`\[\]()]/.test(text);
1464
- if (!hasMarkdown) {
1465
- return { text, parseMode: void 0 };
1466
- }
1467
- try {
1468
- let formatted = text;
1469
- const escapeChars = ["\\", "_", "*", "[", "]", "(", ")", "~", "`", ">", "#", "+", "-", "=", "|", "{", "}", ".", "!"];
1470
- const placeholders = [];
1471
- let placeholderIndex = 0;
1472
- formatted = formatted.replace(/```([\s\S]*?)```/g, (match, code) => {
1473
- const placeholder = `__CODEBLOCK_${placeholderIndex++}__`;
1474
- placeholders.push({ placeholder, original: "```" + code.replace(/\\/g, "\\\\") + "```" });
1475
- return placeholder;
1476
- });
1477
- formatted = formatted.replace(/`([^`]+)`/g, (match, code) => {
1478
- const placeholder = `__INLINECODE_${placeholderIndex++}__`;
1479
- placeholders.push({ placeholder, original: "`" + code.replace(/\\/g, "\\\\") + "`" });
1480
- return placeholder;
1481
- });
1482
- formatted = formatted.replace(/\*\*(.+?)\*\*/g, (match, text2) => {
1483
- const placeholder = `__BOLD_${placeholderIndex++}__`;
1484
- placeholders.push({ placeholder, original: "*" + text2 + "*" });
1485
- return placeholder;
1486
- });
1487
- formatted = formatted.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, (match, text2) => {
1488
- const placeholder = `__ITALIC_${placeholderIndex++}__`;
1489
- placeholders.push({ placeholder, original: "_" + text2 + "_" });
1490
- return placeholder;
1461
+ // src/services/browser.ts
1462
+ import { chromium } from "playwright";
1463
+ var browser = null;
1464
+ var context = null;
1465
+ var activePage = null;
1466
+ var defaultConfig = {
1467
+ headless: false,
1468
+ // Show browser so user can see what's happening
1469
+ slowMo: 50,
1470
+ // Slight delay for visibility
1471
+ viewport: { width: 1280, height: 800 }
1472
+ };
1473
+ async function initBrowser(config = {}) {
1474
+ const cfg = { ...defaultConfig, ...config };
1475
+ if (!browser) {
1476
+ browser = await chromium.launch({
1477
+ headless: cfg.headless,
1478
+ slowMo: cfg.slowMo
1491
1479
  });
1492
- formatted = formatted.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text2, url) => {
1493
- const placeholder = `__LINK_${placeholderIndex++}__`;
1494
- placeholders.push({ placeholder, original: "[" + text2 + "](" + url + ")" });
1495
- return placeholder;
1480
+ }
1481
+ if (!context) {
1482
+ context = await browser.newContext({
1483
+ viewport: cfg.viewport,
1484
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
1496
1485
  });
1497
- for (const char of escapeChars) {
1498
- if (char === "\\") continue;
1499
- formatted = formatted.split(char).join("\\" + char);
1500
- }
1501
- for (const { placeholder, original } of placeholders) {
1502
- formatted = formatted.replace(placeholder, original);
1503
- }
1504
- return { text: formatted, parseMode: "MarkdownV2" };
1486
+ }
1487
+ if (!activePage) {
1488
+ activePage = await context.newPage();
1489
+ }
1490
+ return activePage;
1491
+ }
1492
+ async function getPage() {
1493
+ if (!activePage) {
1494
+ return initBrowser();
1495
+ }
1496
+ return activePage;
1497
+ }
1498
+ async function navigateTo(url) {
1499
+ const page = await getPage();
1500
+ await page.goto(url, { waitUntil: "domcontentloaded" });
1501
+ }
1502
+ async function takeScreenshot() {
1503
+ const page = await getPage();
1504
+ const buffer = await page.screenshot({ type: "png" });
1505
+ return buffer.toString("base64");
1506
+ }
1507
+ async function clickElement(selector, timeout = 1e4) {
1508
+ const page = await getPage();
1509
+ try {
1510
+ await page.click(selector, { timeout });
1511
+ return true;
1505
1512
  } catch {
1506
- return { text, parseMode: void 0 };
1513
+ return false;
1507
1514
  }
1508
1515
  }
1509
- async function sendFormattedMessage(ctx, text) {
1510
- const { text: formatted, parseMode } = formatForTelegram(text);
1516
+ async function typeInElement(selector, text, timeout = 1e4) {
1517
+ const page = await getPage();
1511
1518
  try {
1512
- if (parseMode) {
1513
- await ctx.reply(formatted, { parse_mode: parseMode });
1514
- } else {
1515
- await ctx.reply(text);
1516
- }
1519
+ await page.fill(selector, text, { timeout });
1520
+ return true;
1517
1521
  } catch {
1518
- await ctx.reply(text);
1522
+ return false;
1519
1523
  }
1520
1524
  }
1521
- var TelegramBotService = class extends EventEmitter {
1522
- bot = null;
1523
- isRunning = false;
1524
- allowedChatIds = /* @__PURE__ */ new Set();
1525
- chatHistory = /* @__PURE__ */ new Map();
1526
- constructor() {
1527
- super();
1525
+ async function pressKey2(key) {
1526
+ const page = await getPage();
1527
+ await page.keyboard.press(key);
1528
+ }
1529
+ async function scroll(direction, amount = 500) {
1530
+ const page = await getPage();
1531
+ await page.mouse.wheel(0, direction === "down" ? amount : -amount);
1532
+ }
1533
+ async function getPageText() {
1534
+ const page = await getPage();
1535
+ return await page.evaluate(() => document.body.innerText);
1536
+ }
1537
+ async function elementExists(selector) {
1538
+ const page = await getPage();
1539
+ try {
1540
+ const element = await page.$(selector);
1541
+ return element !== null;
1542
+ } catch {
1543
+ return false;
1528
1544
  }
1529
- /**
1530
- * Start the Telegram bot
1531
- */
1532
- async start() {
1533
- if (this.isRunning) {
1534
- return;
1535
- }
1536
- const botToken = getApiKey("telegram");
1537
- if (!botToken) {
1538
- throw new Error("Telegram bot token not configured. Use: cnapse auth telegram YOUR_BOT_TOKEN");
1545
+ }
1546
+ var aiChatConfigs = {
1547
+ perplexity: {
1548
+ url: "https://www.perplexity.ai",
1549
+ inputSelector: 'textarea[placeholder*="Ask"]',
1550
+ submitKey: "Enter",
1551
+ responseSelector: '.prose, [class*="answer"], [class*="response"]',
1552
+ waitForResponse: 15e3
1553
+ },
1554
+ chatgpt: {
1555
+ url: "https://chat.openai.com",
1556
+ inputSelector: 'textarea[id="prompt-textarea"], textarea[data-id="root"]',
1557
+ submitSelector: 'button[data-testid="send-button"]',
1558
+ responseSelector: '[data-message-author-role="assistant"]',
1559
+ waitForResponse: 2e4
1560
+ },
1561
+ claude: {
1562
+ url: "https://claude.ai",
1563
+ inputSelector: '[contenteditable="true"], textarea',
1564
+ submitKey: "Enter",
1565
+ responseSelector: '[data-testid="message-content"]',
1566
+ waitForResponse: 2e4
1567
+ },
1568
+ copilot: {
1569
+ url: "https://copilot.microsoft.com",
1570
+ inputSelector: 'textarea, [contenteditable="true"]',
1571
+ submitKey: "Enter",
1572
+ responseSelector: '[class*="response"], [class*="message"]',
1573
+ waitForResponse: 15e3
1574
+ },
1575
+ google: {
1576
+ url: "https://www.google.com",
1577
+ inputSelector: 'textarea[name="q"], input[name="q"]',
1578
+ submitKey: "Enter",
1579
+ responseSelector: "#search",
1580
+ waitForResponse: 5e3
1581
+ }
1582
+ };
1583
+ async function askAI(site, question, includeScreenshot = false) {
1584
+ const config = aiChatConfigs[site];
1585
+ if (!config) {
1586
+ throw new Error(`Unknown AI site: ${site}`);
1587
+ }
1588
+ const page = await getPage();
1589
+ await page.goto(config.url, { waitUntil: "domcontentloaded" });
1590
+ await page.waitForTimeout(2e3);
1591
+ try {
1592
+ await page.waitForSelector(config.inputSelector, { timeout: 1e4 });
1593
+ await page.fill(config.inputSelector, question);
1594
+ } catch {
1595
+ await page.click(config.inputSelector);
1596
+ await page.type(config.inputSelector, question, { delay: 30 });
1597
+ }
1598
+ if (config.submitSelector) {
1599
+ await page.click(config.submitSelector);
1600
+ } else if (config.submitKey) {
1601
+ await page.keyboard.press(config.submitKey);
1602
+ }
1603
+ await page.waitForTimeout(config.waitForResponse);
1604
+ let response = "";
1605
+ try {
1606
+ const elements = await page.$$(config.responseSelector);
1607
+ if (elements.length > 0) {
1608
+ const lastElement = elements[elements.length - 1];
1609
+ response = await lastElement.textContent() || "";
1539
1610
  }
1611
+ } catch {
1612
+ response = await getPageText();
1613
+ }
1614
+ let screenshot;
1615
+ if (includeScreenshot) {
1616
+ screenshot = await takeScreenshot();
1617
+ }
1618
+ return { response: response.trim(), screenshot };
1619
+ }
1620
+ async function getFullAIResponse(site, maxScrolls = 5) {
1621
+ const config = aiChatConfigs[site];
1622
+ const page = await getPage();
1623
+ const responseParts = [];
1624
+ for (let i = 0; i < maxScrolls; i++) {
1540
1625
  try {
1541
- const { Telegraf } = await import("telegraf");
1542
- this.bot = new Telegraf(botToken);
1543
- const config = getConfig();
1544
- if (config.telegram?.chatId) {
1545
- this.allowedChatIds.add(config.telegram.chatId);
1626
+ const elements = await page.$$(config.responseSelector);
1627
+ if (elements.length > 0) {
1628
+ const lastElement = elements[elements.length - 1];
1629
+ const text = await lastElement.textContent();
1630
+ if (text) {
1631
+ responseParts.push(text.trim());
1632
+ }
1546
1633
  }
1547
- this.setupHandlers();
1548
- await this.bot.launch();
1549
- this.isRunning = true;
1550
- this.emit("started");
1551
- } catch (error) {
1552
- throw new Error(`Failed to start Telegram bot: ${error instanceof Error ? error.message : "Unknown error"}`);
1553
- }
1554
- }
1555
- /**
1556
- * Stop the Telegram bot
1557
- */
1558
- async stop() {
1559
- if (!this.isRunning || !this.bot) {
1560
- return;
1561
- }
1562
- this.bot.stop("SIGTERM");
1563
- this.isRunning = false;
1564
- this.bot = null;
1565
- this.emit("stopped");
1566
- }
1567
- /**
1568
- * Check if bot is running
1569
- */
1570
- get running() {
1571
- return this.isRunning;
1572
- }
1573
- /**
1574
- * Setup message and command handlers
1575
- */
1576
- setupHandlers() {
1577
- if (!this.bot) return;
1578
- this.bot.command("start", async (ctx) => {
1579
- const chatId = ctx.chat.id;
1580
- this.allowedChatIds.add(chatId);
1581
- await ctx.reply(
1582
- `\u{1F916} C-napse connected!
1583
-
1584
- Commands:
1585
- /screen - Take screenshot
1586
- /describe - Screenshot + AI description
1587
- /run <cmd> - Execute command
1588
- /status - System status
1589
-
1590
- Your chat ID: ${chatId}`
1591
- );
1592
- });
1593
- this.bot.command("screen", async (ctx) => {
1594
- if (!this.isAllowed(ctx.chat.id)) {
1595
- await ctx.reply("\u26D4 Not authorized. Send /start first.");
1596
- return;
1597
- }
1598
- await ctx.reply("\u{1F4F8} Taking screenshot...");
1599
- try {
1600
- const screenshot = await captureScreenshot();
1601
- if (!screenshot) {
1602
- await ctx.reply("\u274C Failed to capture screenshot");
1603
- return;
1604
- }
1605
- const buffer = Buffer.from(screenshot, "base64");
1606
- await ctx.replyWithPhoto({ source: buffer }, { caption: "\u{1F4F8} Current screen" });
1607
- } catch (error) {
1608
- await ctx.reply(`\u274C Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1609
- }
1610
- });
1611
- this.bot.command("describe", async (ctx) => {
1612
- if (!this.isAllowed(ctx.chat.id)) {
1613
- await ctx.reply("\u26D4 Not authorized. Send /start first.");
1614
- return;
1615
- }
1616
- await ctx.reply("\u{1F50D} Analyzing screen...");
1617
- try {
1618
- const result = await describeScreen();
1619
- const buffer = Buffer.from(result.screenshot, "base64");
1620
- const caption = `\u{1F5A5}\uFE0F Screen Analysis:
1621
-
1622
- ${result.description}`.slice(0, 1024);
1623
- await ctx.replyWithPhoto({ source: buffer }, { caption });
1624
- if (result.description.length > 900) {
1625
- await ctx.reply(result.description);
1626
- }
1627
- } catch (error) {
1628
- await ctx.reply(`\u274C Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1629
- }
1630
- });
1631
- this.bot.command("run", async (ctx) => {
1632
- if (!this.isAllowed(ctx.chat.id)) {
1633
- await ctx.reply("\u26D4 Not authorized. Send /start first.");
1634
- return;
1635
- }
1636
- const cmd = ctx.message.text.replace("/run ", "").trim();
1637
- if (!cmd) {
1638
- await ctx.reply("Usage: /run <command>\nExample: /run dir");
1639
- return;
1640
- }
1641
- await ctx.reply(`\u2699\uFE0F Running: ${cmd}`);
1642
- try {
1643
- const result = await runCommand(cmd, 3e4);
1644
- if (result.success) {
1645
- const output = result.output.slice(0, 4e3) || "(no output)";
1646
- await ctx.reply(`\u2705 Output:
1647
- \`\`\`
1648
- ${output}
1649
- \`\`\``, { parse_mode: "Markdown" });
1650
- } else {
1651
- await ctx.reply(`\u274C Error:
1652
- \`\`\`
1653
- ${result.error}
1654
- \`\`\``, { parse_mode: "Markdown" });
1655
- }
1656
- } catch (error) {
1657
- await ctx.reply(`\u274C Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1658
- }
1659
- });
1660
- this.bot.command("status", async (ctx) => {
1661
- if (!this.isAllowed(ctx.chat.id)) {
1662
- await ctx.reply("\u26D4 Not authorized. Send /start first.");
1663
- return;
1664
- }
1665
- const config = getConfig();
1666
- const status = [
1667
- "\u{1F4CA} C-napse Status",
1668
- "",
1669
- `Provider: ${config.provider}`,
1670
- `Model: ${config.model}`,
1671
- `Platform: ${process.platform}`,
1672
- `Node: ${process.version}`
1673
- ].join("\n");
1674
- await ctx.reply(status);
1675
- });
1676
- this.bot.on("text", async (ctx) => {
1677
- if (!this.isAllowed(ctx.chat.id)) {
1678
- return;
1679
- }
1680
- if (ctx.message.text.startsWith("/")) {
1681
- return;
1682
- }
1683
- const chatId = ctx.chat.id;
1684
- const userText = ctx.message.text;
1685
- const from = ctx.from.username || ctx.from.first_name || "User";
1686
- const message = {
1687
- chatId,
1688
- text: userText,
1689
- from
1690
- };
1691
- this.emit("message", message);
1692
- if (!this.chatHistory.has(chatId)) {
1693
- this.chatHistory.set(chatId, []);
1694
- }
1695
- const history = this.chatHistory.get(chatId);
1696
- history.push({ role: "user", content: userText });
1697
- if (history.length > 10) {
1698
- history.splice(0, history.length - 10);
1699
- }
1700
- try {
1701
- await ctx.sendChatAction("typing");
1702
- const computerControlResult = await this.tryComputerControl(userText);
1703
- if (computerControlResult) {
1704
- await sendFormattedMessage(ctx, computerControlResult);
1705
- history.push({ role: "assistant", content: computerControlResult });
1706
- return;
1707
- }
1708
- const isVisionRequest = /screen|see|look|what('?s| is) (on|visible)|show me|screenshot/i.test(userText);
1709
- let response;
1710
- if (isVisionRequest) {
1711
- const screenshot = await captureScreenshot();
1712
- if (screenshot) {
1713
- response = await chatWithVision(history, screenshot);
1714
- } else {
1715
- response = await chat(history);
1716
- }
1717
- } else {
1718
- response = await chat(history);
1719
- }
1720
- history.push({ role: "assistant", content: response.content });
1721
- const responseText = response.content || "(no response)";
1722
- if (responseText.length > 4e3) {
1723
- const chunks = responseText.match(/.{1,4000}/gs) || [responseText];
1724
- for (const chunk of chunks) {
1725
- await sendFormattedMessage(ctx, chunk);
1726
- }
1727
- } else {
1728
- await sendFormattedMessage(ctx, responseText);
1729
- }
1730
- } catch (error) {
1731
- const errorMsg = error instanceof Error ? error.message : "Unknown error";
1732
- await ctx.reply(`\u274C Error: ${errorMsg}`);
1733
- this.emit("error", new Error(errorMsg));
1734
- }
1735
- });
1736
- this.bot.catch((err2) => {
1737
- this.emit("error", err2);
1738
- });
1739
- }
1740
- /**
1741
- * Check if chat is authorized
1742
- */
1743
- isAllowed(chatId) {
1744
- if (this.allowedChatIds.size === 0) {
1745
- return true;
1746
- }
1747
- return this.allowedChatIds.has(chatId);
1748
- }
1749
- /**
1750
- * Try to execute computer control commands directly
1751
- * Returns response string if handled, null if not a computer command
1752
- */
1753
- async tryComputerControl(text) {
1754
- const lower = text.toLowerCase();
1755
- let match = lower.match(/minimize\s+(?:the\s+)?(.+)/i);
1756
- if (match) {
1757
- const result = await minimizeWindow(match[1].trim());
1758
- return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
1759
- }
1760
- match = lower.match(/maximize\s+(?:the\s+)?(.+)/i);
1761
- if (match) {
1762
- const result = await maximizeWindow(match[1].trim());
1763
- return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
1764
- }
1765
- match = lower.match(/close\s+(?:the\s+)?(.+)/i);
1766
- if (match) {
1767
- const result = await closeWindow(match[1].trim());
1768
- return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
1769
- }
1770
- match = lower.match(/restore\s+(?:the\s+)?(.+)/i);
1771
- if (match) {
1772
- const result = await restoreWindow(match[1].trim());
1773
- return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
1774
- }
1775
- match = lower.match(/(?:focus|open|switch to)\s+(?:the\s+)?(.+)/i);
1776
- if (match) {
1777
- const result = await focusWindow(match[1].trim());
1778
- return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
1779
- }
1780
- match = text.match(/type\s+["'](.+)["']/i);
1781
- if (match) {
1782
- const result = await typeText(match[1]);
1783
- return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
1784
- }
1785
- match = lower.match(/press\s+(?:the\s+)?(\w+)/i);
1786
- if (match) {
1787
- const result = await pressKey(match[1]);
1788
- return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
1789
- }
1790
- if (/^click$/i.test(lower) || /click\s+(?:the\s+)?mouse/i.test(lower)) {
1791
- const result = await clickMouse("left");
1792
- return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
1793
- }
1794
- if (/right\s*click/i.test(lower)) {
1795
- const result = await clickMouse("right");
1796
- return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
1797
- }
1798
- if (/double\s*click/i.test(lower)) {
1799
- const result = await doubleClick();
1800
- return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
1801
- }
1802
- match = lower.match(/move\s+(?:the\s+)?mouse\s+(?:to\s+)?(\d+)[,\s]+(\d+)/i);
1803
- if (match) {
1804
- const result = await moveMouse(parseInt(match[1]), parseInt(match[2]));
1805
- return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
1806
- }
1807
- match = lower.match(/scroll\s+(up|down)(?:\s+(\d+))?/i);
1808
- if (match) {
1809
- const amount = match[1] === "up" ? parseInt(match[2]) || 3 : -(parseInt(match[2]) || 3);
1810
- const result = await scrollMouse(amount);
1811
- return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
1812
- }
1813
- if (/list\s+(?:all\s+)?windows/i.test(lower) || /what\s+windows/i.test(lower)) {
1814
- const result = await listWindows();
1815
- return result.success ? `\u{1F4CB} Open Windows:
1816
- ${result.output}` : `\u274C ${result.error}`;
1817
- }
1818
- if (/(?:active|current|focused)\s+window/i.test(lower) || /what\s+(?:window|app)/i.test(lower)) {
1819
- const result = await getActiveWindow();
1820
- return result.success ? `\u{1FA9F} Active: ${result.output}` : `\u274C ${result.error}`;
1821
- }
1822
- if (/mouse\s+position/i.test(lower) || /where.*mouse/i.test(lower)) {
1823
- const result = await getMousePosition();
1824
- return result.success ? `\u{1F5B1}\uFE0F ${result.output}` : `\u274C ${result.error}`;
1825
- }
1826
- return null;
1827
- }
1828
- /**
1829
- * Send a message to a specific chat
1830
- */
1831
- async sendMessage(chatId, text) {
1832
- if (!this.bot || !this.isRunning) {
1833
- throw new Error("Telegram bot is not running");
1834
- }
1835
- await this.bot.telegram.sendMessage(chatId, text);
1836
- }
1837
- /**
1838
- * Send a photo to a specific chat
1839
- */
1840
- async sendPhoto(chatId, base64Image, caption) {
1841
- if (!this.bot || !this.isRunning) {
1842
- throw new Error("Telegram bot is not running");
1843
- }
1844
- const buffer = Buffer.from(base64Image, "base64");
1845
- await this.bot.telegram.sendPhoto(chatId, { source: buffer }, { caption });
1846
- }
1847
- };
1848
- var instance = null;
1849
- function getTelegramBot() {
1850
- if (!instance) {
1851
- instance = new TelegramBotService();
1852
- }
1853
- return instance;
1854
- }
1855
-
1856
- // src/hooks/useTelegram.ts
1857
- function useTelegram(onMessage) {
1858
- const [isEnabled, setIsEnabled] = useState4(false);
1859
- const [isStarting, setIsStarting] = useState4(false);
1860
- const [error, setError] = useState4(null);
1861
- const [lastMessage, setLastMessage] = useState4(null);
1862
- const onMessageRef = useRef2(onMessage);
1863
- useEffect2(() => {
1864
- onMessageRef.current = onMessage;
1865
- }, [onMessage]);
1866
- const start = useCallback3(async () => {
1867
- if (isEnabled) return;
1868
- setIsStarting(true);
1869
- setError(null);
1870
- try {
1871
- const bot = getTelegramBot();
1872
- bot.on("message", (msg) => {
1873
- setLastMessage(msg);
1874
- onMessageRef.current?.(msg);
1875
- });
1876
- bot.on("error", (err2) => {
1877
- setError(err2.message);
1878
- });
1879
- await bot.start();
1880
- setIsEnabled(true);
1881
- } catch (err2) {
1882
- const errorMsg = err2 instanceof Error ? err2.message : "Failed to start Telegram bot";
1883
- setError(errorMsg);
1884
- throw err2;
1885
- } finally {
1886
- setIsStarting(false);
1887
- }
1888
- }, [isEnabled]);
1889
- const stop = useCallback3(async () => {
1890
- if (!isEnabled) return;
1891
- try {
1892
- const bot = getTelegramBot();
1893
- await bot.stop();
1894
- setIsEnabled(false);
1895
- } catch (err2) {
1896
- const errorMsg = err2 instanceof Error ? err2.message : "Failed to stop Telegram bot";
1897
- setError(errorMsg);
1898
- throw err2;
1899
- }
1900
- }, [isEnabled]);
1901
- const toggle = useCallback3(async () => {
1902
- if (isEnabled) {
1903
- await stop();
1904
- } else {
1905
- await start();
1906
- }
1907
- }, [isEnabled, start, stop]);
1908
- return {
1909
- isEnabled,
1910
- isStarting,
1911
- error,
1912
- lastMessage,
1913
- toggle,
1914
- start,
1915
- stop
1916
- };
1917
- }
1918
-
1919
- // src/hooks/useTasks.ts
1920
- import { useState as useState5, useCallback as useCallback4 } from "react";
1921
-
1922
- // src/services/browser.ts
1923
- import { chromium } from "playwright";
1924
- var browser = null;
1925
- var context = null;
1926
- var activePage = null;
1927
- var defaultConfig = {
1928
- headless: false,
1929
- // Show browser so user can see what's happening
1930
- slowMo: 50,
1931
- // Slight delay for visibility
1932
- viewport: { width: 1280, height: 800 }
1933
- };
1934
- async function initBrowser(config = {}) {
1935
- const cfg = { ...defaultConfig, ...config };
1936
- if (!browser) {
1937
- browser = await chromium.launch({
1938
- headless: cfg.headless,
1939
- slowMo: cfg.slowMo
1940
- });
1941
- }
1942
- if (!context) {
1943
- context = await browser.newContext({
1944
- viewport: cfg.viewport,
1945
- userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
1946
- });
1947
- }
1948
- if (!activePage) {
1949
- activePage = await context.newPage();
1950
- }
1951
- return activePage;
1952
- }
1953
- async function getPage() {
1954
- if (!activePage) {
1955
- return initBrowser();
1956
- }
1957
- return activePage;
1958
- }
1959
- async function navigateTo(url) {
1960
- const page = await getPage();
1961
- await page.goto(url, { waitUntil: "domcontentloaded" });
1962
- }
1963
- async function takeScreenshot() {
1964
- const page = await getPage();
1965
- const buffer = await page.screenshot({ type: "png" });
1966
- return buffer.toString("base64");
1967
- }
1968
- async function clickElement(selector, timeout = 1e4) {
1969
- const page = await getPage();
1970
- try {
1971
- await page.click(selector, { timeout });
1972
- return true;
1973
- } catch {
1974
- return false;
1975
- }
1976
- }
1977
- async function typeInElement(selector, text, timeout = 1e4) {
1978
- const page = await getPage();
1979
- try {
1980
- await page.fill(selector, text, { timeout });
1981
- return true;
1982
- } catch {
1983
- return false;
1984
- }
1985
- }
1986
- async function pressKey2(key) {
1987
- const page = await getPage();
1988
- await page.keyboard.press(key);
1989
- }
1990
- async function scroll(direction, amount = 500) {
1991
- const page = await getPage();
1992
- await page.mouse.wheel(0, direction === "down" ? amount : -amount);
1993
- }
1994
- async function getPageText() {
1995
- const page = await getPage();
1996
- return await page.evaluate(() => document.body.innerText);
1997
- }
1998
- async function elementExists(selector) {
1999
- const page = await getPage();
2000
- try {
2001
- const element = await page.$(selector);
2002
- return element !== null;
2003
- } catch {
2004
- return false;
2005
- }
2006
- }
2007
- var aiChatConfigs = {
2008
- perplexity: {
2009
- url: "https://www.perplexity.ai",
2010
- inputSelector: 'textarea[placeholder*="Ask"]',
2011
- submitKey: "Enter",
2012
- responseSelector: '.prose, [class*="answer"], [class*="response"]',
2013
- waitForResponse: 15e3
2014
- },
2015
- chatgpt: {
2016
- url: "https://chat.openai.com",
2017
- inputSelector: 'textarea[id="prompt-textarea"], textarea[data-id="root"]',
2018
- submitSelector: 'button[data-testid="send-button"]',
2019
- responseSelector: '[data-message-author-role="assistant"]',
2020
- waitForResponse: 2e4
2021
- },
2022
- claude: {
2023
- url: "https://claude.ai",
2024
- inputSelector: '[contenteditable="true"], textarea',
2025
- submitKey: "Enter",
2026
- responseSelector: '[data-testid="message-content"]',
2027
- waitForResponse: 2e4
2028
- },
2029
- copilot: {
2030
- url: "https://copilot.microsoft.com",
2031
- inputSelector: 'textarea, [contenteditable="true"]',
2032
- submitKey: "Enter",
2033
- responseSelector: '[class*="response"], [class*="message"]',
2034
- waitForResponse: 15e3
2035
- },
2036
- google: {
2037
- url: "https://www.google.com",
2038
- inputSelector: 'textarea[name="q"], input[name="q"]',
2039
- submitKey: "Enter",
2040
- responseSelector: "#search",
2041
- waitForResponse: 5e3
2042
- }
2043
- };
2044
- async function askAI(site, question, includeScreenshot = false) {
2045
- const config = aiChatConfigs[site];
2046
- if (!config) {
2047
- throw new Error(`Unknown AI site: ${site}`);
2048
- }
2049
- const page = await getPage();
2050
- await page.goto(config.url, { waitUntil: "domcontentloaded" });
2051
- await page.waitForTimeout(2e3);
2052
- try {
2053
- await page.waitForSelector(config.inputSelector, { timeout: 1e4 });
2054
- await page.fill(config.inputSelector, question);
2055
- } catch {
2056
- await page.click(config.inputSelector);
2057
- await page.type(config.inputSelector, question, { delay: 30 });
2058
- }
2059
- if (config.submitSelector) {
2060
- await page.click(config.submitSelector);
2061
- } else if (config.submitKey) {
2062
- await page.keyboard.press(config.submitKey);
2063
- }
2064
- await page.waitForTimeout(config.waitForResponse);
2065
- let response = "";
2066
- try {
2067
- const elements = await page.$$(config.responseSelector);
2068
- if (elements.length > 0) {
2069
- const lastElement = elements[elements.length - 1];
2070
- response = await lastElement.textContent() || "";
2071
- }
2072
- } catch {
2073
- response = await getPageText();
2074
- }
2075
- let screenshot;
2076
- if (includeScreenshot) {
2077
- screenshot = await takeScreenshot();
2078
- }
2079
- return { response: response.trim(), screenshot };
2080
- }
2081
- async function getFullAIResponse(site, maxScrolls = 5) {
2082
- const config = aiChatConfigs[site];
2083
- const page = await getPage();
2084
- const responseParts = [];
2085
- for (let i = 0; i < maxScrolls; i++) {
2086
- try {
2087
- const elements = await page.$$(config.responseSelector);
2088
- if (elements.length > 0) {
2089
- const lastElement = elements[elements.length - 1];
2090
- const text = await lastElement.textContent();
2091
- if (text) {
2092
- responseParts.push(text.trim());
2093
- }
2094
- }
2095
- await page.mouse.wheel(0, 500);
2096
- await page.waitForTimeout(1e3);
2097
- const atBottom = await page.evaluate(() => {
2098
- return window.innerHeight + window.scrollY >= document.body.scrollHeight - 100;
2099
- });
2100
- if (atBottom) break;
2101
- } catch {
2102
- break;
1634
+ await page.mouse.wheel(0, 500);
1635
+ await page.waitForTimeout(1e3);
1636
+ const atBottom = await page.evaluate(() => {
1637
+ return window.innerHeight + window.scrollY >= document.body.scrollHeight - 100;
1638
+ });
1639
+ if (atBottom) break;
1640
+ } catch {
1641
+ break;
2103
1642
  }
2104
1643
  }
2105
1644
  return responseParts;
@@ -2902,256 +2441,787 @@ Create a well-organized summary with:
2902
2441
  3. Any notable facts or statistics
2903
2442
  4. Conclusion
2904
2443
 
2905
- Be thorough but concise.`
2906
- }]);
2907
- step.result = `\u{1F52C} Research Summary: ${researchQuery}
2444
+ Be thorough but concise.`
2445
+ }]);
2446
+ step.result = `\u{1F52C} Research Summary: ${researchQuery}
2447
+
2448
+ ${synthesis.content}`;
2449
+ break;
2450
+ }
2451
+ case "ask_llm": {
2452
+ const [llmName, ...questionParts] = params.split("|");
2453
+ const question = questionParts.join("|");
2454
+ const currentScreen = await describeScreen();
2455
+ const fullQuestion = `I'm looking at my screen and I need help. ${question}
2456
+
2457
+ Here's what I see on my screen: ${currentScreen.description}`;
2458
+ const supportedLLMs = ["perplexity", "chatgpt", "claude", "copilot"];
2459
+ const llmLower = llmName.toLowerCase();
2460
+ if (!supportedLLMs.includes(llmLower)) {
2461
+ throw new Error(`Unknown LLM: ${llmName}. Supported: ${supportedLLMs.join(", ")}`);
2462
+ }
2463
+ const result = await askAI(llmLower, fullQuestion, false);
2464
+ const fullParts = await getFullAIResponse(llmLower, 3);
2465
+ const finalResponse = fullParts.length > 0 ? fullParts.join("\n\n") : result.response;
2466
+ step.result = `\u{1F916} ${llmName} says:
2467
+
2468
+ ${finalResponse}`;
2469
+ break;
2470
+ }
2471
+ case "learn_ui": {
2472
+ const uiScreen = await describeScreen();
2473
+ const uiAnalysis = await chat([{
2474
+ role: "user",
2475
+ content: `Analyze this screenshot and identify all interactive UI elements. List:
2476
+ 1. All clickable buttons and their likely functions
2477
+ 2. Text input fields
2478
+ 3. Menus and dropdowns
2479
+ 4. Links
2480
+ 5. Any keyboard shortcuts visible
2481
+ 6. The main actions available in this interface
2482
+
2483
+ Question: ${params}
2484
+
2485
+ Be specific about locations (top-left, center, etc.) and what each element does.`
2486
+ }]);
2487
+ step.result = `\u{1F50D} UI Analysis:
2488
+
2489
+ ${uiAnalysis.content}`;
2490
+ break;
2491
+ }
2492
+ case "adaptive_do": {
2493
+ const goal = params;
2494
+ const maxAttempts = 5;
2495
+ const actionHistory = [];
2496
+ let accomplished = false;
2497
+ const page = await getPage();
2498
+ for (let attempt = 0; attempt < maxAttempts && !accomplished; attempt++) {
2499
+ const screenshot = await takeScreenshot();
2500
+ const currentState = await chat([{
2501
+ role: "user",
2502
+ content: `Describe what you see on this screen. What app/website is it? What elements are visible?`
2503
+ }]);
2504
+ const nextAction = await chat([{
2505
+ role: "user",
2506
+ content: `GOAL: ${goal}
2507
+
2508
+ CURRENT SCREEN: ${currentState.content}
2509
+
2510
+ PREVIOUS ACTIONS TAKEN:
2511
+ ${actionHistory.length > 0 ? actionHistory.join("\n") : "None yet"}
2512
+
2513
+ Based on what you see, what's the SINGLE next action to take?
2514
+ Options:
2515
+ - click: Click element (describe CSS selector or visible text)
2516
+ - type: Type something (specify selector and text)
2517
+ - press: Press a key (specify key)
2518
+ - scroll: Scroll up/down
2519
+ - navigate: Go to URL
2520
+ - done: Goal is accomplished
2521
+ - stuck: Can't figure out what to do
2522
+
2523
+ Respond in format:
2524
+ ACTION: <action_type>
2525
+ SELECTOR: <css selector or text to find>
2526
+ VALUE: <text to type or URL>
2527
+ REASONING: <why>`
2528
+ }]);
2529
+ const actionContent = nextAction.content;
2530
+ const actionMatch = actionContent.match(/ACTION:\s*(\w+)/i);
2531
+ const selectorMatch = actionContent.match(/SELECTOR:\s*(.+?)(?:\n|$)/i);
2532
+ const valueMatch = actionContent.match(/VALUE:\s*(.+?)(?:\n|$)/i);
2533
+ if (!actionMatch) {
2534
+ actionHistory.push(`Attempt ${attempt + 1}: Couldn't parse action`);
2535
+ continue;
2536
+ }
2537
+ const action = actionMatch[1].toLowerCase();
2538
+ const selector = selectorMatch?.[1]?.trim() || "";
2539
+ const value = valueMatch?.[1]?.trim() || "";
2540
+ if (action === "done") {
2541
+ accomplished = true;
2542
+ actionHistory.push(`Attempt ${attempt + 1}: Goal accomplished!`);
2543
+ break;
2544
+ }
2545
+ if (action === "stuck") {
2546
+ actionHistory.push(`Attempt ${attempt + 1}: Got stuck, asking Perplexity for help...`);
2547
+ const helpRequest = `I'm trying to: ${goal}
2548
+
2549
+ I'm stuck. What should I do next? Be specific about what to click or type.`;
2550
+ const advice = await askAI("perplexity", helpRequest, false);
2551
+ actionHistory.push(`Got advice: ${advice.response.slice(0, 200)}...`);
2552
+ await navigateTo(page.url());
2553
+ continue;
2554
+ }
2555
+ try {
2556
+ switch (action) {
2557
+ case "click":
2558
+ if (selector) {
2559
+ const clicked = await clickElement(selector);
2560
+ if (!clicked) {
2561
+ await page.getByText(selector).first().click({ timeout: 5e3 });
2562
+ }
2563
+ }
2564
+ actionHistory.push(`Attempt ${attempt + 1}: Clicked "${selector}"`);
2565
+ break;
2566
+ case "type":
2567
+ if (selector && value) {
2568
+ const typed = await typeInElement(selector, value);
2569
+ if (!typed) {
2570
+ await page.getByPlaceholder(selector).first().fill(value);
2571
+ }
2572
+ }
2573
+ actionHistory.push(`Attempt ${attempt + 1}: Typed "${value}" in "${selector}"`);
2574
+ break;
2575
+ case "press":
2576
+ await pressKey2(value || selector);
2577
+ actionHistory.push(`Attempt ${attempt + 1}: Pressed ${value || selector}`);
2578
+ break;
2579
+ case "scroll":
2580
+ await scroll(value.toLowerCase().includes("up") ? "up" : "down");
2581
+ actionHistory.push(`Attempt ${attempt + 1}: Scrolled ${value || "down"}`);
2582
+ break;
2583
+ case "navigate":
2584
+ const url = value.startsWith("http") ? value : `https://${value}`;
2585
+ await navigateTo(url);
2586
+ actionHistory.push(`Attempt ${attempt + 1}: Navigated to ${url}`);
2587
+ break;
2588
+ default:
2589
+ actionHistory.push(`Attempt ${attempt + 1}: Unknown action ${action}`);
2590
+ }
2591
+ } catch (e) {
2592
+ actionHistory.push(`Attempt ${attempt + 1}: Action failed - ${e}`);
2593
+ }
2594
+ await sleep(2e3);
2595
+ }
2596
+ step.result = `\u{1F3AF} Adaptive Agent Result:
2597
+
2598
+ Goal: ${goal}
2599
+ Accomplished: ${accomplished ? "Yes \u2705" : "Partial/No \u274C"}
2908
2600
 
2909
- ${synthesis.content}`;
2601
+ Action Log:
2602
+ ${actionHistory.join("\n")}`;
2910
2603
  break;
2911
2604
  }
2912
- case "ask_llm": {
2913
- const [llmName, ...questionParts] = params.split("|");
2914
- const question = questionParts.join("|");
2915
- const currentScreen = await describeScreen();
2916
- const fullQuestion = `I'm looking at my screen and I need help. ${question}
2917
-
2918
- Here's what I see on my screen: ${currentScreen.description}`;
2919
- const supportedLLMs = ["perplexity", "chatgpt", "claude", "copilot"];
2920
- const llmLower = llmName.toLowerCase();
2921
- if (!supportedLLMs.includes(llmLower)) {
2922
- throw new Error(`Unknown LLM: ${llmName}. Supported: ${supportedLLMs.join(", ")}`);
2923
- }
2924
- const result = await askAI(llmLower, fullQuestion, false);
2925
- const fullParts = await getFullAIResponse(llmLower, 3);
2926
- const finalResponse = fullParts.length > 0 ? fullParts.join("\n\n") : result.response;
2927
- step.result = `\u{1F916} ${llmName} says:
2928
-
2929
- ${finalResponse}`;
2605
+ case "chat":
2606
+ step.result = `Task noted: ${params}`;
2930
2607
  break;
2608
+ default:
2609
+ throw new Error(`Unknown action: ${actionType}`);
2610
+ }
2611
+ }
2612
+ async function executeTask(task, onProgress) {
2613
+ task.status = "running";
2614
+ for (const step of task.steps) {
2615
+ if (task.status === "failed") {
2616
+ step.status = "skipped";
2617
+ continue;
2931
2618
  }
2932
- case "learn_ui": {
2933
- const uiScreen = await describeScreen();
2934
- const uiAnalysis = await chat([{
2935
- role: "user",
2936
- content: `Analyze this screenshot and identify all interactive UI elements. List:
2937
- 1. All clickable buttons and their likely functions
2938
- 2. Text input fields
2939
- 3. Menus and dropdowns
2940
- 4. Links
2941
- 5. Any keyboard shortcuts visible
2942
- 6. The main actions available in this interface
2943
-
2944
- Question: ${params}
2619
+ step.status = "running";
2620
+ onProgress?.(task, step);
2621
+ try {
2622
+ await executeStep(step);
2623
+ step.status = "completed";
2624
+ } catch (error) {
2625
+ step.status = "failed";
2626
+ step.error = error instanceof Error ? error.message : "Unknown error";
2627
+ task.status = "failed";
2628
+ }
2629
+ onProgress?.(task, step);
2630
+ }
2631
+ if (task.status !== "failed") {
2632
+ task.status = "completed";
2633
+ const steps = task.steps.map((s) => ({
2634
+ description: s.description,
2635
+ action: s.action
2636
+ }));
2637
+ saveTaskPattern(task.description, steps);
2638
+ }
2639
+ task.completedAt = /* @__PURE__ */ new Date();
2640
+ return task;
2641
+ }
2642
+ function sleep(ms) {
2643
+ return new Promise((resolve) => setTimeout(resolve, ms));
2644
+ }
2645
+ function getTaskMemoryStats() {
2646
+ const memory = loadTaskMemory();
2647
+ const totalUses = memory.patterns.reduce((sum, p) => sum + p.successCount, 0);
2648
+ const topPatterns = memory.patterns.sort((a, b) => b.successCount - a.successCount).slice(0, 5).map((p) => `"${p.input}" (${p.successCount}x)`);
2649
+ return {
2650
+ patternCount: memory.patterns.length,
2651
+ totalUses,
2652
+ topPatterns
2653
+ };
2654
+ }
2655
+ function clearTaskMemory() {
2656
+ try {
2657
+ if (fs2.existsSync(TASK_MEMORY_FILE)) {
2658
+ fs2.unlinkSync(TASK_MEMORY_FILE);
2659
+ }
2660
+ } catch {
2661
+ }
2662
+ }
2663
+ function formatTask(task) {
2664
+ const statusEmoji = {
2665
+ pending: "\u23F3",
2666
+ running: "\u{1F504}",
2667
+ completed: "\u2705",
2668
+ failed: "\u274C"
2669
+ };
2670
+ const stepStatusEmoji = {
2671
+ pending: "\u25CB",
2672
+ running: "\u25D0",
2673
+ completed: "\u25CF",
2674
+ failed: "\u2717",
2675
+ skipped: "\u25CC"
2676
+ };
2677
+ let output = `${statusEmoji[task.status]} Task: ${task.description}
2945
2678
 
2946
- Be specific about locations (top-left, center, etc.) and what each element does.`
2947
- }]);
2948
- step.result = `\u{1F50D} UI Analysis:
2679
+ `;
2680
+ for (const step of task.steps) {
2681
+ output += ` ${stepStatusEmoji[step.status]} ${step.description}`;
2682
+ if (step.result) {
2683
+ output += ` \u2192 ${step.result}`;
2684
+ }
2685
+ if (step.error) {
2686
+ output += ` (Error: ${step.error})`;
2687
+ }
2688
+ output += "\n";
2689
+ }
2690
+ return output;
2691
+ }
2949
2692
 
2950
- ${uiAnalysis.content}`;
2951
- break;
2693
+ // src/services/telegram.ts
2694
+ function formatForTelegram(text) {
2695
+ const hasMarkdown = /[*_`\[\]()]/.test(text);
2696
+ if (!hasMarkdown) {
2697
+ return { text, parseMode: void 0 };
2698
+ }
2699
+ try {
2700
+ let formatted = text;
2701
+ const escapeChars = ["\\", "_", "*", "[", "]", "(", ")", "~", "`", ">", "#", "+", "-", "=", "|", "{", "}", ".", "!"];
2702
+ const placeholders = [];
2703
+ let placeholderIndex = 0;
2704
+ formatted = formatted.replace(/```([\s\S]*?)```/g, (match, code) => {
2705
+ const placeholder = `__CODEBLOCK_${placeholderIndex++}__`;
2706
+ placeholders.push({ placeholder, original: "```" + code.replace(/\\/g, "\\\\") + "```" });
2707
+ return placeholder;
2708
+ });
2709
+ formatted = formatted.replace(/`([^`]+)`/g, (match, code) => {
2710
+ const placeholder = `__INLINECODE_${placeholderIndex++}__`;
2711
+ placeholders.push({ placeholder, original: "`" + code.replace(/\\/g, "\\\\") + "`" });
2712
+ return placeholder;
2713
+ });
2714
+ formatted = formatted.replace(/\*\*(.+?)\*\*/g, (match, text2) => {
2715
+ const placeholder = `__BOLD_${placeholderIndex++}__`;
2716
+ placeholders.push({ placeholder, original: "*" + text2 + "*" });
2717
+ return placeholder;
2718
+ });
2719
+ formatted = formatted.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, (match, text2) => {
2720
+ const placeholder = `__ITALIC_${placeholderIndex++}__`;
2721
+ placeholders.push({ placeholder, original: "_" + text2 + "_" });
2722
+ return placeholder;
2723
+ });
2724
+ formatted = formatted.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text2, url) => {
2725
+ const placeholder = `__LINK_${placeholderIndex++}__`;
2726
+ placeholders.push({ placeholder, original: "[" + text2 + "](" + url + ")" });
2727
+ return placeholder;
2728
+ });
2729
+ for (const char of escapeChars) {
2730
+ if (char === "\\") continue;
2731
+ formatted = formatted.split(char).join("\\" + char);
2952
2732
  }
2953
- case "adaptive_do": {
2954
- const goal = params;
2955
- const maxAttempts = 5;
2956
- const actionHistory = [];
2957
- let accomplished = false;
2958
- const page = await getPage();
2959
- for (let attempt = 0; attempt < maxAttempts && !accomplished; attempt++) {
2960
- const screenshot = await takeScreenshot();
2961
- const currentState = await chat([{
2962
- role: "user",
2963
- content: `Describe what you see on this screen. What app/website is it? What elements are visible?`
2964
- }]);
2965
- const nextAction = await chat([{
2966
- role: "user",
2967
- content: `GOAL: ${goal}
2733
+ for (const { placeholder, original } of placeholders) {
2734
+ formatted = formatted.replace(placeholder, original);
2735
+ }
2736
+ return { text: formatted, parseMode: "MarkdownV2" };
2737
+ } catch {
2738
+ return { text, parseMode: void 0 };
2739
+ }
2740
+ }
2741
+ async function sendFormattedMessage(ctx, text) {
2742
+ const { text: formatted, parseMode } = formatForTelegram(text);
2743
+ try {
2744
+ if (parseMode) {
2745
+ await ctx.reply(formatted, { parse_mode: parseMode });
2746
+ } else {
2747
+ await ctx.reply(text);
2748
+ }
2749
+ } catch {
2750
+ await ctx.reply(text);
2751
+ }
2752
+ }
2753
+ var TelegramBotService = class extends EventEmitter {
2754
+ bot = null;
2755
+ isRunning = false;
2756
+ allowedChatIds = /* @__PURE__ */ new Set();
2757
+ chatHistory = /* @__PURE__ */ new Map();
2758
+ constructor() {
2759
+ super();
2760
+ }
2761
+ /**
2762
+ * Start the Telegram bot
2763
+ */
2764
+ async start() {
2765
+ if (this.isRunning) {
2766
+ return;
2767
+ }
2768
+ const botToken = getApiKey("telegram");
2769
+ if (!botToken) {
2770
+ throw new Error("Telegram bot token not configured. Use: cnapse auth telegram YOUR_BOT_TOKEN");
2771
+ }
2772
+ try {
2773
+ const { Telegraf } = await import("telegraf");
2774
+ this.bot = new Telegraf(botToken);
2775
+ const config = getConfig();
2776
+ if (config.telegram?.chatId) {
2777
+ this.allowedChatIds.add(config.telegram.chatId);
2778
+ }
2779
+ this.setupHandlers();
2780
+ await this.bot.launch();
2781
+ this.isRunning = true;
2782
+ this.emit("started");
2783
+ } catch (error) {
2784
+ throw new Error(`Failed to start Telegram bot: ${error instanceof Error ? error.message : "Unknown error"}`);
2785
+ }
2786
+ }
2787
+ /**
2788
+ * Stop the Telegram bot
2789
+ */
2790
+ async stop() {
2791
+ if (!this.isRunning || !this.bot) {
2792
+ return;
2793
+ }
2794
+ this.bot.stop("SIGTERM");
2795
+ this.isRunning = false;
2796
+ this.bot = null;
2797
+ this.emit("stopped");
2798
+ }
2799
+ /**
2800
+ * Check if bot is running
2801
+ */
2802
+ get running() {
2803
+ return this.isRunning;
2804
+ }
2805
+ /**
2806
+ * Setup message and command handlers
2807
+ */
2808
+ setupHandlers() {
2809
+ if (!this.bot) return;
2810
+ this.bot.command("start", async (ctx) => {
2811
+ const chatId = ctx.chat.id;
2812
+ this.allowedChatIds.add(chatId);
2813
+ await ctx.reply(
2814
+ `\u{1F916} C-napse connected!
2968
2815
 
2969
- CURRENT SCREEN: ${currentState.content}
2816
+ Commands:
2817
+ /screen - Take screenshot
2818
+ /describe - Screenshot + AI description
2819
+ /task <desc> - Multi-step automation
2820
+ /run <cmd> - Execute shell command
2821
+ /status - System status
2970
2822
 
2971
- PREVIOUS ACTIONS TAKEN:
2972
- ${actionHistory.length > 0 ? actionHistory.join("\n") : "None yet"}
2823
+ Examples:
2824
+ \u2022 /task open folder E:/Test and list files
2825
+ \u2022 /task open notepad and type hello
2826
+ \u2022 minimize chrome
2827
+ \u2022 what windows are open?
2973
2828
 
2974
- Based on what you see, what's the SINGLE next action to take?
2975
- Options:
2976
- - click: Click element (describe CSS selector or visible text)
2977
- - type: Type something (specify selector and text)
2978
- - press: Press a key (specify key)
2979
- - scroll: Scroll up/down
2980
- - navigate: Go to URL
2981
- - done: Goal is accomplished
2982
- - stuck: Can't figure out what to do
2829
+ Your chat ID: ${chatId}`
2830
+ );
2831
+ });
2832
+ this.bot.command("screen", async (ctx) => {
2833
+ if (!this.isAllowed(ctx.chat.id)) {
2834
+ await ctx.reply("\u26D4 Not authorized. Send /start first.");
2835
+ return;
2836
+ }
2837
+ await ctx.reply("\u{1F4F8} Taking screenshot...");
2838
+ try {
2839
+ const screenshot = await captureScreenshot();
2840
+ if (!screenshot) {
2841
+ await ctx.reply("\u274C Failed to capture screenshot");
2842
+ return;
2843
+ }
2844
+ const buffer = Buffer.from(screenshot, "base64");
2845
+ await ctx.replyWithPhoto({ source: buffer }, { caption: "\u{1F4F8} Current screen" });
2846
+ } catch (error) {
2847
+ await ctx.reply(`\u274C Error: ${error instanceof Error ? error.message : "Unknown error"}`);
2848
+ }
2849
+ });
2850
+ this.bot.command("describe", async (ctx) => {
2851
+ if (!this.isAllowed(ctx.chat.id)) {
2852
+ await ctx.reply("\u26D4 Not authorized. Send /start first.");
2853
+ return;
2854
+ }
2855
+ await ctx.reply("\u{1F50D} Analyzing screen...");
2856
+ try {
2857
+ const result = await describeScreen();
2858
+ const buffer = Buffer.from(result.screenshot, "base64");
2859
+ const caption = `\u{1F5A5}\uFE0F Screen Analysis:
2983
2860
 
2984
- Respond in format:
2985
- ACTION: <action_type>
2986
- SELECTOR: <css selector or text to find>
2987
- VALUE: <text to type or URL>
2988
- REASONING: <why>`
2989
- }]);
2990
- const actionContent = nextAction.content;
2991
- const actionMatch = actionContent.match(/ACTION:\s*(\w+)/i);
2992
- const selectorMatch = actionContent.match(/SELECTOR:\s*(.+?)(?:\n|$)/i);
2993
- const valueMatch = actionContent.match(/VALUE:\s*(.+?)(?:\n|$)/i);
2994
- if (!actionMatch) {
2995
- actionHistory.push(`Attempt ${attempt + 1}: Couldn't parse action`);
2996
- continue;
2861
+ ${result.description}`.slice(0, 1024);
2862
+ await ctx.replyWithPhoto({ source: buffer }, { caption });
2863
+ if (result.description.length > 900) {
2864
+ await ctx.reply(result.description);
2997
2865
  }
2998
- const action = actionMatch[1].toLowerCase();
2999
- const selector = selectorMatch?.[1]?.trim() || "";
3000
- const value = valueMatch?.[1]?.trim() || "";
3001
- if (action === "done") {
3002
- accomplished = true;
3003
- actionHistory.push(`Attempt ${attempt + 1}: Goal accomplished!`);
3004
- break;
2866
+ } catch (error) {
2867
+ await ctx.reply(`\u274C Error: ${error instanceof Error ? error.message : "Unknown error"}`);
2868
+ }
2869
+ });
2870
+ this.bot.command("run", async (ctx) => {
2871
+ if (!this.isAllowed(ctx.chat.id)) {
2872
+ await ctx.reply("\u26D4 Not authorized. Send /start first.");
2873
+ return;
2874
+ }
2875
+ const cmd = ctx.message.text.replace("/run ", "").trim();
2876
+ if (!cmd) {
2877
+ await ctx.reply("Usage: /run <command>\nExample: /run dir");
2878
+ return;
2879
+ }
2880
+ await ctx.reply(`\u2699\uFE0F Running: ${cmd}`);
2881
+ try {
2882
+ const result = await runCommand(cmd, 3e4);
2883
+ if (result.success) {
2884
+ const output = result.output.slice(0, 4e3) || "(no output)";
2885
+ await ctx.reply(`\u2705 Output:
2886
+ \`\`\`
2887
+ ${output}
2888
+ \`\`\``, { parse_mode: "Markdown" });
2889
+ } else {
2890
+ await ctx.reply(`\u274C Error:
2891
+ \`\`\`
2892
+ ${result.error}
2893
+ \`\`\``, { parse_mode: "Markdown" });
3005
2894
  }
3006
- if (action === "stuck") {
3007
- actionHistory.push(`Attempt ${attempt + 1}: Got stuck, asking Perplexity for help...`);
3008
- const helpRequest = `I'm trying to: ${goal}
2895
+ } catch (error) {
2896
+ await ctx.reply(`\u274C Error: ${error instanceof Error ? error.message : "Unknown error"}`);
2897
+ }
2898
+ });
2899
+ this.bot.command("status", async (ctx) => {
2900
+ if (!this.isAllowed(ctx.chat.id)) {
2901
+ await ctx.reply("\u26D4 Not authorized. Send /start first.");
2902
+ return;
2903
+ }
2904
+ const config = getConfig();
2905
+ const status = [
2906
+ "\u{1F4CA} C-napse Status",
2907
+ "",
2908
+ `Provider: ${config.provider}`,
2909
+ `Model: ${config.model}`,
2910
+ `Platform: ${process.platform}`,
2911
+ `Node: ${process.version}`
2912
+ ].join("\n");
2913
+ await ctx.reply(status);
2914
+ });
2915
+ this.bot.command("task", async (ctx) => {
2916
+ if (!this.isAllowed(ctx.chat.id)) {
2917
+ await ctx.reply("\u26D4 Not authorized. Send /start first.");
2918
+ return;
2919
+ }
2920
+ const taskDescription = ctx.message.text.replace("/task", "").trim();
2921
+ if (!taskDescription) {
2922
+ await ctx.reply(
2923
+ "\u{1F4CB} Usage: /task <description>\n\nExamples:\n\u2022 /task open notepad and type hello\n\u2022 /task open folder E:/Test and list files\n\u2022 /task search google for weather today\n\u2022 /task open chrome and go to github.com"
2924
+ );
2925
+ return;
2926
+ }
2927
+ await ctx.reply(`\u{1F3AF} Parsing task: "${taskDescription}"`);
2928
+ try {
2929
+ const task = await parseTask(taskDescription);
2930
+ let stepsPreview = `\u{1F4CB} Task broken into ${task.steps.length} steps:
3009
2931
 
3010
- I'm stuck. What should I do next? Be specific about what to click or type.`;
3011
- const advice = await askAI("perplexity", helpRequest, false);
3012
- actionHistory.push(`Got advice: ${advice.response.slice(0, 200)}...`);
3013
- await navigateTo(page.url());
3014
- continue;
2932
+ `;
2933
+ task.steps.forEach((step, i) => {
2934
+ stepsPreview += `${i + 1}. ${step.description}
2935
+ `;
2936
+ });
2937
+ stepsPreview += "\n\u23F3 Executing...";
2938
+ await ctx.reply(stepsPreview);
2939
+ let lastUpdate = Date.now();
2940
+ const updatedTask = await executeTask(task, async (t, step) => {
2941
+ const now = Date.now();
2942
+ if (now - lastUpdate > 2e3) {
2943
+ lastUpdate = now;
2944
+ const stepNum = t.steps.indexOf(step) + 1;
2945
+ const status = step.status === "running" ? "\u{1F504}" : step.status === "completed" ? "\u2705" : "\u274C";
2946
+ await ctx.sendChatAction("typing");
2947
+ }
2948
+ });
2949
+ const result = formatTask(updatedTask);
2950
+ if (result.length > 4e3) {
2951
+ const chunks = result.match(/.{1,4000}/gs) || [result];
2952
+ for (const chunk of chunks) {
2953
+ await sendFormattedMessage(ctx, chunk);
2954
+ }
2955
+ } else {
2956
+ await sendFormattedMessage(ctx, result);
3015
2957
  }
3016
- try {
3017
- switch (action) {
3018
- case "click":
3019
- if (selector) {
3020
- const clicked = await clickElement(selector);
3021
- if (!clicked) {
3022
- await page.getByText(selector).first().click({ timeout: 5e3 });
3023
- }
3024
- }
3025
- actionHistory.push(`Attempt ${attempt + 1}: Clicked "${selector}"`);
3026
- break;
3027
- case "type":
3028
- if (selector && value) {
3029
- const typed = await typeInElement(selector, value);
3030
- if (!typed) {
3031
- await page.getByPlaceholder(selector).first().fill(value);
3032
- }
3033
- }
3034
- actionHistory.push(`Attempt ${attempt + 1}: Typed "${value}" in "${selector}"`);
3035
- break;
3036
- case "press":
3037
- await pressKey2(value || selector);
3038
- actionHistory.push(`Attempt ${attempt + 1}: Pressed ${value || selector}`);
3039
- break;
3040
- case "scroll":
3041
- await scroll(value.toLowerCase().includes("up") ? "up" : "down");
3042
- actionHistory.push(`Attempt ${attempt + 1}: Scrolled ${value || "down"}`);
3043
- break;
3044
- case "navigate":
3045
- const url = value.startsWith("http") ? value : `https://${value}`;
3046
- await navigateTo(url);
3047
- actionHistory.push(`Attempt ${attempt + 1}: Navigated to ${url}`);
3048
- break;
3049
- default:
3050
- actionHistory.push(`Attempt ${attempt + 1}: Unknown action ${action}`);
2958
+ const hasScreenStep = updatedTask.steps.some(
2959
+ (s) => s.action.includes("screenshot") || s.action.includes("describe")
2960
+ );
2961
+ if (hasScreenStep || updatedTask.status === "completed") {
2962
+ try {
2963
+ const screenshot = await captureScreenshot();
2964
+ if (screenshot) {
2965
+ const buffer = Buffer.from(screenshot, "base64");
2966
+ await ctx.replyWithPhoto({ source: buffer }, {
2967
+ caption: updatedTask.status === "completed" ? "\u2705 Task completed - current screen" : "\u{1F4F8} Final screen state"
2968
+ });
2969
+ }
2970
+ } catch {
3051
2971
  }
3052
- } catch (e) {
3053
- actionHistory.push(`Attempt ${attempt + 1}: Action failed - ${e}`);
3054
2972
  }
3055
- await sleep(2e3);
2973
+ } catch (error) {
2974
+ await ctx.reply(`\u274C Task failed: ${error instanceof Error ? error.message : "Unknown error"}`);
2975
+ }
2976
+ });
2977
+ this.bot.on("text", async (ctx) => {
2978
+ if (!this.isAllowed(ctx.chat.id)) {
2979
+ return;
2980
+ }
2981
+ if (ctx.message.text.startsWith("/")) {
2982
+ return;
2983
+ }
2984
+ const chatId = ctx.chat.id;
2985
+ const userText = ctx.message.text;
2986
+ const from = ctx.from.username || ctx.from.first_name || "User";
2987
+ const message = {
2988
+ chatId,
2989
+ text: userText,
2990
+ from
2991
+ };
2992
+ this.emit("message", message);
2993
+ if (!this.chatHistory.has(chatId)) {
2994
+ this.chatHistory.set(chatId, []);
2995
+ }
2996
+ const history = this.chatHistory.get(chatId);
2997
+ history.push({ role: "user", content: userText });
2998
+ if (history.length > 10) {
2999
+ history.splice(0, history.length - 10);
3000
+ }
3001
+ try {
3002
+ await ctx.sendChatAction("typing");
3003
+ const computerControlResult = await this.tryComputerControl(userText);
3004
+ if (computerControlResult) {
3005
+ await sendFormattedMessage(ctx, computerControlResult);
3006
+ history.push({ role: "assistant", content: computerControlResult });
3007
+ return;
3008
+ }
3009
+ const isVisionRequest = /screen|see|look|what('?s| is) (on|visible)|show me|screenshot/i.test(userText);
3010
+ let response;
3011
+ if (isVisionRequest) {
3012
+ const screenshot = await captureScreenshot();
3013
+ if (screenshot) {
3014
+ response = await chatWithVision(history, screenshot);
3015
+ } else {
3016
+ response = await chat(history);
3017
+ }
3018
+ } else {
3019
+ response = await chat(history);
3020
+ }
3021
+ history.push({ role: "assistant", content: response.content });
3022
+ const responseText = response.content || "(no response)";
3023
+ if (responseText.length > 4e3) {
3024
+ const chunks = responseText.match(/.{1,4000}/gs) || [responseText];
3025
+ for (const chunk of chunks) {
3026
+ await sendFormattedMessage(ctx, chunk);
3027
+ }
3028
+ } else {
3029
+ await sendFormattedMessage(ctx, responseText);
3030
+ }
3031
+ } catch (error) {
3032
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
3033
+ await ctx.reply(`\u274C Error: ${errorMsg}`);
3034
+ this.emit("error", new Error(errorMsg));
3056
3035
  }
3057
- step.result = `\u{1F3AF} Adaptive Agent Result:
3058
-
3059
- Goal: ${goal}
3060
- Accomplished: ${accomplished ? "Yes \u2705" : "Partial/No \u274C"}
3061
-
3062
- Action Log:
3063
- ${actionHistory.join("\n")}`;
3064
- break;
3036
+ });
3037
+ this.bot.catch((err2) => {
3038
+ this.emit("error", err2);
3039
+ });
3040
+ }
3041
+ /**
3042
+ * Check if chat is authorized
3043
+ */
3044
+ isAllowed(chatId) {
3045
+ if (this.allowedChatIds.size === 0) {
3046
+ return true;
3065
3047
  }
3066
- case "chat":
3067
- step.result = `Task noted: ${params}`;
3068
- break;
3069
- default:
3070
- throw new Error(`Unknown action: ${actionType}`);
3048
+ return this.allowedChatIds.has(chatId);
3071
3049
  }
3072
- }
3073
- async function executeTask(task, onProgress) {
3074
- task.status = "running";
3075
- for (const step of task.steps) {
3076
- if (task.status === "failed") {
3077
- step.status = "skipped";
3078
- continue;
3050
+ /**
3051
+ * Try to execute computer control commands directly
3052
+ * Returns response string if handled, null if not a computer command
3053
+ */
3054
+ async tryComputerControl(text) {
3055
+ const lower = text.toLowerCase();
3056
+ let match = lower.match(/minimize\s+(?:the\s+)?(.+)/i);
3057
+ if (match) {
3058
+ const result = await minimizeWindow(match[1].trim());
3059
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
3079
3060
  }
3080
- step.status = "running";
3081
- onProgress?.(task, step);
3082
- try {
3083
- await executeStep(step);
3084
- step.status = "completed";
3085
- } catch (error) {
3086
- step.status = "failed";
3087
- step.error = error instanceof Error ? error.message : "Unknown error";
3088
- task.status = "failed";
3061
+ match = lower.match(/maximize\s+(?:the\s+)?(.+)/i);
3062
+ if (match) {
3063
+ const result = await maximizeWindow(match[1].trim());
3064
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
3089
3065
  }
3090
- onProgress?.(task, step);
3066
+ match = lower.match(/close\s+(?:the\s+)?(.+)/i);
3067
+ if (match) {
3068
+ const result = await closeWindow(match[1].trim());
3069
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
3070
+ }
3071
+ match = lower.match(/restore\s+(?:the\s+)?(.+)/i);
3072
+ if (match) {
3073
+ const result = await restoreWindow(match[1].trim());
3074
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
3075
+ }
3076
+ const looksLikeTask = /\b(and|then|after|tell me|list|what|show|describe|check|find|search|create|write|type\s+.+\s+in)\b/i.test(text);
3077
+ if (!looksLikeTask) {
3078
+ match = lower.match(/(?:focus|open|switch to)\s+(?:the\s+)?(\w+(?:\s+\w+)?)/i);
3079
+ if (match) {
3080
+ const result = await focusWindow(match[1].trim());
3081
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
3082
+ }
3083
+ }
3084
+ match = text.match(/type\s+["'](.+)["']/i);
3085
+ if (match) {
3086
+ const result = await typeText(match[1]);
3087
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
3088
+ }
3089
+ match = lower.match(/press\s+(?:the\s+)?(\w+)/i);
3090
+ if (match) {
3091
+ const result = await pressKey(match[1]);
3092
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
3093
+ }
3094
+ if (/^click$/i.test(lower) || /click\s+(?:the\s+)?mouse/i.test(lower)) {
3095
+ const result = await clickMouse("left");
3096
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
3097
+ }
3098
+ if (/right\s*click/i.test(lower)) {
3099
+ const result = await clickMouse("right");
3100
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
3101
+ }
3102
+ if (/double\s*click/i.test(lower)) {
3103
+ const result = await doubleClick();
3104
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
3105
+ }
3106
+ match = lower.match(/move\s+(?:the\s+)?mouse\s+(?:to\s+)?(\d+)[,\s]+(\d+)/i);
3107
+ if (match) {
3108
+ const result = await moveMouse(parseInt(match[1]), parseInt(match[2]));
3109
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
3110
+ }
3111
+ match = lower.match(/scroll\s+(up|down)(?:\s+(\d+))?/i);
3112
+ if (match) {
3113
+ const amount = match[1] === "up" ? parseInt(match[2]) || 3 : -(parseInt(match[2]) || 3);
3114
+ const result = await scrollMouse(amount);
3115
+ return result.success ? `\u2705 ${result.output}` : `\u274C ${result.error}`;
3116
+ }
3117
+ if (/list\s+(?:all\s+)?windows/i.test(lower) || /what\s+windows/i.test(lower)) {
3118
+ const result = await listWindows();
3119
+ return result.success ? `\u{1F4CB} Open Windows:
3120
+ ${result.output}` : `\u274C ${result.error}`;
3121
+ }
3122
+ if (/(?:active|current|focused)\s+window/i.test(lower) || /what\s+(?:window|app)/i.test(lower)) {
3123
+ const result = await getActiveWindow();
3124
+ return result.success ? `\u{1FA9F} Active: ${result.output}` : `\u274C ${result.error}`;
3125
+ }
3126
+ if (/mouse\s+position/i.test(lower) || /where.*mouse/i.test(lower)) {
3127
+ const result = await getMousePosition();
3128
+ return result.success ? `\u{1F5B1}\uFE0F ${result.output}` : `\u274C ${result.error}`;
3129
+ }
3130
+ return null;
3091
3131
  }
3092
- if (task.status !== "failed") {
3093
- task.status = "completed";
3094
- const steps = task.steps.map((s) => ({
3095
- description: s.description,
3096
- action: s.action
3097
- }));
3098
- saveTaskPattern(task.description, steps);
3132
+ /**
3133
+ * Send a message to a specific chat
3134
+ */
3135
+ async sendMessage(chatId, text) {
3136
+ if (!this.bot || !this.isRunning) {
3137
+ throw new Error("Telegram bot is not running");
3138
+ }
3139
+ await this.bot.telegram.sendMessage(chatId, text);
3099
3140
  }
3100
- task.completedAt = /* @__PURE__ */ new Date();
3101
- return task;
3102
- }
3103
- function sleep(ms) {
3104
- return new Promise((resolve) => setTimeout(resolve, ms));
3105
- }
3106
- function getTaskMemoryStats() {
3107
- const memory = loadTaskMemory();
3108
- const totalUses = memory.patterns.reduce((sum, p) => sum + p.successCount, 0);
3109
- const topPatterns = memory.patterns.sort((a, b) => b.successCount - a.successCount).slice(0, 5).map((p) => `"${p.input}" (${p.successCount}x)`);
3110
- return {
3111
- patternCount: memory.patterns.length,
3112
- totalUses,
3113
- topPatterns
3114
- };
3115
- }
3116
- function clearTaskMemory() {
3117
- try {
3118
- if (fs2.existsSync(TASK_MEMORY_FILE)) {
3119
- fs2.unlinkSync(TASK_MEMORY_FILE);
3141
+ /**
3142
+ * Send a photo to a specific chat
3143
+ */
3144
+ async sendPhoto(chatId, base64Image, caption) {
3145
+ if (!this.bot || !this.isRunning) {
3146
+ throw new Error("Telegram bot is not running");
3120
3147
  }
3121
- } catch {
3148
+ const buffer = Buffer.from(base64Image, "base64");
3149
+ await this.bot.telegram.sendPhoto(chatId, { source: buffer }, { caption });
3150
+ }
3151
+ };
3152
+ var instance = null;
3153
+ function getTelegramBot() {
3154
+ if (!instance) {
3155
+ instance = new TelegramBotService();
3122
3156
  }
3157
+ return instance;
3123
3158
  }
3124
- function formatTask(task) {
3125
- const statusEmoji = {
3126
- pending: "\u23F3",
3127
- running: "\u{1F504}",
3128
- completed: "\u2705",
3129
- failed: "\u274C"
3130
- };
3131
- const stepStatusEmoji = {
3132
- pending: "\u25CB",
3133
- running: "\u25D0",
3134
- completed: "\u25CF",
3135
- failed: "\u2717",
3136
- skipped: "\u25CC"
3137
- };
3138
- let output = `${statusEmoji[task.status]} Task: ${task.description}
3139
3159
 
3140
- `;
3141
- for (const step of task.steps) {
3142
- output += ` ${stepStatusEmoji[step.status]} ${step.description}`;
3143
- if (step.result) {
3144
- output += ` \u2192 ${step.result}`;
3160
+ // src/hooks/useTelegram.ts
3161
+ function useTelegram(onMessage) {
3162
+ const [isEnabled, setIsEnabled] = useState4(false);
3163
+ const [isStarting, setIsStarting] = useState4(false);
3164
+ const [error, setError] = useState4(null);
3165
+ const [lastMessage, setLastMessage] = useState4(null);
3166
+ const onMessageRef = useRef2(onMessage);
3167
+ useEffect2(() => {
3168
+ onMessageRef.current = onMessage;
3169
+ }, [onMessage]);
3170
+ const start = useCallback3(async () => {
3171
+ if (isEnabled) return;
3172
+ setIsStarting(true);
3173
+ setError(null);
3174
+ try {
3175
+ const bot = getTelegramBot();
3176
+ bot.on("message", (msg) => {
3177
+ setLastMessage(msg);
3178
+ onMessageRef.current?.(msg);
3179
+ });
3180
+ bot.on("error", (err2) => {
3181
+ setError(err2.message);
3182
+ });
3183
+ await bot.start();
3184
+ setIsEnabled(true);
3185
+ } catch (err2) {
3186
+ const errorMsg = err2 instanceof Error ? err2.message : "Failed to start Telegram bot";
3187
+ setError(errorMsg);
3188
+ throw err2;
3189
+ } finally {
3190
+ setIsStarting(false);
3145
3191
  }
3146
- if (step.error) {
3147
- output += ` (Error: ${step.error})`;
3192
+ }, [isEnabled]);
3193
+ const stop = useCallback3(async () => {
3194
+ if (!isEnabled) return;
3195
+ try {
3196
+ const bot = getTelegramBot();
3197
+ await bot.stop();
3198
+ setIsEnabled(false);
3199
+ } catch (err2) {
3200
+ const errorMsg = err2 instanceof Error ? err2.message : "Failed to stop Telegram bot";
3201
+ setError(errorMsg);
3202
+ throw err2;
3148
3203
  }
3149
- output += "\n";
3150
- }
3151
- return output;
3204
+ }, [isEnabled]);
3205
+ const toggle = useCallback3(async () => {
3206
+ if (isEnabled) {
3207
+ await stop();
3208
+ } else {
3209
+ await start();
3210
+ }
3211
+ }, [isEnabled, start, stop]);
3212
+ return {
3213
+ isEnabled,
3214
+ isStarting,
3215
+ error,
3216
+ lastMessage,
3217
+ toggle,
3218
+ start,
3219
+ stop
3220
+ };
3152
3221
  }
3153
3222
 
3154
3223
  // src/hooks/useTasks.ts
3224
+ import { useState as useState5, useCallback as useCallback4 } from "react";
3155
3225
  function useTasks(onProgress) {
3156
3226
  const [isRunning, setIsRunning] = useState5(false);
3157
3227
  const [currentTask, setCurrentTask] = useState5(null);