@projectservan8n/cnapse 0.4.0 → 0.5.0

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
@@ -11,8 +11,8 @@ import {
11
11
  import { render } from "ink";
12
12
 
13
13
  // src/components/App.tsx
14
- import { useState as useState2, useEffect, useRef } from "react";
15
- import { Box as Box6, Text as Text6, useApp, useInput as useInput2 } from "ink";
14
+ import { useState as useState7, useCallback as useCallback5 } from "react";
15
+ import { Box as Box7, Text as Text7, useApp, useInput as useInput3 } from "ink";
16
16
 
17
17
  // src/components/Header.tsx
18
18
  import { Box, Text } from "ink";
@@ -111,11 +111,12 @@ var MENU_ITEMS = [
111
111
  { command: "/screen", shortcut: "Ctrl+S", description: "Take screenshot and describe", category: "actions" },
112
112
  { command: "/task", description: "Run multi-step task", category: "actions" },
113
113
  { command: "/telegram", shortcut: "Ctrl+T", description: "Toggle Telegram bot", category: "actions" },
114
+ { command: "/memory", description: "View/clear learned task patterns", category: "actions" },
114
115
  // Settings
115
116
  { command: "/config", description: "Show/edit configuration", category: "settings" },
116
117
  { command: "/watch", shortcut: "Ctrl+W", description: "Toggle screen watching", category: "settings" },
117
118
  { command: "/model", description: "Change AI model", category: "settings" },
118
- { command: "/provider", description: "Change AI provider", category: "settings" }
119
+ { command: "/provider", shortcut: "Ctrl+P", description: "Change AI provider", category: "settings" }
119
120
  ];
120
121
  function HelpMenu({ onClose, onSelect }) {
121
122
  const [selectedIndex, setSelectedIndex] = useState(0);
@@ -190,6 +191,143 @@ function HelpMenu({ onClose, onSelect }) {
190
191
  );
191
192
  }
192
193
 
194
+ // src/components/ProviderSelector.tsx
195
+ import { useState as useState2 } from "react";
196
+ import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
197
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
198
+ var PROVIDERS = [
199
+ {
200
+ id: "ollama",
201
+ name: "Ollama",
202
+ description: "Local AI - Free, private, no API key",
203
+ defaultModel: "qwen2.5:0.5b",
204
+ models: ["qwen2.5:0.5b", "qwen2.5:1.5b", "qwen2.5:7b", "llama3.2:1b", "llama3.2:3b", "mistral:7b", "codellama:7b", "llava:7b"]
205
+ },
206
+ {
207
+ id: "openrouter",
208
+ name: "OpenRouter",
209
+ description: "Many models, pay-per-use",
210
+ defaultModel: "qwen/qwen-2.5-coder-32b-instruct",
211
+ models: [
212
+ "qwen/qwen-2.5-coder-32b-instruct",
213
+ "anthropic/claude-3.5-sonnet",
214
+ "openai/gpt-4o",
215
+ "openai/gpt-4o-mini",
216
+ "google/gemini-pro-1.5",
217
+ "meta-llama/llama-3.1-70b-instruct"
218
+ ]
219
+ },
220
+ {
221
+ id: "anthropic",
222
+ name: "Anthropic",
223
+ description: "Claude models - Best for coding",
224
+ defaultModel: "claude-3-5-sonnet-20241022",
225
+ models: ["claude-3-5-sonnet-20241022", "claude-3-opus-20240229", "claude-3-haiku-20240307"]
226
+ },
227
+ {
228
+ id: "openai",
229
+ name: "OpenAI",
230
+ description: "GPT models",
231
+ defaultModel: "gpt-4o",
232
+ models: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-3.5-turbo"]
233
+ }
234
+ ];
235
+ function ProviderSelector({ onClose, onSelect }) {
236
+ const config = getConfig();
237
+ const [mode, setMode] = useState2("provider");
238
+ const [providerIndex, setProviderIndex] = useState2(() => {
239
+ const idx = PROVIDERS.findIndex((p) => p.id === config.provider);
240
+ return idx >= 0 ? idx : 0;
241
+ });
242
+ const [modelIndex, setModelIndex] = useState2(0);
243
+ const [selectedProvider, setSelectedProvider] = useState2(null);
244
+ useInput2((input, key) => {
245
+ if (key.escape) {
246
+ onClose();
247
+ return;
248
+ }
249
+ if (mode === "provider") {
250
+ if (key.upArrow) {
251
+ setProviderIndex((prev) => prev > 0 ? prev - 1 : PROVIDERS.length - 1);
252
+ } else if (key.downArrow) {
253
+ setProviderIndex((prev) => prev < PROVIDERS.length - 1 ? prev + 1 : 0);
254
+ } else if (key.return) {
255
+ const provider = PROVIDERS[providerIndex];
256
+ setSelectedProvider(provider);
257
+ const currentModelIdx = provider.models.findIndex((m) => m === config.model);
258
+ setModelIndex(currentModelIdx >= 0 ? currentModelIdx : 0);
259
+ setMode("model");
260
+ }
261
+ } else if (mode === "model" && selectedProvider) {
262
+ if (key.upArrow) {
263
+ setModelIndex((prev) => prev > 0 ? prev - 1 : selectedProvider.models.length - 1);
264
+ } else if (key.downArrow) {
265
+ setModelIndex((prev) => prev < selectedProvider.models.length - 1 ? prev + 1 : 0);
266
+ } else if (key.return) {
267
+ const model = selectedProvider.models[modelIndex];
268
+ setProvider(selectedProvider.id);
269
+ setModel(model);
270
+ onSelect(selectedProvider.id, model);
271
+ onClose();
272
+ } else if (key.leftArrow || input === "b") {
273
+ setMode("provider");
274
+ }
275
+ }
276
+ });
277
+ if (mode === "provider") {
278
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, children: [
279
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "Select Provider" }) }),
280
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "Use arrows to navigate, Enter to select, Esc to cancel" }) }),
281
+ PROVIDERS.map((provider, index) => {
282
+ const isSelected = index === providerIndex;
283
+ const isCurrent = provider.id === config.provider;
284
+ return /* @__PURE__ */ jsxs5(Box6, { marginY: 0, children: [
285
+ /* @__PURE__ */ jsxs5(Text6, { color: isSelected ? "cyan" : "white", children: [
286
+ isSelected ? "\u276F " : " ",
287
+ provider.name,
288
+ isCurrent && /* @__PURE__ */ jsx6(Text6, { color: "green", children: " (current)" })
289
+ ] }),
290
+ isSelected && /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
291
+ " - ",
292
+ provider.description
293
+ ] })
294
+ ] }, provider.id);
295
+ }),
296
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
297
+ "Current: ",
298
+ config.provider,
299
+ " / ",
300
+ config.model
301
+ ] }) })
302
+ ] });
303
+ }
304
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, children: [
305
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsxs5(Text6, { bold: true, color: "cyan", children: [
306
+ "Select Model for ",
307
+ selectedProvider?.name
308
+ ] }) }),
309
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "Arrows to navigate, Enter to select, B/Left to go back" }) }),
310
+ selectedProvider?.models.map((model, index) => {
311
+ const isSelected = index === modelIndex;
312
+ const isCurrent = model === config.model && selectedProvider.id === config.provider;
313
+ const isDefault = model === selectedProvider.defaultModel;
314
+ return /* @__PURE__ */ jsx6(Box6, { marginY: 0, children: /* @__PURE__ */ jsxs5(Text6, { color: isSelected ? "cyan" : "white", children: [
315
+ isSelected ? "\u276F " : " ",
316
+ model,
317
+ isCurrent && /* @__PURE__ */ jsx6(Text6, { color: "green", children: " (current)" }),
318
+ isDefault && !isCurrent && /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: " (default)" })
319
+ ] }) }, model);
320
+ }),
321
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
322
+ "Provider: ",
323
+ selectedProvider?.name
324
+ ] }) })
325
+ ] });
326
+ }
327
+
328
+ // src/hooks/useChat.ts
329
+ import { useState as useState3, useCallback, useRef, useEffect } from "react";
330
+
193
331
  // src/lib/api.ts
