@projectservan8n/cnapse 0.4.0 → 0.5.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
@@ -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,349 @@ function HelpMenu({ onClose, onSelect }) {
190
191
  );
191
192
  }
192
193
 
194
+ // src/components/ProviderSelector.tsx
195
+ import { useState as useState2, useEffect } from "react";
196
+ import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
197
+ import TextInput2 from "ink-text-input";
198
+ import Spinner from "ink-spinner";
199
+
200
+ // src/lib/ollama.ts
201
+ import { exec } from "child_process";
202
+ import { promisify } from "util";
203
+ var execAsync = promisify(exec);
204
+ async function checkOllamaStatus() {
205
+ try {
206
+ const { stdout } = await execAsync("ollama list", { timeout: 1e4 });
207
+ const lines = stdout.trim().split("\n");
208
+ const models = [];
209
+ for (let i = 1; i < lines.length; i++) {
210
+ const line = lines[i];
211
+ if (!line?.trim()) continue;
212
+ const parts = line.split(/\s{2,}/);
213
+ if (parts.length >= 3) {
214
+ models.push({
215
+ name: parts[0]?.trim() || "",
216
+ size: parts[2]?.trim() || "",
217
+ modified: parts[3]?.trim() || ""
218
+ });
219
+ }
220
+ }
221
+ return {
222
+ installed: true,
223
+ running: true,
224
+ models
225
+ };
226
+ } catch (err2) {
227
+ const errorMsg = err2 instanceof Error ? err2.message : "Unknown error";
228
+ if (errorMsg.includes("connect") || errorMsg.includes("refused")) {
229
+ return {
230
+ installed: true,
231
+ running: false,
232
+ models: [],
233
+ error: "Ollama is not running. Start it with: ollama serve"
234
+ };
235
+ }
236
+ if (errorMsg.includes("not found") || errorMsg.includes("not recognized")) {
237
+ return {
238
+ installed: false,
239
+ running: false,
240
+ models: [],
241
+ error: "Ollama not installed. Get it at: https://ollama.ai"
242
+ };
243
+ }
244
+ return {
245
+ installed: false,
246
+ running: false,
247
+ models: [],
248
+ error: errorMsg
249
+ };
250
+ }
251
+ }
252
+ function hasModel(status, modelId) {
253
+ const modelName = modelId.split(":")[0]?.toLowerCase() || "";
254
+ return status.models.some((m) => m.name.toLowerCase().startsWith(modelName));
255
+ }
256
+
257
+ // src/components/ProviderSelector.tsx
258
+ import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
259
+ var PROVIDERS = [
260
+ {
261
+ id: "ollama",
262
+ name: "Ollama",
263
+ description: "Local AI - Free, private",
264
+ needsApiKey: false,
265
+ models: [
266
+ { id: "qwen2.5:0.5b", name: "Qwen 2.5 0.5B (fast)", recommended: true },
267
+ { id: "qwen2.5:1.5b", name: "Qwen 2.5 1.5B" },
268
+ { id: "qwen2.5:7b", name: "Qwen 2.5 7B (quality)" },
269
+ { id: "llama3.2:1b", name: "Llama 3.2 1B" },
270
+ { id: "llama3.2:3b", name: "Llama 3.2 3B" },
271
+ { id: "codellama:7b", name: "Code Llama 7B" },
272
+ { id: "llava:7b", name: "LLaVA 7B (vision)" }
273
+ ]
274
+ },
275
+ {
276
+ id: "openrouter",
277
+ name: "OpenRouter",
278
+ description: "Many models, pay-per-use",
279
+ needsApiKey: true,
280
+ models: [
281
+ { id: "qwen/qwen-2.5-coder-32b-instruct", name: "Qwen Coder 32B", recommended: true },
282
+ { id: "anthropic/claude-3.5-sonnet", name: "Claude 3.5 Sonnet" },
283
+ { id: "openai/gpt-4o", name: "GPT-4o" },
284
+ { id: "openai/gpt-4o-mini", name: "GPT-4o Mini" },
285
+ { id: "google/gemini-pro-1.5", name: "Gemini Pro 1.5" }
286
+ ]
287
+ },
288
+ {
289
+ id: "anthropic",
290
+ name: "Anthropic",
291
+ description: "Claude - Best reasoning",
292
+ needsApiKey: true,
293
+ models: [
294
+ { id: "claude-3-5-sonnet-20241022", name: "Claude 3.5 Sonnet", recommended: true },
295
+ { id: "claude-3-opus-20240229", name: "Claude 3 Opus" },
296
+ { id: "claude-3-haiku-20240307", name: "Claude 3 Haiku" }
297
+ ]
298
+ },
299
+ {
300
+ id: "openai",
301
+ name: "OpenAI",
302
+ description: "GPT models",
303
+ needsApiKey: true,
304
+ models: [
305
+ { id: "gpt-4o", name: "GPT-4o", recommended: true },
306
+ { id: "gpt-4o-mini", name: "GPT-4o Mini" },
307
+ { id: "gpt-4-turbo", name: "GPT-4 Turbo" }
308
+ ]
309
+ }
310
+ ];
311
+ function ProviderSelector({ onClose, onSelect }) {
312
+ const config = getConfig();
313
+ const [step, setStep] = useState2("provider");
314
+ const [providerIndex, setProviderIndex] = useState2(() => {
315
+ const idx = PROVIDERS.findIndex((p) => p.id === config.provider);
316
+ return idx >= 0 ? idx : 0;
317
+ });
318
+ const [modelIndex, setModelIndex] = useState2(0);
319
+ const [apiKeyInput, setApiKeyInput] = useState2("");
320
+ const [selectedProvider, setSelectedProvider] = useState2(null);
321
+ const [ollamaStatus, setOllamaStatus] = useState2(null);
322
+ const [checkingOllama, setCheckingOllama] = useState2(false);
323
+ useEffect(() => {
324
+ if (step === "model" && selectedProvider?.id === "ollama" && !ollamaStatus) {
325
+ setCheckingOllama(true);
326
+ checkOllamaStatus().then((status) => {
327
+ setOllamaStatus(status);
328
+ setCheckingOllama(false);
329
+ if (!status.running) {
330
+ setStep("ollamaError");
331
+ }
332
+ });
333
+ }
334
+ }, [step, selectedProvider, ollamaStatus]);
335
+ useInput2((input, key) => {
336
+ if (key.escape) {
337
+ onClose();
338
+ return;
339
+ }
340
+ if (step === "provider") {
341
+ if (key.upArrow) {
342
+ setProviderIndex((prev) => prev > 0 ? prev - 1 : PROVIDERS.length - 1);
343
+ } else if (key.downArrow) {
344
+ setProviderIndex((prev) => prev < PROVIDERS.length - 1 ? prev + 1 : 0);
345
+ } else if (key.return) {
346
+ const provider = PROVIDERS[providerIndex];
347
+ setSelectedProvider(provider);
348
+ const currentIdx = provider.models.findIndex((m) => m.id === config.model);
349
+ const recommendedIdx = provider.models.findIndex((m) => m.recommended);
350
+ setModelIndex(currentIdx >= 0 ? currentIdx : recommendedIdx >= 0 ? recommendedIdx : 0);
351
+ if (provider.needsApiKey) {
352
+ const apiKeyProvider = provider.id;
353
+ if (!config.apiKeys[apiKeyProvider]) {
354
+ setStep("apiKey");
355
+ } else {
356
+ setStep("model");
357
+ }
358
+ } else {
359
+ setStep("model");
360
+ }
361
+ }
362
+ } else if (step === "model" && selectedProvider) {
363
+ if (key.upArrow) {
364
+ setModelIndex((prev) => prev > 0 ? prev - 1 : selectedProvider.models.length - 1);
365
+ } else if (key.downArrow) {
366
+ setModelIndex((prev) => prev < selectedProvider.models.length - 1 ? prev + 1 : 0);
367
+ } else if (key.return) {
368
+ const model = selectedProvider.models[modelIndex];
369
+ if (selectedProvider.id === "ollama" && ollamaStatus && !hasModel(ollamaStatus, model.id)) {
370
+ }
371
+ setProvider(selectedProvider.id);
372
+ setModel(model.id);
373
+ setStep("done");
374
+ onSelect(selectedProvider.id, model.id);
375
+ setTimeout(() => onClose(), 1500);
376
+ } else if (key.leftArrow || input === "b") {
377
+ setStep("provider");
378
+ setOllamaStatus(null);
379
+ }
380
+ } else if (step === "ollamaError") {
381
+ if (key.return || input === "b") {
382
+ setStep("provider");
383
+ setOllamaStatus(null);
384
+ }
385
+ }
386
+ });
387
+ const handleApiKeySubmit = (value) => {
388
+ if (value.trim() && selectedProvider) {
389
+ setApiKey(selectedProvider.id, value.trim());
390
+ setStep("model");
391
+ }
392
+ };
393
+ if (step === "provider") {
394
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, width: 60, children: [
395
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "Select Provider" }) }),
396
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", dimColor: true, children: "Arrows to navigate, Enter to select" }) }),
397
+ PROVIDERS.map((provider, index) => {
398
+ const isSelected = index === providerIndex;
399
+ const isCurrent = provider.id === config.provider;
400
+ const hasKey = provider.needsApiKey && provider.id !== "ollama" ? !!config.apiKeys[provider.id] : true;
401
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
402
+ /* @__PURE__ */ jsxs5(Text6, { color: isSelected ? "cyan" : "white", children: [
403
+ isSelected ? "\u276F " : " ",
404
+ provider.name,
405
+ isCurrent && /* @__PURE__ */ jsx6(Text6, { color: "green", children: " (current)" }),
406
+ provider.needsApiKey && !hasKey && /* @__PURE__ */ jsx6(Text6, { color: "red", children: " (needs key)" }),
407
+ provider.needsApiKey && hasKey && !isCurrent && /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: " (key saved)" })
408
+ ] }),
409
+ isSelected && /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
410
+ " ",
411
+ provider.description
412
+ ] })
413
+ ] }, provider.id);
414
+ }),
415
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", dimColor: true, children: "Press Esc to cancel" }) })
416
+ ] });
417
+ }
418
+ if (step === "apiKey" && selectedProvider) {
419
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, width: 60, children: [
420
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "Enter API Key" }) }),
421
+ /* @__PURE__ */ jsxs5(Text6, { children: [
422
+ /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713" }),
423
+ " Provider: ",
424
+ selectedProvider.name
425
+ ] }),
426
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
427
+ /* @__PURE__ */ jsxs5(Text6, { color: "gray", dimColor: true, children: [
428
+ selectedProvider.id === "openrouter" && "Get key: openrouter.ai/keys",
429
+ selectedProvider.id === "anthropic" && "Get key: console.anthropic.com",
430
+ selectedProvider.id === "openai" && "Get key: platform.openai.com/api-keys"
431
+ ] }),
432
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, children: [
433
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u276F " }),
434
+ /* @__PURE__ */ jsx6(
435
+ TextInput2,
436
+ {
437
+ value: apiKeyInput,
438
+ onChange: setApiKeyInput,
439
+ onSubmit: handleApiKeySubmit,
440
+ mask: "*"
441
+ }
442
+ )
443
+ ] })
444
+ ] }),
445
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", dimColor: true, children: "Press Esc to cancel" }) })
446
+ ] });
447
+ }
448
+ if (step === "ollamaError" && ollamaStatus) {
449
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "red", padding: 1, width: 60, children: [
450
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: "red", children: "Ollama Not Available" }) }),
451
+ /* @__PURE__ */ jsx6(Text6, { color: "red", children: ollamaStatus.error }),
452
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
453
+ !ollamaStatus.installed && /* @__PURE__ */ jsxs5(Fragment, { children: [
454
+ /* @__PURE__ */ jsx6(Text6, { children: "1. Install Ollama from https://ollama.ai" }),
455
+ /* @__PURE__ */ jsx6(Text6, { children: "2. Run: ollama pull qwen2.5:0.5b" }),
456
+ /* @__PURE__ */ jsx6(Text6, { children: "3. Try again" })
457
+ ] }),
458
+ ollamaStatus.installed && !ollamaStatus.running && /* @__PURE__ */ jsxs5(Fragment, { children: [
459
+ /* @__PURE__ */ jsx6(Text6, { children: "1. Start Ollama: ollama serve" }),
460
+ /* @__PURE__ */ jsx6(Text6, { children: "2. Or run any model: ollama run qwen2.5:0.5b" }),
461
+ /* @__PURE__ */ jsx6(Text6, { children: "3. Try again" })
462
+ ] })
463
+ ] }),
464
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", dimColor: true, children: "Press Enter or B to go back" }) })
465
+ ] });
466
+ }
467
+ if (step === "model" && selectedProvider) {
468
+ const isOllama = selectedProvider.id === "ollama";
469
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, width: 60, children: [
470
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "Select Model" }) }),
471
+ /* @__PURE__ */ jsxs5(Text6, { children: [
472
+ /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713" }),
473
+ " Provider: ",
474
+ selectedProvider.name
475
+ ] }),
476
+ isOllama && checkingOllama && /* @__PURE__ */ jsxs5(Box6, { marginY: 1, children: [
477
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: /* @__PURE__ */ jsx6(Spinner, { type: "dots" }) }),
478
+ /* @__PURE__ */ jsx6(Text6, { children: " Checking Ollama status..." })
479
+ ] }),
480
+ isOllama && ollamaStatus && ollamaStatus.running && /* @__PURE__ */ jsxs5(Text6, { color: "green", children: [
481
+ "\u2713 Ollama running (",
482
+ ollamaStatus.models.length,
483
+ " models installed)"
484
+ ] }),
485
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", dimColor: true, children: "Arrows to navigate, Enter to select, B to go back" }) }),
486
+ selectedProvider.models.map((model, index) => {
487
+ const isSelected = index === modelIndex;
488
+ const isCurrent = model.id === config.model && selectedProvider.id === config.provider;
489
+ let modelStatus = "";
490
+ if (isOllama && ollamaStatus) {
491
+ const available = hasModel(ollamaStatus, model.id);
492
+ modelStatus = available ? " (installed)" : " (not installed)";
493
+ }
494
+ return /* @__PURE__ */ jsxs5(Text6, { color: isSelected ? "cyan" : "white", children: [
495
+ isSelected ? "\u276F " : " ",
496
+ model.name,
497
+ model.recommended && /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: " *" }),
498
+ isCurrent && /* @__PURE__ */ jsx6(Text6, { color: "green", children: " (current)" }),
499
+ isOllama && ollamaStatus && (hasModel(ollamaStatus, model.id) ? /* @__PURE__ */ jsx6(Text6, { color: "green", children: modelStatus }) : /* @__PURE__ */ jsx6(Text6, { color: "red", children: modelStatus }))
500
+ ] }, model.id);
501
+ }),
502
+ isOllama && /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
503
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", dimColor: true, children: "* = Recommended" }),
504
+ ollamaStatus && !hasModel(ollamaStatus, selectedProvider.models[modelIndex]?.id || "") && /* @__PURE__ */ jsxs5(Text6, { color: "yellow", children: [
505
+ "Run: ollama pull ",
506
+ selectedProvider.models[modelIndex]?.id
507
+ ] })
508
+ ] }),
509
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", dimColor: true, children: "Press Esc to cancel" }) })
510
+ ] });
511
+ }
512
+ if (step === "done" && selectedProvider) {
513
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "green", padding: 1, width: 60, children: [
514
+ /* @__PURE__ */ jsx6(Text6, { color: "green", bold: true, children: "Configuration Updated!" }),
515
+ /* @__PURE__ */ jsxs5(Text6, { children: [
516
+ /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713" }),
517
+ " Provider: ",
518
+ selectedProvider.name
519
+ ] }),
520
+ /* @__PURE__ */ jsxs5(Text6, { children: [
521
+ /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713" }),
522
+ " Model: ",
523
+ selectedProvider.models[modelIndex]?.name
524
+ ] }),
525
+ selectedProvider.id === "ollama" && ollamaStatus && !hasModel(ollamaStatus, selectedProvider.models[modelIndex]?.id || "") && /* @__PURE__ */ jsxs5(Text6, { color: "yellow", children: [
526
+ "Remember to run: ollama pull ",
527
+ selectedProvider.models[modelIndex]?.id
528
+ ] })
529
+ ] });
530
+ }
531
+ return null;
532
+ }
533
+
534
+ // src/hooks/useChat.ts
535
+ import { useState as useState3, useCallback, useRef, useEffect as useEffect2 } from "react";
536
+
193
537
  // src/lib/api.ts