194
332
  var SYSTEM_PROMPT = `You are C-napse, a helpful AI assistant for PC automation running on the user's desktop.
195
333
  You can help with coding, file management, shell commands, and more. Be concise and helpful.
@@ -347,6 +485,107 @@ async function getScreenDescription() {
347
485
  }
348
486
  }
349
487
 
488
+ // src/hooks/useChat.ts
489
+ var WELCOME_MESSAGE = {
490
+ id: "0",
491
+ role: "system",
492
+ content: "Welcome to C-napse! Type your message and press Enter.\n\nShortcuts: Ctrl+H for help, Ctrl+P for provider",
493
+ timestamp: /* @__PURE__ */ new Date()
494
+ };
495
+ function useChat(screenWatch = false) {
496
+ const [messages, setMessages] = useState3([WELCOME_MESSAGE]);
497
+ const [isProcessing, setIsProcessing] = useState3(false);
498
+ const [error, setError] = useState3(null);
499
+ const screenContextRef = useRef(null);
500
+ useEffect(() => {
501
+ if (!screenWatch) {
502
+ screenContextRef.current = null;
503
+ return;
504
+ }
505
+ const checkScreen = async () => {
506
+ const desc = await getScreenDescription();
507
+ if (desc) {
508
+ screenContextRef.current = desc;
509
+ }
510
+ };
511
+ checkScreen();
512
+ const interval = setInterval(checkScreen, 5e3);
513
+ return () => clearInterval(interval);
514
+ }, [screenWatch]);
515
+ const addSystemMessage = useCallback((content) => {
516
+ setMessages((prev) => [
517
+ ...prev,
518
+ {
519
+ id: Date.now().toString(),
520
+ role: "system",
521
+ content,
522
+ timestamp: /* @__PURE__ */ new Date()
523
+ }
524
+ ]);
525
+ }, []);
526
+ const sendMessage = useCallback(async (content) => {
527
+ if (!content.trim() || isProcessing) return;
528
+ setError(null);
529
+ const userMsg = {
530
+ id: Date.now().toString(),
531
+ role: "user",
532
+ content,
533
+ timestamp: /* @__PURE__ */ new Date()
534
+ };
535
+ const assistantId = (Date.now() + 1).toString();
536
+ const assistantMsg = {
537
+ id: assistantId,
538
+ role: "assistant",
539
+ content: "",
540
+ timestamp: /* @__PURE__ */ new Date(),
541
+ isStreaming: true
542
+ };
543
+ setMessages((prev) => [...prev, userMsg, assistantMsg]);
544
+ setIsProcessing(true);
545
+ try {
546
+ const apiMessages = messages.filter((m) => m.role === "user" || m.role === "assistant").slice(-10).map((m) => ({ role: m.role, content: m.content }));
547
+ let finalContent = content;
548
+ if (screenWatch && screenContextRef.current) {
549
+ finalContent = `[Screen context: ${screenContextRef.current}]
550
+
551
+ ${content}`;
552
+ }
553
+ apiMessages.push({ role: "user", content: finalContent });
554
+ const response = await chat(apiMessages);
555
+ setMessages(
556
+ (prev) => prev.map(
557
+ (m) => m.id === assistantId ? { ...m, content: response.content || "(no response)", isStreaming: false } : m
558
+ )
559
+ );
560
+ } catch (err2) {
561
+ const errorMsg = err2 instanceof Error ? err2.message : "Unknown error";
562
+ setError(errorMsg);
563
+ setMessages(
564
+ (prev) => prev.map(
565
+ (m) => m.id === assistantId ? { ...m, content: `Error: ${errorMsg}`, isStreaming: false } : m
566
+ )
567
+ );
568
+ } finally {
569
+ setIsProcessing(false);
570
+ }
571
+ }, [messages, isProcessing, screenWatch]);
572
+ const clearMessages = useCallback(() => {
573
+ setMessages([WELCOME_MESSAGE]);
574
+ setError(null);
575
+ }, []);
576
+ return {
577
+ messages,
578
+ isProcessing,
579
+ error,
580
+ sendMessage,
581
+ addSystemMessage,
582
+ clearMessages
583
+ };
584
+ }
585
+
586
+ // src/hooks/useVision.ts
587
+ import { useState as useState4, useCallback as useCallback2 } from "react";
588
+
350
589
  // src/lib/vision.ts
351
590
  async function describeScreen() {
352
591
  const screenshot = await captureScreenshot();
@@ -370,10 +609,10 @@ async function captureScreenFallback() {
370
609
  const { exec: exec5 } = await import("child_process");
371
610
  const { promisify: promisify5 } = await import("util");
372
611
  const { tmpdir } = await import("os");
373
- const { join } = await import("path");
612
+ const { join: join2 } = await import("path");
374
613
  const { readFile, unlink } = await import("fs/promises");
375
614
  const execAsync5 = promisify5(exec5);
376
- const tempFile = join(tmpdir(), `cnapse-screen-${Date.now()}.png`);
615
+ const tempFile = join2(tmpdir(), `cnapse-screen-${Date.now()}.png`);
377
616
  try {
378
617
  const platform = process.platform;
379
618
  if (platform === "win32") {
@@ -551,6 +790,40 @@ async function analyzeWithOpenAI(base64Image, prompt) {
551
790
  return data.choices?.[0]?.message?.content || "Unable to analyze image";
552
791
  }
553
792
 
793
+ // src/hooks/useVision.ts
794
+ function useVision() {
795
+ const [isAnalyzing, setIsAnalyzing] = useState4(false);
796
+ const [lastDescription, setLastDescription] = useState4(null);
797
+ const [lastScreenshot, setLastScreenshot] = useState4(null);
798
+ const [error, setError] = useState4(null);
799
+ const analyze = useCallback2(async () => {
800
+ setIsAnalyzing(true);
801
+ setError(null);
802
+ try {
803
+ const result = await describeScreen();
804
+ setLastDescription(result.description);
805
+ setLastScreenshot(result.screenshot);
806
+ return result.description;
807
+ } catch (err2) {
808
+ const errorMsg = err2 instanceof Error ? err2.message : "Vision analysis failed";
809
+ setError(errorMsg);
810
+ throw err2;
811
+ } finally {
812
+ setIsAnalyzing(false);
813
+ }
814
+ }, []);
815
+ return {
816
+ isAnalyzing,
817
+ lastDescription,
818
+ lastScreenshot,
819
+ error,
820
+ analyze
821
+ };
822
+ }
823
+
824
+ // src/hooks/useTelegram.ts
825
+ import { useState as useState5, useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2 } from "react";
826
+
554
827
  // src/services/telegram.ts
555
828
  import { EventEmitter } from "events";
556
829
 
@@ -991,48 +1264,231 @@ function getTelegramBot() {
991
1264
  return instance;
992
1265
  }
993
1266
 
1267
+ // src/hooks/useTelegram.ts
1268
+ function useTelegram(onMessage) {
1269
+ const [isEnabled, setIsEnabled] = useState5(false);
1270
+ const [isStarting, setIsStarting] = useState5(false);
1271
+ const [error, setError] = useState5(null);
1272
+ const [lastMessage, setLastMessage] = useState5(null);
1273
+ const onMessageRef = useRef2(onMessage);
1274
+ useEffect2(() => {
1275
+ onMessageRef.current = onMessage;
1276
+ }, [onMessage]);
1277
+ const start = useCallback3(async () => {
1278
+ if (isEnabled) return;
1279
+ setIsStarting(true);
1280
+ setError(null);
1281
+ try {
1282
+ const bot = getTelegramBot();
1283
+ bot.on("message", (msg) => {
1284
+ setLastMessage(msg);
1285
+ onMessageRef.current?.(msg);
1286
+ });
1287
+ bot.on("error", (err2) => {
1288
+ setError(err2.message);
1289
+ });
1290
+ await bot.start();
1291
+ setIsEnabled(true);
1292
+ } catch (err2) {
1293
+ const errorMsg = err2 instanceof Error ? err2.message : "Failed to start Telegram bot";
1294
+ setError(errorMsg);
1295
+ throw err2;
1296
+ } finally {
1297
+ setIsStarting(false);
1298
+ }
1299
+ }, [isEnabled]);
1300
+ const stop = useCallback3(async () => {
1301
+ if (!isEnabled) return;
1302
+ try {
1303
+ const bot = getTelegramBot();
1304
+ await bot.stop();
1305
+ setIsEnabled(false);
1306
+ } catch (err2) {
1307
+ const errorMsg = err2 instanceof Error ? err2.message : "Failed to stop Telegram bot";
1308
+ setError(errorMsg);
1309
+ throw err2;
1310
+ }
1311
+ }, [isEnabled]);
1312
+ const toggle = useCallback3(async () => {
1313
+ if (isEnabled) {
1314
+ await stop();
1315
+ } else {
1316
+ await start();
1317
+ }
1318
+ }, [isEnabled, start, stop]);
1319
+ return {
1320
+ isEnabled,
1321
+ isStarting,
1322
+ error,
1323
+ lastMessage,
1324
+ toggle,
1325
+ start,
1326
+ stop
1327
+ };
1328
+ }
1329
+
1330
+ // src/hooks/useTasks.ts
1331
+ import { useState as useState6, useCallback as useCallback4 } from "react";
1332
+
994
1333
  // src/lib/tasks.ts
995
- async function parseTask(input) {
996
- const systemPrompt = `You are a task parser for PC automation. Convert user requests into specific, executable steps.
1334
+ import * as fs from "fs";
1335
+ import * as path from "path";
1336
+ import * as os from "os";
1337
+ var TASK_MEMORY_FILE = path.join(os.homedir(), ".cnapse", "task-memory.json");
1338
+ function loadTaskMemory() {
1339
+ try {
1340
+ if (fs.existsSync(TASK_MEMORY_FILE)) {
1341
+ const data = fs.readFileSync(TASK_MEMORY_FILE, "utf-8");
1342
+ return JSON.parse(data);
1343
+ }
1344
+ } catch {
1345
+ }
1346
+ return { patterns: [], version: 1 };
1347
+ }
1348
+ function saveTaskPattern(input, steps) {
1349
+ try {
1350
+ const memory = loadTaskMemory();
1351
+ const normalized = normalizeInput(input);
1352
+ const existing = memory.patterns.find((p) => p.normalizedInput === normalized);
1353
+ if (existing) {
1354
+ existing.steps = steps;
1355
+ existing.successCount++;
1356
+ existing.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
1357
+ } else {
1358
+ memory.patterns.push({
1359
+ input,
1360
+ normalizedInput: normalized,
1361
+ steps,
1362
+ successCount: 1,
1363
+ lastUsed: (/* @__PURE__ */ new Date()).toISOString()
1364
+ });
1365
+ }
1366
+ memory.patterns = memory.patterns.sort((a, b) => b.successCount - a.successCount).slice(0, 100);
1367
+ const dir = path.dirname(TASK_MEMORY_FILE);
1368
+ if (!fs.existsSync(dir)) {
1369
+ fs.mkdirSync(dir, { recursive: true });
1370
+ }
1371
+ fs.writeFileSync(TASK_MEMORY_FILE, JSON.stringify(memory, null, 2));
1372
+ } catch {
1373
+ }
1374
+ }
1375
+ function normalizeInput(input) {
1376
+ return input.toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
1377
+ }
1378
+ function findSimilarPatterns(input) {
1379
+ const memory = loadTaskMemory();
1380
+ const normalized = normalizeInput(input);
1381
+ const words = normalized.split(" ").filter((w) => w.length > 2);
1382
+ return memory.patterns.filter((pattern) => {
1383
+ const patternWords = pattern.normalizedInput.split(" ");
1384
+ const matches = words.filter((w) => patternWords.includes(w));
1385
+ return matches.length >= Math.min(2, words.length * 0.5);
1386
+ }).sort((a, b) => b.successCount - a.successCount).slice(0, 3);
1387
+ }
1388
+ function buildChainOfThoughtPrompt(input) {
1389
+ const similarPatterns = findSimilarPatterns(input);
1390
+ let learnedExamples = "";
1391
+ if (similarPatterns.length > 0) {
1392
+ learnedExamples = `
1393
+ ## LEARNED PATTERNS (from successful past tasks)
1394
+ These patterns worked before - use them as reference:
1395
+
1396
+ ${similarPatterns.map((p, i) => `
1397
+ Pattern ${i + 1} (used ${p.successCount} times):
1398
+ Input: "${p.input}"
1399
+ Steps: ${JSON.stringify(p.steps, null, 2)}
1400
+ `).join("\n")}
1401
+ `;
1402
+ }
1403
+ return `You are a task parser for Windows PC automation. Your job is to convert natural language into precise, executable steps.
1404
+
1405
+ ## THINKING PROCESS
1406
+ Before outputting steps, THINK through these questions:
1407
+
1408
+ 1. **WHAT** is the main goal?
1409
+ - What application needs to open?
1410
+ - What action needs to happen inside it?
1411
+ - What is the expected end result?
1412
+
1413
+ 2. **HOW** to achieve it on Windows?
1414
+ - Use Win+R (meta+r) to open Run dialog for apps
1415
+ - Wait 1-3 seconds after opening apps for them to load
1416
+ - Use keyboard shortcuts when possible (faster, more reliable)
1417
+ - Common shortcuts: Ctrl+S (save), Ctrl+O (open), Ctrl+N (new), Alt+F4 (close)
1418
+
1419
+ 3. **SEQUENCE** - what order makes sense?
1420
+ - Open app FIRST
1421
+ - WAIT for it to load
1422
+ - THEN interact with it
1423
+ - Add waits between actions that need time
997
1424
 
998
- Available actions:
999
- - open_app: Open an application (e.g., "open_app:notepad", "open_app:vscode")
1000
- - type_text: Type text (e.g., "type_text:Hello World")
1001
- - press_key: Press a key (e.g., "press_key:enter", "press_key:escape")
1002
- - key_combo: Key combination (e.g., "key_combo:control+s", "key_combo:alt+f4")
1003
- - click: Click mouse (e.g., "click:left", "click:right")
1004
- - wait: Wait seconds (e.g., "wait:2")
1005
- - focus_window: Focus window by title (e.g., "focus_window:Notepad")
1006
- - screenshot: Take screenshot and describe
1425
+ 4. **EDGE CASES** - what could go wrong?
1426
+ - App might already be open -> focus_window first
1427
+ - Dialogs might appear -> handle or dismiss them
1428
+ - Typing too fast -> add small waits
1007
1429
 
1008
- Respond ONLY with a JSON array of steps, no other text:
1430
+ ## AVAILABLE ACTIONS
1431
+ - open_app: Open app via Run dialog (e.g., "open_app:notepad", "open_app:code", "open_app:chrome")
1432
+ - type_text: Type text string (e.g., "type_text:Hello World")
1433
+ - press_key: Single key (e.g., "press_key:enter", "press_key:escape", "press_key:tab")
1434
+ - key_combo: Key combination (e.g., "key_combo:control+s", "key_combo:alt+f4", "key_combo:meta+r")
1435
+ - click: Mouse click (e.g., "click:left", "click:right")
1436
+ - wait: Wait N seconds (e.g., "wait:2" - use 1-3s for app loads)
1437
+ - focus_window: Focus by title (e.g., "focus_window:Notepad")
1438
+ - screenshot: Capture and describe screen
1439
+ ${learnedExamples}
1440
+ ## EXAMPLES WITH REASONING
1441
+
1442
+ ### Example 1: "open notepad and type hello"
1443
+ Thinking:
1444
+ - Goal: Open Notepad, then type text into it
1445
+ - How: Win+R -> notepad -> Enter to open, then type
1446
+ - Sequence: Open -> Wait for load -> Type
1447
+ - Edge case: Need wait time for Notepad window to be ready
1448
+
1449
+ Output:
1009
1450
  [
1010
- { "description": "Human readable step", "action": "action_type:params" },
1011
- ...
1451
+ { "description": "Open Notepad via Run dialog", "action": "open_app:notepad" },
1452
+ { "description": "Wait for Notepad to fully load", "action": "wait:2" },
1453
+ { "description": "Type the greeting text", "action": "type_text:hello" }
1454
+ ]
1455
+
1456
+ ### Example 2: "save the current document"
1457
+ Thinking:
1458
+ - Goal: Save whatever is in the current app
1459
+ - How: Ctrl+S is universal save shortcut
1460
+ - Sequence: Just the key combo, maybe wait for save
1461
+ - Edge case: If file is new, Save As dialog might appear
1462
+
1463
+ Output:
1464
+ [
1465
+ { "description": "Press Ctrl+S to save", "action": "key_combo:control+s" },
1466
+ { "description": "Wait for save to complete", "action": "wait:1" }
1012
1467
  ]
1013
1468
 
1014
- Example input: "open notepad and type hello world"
1015
- Example output:
1469
+ ### Example 3: "close this window"
1470
+ Thinking:
1471
+ - Goal: Close the current active window
1472
+ - How: Alt+F4 closes active window on Windows
1473
+ - Sequence: Single action
1474
+ - Edge case: Might prompt to save - user handles that
1475
+
1476
+ Output:
1016
1477
  [
1017
- { "description": "Open Notepad", "action": "open_app:notepad" },
1018
- { "description": "Wait for Notepad to open", "action": "wait:2" },
1019
- { "description": "Type hello world", "action": "type_text:Hello World" }
1478
+ { "description": "Close active window with Alt+F4", "action": "key_combo:alt+f4" }
1020
1479
  ]
1021
1480
 
1022
- Example input: "open vscode, go to folder E:\\Projects, then open terminal"
1023
- Example output:
1481
+ ## YOUR TASK
1482
+ Now parse this request: "${input}"
1483
+
1484
+ First, briefly think through the 4 questions above, then output ONLY a JSON array:
1024
1485
  [
1025
- { "description": "Open VS Code", "action": "open_app:code" },
1026
- { "description": "Wait for VS Code to load", "action": "wait:3" },
1027
- { "description": "Open folder with Ctrl+K Ctrl+O", "action": "key_combo:control+k" },
1028
- { "description": "Wait for dialog", "action": "wait:1" },
1029
- { "description": "Continue folder open", "action": "key_combo:control+o" },
1030
- { "description": "Wait for folder dialog", "action": "wait:1" },
1031
- { "description": "Type folder path", "action": "type_text:E:\\\\Projects" },
1032
- { "description": "Press Enter to open folder", "action": "press_key:enter" },
1033
- { "description": "Wait for folder to load", "action": "wait:2" },
1034
- { "description": "Open terminal with Ctrl+\`", "action": "key_combo:control+\`" }
1486
+ { "description": "Human readable step", "action": "action_type:params" },
1487
+ ...
1035
1488
  ]`;
1489
+ }
1490
+ async function parseTask(input) {
1491
+ const systemPrompt = buildChainOfThoughtPrompt(input);
1036
1492
  const messages = [
1037
1493
  { role: "user", content: input }
1038
1494
  ];
@@ -1143,6 +1599,11 @@ async function executeTask(task, onProgress) {
1143
1599
  }
1144
1600
  if (task.status !== "failed") {
1145
1601
  task.status = "completed";
1602
+ const steps = task.steps.map((s) => ({
1603
+ description: s.description,
1604
+ action: s.action
1605
+ }));
1606
+ saveTaskPattern(task.description, steps);
1146
1607
  }
1147
1608
  task.completedAt = /* @__PURE__ */ new Date();
1148
1609
  return task;
@@ -1150,6 +1611,24 @@ async function executeTask(task, onProgress) {
1150
1611
  function sleep(ms) {
1151
1612
  return new Promise((resolve) => setTimeout(resolve, ms));
1152
1613
  }
1614
+ function getTaskMemoryStats() {
1615
+ const memory = loadTaskMemory();
1616
+ const totalUses = memory.patterns.reduce((sum, p) => sum + p.successCount, 0);
1617
+ const topPatterns = memory.patterns.sort((a, b) => b.successCount - a.successCount).slice(0, 5).map((p) => `"${p.input}" (${p.successCount}x)`);
1618
+ return {
1619
+ patternCount: memory.patterns.length,
1620
+ totalUses,
1621
+ topPatterns
1622
+ };
1623
+ }
1624
+ function clearTaskMemory() {
1625
+ try {
1626
+ if (fs.existsSync(TASK_MEMORY_FILE)) {
1627
+ fs.unlinkSync(TASK_MEMORY_FILE);
1628
+ }
1629
+ } catch {
1630
+ }
1631
+ }
1153
1632
  function formatTask(task) {
1154
1633
  const statusEmoji = {
1155
1634
  pending: "\u23F3",
@@ -1180,293 +1659,251 @@ function formatTask(task) {
1180
1659
  return output;
1181
1660
  }
1182
1661
 
1662
+ // src/hooks/useTasks.ts
1663
+ function useTasks(onProgress) {
1664
+ const [isRunning, setIsRunning] = useState6(false);
1665
+ const [currentTask, setCurrentTask] = useState6(null);
1666
+ const [currentStep, setCurrentStep] = useState6(null);
1667
+ const [error, setError] = useState6(null);
1668
+ const run = useCallback4(async (description) => {
1669
+ setIsRunning(true);
1670
+ setError(null);
1671
+ try {
1672
+ const task = await parseTask(description);
1673
+ setCurrentTask(task);
1674
+ const result = await executeTask(task, (updatedTask, step) => {
1675
+ setCurrentTask({ ...updatedTask });
1676
+ setCurrentStep(step);
1677
+ onProgress?.(updatedTask, step);
1678
+ });
1679
+ setCurrentTask(result);
1680
+ return result;
1681
+ } catch (err2) {
1682
+ const errorMsg = err2 instanceof Error ? err2.message : "Task failed";
1683
+ setError(errorMsg);
1684
+ throw err2;
1685
+ } finally {
1686
+ setIsRunning(false);
1687
+ setCurrentStep(null);
1688
+ }
1689
+ }, [onProgress]);
1690
+ return {
1691
+ isRunning,
1692
+ currentTask,
1693
+ currentStep,
1694
+ error,
1695
+ run,
1696
+ format: formatTask,
1697
+ getMemoryStats: getTaskMemoryStats,
1698
+ clearMemory: clearTaskMemory
1699
+ };
1700
+ }
1701
+
1183
1702
  // src/components/App.tsx
1184
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1703
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1185
1704
  function App() {
1186
1705
  const { exit } = useApp();
1187
- const [messages, setMessages] = useState2([
1188
- {
1189
- id: "0",
1190
- role: "system",
1191
- content: "Welcome to C-napse! Type your message and press Enter.\n\nShortcuts:\n Ctrl+C - Exit\n Ctrl+W - Toggle screen watch\n /clear - Clear chat\n /help - Show help",
1192
- timestamp: /* @__PURE__ */ new Date()
1193
- }
1194
- ]);
1195
- const [input, setInput] = useState2("");
1196
- const [isProcessing, setIsProcessing] = useState2(false);
1197
- const [status, setStatus] = useState2("Ready");
1198
- const [error, setError] = useState2(null);
1199
- const [screenWatch, setScreenWatch] = useState2(false);
1200
- const [showHelpMenu, setShowHelpMenu] = useState2(false);
1201
- const [telegramEnabled, setTelegramEnabled] = useState2(false);
1202
- const screenContextRef = useRef(null);
1203
- useEffect(() => {
1204
- if (!screenWatch) {
1205
- screenContextRef.current = null;
1206
- return;
1207
- }
1208
- const checkScreen = async () => {
1209
- const desc = await getScreenDescription();
1210
- if (desc) {
1211
- screenContextRef.current = desc;
1212
- }
1213
- };
1214
- checkScreen();
1215
- const interval = setInterval(checkScreen, 5e3);
1216
- return () => clearInterval(interval);
1217
- }, [screenWatch]);
1218
- useInput2((inputChar, key) => {
1219
- if (showHelpMenu) {
1220
- return;
1221
- }
1222
- if (key.ctrl && inputChar === "c") {
1223
- exit();
1224
- }
1225
- if (key.ctrl && inputChar === "l") {
1226
- setMessages([messages[0]]);
1227
- setError(null);
1706
+ const [overlay, setOverlay] = useState7("none");
1707
+ const [screenWatch, setScreenWatch] = useState7(false);
1708
+ const [status, setStatus] = useState7("Ready");
1709
+ const chat2 = useChat(screenWatch);
1710
+ const vision = useVision();
1711
+ const telegram = useTelegram((msg) => {
1712
+ chat2.addSystemMessage(`\u{1F4F1} Telegram [${msg.from}]: ${msg.text}`);
1713
+ });
1714
+ const tasks = useTasks((task, step) => {
1715
+ if (step.status === "running") {
1716
+ setStatus(`Running: ${step.description}`);
1228
1717
  }
1718
+ });
1719
+ useInput3((inputChar, key) => {
1720
+ if (overlay !== "none") return;
1721
+ if (key.ctrl && inputChar === "c") exit();
1722
+ if (key.ctrl && inputChar === "l") chat2.clearMessages();
1723
+ if (key.ctrl && inputChar === "h") setOverlay("help");
1724
+ if (key.ctrl && inputChar === "p") setOverlay("provider");
1229
1725
  if (key.ctrl && inputChar === "w") {
1230
1726
  setScreenWatch((prev) => {
1231
1727
  const newState = !prev;
1232
- addSystemMessage(
1233
- newState ? "\u{1F5A5}\uFE0F Screen watching enabled. AI will have context of your screen." : "\u{1F5A5}\uFE0F Screen watching disabled."
1728
+ chat2.addSystemMessage(
1729
+ newState ? "\u{1F5A5}\uFE0F Screen watching enabled." : "\u{1F5A5}\uFE0F Screen watching disabled."
1234
1730
  );
1235
1731
  return newState;
1236
1732
  });
1237
1733
  }
1238
- if (key.ctrl && inputChar === "h") {
1239
- setShowHelpMenu(true);
1240
- }
1241
1734
  if (key.ctrl && inputChar === "t") {
1242
- setTelegramEnabled((prev) => {
1243
- const newState = !prev;
1244
- addSystemMessage(
1245
- newState ? "\u{1F4F1} Telegram bot enabled." : "\u{1F4F1} Telegram bot disabled."
1246
- );
1247
- return newState;
1248
- });
1735
+ handleTelegramToggle();
1249
1736
  }
1250
1737
  });
1251
- const handleSubmit = async (value) => {
1252
- if (!value.trim() || isProcessing) return;
1253
- const userInput = value.trim();
1254
- setInput("");
1255
- setError(null);
1256
- if (userInput.startsWith("/")) {
1257
- handleCommand(userInput);
1258
- return;
1259
- }
1260
- const userMsg = {
1261
- id: Date.now().toString(),
1262
- role: "user",
1263
- content: userInput,
1264
- timestamp: /* @__PURE__ */ new Date()
1265
- };
1266
- setMessages((prev) => [...prev, userMsg]);
1267
- const assistantId = (Date.now() + 1).toString();
1268
- setMessages((prev) => [
1269
- ...prev,
1270
- {
1271
- id: assistantId,
1272
- role: "assistant",
1273
- content: "",
1274
- timestamp: /* @__PURE__ */ new Date(),
1275
- isStreaming: true
1276
- }
1277
- ]);
1278
- setIsProcessing(true);
1279
- setStatus("Thinking...");
1280
- try {
1281
- const apiMessages = messages.filter((m) => m.role === "user" || m.role === "assistant").slice(-10).map((m) => ({ role: m.role, content: m.content }));
1282
- let finalInput = userInput;
1283
- if (screenWatch && screenContextRef.current) {
1284
- finalInput = `[Screen context: ${screenContextRef.current}]
1285
-
1286
- ${userInput}`;
1287
- }
1288
- apiMessages.push({ role: "user", content: finalInput });
1289
- const response = await chat(apiMessages);
1290
- setMessages(
1291
- (prev) => prev.map(
1292
- (m) => m.id === assistantId ? { ...m, content: response.content || "(no response)", isStreaming: false } : m
1293
- )
1294
- );
1295
- } catch (err2) {
1296
- const errorMsg = err2 instanceof Error ? err2.message : "Unknown error";
1297
- setError(errorMsg);
1298
- setMessages(
1299
- (prev) => prev.map(
1300
- (m) => m.id === assistantId ? { ...m, content: `Error: ${errorMsg}`, isStreaming: false } : m
1301
- )
1302
- );
1303
- } finally {
1304
- setIsProcessing(false);
1305
- setStatus("Ready");
1306
- }
1307
- };
1308
- const handleCommand = (cmd) => {
1738
+ const handleCommand = useCallback5(async (cmd) => {
1309
1739
  const parts = cmd.slice(1).split(" ");
1310
1740
  const command = parts[0];
1311
1741
  const args2 = parts.slice(1).join(" ");
1312
1742
  switch (command) {
1313
1743
  case "clear":
1314
- setMessages([messages[0]]);
1315
- addSystemMessage("Chat cleared.");
1744
+ chat2.clearMessages();
1745
+ chat2.addSystemMessage("Chat cleared.");
1316
1746
  break;
1317
1747
  case "help":
1318
- setShowHelpMenu(true);
1748
+ setOverlay("help");
1749
+ break;
1750
+ case "provider":
1751
+ case "model":
1752
+ setOverlay("provider");
1753
+ break;
1754
+ case "config": {
1755
+ const config = getConfig();
1756
+ chat2.addSystemMessage(
1757
+ `\u2699\uFE0F Configuration:
1758
+ Provider: ${config.provider}
1759
+ Model: ${config.model}
1760
+ Ollama: ${config.ollamaHost}
1761
+
1762
+ Use /provider to change`
1763
+ );
1764
+ break;
1765
+ }
1766
+ case "screen":
1767
+ await handleScreenCommand();
1319
1768
  break;
1320
1769
  case "watch":
1321
1770
  setScreenWatch((prev) => {
1322
1771
  const newState = !prev;
1323
- addSystemMessage(
1772
+ chat2.addSystemMessage(
1324
1773
  newState ? "\u{1F5A5}\uFE0F Screen watching enabled." : "\u{1F5A5}\uFE0F Screen watching disabled."
1325
1774
  );
1326
1775
  return newState;
1327
1776
  });
1328
1777
  break;
1329
1778
  case "telegram":
1330
- handleTelegramToggle();
1331
- break;
1332
- case "screen":
1333
- handleScreenCommand();
1779
+ await handleTelegramToggle();
1334
1780
  break;
1335
1781
  case "task":
1336
1782
  if (args2) {
1337
- handleTaskCommand(args2);
1783
+ await handleTaskCommand(args2);
1338
1784
  } else {
1339
- addSystemMessage("Usage: /task <description>\nExample: /task open notepad and type hello");
1785
+ chat2.addSystemMessage("Usage: /task <description>\nExample: /task open notepad and type hello");
1340
1786
  }
1341
1787
  break;
1342
- case "config":
1343
- addSystemMessage("\u2699\uFE0F Configuration:\n Provider: Use cnapse config\n Model: Use cnapse config set model <name>");
1344
- break;
1345
- case "model":
1346
- addSystemMessage("\u{1F916} Model selection coming soon.\nUse: cnapse config set model <name>");
1347
- break;
1348
- case "provider":
1349
- addSystemMessage("\u{1F50C} Provider selection coming soon.\nUse: cnapse config set provider <name>");
1788
+ case "memory":
1789
+ if (args2 === "clear") {
1790
+ tasks.clearMemory();
1791
+ chat2.addSystemMessage("\u{1F9E0} Task memory cleared.");
1792
+ } else {
1793
+ const stats = tasks.getMemoryStats();
1794
+ chat2.addSystemMessage(
1795
+ `\u{1F9E0} Task Memory:
1796
+
1797
+ Learned patterns: ${stats.patternCount}
1798
+ Total successful uses: ${stats.totalUses}
1799
+
1800
+ ` + (stats.topPatterns.length > 0 ? ` Top patterns:
1801
+ ${stats.topPatterns.map((p) => ` \u2022 ${p}`).join("\n")}
1802
+
1803
+ ` : " No patterns learned yet.\n\n") + `The more you use /task, the smarter it gets!
1804
+ Use /memory clear to reset.`
1805
+ );
1806
+ }
1350
1807
  break;
1351
1808
  case "quit":
1352
1809
  case "exit":
1353
1810
  exit();
1354
1811
  break;
1355
1812
  default:
1356
- addSystemMessage(`Unknown command: ${command}
1357
- Type /help to see available commands.`);
1813
+ chat2.addSystemMessage(`Unknown command: ${command}
1814
+ Type /help for commands`);
1358
1815
  }
1359
- };
1360
- const handleHelpMenuSelect = (command) => {
1361
- handleCommand(command);
1362
- };
1363
- const handleScreenCommand = async () => {
1364
- addSystemMessage("\u{1F4F8} Taking screenshot and analyzing...");
1365
- setStatus("Analyzing screen...");
1366
- setIsProcessing(true);
1816
+ }, [chat2, exit]);
1817
+ const handleScreenCommand = useCallback5(async () => {
1818
+ chat2.addSystemMessage("\u{1F4F8} Analyzing screen...");
1819
+ setStatus("Analyzing...");
1367
1820
  try {
1368
- const result = await describeScreen();
1369
- addSystemMessage(`\u{1F5A5}\uFE0F Screen Analysis:
1821
+ const description = await vision.analyze();
1822
+ chat2.addSystemMessage(`\u{1F5A5}\uFE0F Screen:
1370
1823
 
1371
- ${result.description}`);
1824
+ ${description}`);
1372
1825
  } catch (err2) {
1373
- const errorMsg = err2 instanceof Error ? err2.message : "Vision analysis failed";
1374
- addSystemMessage(`\u274C Screen capture failed: ${errorMsg}`);
1826
+ chat2.addSystemMessage(`\u274C ${vision.error || "Vision failed"}`);
1375
1827
  } finally {
1376
- setIsProcessing(false);
1377
1828
  setStatus("Ready");
1378
1829
  }
1379
- };
1380
- const handleTaskCommand = async (taskDescription) => {
1381
- addSystemMessage(`\u{1F4CB} Parsing task: ${taskDescription}`);
1382
- setStatus("Parsing task...");
1383
- setIsProcessing(true);
1384
- try {
1385
- const task = await parseTask(taskDescription);
1386
- addSystemMessage(`\u{1F4CB} Task planned (${task.steps.length} steps):
1387
- ${formatTask(task)}`);
1388
- addSystemMessage("\u{1F680} Executing task...");
1389
- setStatus("Executing task...");
1390
- await executeTask(task, (updatedTask, currentStep) => {
1391
- if (currentStep.status === "running") {
1392
- setStatus(`Running: ${currentStep.description}`);
1393
- }
1394
- });
1395
- addSystemMessage(`
1396
- ${formatTask(task)}`);
1397
- if (task.status === "completed") {
1398
- addSystemMessage("\u2705 Task completed successfully!");
1399
- } else {
1400
- addSystemMessage("\u274C Task failed. Check the steps above for errors.");
1401
- }
1402
- } catch (err2) {
1403
- const errorMsg = err2 instanceof Error ? err2.message : "Task failed";
1404
- addSystemMessage(`\u274C Task error: ${errorMsg}`);
1405
- } finally {
1406
- setIsProcessing(false);
1407
- setStatus("Ready");
1408
- }
1409
- };
1410
- const handleTelegramToggle = async () => {
1411
- const bot = getTelegramBot();
1412
- if (telegramEnabled) {
1413
- try {
1414
- await bot.stop();
1415
- setTelegramEnabled(false);
1416
- addSystemMessage("\u{1F4F1} Telegram bot stopped.");
1417
- } catch (err2) {
1418
- const errorMsg = err2 instanceof Error ? err2.message : "Failed to stop bot";
1419
- addSystemMessage(`\u274C Error stopping bot: ${errorMsg}`);
1420
- }
1830
+ }, [chat2, vision]);
1831
+ const handleTelegramToggle = useCallback5(async () => {
1832
+ if (telegram.isEnabled) {
1833
+ await telegram.stop();
1834
+ chat2.addSystemMessage("\u{1F4F1} Telegram stopped.");
1421
1835
  } else {
1422
- addSystemMessage("\u{1F4F1} Starting Telegram bot...");
1836
+ chat2.addSystemMessage("\u{1F4F1} Starting Telegram...");
1423
1837
  setStatus("Starting Telegram...");
1424
1838
  try {
1425
- bot.on("message", (msg) => {
1426
- addSystemMessage(`\u{1F4F1} Telegram [${msg.from}]: ${msg.text}`);
1427
- });
1428
- bot.on("error", (error2) => {
1429
- addSystemMessage(`\u{1F4F1} Telegram error: ${error2.message}`);
1430
- });
1431
- await bot.start();
1432
- setTelegramEnabled(true);
1433
- addSystemMessage(
1434
- "\u{1F4F1} Telegram bot started!\n\nOpen Telegram and send /start to your bot to connect.\nCommands: /screen, /describe, /run <cmd>, /status"
1839
+ await telegram.start();
1840
+ chat2.addSystemMessage(
1841
+ "\u{1F4F1} Telegram started!\nSend /start to your bot to connect.\nCommands: /screen, /describe, /run, /status"
1435
1842
  );
1436
- } catch (err2) {
1437
- const errorMsg = err2 instanceof Error ? err2.message : "Failed to start bot";
1438
- addSystemMessage(`\u274C Telegram error: ${errorMsg}`);
1843
+ } catch {
1844
+ chat2.addSystemMessage(`\u274C ${telegram.error || "Telegram failed"}`);
1439
1845
  } finally {
1440
1846
  setStatus("Ready");
1441
1847
  }
1442
1848
  }
1443
- };
1444
- const addSystemMessage = (content) => {
1445
- setMessages((prev) => [
1446
- ...prev,
1849
+ }, [chat2, telegram]);
1850
+ const handleTaskCommand = useCallback5(async (description) => {
1851
+ chat2.addSystemMessage(`\u{1F4CB} Parsing: ${description}`);
1852
+ setStatus("Parsing task...");
1853
+ try {
1854
+ const task = await tasks.run(description);
1855
+ chat2.addSystemMessage(`
1856
+ ${tasks.format(task)}`);
1857
+ chat2.addSystemMessage(
1858
+ task.status === "completed" ? "\u2705 Task completed!" : "\u274C Task failed."
1859
+ );
1860
+ } catch {
1861
+ chat2.addSystemMessage(`\u274C ${tasks.error || "Task failed"}`);
1862
+ } finally {
1863
+ setStatus("Ready");
1864
+ }
1865
+ }, [chat2, tasks]);
1866
+ const handleSubmit = useCallback5(async (value) => {
1867
+ if (!value.trim()) return;
1868
+ if (value.startsWith("/")) {
1869
+ await handleCommand(value);
1870
+ } else {
1871
+ setStatus("Thinking...");
1872
+ await chat2.sendMessage(value);
1873
+ setStatus("Ready");
1874
+ }
1875
+ }, [chat2, handleCommand]);
1876
+ const handleProviderSelect = useCallback5((provider, model) => {
1877
+ chat2.addSystemMessage(`\u2705 Updated: ${provider} / ${model}`);
1878
+ }, [chat2]);
1879
+ if (overlay === "help") {
1880
+ return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: "100%", alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx7(
1881
+ HelpMenu,
1447
1882
  {
1448
- id: Date.now().toString(),
1449
- role: "system",
1450
- content,
1451
- timestamp: /* @__PURE__ */ new Date()
1883
+ onClose: () => setOverlay("none"),
1884
+ onSelect: (cmd) => {
1885
+ setOverlay("none");
1886
+ handleCommand(cmd);
1887
+ }
1452
1888
  }
1453
- ]);
1454
- };
1455
- const visibleMessages = messages.slice(-20);
1456
- if (showHelpMenu) {
1457
- return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", height: "100%", alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx6(
1458
- HelpMenu,
1889
+ ) });
1890
+ }
1891
+ if (overlay === "provider") {
1892
+ return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: "100%", alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx7(
1893
+ ProviderSelector,
1459
1894
  {
1460
- onClose: () => setShowHelpMenu(false),
1461
- onSelect: handleHelpMenuSelect
1895
+ onClose: () => setOverlay("none"),
1896
+ onSelect: handleProviderSelect
1462
1897
  }
1463
1898
  ) });
1464
1899
  }
1465
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", height: "100%", children: [
1466
- /* @__PURE__ */ jsx6(Header, { screenWatch, telegramEnabled }),
1467
- /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "gray", padding: 1, children: [
1468
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "gray", children: " Chat " }),
1469
- visibleMessages.map((msg) => /* @__PURE__ */ jsx6(
1900
+ const visibleMessages = chat2.messages.slice(-20);
1901
+ const isProcessing = chat2.isProcessing || vision.isAnalyzing || tasks.isRunning || telegram.isStarting;
1902
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", height: "100%", children: [
1903
+ /* @__PURE__ */ jsx7(Header, { screenWatch, telegramEnabled: telegram.isEnabled }),
1904
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "gray", padding: 1, children: [
1905
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "gray", children: " Chat " }),
1906
+ visibleMessages.map((msg) => /* @__PURE__ */ jsx7(
1470
1907
  ChatMessage,
1471
1908
  {
1472
1909
  role: msg.role,
@@ -1477,25 +1914,26 @@ ${formatTask(task)}`);
1477
1914
  msg.id
1478
1915
  ))
1479
1916
  ] }),
1480
- error && /* @__PURE__ */ jsx6(Box6, { marginY: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "red", children: [
1917
+ chat2.error && /* @__PURE__ */ jsx7(Box7, { marginY: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: "red", children: [
1481
1918
  "Error: ",
1482
- error
1919
+ chat2.error
1483
1920
  ] }) }),
1484
- /* @__PURE__ */ jsx6(
1921
+ /* @__PURE__ */ jsx7(
1485
1922
  ChatInput,
1486
1923
  {
1487
- value: input,
1488
- onChange: setInput,
1924
+ value: "",
1925
+ onChange: () => {
1926
+ },
1489
1927
  onSubmit: handleSubmit,
1490
1928
  isProcessing
1491
1929
  }
1492
1930
  ),
1493
- /* @__PURE__ */ jsx6(StatusBar, { status })
1931
+ /* @__PURE__ */ jsx7(StatusBar, { status })
1494
1932
  ] });
1495
1933
  }
1496
1934
 
1497
1935
  // src/index.tsx
1498
- import { jsx as jsx7 } from "react/jsx-runtime";
1936
+ import { jsx as jsx8 } from "react/jsx-runtime";
1499
1937
  var args = process.argv.slice(2);
1500
1938
  if (args.length > 0) {
1501
1939
  const command = args[0];
@@ -1591,7 +2029,7 @@ Manual Setup:
1591
2029
  }
1592
2030
  case "init": {
1593
2031
  const { Setup } = await import("./Setup-Q32JPHGP.js");
1594
- render(/* @__PURE__ */ jsx7(Setup, {}));
2032
+ render(/* @__PURE__ */ jsx8(Setup, {}));
1595
2033
  process.exit(0);
1596
2034
  }
1597
2035
  default: {
@@ -1599,4 +2037,4 @@ Manual Setup:
1599
2037
  }
1600
2038
  }
1601
2039
  }
1602
- render(/* @__PURE__ */ jsx7(App, {}));
2040
+ render(/* @__PURE__ */ jsx8(App, {}));