194
538
  var SYSTEM_PROMPT = `You are C-napse, a helpful AI assistant for PC automation running on the user's desktop.
195
539
  You can help with coding, file management, shell commands, and more. Be concise and helpful.
@@ -322,24 +666,24 @@ async function chatOpenAI(messages, model) {
322
666
  }
323
667
 
324
668
  // src/lib/screen.ts
325
- import { exec } from "child_process";
326
- import { promisify } from "util";
327
- var execAsync = promisify(exec);
669
+ import { exec as exec2 } from "child_process";
670
+ import { promisify as promisify2 } from "util";
671
+ var execAsync2 = promisify2(exec2);
328
672
  async function getScreenDescription() {
329
673
  try {
330
674
  const platform = process.platform;
331
675
  if (platform === "win32") {
332
- const { stdout } = await execAsync(`
676
+ const { stdout } = await execAsync2(`
333
677
  Add-Type -AssemblyName System.Windows.Forms
334
678
  $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
335
679
  Write-Output "$($screen.Width)x$($screen.Height)"
336
680
  `, { shell: "powershell.exe" });
337
681
  return `Screen ${stdout.trim()} captured`;
338
682
  } else if (platform === "darwin") {
339
- const { stdout } = await execAsync(`system_profiler SPDisplaysDataType | grep Resolution | head -1`);
683
+ const { stdout } = await execAsync2(`system_profiler SPDisplaysDataType | grep Resolution | head -1`);
340
684
  return `Screen ${stdout.trim()}`;
341
685
  } else {
342
- const { stdout } = await execAsync(`xdpyinfo | grep dimensions | awk '{print $2}'`);
686
+ const { stdout } = await execAsync2(`xdpyinfo | grep dimensions | awk '{print $2}'`);
343
687
  return `Screen ${stdout.trim()} captured`;
344
688
  }
345
689
  } catch {
@@ -347,6 +691,107 @@ async function getScreenDescription() {
347
691
  }
348
692
  }
349
693
 
694
+ // src/hooks/useChat.ts
695
+ var WELCOME_MESSAGE = {
696
+ id: "0",
697
+ role: "system",
698
+ content: "Welcome to C-napse! Type your message and press Enter.\n\nShortcuts: Ctrl+H for help, Ctrl+P for provider",
699
+ timestamp: /* @__PURE__ */ new Date()
700
+ };
701
+ function useChat(screenWatch = false) {
702
+ const [messages, setMessages] = useState3([WELCOME_MESSAGE]);
703
+ const [isProcessing, setIsProcessing] = useState3(false);
704
+ const [error, setError] = useState3(null);
705
+ const screenContextRef = useRef(null);
706
+ useEffect2(() => {
707
+ if (!screenWatch) {
708
+ screenContextRef.current = null;
709
+ return;
710
+ }
711
+ const checkScreen = async () => {
712
+ const desc = await getScreenDescription();
713
+ if (desc) {
714
+ screenContextRef.current = desc;
715
+ }
716
+ };
717
+ checkScreen();
718
+ const interval = setInterval(checkScreen, 5e3);
719
+ return () => clearInterval(interval);
720
+ }, [screenWatch]);
721
+ const addSystemMessage = useCallback((content) => {
722
+ setMessages((prev) => [
723
+ ...prev,
724
+ {
725
+ id: Date.now().toString(),
726
+ role: "system",
727
+ content,
728
+ timestamp: /* @__PURE__ */ new Date()
729
+ }
730
+ ]);
731
+ }, []);
732
+ const sendMessage = useCallback(async (content) => {
733
+ if (!content.trim() || isProcessing) return;
734
+ setError(null);
735
+ const userMsg = {
736
+ id: Date.now().toString(),
737
+ role: "user",
738
+ content,
739
+ timestamp: /* @__PURE__ */ new Date()
740
+ };
741
+ const assistantId = (Date.now() + 1).toString();
742
+ const assistantMsg = {
743
+ id: assistantId,
744
+ role: "assistant",
745
+ content: "",
746
+ timestamp: /* @__PURE__ */ new Date(),
747
+ isStreaming: true
748
+ };
749
+ setMessages((prev) => [...prev, userMsg, assistantMsg]);
750
+ setIsProcessing(true);
751
+ try {
752
+ const apiMessages = messages.filter((m) => m.role === "user" || m.role === "assistant").slice(-10).map((m) => ({ role: m.role, content: m.content }));
753
+ let finalContent = content;
754
+ if (screenWatch && screenContextRef.current) {
755
+ finalContent = `[Screen context: ${screenContextRef.current}]
756
+
757
+ ${content}`;
758
+ }
759
+ apiMessages.push({ role: "user", content: finalContent });
760
+ const response = await chat(apiMessages);
761
+ setMessages(
762
+ (prev) => prev.map(
763
+ (m) => m.id === assistantId ? { ...m, content: response.content || "(no response)", isStreaming: false } : m
764
+ )
765
+ );
766
+ } catch (err2) {
767
+ const errorMsg = err2 instanceof Error ? err2.message : "Unknown error";
768
+ setError(errorMsg);
769
+ setMessages(
770
+ (prev) => prev.map(
771
+ (m) => m.id === assistantId ? { ...m, content: `Error: ${errorMsg}`, isStreaming: false } : m
772
+ )
773
+ );
774
+ } finally {
775
+ setIsProcessing(false);
776
+ }
777
+ }, [messages, isProcessing, screenWatch]);
778
+ const clearMessages = useCallback(() => {
779
+ setMessages([WELCOME_MESSAGE]);
780
+ setError(null);
781
+ }, []);
782
+ return {
783
+ messages,
784
+ isProcessing,
785
+ error,
786
+ sendMessage,
787
+ addSystemMessage,
788
+ clearMessages
789
+ };
790
+ }
791
+
792
+ // src/hooks/useVision.ts
793
+ import { useState as useState4, useCallback as useCallback2 } from "react";
794
+
350
795
  // src/lib/vision.ts
351
796
  async function describeScreen() {
352
797
  const screenshot = await captureScreenshot();
@@ -367,17 +812,17 @@ async function captureScreenshot() {
367
812
  }
368
813
  }
369
814
  async function captureScreenFallback() {
370
- const { exec: exec5 } = await import("child_process");
371
- const { promisify: promisify5 } = await import("util");
815
+ const { exec: exec6 } = await import("child_process");
816
+ const { promisify: promisify6 } = await import("util");
372
817
  const { tmpdir } = await import("os");
373
- const { join } = await import("path");
818
+ const { join: join2 } = await import("path");
374
819
  const { readFile, unlink } = await import("fs/promises");
375
- const execAsync5 = promisify5(exec5);
376
- const tempFile = join(tmpdir(), `cnapse-screen-${Date.now()}.png`);
820
+ const execAsync6 = promisify6(exec6);
821
+ const tempFile = join2(tmpdir(), `cnapse-screen-${Date.now()}.png`);
377
822
  try {
378
823
  const platform = process.platform;
379
824
  if (platform === "win32") {
380
- await execAsync5(`
825
+ await execAsync6(`
381
826
  Add-Type -AssemblyName System.Windows.Forms
382
827
  $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
383
828
  $bitmap = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height)
@@ -388,9 +833,9 @@ async function captureScreenFallback() {
388
833
  $bitmap.Dispose()
389
834
  `, { shell: "powershell.exe" });
390
835
  } else if (platform === "darwin") {
391
- await execAsync5(`screencapture -x "${tempFile}"`);
836
+ await execAsync6(`screencapture -x "${tempFile}"`);
392
837
  } else {
393
- await execAsync5(`gnome-screenshot -f "${tempFile}" 2>/dev/null || scrot "${tempFile}" 2>/dev/null || import -window root "${tempFile}"`);
838
+ await execAsync6(`gnome-screenshot -f "${tempFile}" 2>/dev/null || scrot "${tempFile}" 2>/dev/null || import -window root "${tempFile}"`);
394
839
  }
395
840
  const imageBuffer = await readFile(tempFile);
396
841
  await unlink(tempFile).catch(() => {
@@ -551,25 +996,59 @@ async function analyzeWithOpenAI(base64Image, prompt) {
551
996
  return data.choices?.[0]?.message?.content || "Unable to analyze image";
552
997
  }
553
998
 
999
+ // src/hooks/useVision.ts
1000
+ function useVision() {
1001
+ const [isAnalyzing, setIsAnalyzing] = useState4(false);
1002
+ const [lastDescription, setLastDescription] = useState4(null);
1003
+ const [lastScreenshot, setLastScreenshot] = useState4(null);
1004
+ const [error, setError] = useState4(null);
1005
+ const analyze = useCallback2(async () => {
1006
+ setIsAnalyzing(true);
1007
+ setError(null);
1008
+ try {
1009
+ const result = await describeScreen();
1010
+ setLastDescription(result.description);
1011
+ setLastScreenshot(result.screenshot);
1012
+ return result.description;
1013
+ } catch (err2) {
1014
+ const errorMsg = err2 instanceof Error ? err2.message : "Vision analysis failed";
1015
+ setError(errorMsg);
1016
+ throw err2;
1017
+ } finally {
1018
+ setIsAnalyzing(false);
1019
+ }
1020
+ }, []);
1021
+ return {
1022
+ isAnalyzing,
1023
+ lastDescription,
1024
+ lastScreenshot,
1025
+ error,
1026
+ analyze
1027
+ };
1028
+ }
1029
+
1030
+ // src/hooks/useTelegram.ts
1031
+ import { useState as useState5, useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2 } from "react";
1032
+
554
1033
  // src/services/telegram.ts
555
1034
  import { EventEmitter } from "events";
556
1035
 
557
1036
  // src/tools/shell.ts
558
- import { exec as exec4 } from "child_process";
559
- import { promisify as promisify4 } from "util";
1037
+ import { exec as exec5 } from "child_process";
1038
+ import { promisify as promisify5 } from "util";
560
1039
 
561
1040
  // src/tools/clipboard.ts
562
1041
  import clipboardy from "clipboardy";
563
1042
 
564
1043
  // src/tools/process.ts
565
- import { exec as exec2 } from "child_process";
566
- import { promisify as promisify2 } from "util";
567
- var execAsync2 = promisify2(exec2);
568
-
569
- // src/tools/computer.ts
570
1044
  import { exec as exec3 } from "child_process";
571
1045
  import { promisify as promisify3 } from "util";
572
1046
  var execAsync3 = promisify3(exec3);
1047
+
1048
+ // src/tools/computer.ts
1049
+ import { exec as exec4 } from "child_process";
1050
+ import { promisify as promisify4 } from "util";
1051
+ var execAsync4 = promisify4(exec4);
573
1052
  async function clickMouse(button = "left") {
574
1053
  try {
575
1054
  if (process.platform === "win32") {
@@ -579,12 +1058,12 @@ Add-Type -MemberDefinition @"
579
1058
  public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
580
1059
  "@ -Name Mouse -Namespace Win32
581
1060
  ${button === "left" ? "[Win32.Mouse]::mouse_event(0x02, 0, 0, 0, 0); [Win32.Mouse]::mouse_event(0x04, 0, 0, 0, 0)" : button === "right" ? "[Win32.Mouse]::mouse_event(0x08, 0, 0, 0, 0); [Win32.Mouse]::mouse_event(0x10, 0, 0, 0, 0)" : "[Win32.Mouse]::mouse_event(0x20, 0, 0, 0, 0); [Win32.Mouse]::mouse_event(0x40, 0, 0, 0, 0)"}`;
582
- await execAsync3(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
1061
+ await execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
583
1062
  } else if (process.platform === "darwin") {
584
- await execAsync3(`cliclick c:.`);
1063
+ await execAsync4(`cliclick c:.`);
585
1064
  } else {
586
1065
  const btn = button === "left" ? "1" : button === "right" ? "3" : "2";
587
- await execAsync3(`xdotool click ${btn}`);
1066
+ await execAsync4(`xdotool click ${btn}`);
588
1067
  }
589
1068
  return ok(`Clicked ${button} button`);
590
1069
  } catch (error) {
@@ -595,13 +1074,13 @@ async function typeText(text) {
595
1074
  try {
596
1075
  if (process.platform === "win32") {
597
1076
  const escapedText = text.replace(/'/g, "''").replace(/[+^%~(){}[\]]/g, "{$&}");
598
- await execAsync3(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${escapedText}')"`, { shell: "cmd.exe" });
1077
+ await execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${escapedText}')"`, { shell: "cmd.exe" });
599
1078
  } else if (process.platform === "darwin") {
600
1079
  const escaped = text.replace(/'/g, "'\\''");
601
- await execAsync3(`osascript -e 'tell application "System Events" to keystroke "${escaped}"'`);
1080
+ await execAsync4(`osascript -e 'tell application "System Events" to keystroke "${escaped}"'`);
602
1081
  } else {
603
1082
  const escaped = text.replace(/'/g, "'\\''");
604
- await execAsync3(`xdotool type '${escaped}'`);
1083
+ await execAsync4(`xdotool type '${escaped}'`);
605
1084
  }
606
1085
  return ok(`Typed: ${text}`);
607
1086
  } catch (error) {
@@ -642,7 +1121,7 @@ async function pressKey(key) {
642
1121
  "f12": "{F12}"
643
1122
  };
644
1123
  const winKey = winKeyMap[key.toLowerCase()] || key;
645
- await execAsync3(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${winKey}')"`, { shell: "cmd.exe" });
1124
+ await execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${winKey}')"`, { shell: "cmd.exe" });
646
1125
  } else if (process.platform === "darwin") {
647
1126
  const macKeyMap = {
648
1127
  "return": 36,
@@ -660,12 +1139,12 @@ async function pressKey(key) {
660
1139
  };
661
1140
  const keyCode = macKeyMap[key.toLowerCase()];
662
1141
  if (keyCode) {
663
- await execAsync3(`osascript -e 'tell application "System Events" to key code ${keyCode}'`);
1142
+ await execAsync4(`osascript -e 'tell application "System Events" to key code ${keyCode}'`);
664
1143
  } else {
665
- await execAsync3(`osascript -e 'tell application "System Events" to keystroke "${key}"'`);
1144
+ await execAsync4(`osascript -e 'tell application "System Events" to keystroke "${key}"'`);
666
1145
  }
667
1146
  } else {
668
- await execAsync3(`xdotool key ${key}`);
1147
+ await execAsync4(`xdotool key ${key}`);
669
1148
  }
670
1149
  return ok(`Pressed: ${key}`);
671
1150
  } catch (error) {
@@ -678,7 +1157,7 @@ async function keyCombo(keys) {
678
1157
  const hasWin = keys.some((k) => k.toLowerCase() === "meta" || k.toLowerCase() === "win");
679
1158
  const hasR = keys.some((k) => k.toLowerCase() === "r");
680
1159
  if (hasWin && hasR) {
681
- await execAsync3(`powershell -Command "$shell = New-Object -ComObject WScript.Shell; $shell.Run('explorer shell:::{2559a1f3-21d7-11d4-bdaf-00c04f60b9f0}')"`, { shell: "cmd.exe" });
1160
+ await execAsync4(`powershell -Command "$shell = New-Object -ComObject WScript.Shell; $shell.Run('explorer shell:::{2559a1f3-21d7-11d4-bdaf-00c04f60b9f0}')"`, { shell: "cmd.exe" });
682
1161
  return ok(`Pressed: ${keys.join("+")}`);
683
1162
  }
684
1163
  const modifierMap = {
@@ -698,7 +1177,7 @@ async function keyCombo(keys) {
698
1177
  }
699
1178
  }
700
1179
  combo += regularKeys.join("");
701
- await execAsync3(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${combo}')"`, { shell: "cmd.exe" });
1180
+ await execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${combo}')"`, { shell: "cmd.exe" });
702
1181
  } else if (process.platform === "darwin") {
703
1182
  const modifiers = keys.filter((k) => ["control", "ctrl", "alt", "shift", "command", "meta"].includes(k.toLowerCase()));
704
1183
  const regular = keys.filter((k) => !["control", "ctrl", "alt", "shift", "command", "meta"].includes(k.toLowerCase()));
@@ -714,9 +1193,9 @@ async function keyCombo(keys) {
714
1193
  };
715
1194
  cmd += " using {" + modifiers.map((m) => modMap[m.toLowerCase()]).join(", ") + "}";
716
1195
  }
717
- await execAsync3(`osascript -e '${cmd}'`);
1196
+ await execAsync4(`osascript -e '${cmd}'`);
718
1197
  } else {
719
- await execAsync3(`xdotool key ${keys.join("+")}`);
1198
+ await execAsync4(`xdotool key ${keys.join("+")}`);
720
1199
  }
721
1200
  return ok(`Pressed: ${keys.join("+")}`);
722
1201
  } catch (error) {
@@ -727,11 +1206,11 @@ async function focusWindow(title) {
727
1206
  try {
728
1207
  if (process.platform === "win32") {
729
1208
  const escaped = title.replace(/'/g, "''");
730
- await execAsync3(`powershell -Command "$wshell = New-Object -ComObject wscript.shell; $wshell.AppActivate('${escaped}')"`, { shell: "cmd.exe" });
1209
+ await execAsync4(`powershell -Command "$wshell = New-Object -ComObject wscript.shell; $wshell.AppActivate('${escaped}')"`, { shell: "cmd.exe" });
731
1210
  } else if (process.platform === "darwin") {
732
- await execAsync3(`osascript -e 'tell application "${title}" to activate'`);
1211
+ await execAsync4(`osascript -e 'tell application "${title}" to activate'`);
733
1212
  } else {
734
- await execAsync3(`wmctrl -a "${title}"`);
1213
+ await execAsync4(`wmctrl -a "${title}"`);
735
1214
  }
736
1215
  return ok(`Focused window: ${title}`);
737
1216
  } catch (error) {
@@ -748,13 +1227,13 @@ function err(error) {
748
1227
  }
749
1228
 
750
1229
  // src/tools/shell.ts
751
- var execAsync4 = promisify4(exec4);
1230
+ var execAsync5 = promisify5(exec5);
752
1231
  async function runCommand(cmd, timeout = 3e4) {
753
1232
  try {
754
1233
  const isWindows = process.platform === "win32";
755
1234
  const shell = isWindows ? "cmd.exe" : "/bin/sh";
756
1235
  const shellArg = isWindows ? "/C" : "-c";
757
- const { stdout, stderr } = await execAsync4(cmd, {
1236
+ const { stdout, stderr } = await execAsync5(cmd, {
758
1237
  shell,
759
1238
  timeout,
760
1239
  maxBuffer: 10 * 1024 * 1024
@@ -991,48 +1470,231 @@ function getTelegramBot() {
991
1470
  return instance;
992
1471
  }
993
1472
 
1473
+ // src/hooks/useTelegram.ts
1474
+ function useTelegram(onMessage) {
1475
+ const [isEnabled, setIsEnabled] = useState5(false);
1476
+ const [isStarting, setIsStarting] = useState5(false);
1477
+ const [error, setError] = useState5(null);
1478
+ const [lastMessage, setLastMessage] = useState5(null);
1479
+ const onMessageRef = useRef2(onMessage);
1480
+ useEffect3(() => {
1481
+ onMessageRef.current = onMessage;
1482
+ }, [onMessage]);
1483
+ const start = useCallback3(async () => {
1484
+ if (isEnabled) return;
1485
+ setIsStarting(true);
1486
+ setError(null);
1487
+ try {
1488
+ const bot = getTelegramBot();
1489
+ bot.on("message", (msg) => {
1490
+ setLastMessage(msg);
1491
+ onMessageRef.current?.(msg);
1492
+ });
1493
+ bot.on("error", (err2) => {
1494
+ setError(err2.message);
1495
+ });
1496
+ await bot.start();
1497
+ setIsEnabled(true);
1498
+ } catch (err2) {
1499
+ const errorMsg = err2 instanceof Error ? err2.message : "Failed to start Telegram bot";
1500
+ setError(errorMsg);
1501
+ throw err2;
1502
+ } finally {
1503
+ setIsStarting(false);
1504
+ }
1505
+ }, [isEnabled]);
1506
+ const stop = useCallback3(async () => {
1507
+ if (!isEnabled) return;
1508
+ try {
1509
+ const bot = getTelegramBot();
1510
+ await bot.stop();
1511
+ setIsEnabled(false);
1512
+ } catch (err2) {
1513
+ const errorMsg = err2 instanceof Error ? err2.message : "Failed to stop Telegram bot";
1514
+ setError(errorMsg);
1515
+ throw err2;
1516
+ }
1517
+ }, [isEnabled]);
1518
+ const toggle = useCallback3(async () => {
1519
+ if (isEnabled) {
1520
+ await stop();
1521
+ } else {
1522
+ await start();
1523
+ }
1524
+ }, [isEnabled, start, stop]);
1525
+ return {
1526
+ isEnabled,
1527
+ isStarting,
1528
+ error,
1529
+ lastMessage,
1530
+ toggle,
1531
+ start,
1532
+ stop
1533
+ };
1534
+ }
1535
+
1536
+ // src/hooks/useTasks.ts
1537
+ import { useState as useState6, useCallback as useCallback4 } from "react";
1538
+
994
1539
  // 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.
1540
+ import * as fs from "fs";
1541
+ import * as path from "path";
1542
+ import * as os from "os";
1543
+ var TASK_MEMORY_FILE = path.join(os.homedir(), ".cnapse", "task-memory.json");
1544
+ function loadTaskMemory() {
1545
+ try {
1546
+ if (fs.existsSync(TASK_MEMORY_FILE)) {
1547
+ const data = fs.readFileSync(TASK_MEMORY_FILE, "utf-8");
1548
+ return JSON.parse(data);
1549
+ }
1550
+ } catch {
1551
+ }
1552
+ return { patterns: [], version: 1 };
1553
+ }
1554
+ function saveTaskPattern(input, steps) {
1555
+ try {
1556
+ const memory = loadTaskMemory();
1557
+ const normalized = normalizeInput(input);
1558
+ const existing = memory.patterns.find((p) => p.normalizedInput === normalized);
1559
+ if (existing) {
1560
+ existing.steps = steps;
1561
+ existing.successCount++;
1562
+ existing.lastUsed = (/* @__PURE__ */ new Date()).toISOString();
1563
+ } else {
1564
+ memory.patterns.push({
1565
+ input,
1566
+ normalizedInput: normalized,
1567
+ steps,
1568
+ successCount: 1,
1569
+ lastUsed: (/* @__PURE__ */ new Date()).toISOString()
1570
+ });
1571
+ }
1572
+ memory.patterns = memory.patterns.sort((a, b) => b.successCount - a.successCount).slice(0, 100);
1573
+ const dir = path.dirname(TASK_MEMORY_FILE);
1574
+ if (!fs.existsSync(dir)) {
1575
+ fs.mkdirSync(dir, { recursive: true });
1576
+ }
1577
+ fs.writeFileSync(TASK_MEMORY_FILE, JSON.stringify(memory, null, 2));
1578
+ } catch {
1579
+ }
1580
+ }
1581
+ function normalizeInput(input) {
1582
+ return input.toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
1583
+ }
1584
+ function findSimilarPatterns(input) {
1585
+ const memory = loadTaskMemory();
1586
+ const normalized = normalizeInput(input);
1587
+ const words = normalized.split(" ").filter((w) => w.length > 2);
1588
+ return memory.patterns.filter((pattern) => {
1589
+ const patternWords = pattern.normalizedInput.split(" ");
1590
+ const matches = words.filter((w) => patternWords.includes(w));
1591
+ return matches.length >= Math.min(2, words.length * 0.5);
1592
+ }).sort((a, b) => b.successCount - a.successCount).slice(0, 3);
1593
+ }
1594
+ function buildChainOfThoughtPrompt(input) {
1595
+ const similarPatterns = findSimilarPatterns(input);
1596
+ let learnedExamples = "";
1597
+ if (similarPatterns.length > 0) {
1598
+ learnedExamples = `
1599
+ ## LEARNED PATTERNS (from successful past tasks)
1600
+ These patterns worked before - use them as reference:
997
1601
 
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
1602
+ ${similarPatterns.map((p, i) => `
1603
+ Pattern ${i + 1} (used ${p.successCount} times):
1604
+ Input: "${p.input}"
1605
+ Steps: ${JSON.stringify(p.steps, null, 2)}
1606
+ `).join("\n")}
1607
+ `;
1608
+ }
1609
+ return `You are a task parser for Windows PC automation. Your job is to convert natural language into precise, executable steps.
1610
+
1611
+ ## THINKING PROCESS
1612
+ Before outputting steps, THINK through these questions:
1613
+
1614
+ 1. **WHAT** is the main goal?
1615
+ - What application needs to open?
1616
+ - What action needs to happen inside it?
1617
+ - What is the expected end result?
1618
+
1619
+ 2. **HOW** to achieve it on Windows?
1620
+ - Use Win+R (meta+r) to open Run dialog for apps
1621
+ - Wait 1-3 seconds after opening apps for them to load
1622
+ - Use keyboard shortcuts when possible (faster, more reliable)
1623
+ - Common shortcuts: Ctrl+S (save), Ctrl+O (open), Ctrl+N (new), Alt+F4 (close)
1624
+
1625
+ 3. **SEQUENCE** - what order makes sense?
1626
+ - Open app FIRST
1627
+ - WAIT for it to load
1628
+ - THEN interact with it
1629
+ - Add waits between actions that need time
1007
1630
 
1008
- Respond ONLY with a JSON array of steps, no other text:
1631
+ 4. **EDGE CASES** - what could go wrong?
1632
+ - App might already be open -> focus_window first
1633
+ - Dialogs might appear -> handle or dismiss them
1634
+ - Typing too fast -> add small waits
1635
+
1636
+ ## AVAILABLE ACTIONS
1637
+ - open_app: Open app via Run dialog (e.g., "open_app:notepad", "open_app:code", "open_app:chrome")
1638
+ - type_text: Type text string (e.g., "type_text:Hello World")
1639
+ - press_key: Single key (e.g., "press_key:enter", "press_key:escape", "press_key:tab")
1640
+ - key_combo: Key combination (e.g., "key_combo:control+s", "key_combo:alt+f4", "key_combo:meta+r")
1641
+ - click: Mouse click (e.g., "click:left", "click:right")
1642
+ - wait: Wait N seconds (e.g., "wait:2" - use 1-3s for app loads)
1643
+ - focus_window: Focus by title (e.g., "focus_window:Notepad")
1644
+ - screenshot: Capture and describe screen
1645
+ ${learnedExamples}
1646
+ ## EXAMPLES WITH REASONING
1647
+
1648
+ ### Example 1: "open notepad and type hello"
1649
+ Thinking:
1650
+ - Goal: Open Notepad, then type text into it
1651
+ - How: Win+R -> notepad -> Enter to open, then type
1652
+ - Sequence: Open -> Wait for load -> Type
1653
+ - Edge case: Need wait time for Notepad window to be ready
1654
+
1655
+ Output:
1009
1656
  [
1010
- { "description": "Human readable step", "action": "action_type:params" },
1011
- ...
1657
+ { "description": "Open Notepad via Run dialog", "action": "open_app:notepad" },
1658
+ { "description": "Wait for Notepad to fully load", "action": "wait:2" },
1659
+ { "description": "Type the greeting text", "action": "type_text:hello" }
1660
+ ]
1661
+
1662
+ ### Example 2: "save the current document"
1663
+ Thinking:
1664
+ - Goal: Save whatever is in the current app
1665
+ - How: Ctrl+S is universal save shortcut
1666
+ - Sequence: Just the key combo, maybe wait for save
1667
+ - Edge case: If file is new, Save As dialog might appear
1668
+
1669
+ Output:
1670
+ [
1671
+ { "description": "Press Ctrl+S to save", "action": "key_combo:control+s" },
1672
+ { "description": "Wait for save to complete", "action": "wait:1" }
1012
1673
  ]
1013
1674
 
1014
- Example input: "open notepad and type hello world"
1015
- Example output:
1675
+ ### Example 3: "close this window"
1676
+ Thinking:
1677
+ - Goal: Close the current active window
1678
+ - How: Alt+F4 closes active window on Windows
1679
+ - Sequence: Single action
1680
+ - Edge case: Might prompt to save - user handles that
1681
+
1682
+ Output:
1016
1683
  [
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" }
1684
+ { "description": "Close active window with Alt+F4", "action": "key_combo:alt+f4" }
1020
1685
  ]
1021
1686
 
1022
- Example input: "open vscode, go to folder E:\\Projects, then open terminal"
1023
- Example output:
1687
+ ## YOUR TASK
1688
+ Now parse this request: "${input}"
1689
+
1690
+ First, briefly think through the 4 questions above, then output ONLY a JSON array:
1024
1691
  [
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+\`" }
1692
+ { "description": "Human readable step", "action": "action_type:params" },
1693
+ ...
1035
1694
  ]`;
1695
+ }
1696
+ async function parseTask(input) {
1697
+ const systemPrompt = buildChainOfThoughtPrompt(input);
1036
1698
  const messages = [
1037
1699
  { role: "user", content: input }
1038
1700
  ];
@@ -1143,6 +1805,11 @@ async function executeTask(task, onProgress) {
1143
1805
  }
1144
1806
  if (task.status !== "failed") {
1145
1807
  task.status = "completed";
1808
+ const steps = task.steps.map((s) => ({
1809
+ description: s.description,
1810
+ action: s.action
1811
+ }));
1812
+ saveTaskPattern(task.description, steps);
1146
1813
  }
1147
1814
  task.completedAt = /* @__PURE__ */ new Date();
1148
1815
  return task;
@@ -1150,6 +1817,24 @@ async function executeTask(task, onProgress) {
1150
1817
  function sleep(ms) {
1151
1818
  return new Promise((resolve) => setTimeout(resolve, ms));
1152
1819
  }
1820
+ function getTaskMemoryStats() {
1821
+ const memory = loadTaskMemory();
1822
+ const totalUses = memory.patterns.reduce((sum, p) => sum + p.successCount, 0);
1823
+ const topPatterns = memory.patterns.sort((a, b) => b.successCount - a.successCount).slice(0, 5).map((p) => `"${p.input}" (${p.successCount}x)`);
1824
+ return {
1825
+ patternCount: memory.patterns.length,
1826
+ totalUses,
1827
+ topPatterns
1828
+ };
1829
+ }
1830
+ function clearTaskMemory() {
1831
+ try {
1832
+ if (fs.existsSync(TASK_MEMORY_FILE)) {
1833
+ fs.unlinkSync(TASK_MEMORY_FILE);
1834
+ }
1835
+ } catch {
1836
+ }
1837
+ }
1153
1838
  function formatTask(task) {
1154
1839
  const statusEmoji = {
1155
1840
  pending: "\u23F3",
@@ -1180,293 +1865,251 @@ function formatTask(task) {
1180
1865
  return output;
1181
1866
  }
1182
1867
 
1868
+ // src/hooks/useTasks.ts
1869
+ function useTasks(onProgress) {
1870
+ const [isRunning, setIsRunning] = useState6(false);
1871
+ const [currentTask, setCurrentTask] = useState6(null);
1872
+ const [currentStep, setCurrentStep] = useState6(null);
1873
+ const [error, setError] = useState6(null);
1874
+ const run = useCallback4(async (description) => {
1875
+ setIsRunning(true);
1876
+ setError(null);
1877
+ try {
1878
+ const task = await parseTask(description);
1879
+ setCurrentTask(task);
1880
+ const result = await executeTask(task, (updatedTask, step) => {
1881
+ setCurrentTask({ ...updatedTask });
1882
+ setCurrentStep(step);
1883
+ onProgress?.(updatedTask, step);
1884
+ });
1885
+ setCurrentTask(result);
1886
+ return result;
1887
+ } catch (err2) {
1888
+ const errorMsg = err2 instanceof Error ? err2.message : "Task failed";
1889
+ setError(errorMsg);
1890
+ throw err2;
1891
+ } finally {
1892
+ setIsRunning(false);
1893
+ setCurrentStep(null);
1894
+ }
1895
+ }, [onProgress]);
1896
+ return {
1897
+ isRunning,
1898
+ currentTask,
1899
+ currentStep,
1900
+ error,
1901
+ run,
1902
+ format: formatTask,
1903
+ getMemoryStats: getTaskMemoryStats,
1904
+ clearMemory: clearTaskMemory
1905
+ };
1906
+ }
1907
+
1183
1908
  // src/components/App.tsx
1184
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1909
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1185
1910
  function App() {
1186
1911
  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);
1912
+ const [overlay, setOverlay] = useState7("none");
1913
+ const [screenWatch, setScreenWatch] = useState7(false);
1914
+ const [status, setStatus] = useState7("Ready");
1915
+ const chat2 = useChat(screenWatch);
1916
+ const vision = useVision();
1917
+ const telegram = useTelegram((msg) => {
1918
+ chat2.addSystemMessage(`\u{1F4F1} Telegram [${msg.from}]: ${msg.text}`);
1919
+ });
1920
+ const tasks = useTasks((task, step) => {
1921
+ if (step.status === "running") {
1922
+ setStatus(`Running: ${step.description}`);
1228
1923
  }
1924
+ });
1925
+ useInput3((inputChar, key) => {
1926
+ if (overlay !== "none") return;
1927
+ if (key.ctrl && inputChar === "c") exit();
1928
+ if (key.ctrl && inputChar === "l") chat2.clearMessages();
1929
+ if (key.ctrl && inputChar === "h") setOverlay("help");
1930
+ if (key.ctrl && inputChar === "p") setOverlay("provider");
1229
1931
  if (key.ctrl && inputChar === "w") {
1230
1932
  setScreenWatch((prev) => {
1231
1933
  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."
1934
+ chat2.addSystemMessage(
1935
+ newState ? "\u{1F5A5}\uFE0F Screen watching enabled." : "\u{1F5A5}\uFE0F Screen watching disabled."
1234
1936
  );
1235
1937
  return newState;
1236
1938
  });
1237
1939
  }
1238
- if (key.ctrl && inputChar === "h") {
1239
- setShowHelpMenu(true);
1240
- }
1241
1940
  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
- });
1941
+ handleTelegramToggle();
1249
1942
  }
1250
1943
  });
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) => {
1944
+ const handleCommand = useCallback5(async (cmd) => {
1309
1945
  const parts = cmd.slice(1).split(" ");
1310
1946
  const command = parts[0];
1311
1947
  const args2 = parts.slice(1).join(" ");
1312
1948
  switch (command) {
1313
1949
  case "clear":
1314
- setMessages([messages[0]]);
1315
- addSystemMessage("Chat cleared.");
1950
+ chat2.clearMessages();
1951
+ chat2.addSystemMessage("Chat cleared.");
1316
1952
  break;
1317
1953
  case "help":
1318
- setShowHelpMenu(true);
1954
+ setOverlay("help");
1955
+ break;
1956
+ case "provider":
1957
+ case "model":
1958
+ setOverlay("provider");
1959
+ break;
1960
+ case "config": {
1961
+ const config = getConfig();
1962
+ chat2.addSystemMessage(
1963
+ `\u2699\uFE0F Configuration:
1964
+ Provider: ${config.provider}
1965
+ Model: ${config.model}
1966
+ Ollama: ${config.ollamaHost}
1967
+
1968
+ Use /provider to change`
1969
+ );
1970
+ break;
1971
+ }
1972
+ case "screen":
1973
+ await handleScreenCommand();
1319
1974
  break;
1320
1975
  case "watch":
1321
1976
  setScreenWatch((prev) => {
1322
1977
  const newState = !prev;
1323
- addSystemMessage(
1978
+ chat2.addSystemMessage(
1324
1979
  newState ? "\u{1F5A5}\uFE0F Screen watching enabled." : "\u{1F5A5}\uFE0F Screen watching disabled."
1325
1980
  );
1326
1981
  return newState;
1327
1982
  });
1328
1983
  break;
1329
1984
  case "telegram":
1330
- handleTelegramToggle();
1331
- break;
1332
- case "screen":
1333
- handleScreenCommand();
1985
+ await handleTelegramToggle();
1334
1986
  break;
1335
1987
  case "task":
1336
1988
  if (args2) {
1337
- handleTaskCommand(args2);
1989
+ await handleTaskCommand(args2);
1338
1990
  } else {
1339
- addSystemMessage("Usage: /task <description>\nExample: /task open notepad and type hello");
1991
+ chat2.addSystemMessage("Usage: /task <description>\nExample: /task open notepad and type hello");
1340
1992
  }
1341
1993
  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>");
1994
+ case "memory":
1995
+ if (args2 === "clear") {
1996
+ tasks.clearMemory();
1997
+ chat2.addSystemMessage("\u{1F9E0} Task memory cleared.");
1998
+ } else {
1999
+ const stats = tasks.getMemoryStats();
2000
+ chat2.addSystemMessage(
2001
+ `\u{1F9E0} Task Memory:
2002
+
2003
+ Learned patterns: ${stats.patternCount}
2004
+ Total successful uses: ${stats.totalUses}
2005
+
2006
+ ` + (stats.topPatterns.length > 0 ? ` Top patterns:
2007
+ ${stats.topPatterns.map((p) => ` \u2022 ${p}`).join("\n")}
2008
+
2009
+ ` : " No patterns learned yet.\n\n") + `The more you use /task, the smarter it gets!
2010
+ Use /memory clear to reset.`
2011
+ );
2012
+ }
1350
2013
  break;
1351
2014
  case "quit":
1352
2015
  case "exit":
1353
2016
  exit();
1354
2017
  break;
1355
2018
  default:
1356
- addSystemMessage(`Unknown command: ${command}
1357
- Type /help to see available commands.`);
2019
+ chat2.addSystemMessage(`Unknown command: ${command}
2020
+ Type /help for commands`);
1358
2021
  }
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);
2022
+ }, [chat2, exit]);
2023
+ const handleScreenCommand = useCallback5(async () => {
2024
+ chat2.addSystemMessage("\u{1F4F8} Analyzing screen...");
2025
+ setStatus("Analyzing...");
1367
2026
  try {
1368
- const result = await describeScreen();
1369
- addSystemMessage(`\u{1F5A5}\uFE0F Screen Analysis:
2027
+ const description = await vision.analyze();
2028
+ chat2.addSystemMessage(`\u{1F5A5}\uFE0F Screen:
1370
2029
 
1371
- ${result.description}`);
1372
- } catch (err2) {
1373
- const errorMsg = err2 instanceof Error ? err2.message : "Vision analysis failed";
1374
- addSystemMessage(`\u274C Screen capture failed: ${errorMsg}`);
1375
- } finally {
1376
- setIsProcessing(false);
1377
- setStatus("Ready");
1378
- }
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
- }
2030
+ ${description}`);
1402
2031
  } catch (err2) {
1403
- const errorMsg = err2 instanceof Error ? err2.message : "Task failed";
1404
- addSystemMessage(`\u274C Task error: ${errorMsg}`);
2032
+ chat2.addSystemMessage(`\u274C ${vision.error || "Vision failed"}`);
1405
2033
  } finally {
1406
- setIsProcessing(false);
1407
2034
  setStatus("Ready");
1408
2035
  }
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
- }
2036
+ }, [chat2, vision]);
2037
+ const handleTelegramToggle = useCallback5(async () => {
2038
+ if (telegram.isEnabled) {
2039
+ await telegram.stop();
2040
+ chat2.addSystemMessage("\u{1F4F1} Telegram stopped.");
1421
2041
  } else {
1422
- addSystemMessage("\u{1F4F1} Starting Telegram bot...");
2042
+ chat2.addSystemMessage("\u{1F4F1} Starting Telegram...");
1423
2043
  setStatus("Starting Telegram...");
1424
2044
  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"
2045
+ await telegram.start();
2046
+ chat2.addSystemMessage(
2047
+ "\u{1F4F1} Telegram started!\nSend /start to your bot to connect.\nCommands: /screen, /describe, /run, /status"
1435
2048
  );
1436
- } catch (err2) {
1437
- const errorMsg = err2 instanceof Error ? err2.message : "Failed to start bot";
1438
- addSystemMessage(`\u274C Telegram error: ${errorMsg}`);
2049
+ } catch {
2050
+ chat2.addSystemMessage(`\u274C ${telegram.error || "Telegram failed"}`);
1439
2051
  } finally {
1440
2052
  setStatus("Ready");
1441
2053
  }
1442
2054
  }
1443
- };
1444
- const addSystemMessage = (content) => {
1445
- setMessages((prev) => [
1446
- ...prev,
2055
+ }, [chat2, telegram]);
2056
+ const handleTaskCommand = useCallback5(async (description) => {
2057
+ chat2.addSystemMessage(`\u{1F4CB} Parsing: ${description}`);
2058
+ setStatus("Parsing task...");
2059
+ try {
2060
+ const task = await tasks.run(description);
2061
+ chat2.addSystemMessage(`
2062
+ ${tasks.format(task)}`);
2063
+ chat2.addSystemMessage(
2064
+ task.status === "completed" ? "\u2705 Task completed!" : "\u274C Task failed."
2065
+ );
2066
+ } catch {
2067
+ chat2.addSystemMessage(`\u274C ${tasks.error || "Task failed"}`);
2068
+ } finally {
2069
+ setStatus("Ready");
2070
+ }
2071
+ }, [chat2, tasks]);
2072
+ const handleSubmit = useCallback5(async (value) => {
2073
+ if (!value.trim()) return;
2074
+ if (value.startsWith("/")) {
2075
+ await handleCommand(value);
2076
+ } else {
2077
+ setStatus("Thinking...");
2078
+ await chat2.sendMessage(value);
2079
+ setStatus("Ready");
2080
+ }
2081
+ }, [chat2, handleCommand]);
2082
+ const handleProviderSelect = useCallback5((provider, model) => {
2083
+ chat2.addSystemMessage(`\u2705 Updated: ${provider} / ${model}`);
2084
+ }, [chat2]);
2085
+ if (overlay === "help") {
2086
+ return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: "100%", alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx7(
2087
+ HelpMenu,
1447
2088
  {
1448
- id: Date.now().toString(),
1449
- role: "system",
1450
- content,
1451
- timestamp: /* @__PURE__ */ new Date()
2089
+ onClose: () => setOverlay("none"),
2090
+ onSelect: (cmd) => {
2091
+ setOverlay("none");
2092
+ handleCommand(cmd);
2093
+ }
1452
2094
  }
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,
2095
+ ) });
2096
+ }
2097
+ if (overlay === "provider") {
2098
+ return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: "100%", alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx7(
2099
+ ProviderSelector,
1459
2100
  {
1460
- onClose: () => setShowHelpMenu(false),
1461
- onSelect: handleHelpMenuSelect
2101
+ onClose: () => setOverlay("none"),
2102
+ onSelect: handleProviderSelect
1462
2103
  }
1463
2104
  ) });
1464
2105
  }
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(
2106
+ const visibleMessages = chat2.messages.slice(-20);
2107
+ const isProcessing = chat2.isProcessing || vision.isAnalyzing || tasks.isRunning || telegram.isStarting;
2108
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", height: "100%", children: [
2109
+ /* @__PURE__ */ jsx7(Header, { screenWatch, telegramEnabled: telegram.isEnabled }),
2110
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "gray", padding: 1, children: [
2111
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "gray", children: " Chat " }),
2112
+ visibleMessages.map((msg) => /* @__PURE__ */ jsx7(
1470
2113
  ChatMessage,
1471
2114
  {
1472
2115
  role: msg.role,
@@ -1477,94 +2120,102 @@ ${formatTask(task)}`);
1477
2120
  msg.id
1478
2121
  ))
1479
2122
  ] }),
1480
- error && /* @__PURE__ */ jsx6(Box6, { marginY: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "red", children: [
2123
+ chat2.error && /* @__PURE__ */ jsx7(Box7, { marginY: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: "red", children: [
1481
2124
  "Error: ",
1482
- error
2125
+ chat2.error
1483
2126
  ] }) }),
1484
- /* @__PURE__ */ jsx6(
2127
+ /* @__PURE__ */ jsx7(
1485
2128
  ChatInput,
1486
2129
  {
1487
- value: input,
1488
- onChange: setInput,
2130
+ value: "",
2131
+ onChange: () => {
2132
+ },
1489
2133
  onSubmit: handleSubmit,
1490
2134
  isProcessing
1491
2135
  }
1492
2136
  ),
1493
- /* @__PURE__ */ jsx6(StatusBar, { status })
2137
+ /* @__PURE__ */ jsx7(StatusBar, { status })
1494
2138
  ] });
1495
2139
  }
1496
2140
 
1497
2141
  // src/index.tsx
1498
- import { jsx as jsx7 } from "react/jsx-runtime";
2142
+ import { jsx as jsx8 } from "react/jsx-runtime";
1499
2143
  var args = process.argv.slice(2);
1500
- if (args.length > 0) {
1501
- const command = args[0];
1502
- switch (command) {
1503
- case "auth": {
1504
- const provider = args[1];
1505
- const key = args[2];
1506
- if (!provider || !key) {
1507
- console.log("Usage: cnapse auth <provider> <api-key>");
1508
- console.log("Providers: openrouter, anthropic, openai");
1509
- process.exit(1);
1510
- }
1511
- if (!["openrouter", "anthropic", "openai"].includes(provider)) {
1512
- console.log(`Invalid provider: ${provider}`);
1513
- console.log("Valid providers: openrouter, anthropic, openai");
1514
- process.exit(1);
1515
- }
1516
- setApiKey(provider, key);
1517
- console.log(`\u2713 ${provider} API key saved`);
1518
- process.exit(0);
1519
- }
1520
- case "config": {
1521
- const subcommand = args[1];
1522
- if (subcommand === "set") {
2144
+ async function main() {
2145
+ if (args.length > 0) {
2146
+ const command = args[0];
2147
+ switch (command) {
2148
+ case "auth": {
2149
+ const provider = args[1];
1523
2150
  const key = args[2];
1524
- const value = args[3];
1525
- if (key === "provider") {
1526
- if (!["openrouter", "ollama", "anthropic", "openai"].includes(value)) {
1527
- console.log("Valid providers: openrouter, ollama, anthropic, openai");
1528
- process.exit(1);
1529
- }
1530
- setProvider(value);
1531
- console.log(`\u2713 Provider set to: ${value}`);
1532
- } else if (key === "model") {
1533
- setModel(value);
1534
- console.log(`\u2713 Model set to: ${value}`);
1535
- } else {
1536
- console.log("Usage: cnapse config set <provider|model> <value>");
2151
+ if (!provider || !key) {
2152
+ console.log("Usage: cnapse auth <provider> <api-key>");
2153
+ console.log("Providers: openrouter, anthropic, openai");
2154
+ process.exit(1);
2155
+ }
2156
+ if (!["openrouter", "anthropic", "openai"].includes(provider)) {
2157
+ console.log(`Invalid provider: ${provider}`);
2158
+ console.log("Valid providers: openrouter, anthropic, openai");
2159
+ process.exit(1);
1537
2160
  }
2161
+ setApiKey(provider, key);
2162
+ console.log(`\u2713 ${provider} API key saved`);
1538
2163
  process.exit(0);
1539
2164
  }
1540
- if (subcommand === "show" || !subcommand) {
1541
- const config = getConfig();
1542
- console.log("\nC-napse Configuration:");
1543
- console.log(` Provider: ${config.provider}`);
1544
- console.log(` Model: ${config.model}`);
1545
- console.log(` Ollama Host: ${config.ollamaHost}`);
1546
- console.log(` API Keys configured:`);
1547
- console.log(` - OpenRouter: ${config.apiKeys.openrouter ? "\u2713" : "\u2717"}`);
1548
- console.log(` - Anthropic: ${config.apiKeys.anthropic ? "\u2713" : "\u2717"}`);
1549
- console.log(` - OpenAI: ${config.apiKeys.openai ? "\u2713" : "\u2717"}`);
1550
- console.log("");
1551
- process.exit(0);
2165
+ case "config": {
2166
+ const subcommand = args[1];
2167
+ if (!subcommand) {
2168
+ const { ConfigUI } = await import("./ConfigUI-V5TM6KKS.js");
2169
+ render(/* @__PURE__ */ jsx8(ConfigUI, {}));
2170
+ return;
2171
+ }
2172
+ if (subcommand === "set") {
2173
+ const key = args[2];
2174
+ const value = args[3];
2175
+ if (key === "provider") {
2176
+ if (!["openrouter", "ollama", "anthropic", "openai"].includes(value)) {
2177
+ console.log("Valid providers: openrouter, ollama, anthropic, openai");
2178
+ process.exit(1);
2179
+ }
2180
+ setProvider(value);
2181
+ console.log(`\u2713 Provider set to: ${value}`);
2182
+ } else if (key === "model") {
2183
+ setModel(value);
2184
+ console.log(`\u2713 Model set to: ${value}`);
2185
+ } else {
2186
+ console.log("Usage: cnapse config set <provider|model> <value>");
2187
+ }
2188
+ process.exit(0);
2189
+ }
2190
+ if (subcommand === "show") {
2191
+ const config = getConfig();
2192
+ console.log("\nC-napse Configuration:");
2193
+ console.log(` Provider: ${config.provider}`);
2194
+ console.log(` Model: ${config.model}`);
2195
+ console.log(` Ollama Host: ${config.ollamaHost}`);
2196
+ console.log(` API Keys configured:`);
2197
+ console.log(` - OpenRouter: ${config.apiKeys.openrouter ? "\u2713" : "\u2717"}`);
2198
+ console.log(` - Anthropic: ${config.apiKeys.anthropic ? "\u2713" : "\u2717"}`);
2199
+ console.log(` - OpenAI: ${config.apiKeys.openai ? "\u2713" : "\u2717"}`);
2200
+ console.log("");
2201
+ process.exit(0);
2202
+ }
2203
+ console.log("Usage: cnapse config [show|set <key> <value>]");
2204
+ process.exit(1);
1552
2205
  }
1553
- console.log("Usage: cnapse config [show|set <key> <value>]");
1554
- process.exit(1);
1555
- }
1556
- case "help":
1557
- case "--help":
1558
- case "-h": {
1559
- console.log(`
2206
+ case "help":
2207
+ case "--help":
2208
+ case "-h": {
2209
+ console.log(`
1560
2210
  C-napse - Autonomous PC Intelligence
1561
2211
 
1562
2212
  Usage:
1563
2213
  cnapse Start interactive chat
1564
2214
  cnapse init Interactive setup wizard
1565
- cnapse auth <provider> <key> Set API key
1566
- cnapse config Show configuration
2215
+ cnapse config Interactive configuration
2216
+ cnapse config show Show current configuration
1567
2217
  cnapse config set <k> <v> Set config value
2218
+ cnapse auth <provider> <key> Set API key
1568
2219
  cnapse help Show this help
1569
2220
 
1570
2221
  Providers:
@@ -1575,28 +2226,31 @@ Providers:
1575
2226
 
1576
2227
  Quick Start:
1577
2228
  cnapse init # Interactive setup
2229
+ cnapse config # Change provider/model
1578
2230
 
1579
2231
  Manual Setup:
1580
2232
  cnapse auth openrouter sk-or-xxxxx
1581
2233
  cnapse config set provider openrouter
1582
2234
  cnapse config set model qwen/qwen-2.5-coder-32b-instruct
1583
2235
  `);
1584
- process.exit(0);
1585
- }
1586
- case "version":
1587
- case "--version":
1588
- case "-v": {
1589
- console.log("cnapse v0.2.0");
1590
- process.exit(0);
1591
- }
1592
- case "init": {
1593
- const { Setup } = await import("./Setup-Q32JPHGP.js");
1594
- render(/* @__PURE__ */ jsx7(Setup, {}));
1595
- process.exit(0);
1596
- }
1597
- default: {
1598
- break;
2236
+ process.exit(0);
2237
+ }
2238
+ case "version":
2239
+ case "--version":
2240
+ case "-v": {
2241
+ console.log("cnapse v0.5.0");
2242
+ process.exit(0);
2243
+ }
2244
+ case "init": {
2245
+ const { Setup } = await import("./Setup-Q32JPHGP.js");
2246
+ render(/* @__PURE__ */ jsx8(Setup, {}));
2247
+ return;
2248
+ }
2249
+ default: {
2250
+ break;
2251
+ }
1599
2252
  }
1600
2253
  }
2254
+ render(/* @__PURE__ */ jsx8(App, {}));
1601
2255
  }
1602
- render(/* @__PURE__ */ jsx7(App, {}));
2256
+ main();