@projectservan8n/cnapse 0.2.0 → 0.4.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
@@ -1,55 +1,21 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ getApiKey,
4
+ getConfig,
5
+ setApiKey,
6
+ setModel,
7
+ setProvider
8
+ } from "./chunk-COKO6V5J.js";
2
9
 
3
10
  // src/index.tsx
4
11
  import { render } from "ink";
5
12
 
6
13
  // src/components/App.tsx
7
- import { useState } from "react";
8
- import { Box as Box5, Text as Text5, useApp, useInput } from "ink";
14
+ import { useState as useState2, useEffect, useRef } from "react";
15
+ import { Box as Box6, Text as Text6, useApp, useInput as useInput2 } from "ink";
9
16
 
10
17
  // src/components/Header.tsx
11
18
  import { Box, Text } from "ink";
12
-
13
- // src/lib/config.ts
14
- import Conf from "conf";
15
- var config = new Conf({
16
- projectName: "cnapse",
17
- defaults: {
18
- provider: "ollama",
19
- model: "qwen2.5:0.5b",
20
- apiKeys: {},
21
- ollamaHost: "http://localhost:11434",
22
- openrouter: {
23
- siteUrl: "https://github.com/projectservan8n/C-napse",
24
- appName: "C-napse"
25
- }
26
- }
27
- });
28
- function getConfig() {
29
- return {
30
- provider: config.get("provider"),
31
- model: config.get("model"),
32
- apiKeys: config.get("apiKeys"),
33
- ollamaHost: config.get("ollamaHost"),
34
- openrouter: config.get("openrouter")
35
- };
36
- }
37
- function setProvider(provider) {
38
- config.set("provider", provider);
39
- }
40
- function setModel(model) {
41
- config.set("model", model);
42
- }
43
- function setApiKey(provider, key) {
44
- const keys = config.get("apiKeys");
45
- keys[provider] = key;
46
- config.set("apiKeys", keys);
47
- }
48
- function getApiKey(provider) {
49
- return config.get("apiKeys")[provider];
50
- }
51
-
52
- // src/components/Header.tsx
53
19
  import { jsx, jsxs } from "react/jsx-runtime";
54
20
  var ASCII_BANNER = `
55
21
  \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
@@ -59,15 +25,18 @@ var ASCII_BANNER = `
59
25
  \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
60
26
  \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D
61
27
  `.trim();
62
- function Header() {
63
- const config2 = getConfig();
28
+ function Header({ screenWatch = false, telegramEnabled = false }) {
29
+ const config = getConfig();
64
30
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
65
31
  /* @__PURE__ */ jsx(Text, { color: "cyan", children: ASCII_BANNER }),
66
32
  /* @__PURE__ */ jsx(Box, { justifyContent: "center", children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
67
- config2.provider,
33
+ config.provider,
68
34
  " \u2502 ",
69
- config2.model
70
- ] }) })
35
+ config.model,
36
+ screenWatch && /* @__PURE__ */ jsx(Text, { color: "yellow", children: " \u2502 \u{1F5A5}\uFE0F Screen" }),
37
+ telegramEnabled && /* @__PURE__ */ jsx(Text, { color: "blue", children: " \u2502 \u{1F4F1} Telegram" })
38
+ ] }) }),
39
+ /* @__PURE__ */ jsx(Box, { justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: "Press Ctrl+H for help" }) })
71
40
  ] });
72
41
  }
73
42
 
@@ -129,6 +98,98 @@ function StatusBar({ status }) {
129
98
  return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { backgroundColor: "gray", color: "white", children: ` ${status} \u2502 Ctrl+C: Exit \u2502 Enter: Send ` }) });
130
99
  }
131
100
 
101
+ // src/components/HelpMenu.tsx
102
+ import React, { useState } from "react";
103
+ import { Box as Box5, Text as Text5, useInput } from "ink";
104
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
105
+ var MENU_ITEMS = [
106
+ // Navigation
107
+ { command: "/help", shortcut: "Ctrl+H", description: "Show this help menu", category: "navigation" },
108
+ { command: "/clear", shortcut: "Ctrl+L", description: "Clear chat history", category: "navigation" },
109
+ { command: "/quit", shortcut: "Ctrl+C", description: "Exit C-napse", category: "navigation" },
110
+ // Actions
111
+ { command: "/screen", shortcut: "Ctrl+S", description: "Take screenshot and describe", category: "actions" },
112
+ { command: "/task", description: "Run multi-step task", category: "actions" },
113
+ { command: "/telegram", shortcut: "Ctrl+T", description: "Toggle Telegram bot", category: "actions" },
114
+ // Settings
115
+ { command: "/config", description: "Show/edit configuration", category: "settings" },
116
+ { command: "/watch", shortcut: "Ctrl+W", description: "Toggle screen watching", category: "settings" },
117
+ { command: "/model", description: "Change AI model", category: "settings" },
118
+ { command: "/provider", description: "Change AI provider", category: "settings" }
119
+ ];
120
+ function HelpMenu({ onClose, onSelect }) {
121
+ const [selectedIndex, setSelectedIndex] = useState(0);
122
+ const [selectedCategory, setSelectedCategory] = useState("all");
123
+ const filteredItems = selectedCategory === "all" ? MENU_ITEMS : MENU_ITEMS.filter((item) => item.category === selectedCategory);
124
+ useInput((input, key) => {
125
+ if (key.escape) {
126
+ onClose();
127
+ return;
128
+ }
129
+ if (key.upArrow) {
130
+ setSelectedIndex((prev) => prev > 0 ? prev - 1 : filteredItems.length - 1);
131
+ }
132
+ if (key.downArrow) {
133
+ setSelectedIndex((prev) => prev < filteredItems.length - 1 ? prev + 1 : 0);
134
+ }
135
+ if (key.leftArrow || key.rightArrow) {
136
+ const categories2 = ["all", "navigation", "actions", "settings"];
137
+ const currentIdx = categories2.indexOf(selectedCategory);
138
+ if (key.leftArrow) {
139
+ setSelectedCategory(categories2[currentIdx > 0 ? currentIdx - 1 : categories2.length - 1]);
140
+ } else {
141
+ setSelectedCategory(categories2[currentIdx < categories2.length - 1 ? currentIdx + 1 : 0]);
142
+ }
143
+ setSelectedIndex(0);
144
+ }
145
+ if (key.return) {
146
+ const item = filteredItems[selectedIndex];
147
+ if (item) {
148
+ onSelect(item.command);
149
+ onClose();
150
+ }
151
+ }
152
+ });
153
+ const categories = [
154
+ { key: "all", label: "All" },
155
+ { key: "navigation", label: "Navigation" },
156
+ { key: "actions", label: "Actions" },
157
+ { key: "settings", label: "Settings" }
158
+ ];
159
+ return /* @__PURE__ */ jsxs4(
160
+ Box5,
161
+ {
162
+ flexDirection: "column",
163
+ borderStyle: "round",
164
+ borderColor: "cyan",
165
+ padding: 1,
166
+ width: 60,
167
+ children: [
168
+ /* @__PURE__ */ jsx5(Box5, { justifyContent: "center", marginBottom: 1, children: /* @__PURE__ */ jsx5(Text5, { bold: true, color: "cyan", children: "C-napse Help" }) }),
169
+ /* @__PURE__ */ jsx5(Box5, { justifyContent: "center", marginBottom: 1, children: categories.map((cat, idx) => /* @__PURE__ */ jsxs4(React.Fragment, { children: [
170
+ /* @__PURE__ */ jsx5(
171
+ Text5,
172
+ {
173
+ color: selectedCategory === cat.key ? "cyan" : "gray",
174
+ bold: selectedCategory === cat.key,
175
+ children: cat.label
176
+ }
177
+ ),
178
+ idx < categories.length - 1 && /* @__PURE__ */ jsx5(Text5, { color: "gray", children: " \u2502 " })
179
+ ] }, cat.key)) }),
180
+ /* @__PURE__ */ jsx5(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: "Use \u2190\u2192 to switch tabs, \u2191\u2193 to navigate, Enter to select" }) }),
181
+ /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", children: filteredItems.map((item, index) => /* @__PURE__ */ jsxs4(Box5, { children: [
182
+ /* @__PURE__ */ jsx5(Text5, { color: index === selectedIndex ? "cyan" : "white", children: index === selectedIndex ? "\u276F " : " " }),
183
+ /* @__PURE__ */ jsx5(Box5, { width: 12, children: /* @__PURE__ */ jsx5(Text5, { bold: index === selectedIndex, color: index === selectedIndex ? "cyan" : "white", children: item.command }) }),
184
+ item.shortcut && /* @__PURE__ */ jsx5(Box5, { width: 10, children: /* @__PURE__ */ jsx5(Text5, { color: "yellow", dimColor: true, children: item.shortcut }) }),
185
+ /* @__PURE__ */ jsx5(Text5, { color: "gray", children: item.description })
186
+ ] }, item.command)) }),
187
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, justifyContent: "center", children: /* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: "Press Esc to close" }) })
188
+ ]
189
+ }
190
+ );
191
+ }
192
+
132
193
  // src/lib/api.ts
133
194
  var SYSTEM_PROMPT = `You are C-napse, a helpful AI assistant for PC automation running on the user's desktop.
134
195
  You can help with coding, file management, shell commands, and more. Be concise and helpful.
@@ -137,23 +198,23 @@ When responding:
137
198
  - Be direct and practical
138
199
  - Use markdown formatting for code blocks
139
200
  - If asked to do something, explain what you'll do first`;
140
- async function chat(messages) {
141
- const config2 = getConfig();
201
+ async function chat(messages, systemPrompt) {
202
+ const config = getConfig();
142
203
  const allMessages = [
143
- { role: "system", content: SYSTEM_PROMPT },
204
+ { role: "system", content: systemPrompt || SYSTEM_PROMPT },
144
205
  ...messages
145
206
  ];
146
- switch (config2.provider) {
207
+ switch (config.provider) {
147
208
  case "openrouter":
148
- return chatOpenRouter(allMessages, config2.model);
209
+ return chatOpenRouter(allMessages, config.model);
149
210
  case "ollama":
150
- return chatOllama(allMessages, config2.model);
211
+ return chatOllama(allMessages, config.model);
151
212
  case "anthropic":
152
- return chatAnthropic(allMessages, config2.model);
213
+ return chatAnthropic(allMessages, config.model);
153
214
  case "openai":
154
- return chatOpenAI(allMessages, config2.model);
215
+ return chatOpenAI(allMessages, config.model);
155
216
  default:
156
- throw new Error(`Unknown provider: ${config2.provider}`);
217
+ throw new Error(`Unknown provider: ${config.provider}`);
157
218
  }
158
219
  }
159
220
  async function chatOpenRouter(messages, model) {
@@ -161,14 +222,14 @@ async function chatOpenRouter(messages, model) {
161
222
  if (!apiKey) {
162
223
  throw new Error("OpenRouter API key not configured. Run: cnapse auth openrouter <key>");
163
224
  }
164
- const config2 = getConfig();
225
+ const config = getConfig();
165
226
  const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
166
227
  method: "POST",
167
228
  headers: {
168
229
  "Authorization": `Bearer ${apiKey}`,
169
230
  "Content-Type": "application/json",
170
- "HTTP-Referer": config2.openrouter.siteUrl,
171
- "X-Title": config2.openrouter.appName
231
+ "HTTP-Referer": config.openrouter.siteUrl,
232
+ "X-Title": config.openrouter.appName
172
233
  },
173
234
  body: JSON.stringify({
174
235
  model,
@@ -186,8 +247,8 @@ async function chatOpenRouter(messages, model) {
186
247
  return { content, model };
187
248
  }
188
249
  async function chatOllama(messages, model) {
189
- const config2 = getConfig();
190
- const response = await fetch(`${config2.ollamaHost}/api/chat`, {
250
+ const config = getConfig();
251
+ const response = await fetch(`${config.ollamaHost}/api/chat`, {
191
252
  method: "POST",
192
253
  headers: { "Content-Type": "application/json" },
193
254
  body: JSON.stringify({
@@ -260,23 +321,904 @@ async function chatOpenAI(messages, model) {
260
321
  return { content, model };
261
322
  }
262
323
 
324
+ // src/lib/screen.ts
325
+ import { exec } from "child_process";
326
+ import { promisify } from "util";
327
+ var execAsync = promisify(exec);
328
+ async function getScreenDescription() {
329
+ try {
330
+ const platform = process.platform;
331
+ if (platform === "win32") {
332
+ const { stdout } = await execAsync(`
333
+ Add-Type -AssemblyName System.Windows.Forms
334
+ $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
335
+ Write-Output "$($screen.Width)x$($screen.Height)"
336
+ `, { shell: "powershell.exe" });
337
+ return `Screen ${stdout.trim()} captured`;
338
+ } else if (platform === "darwin") {
339
+ const { stdout } = await execAsync(`system_profiler SPDisplaysDataType | grep Resolution | head -1`);
340
+ return `Screen ${stdout.trim()}`;
341
+ } else {
342
+ const { stdout } = await execAsync(`xdpyinfo | grep dimensions | awk '{print $2}'`);
343
+ return `Screen ${stdout.trim()} captured`;
344
+ }
345
+ } catch {
346
+ return null;
347
+ }
348
+ }
349
+
350
+ // src/lib/vision.ts
351
+ async function describeScreen() {
352
+ const screenshot = await captureScreenshot();
353
+ if (!screenshot) {
354
+ throw new Error("Failed to capture screenshot");
355
+ }
356
+ const config = getConfig();
357
+ const description = await analyzeWithVision(screenshot, config.provider);
358
+ return { description, screenshot };
359
+ }
360
+ async function captureScreenshot() {
361
+ try {
362
+ const screenshotDesktop = await import("screenshot-desktop");
363
+ const buffer = await screenshotDesktop.default({ format: "png" });
364
+ return buffer.toString("base64");
365
+ } catch {
366
+ return captureScreenFallback();
367
+ }
368
+ }
369
+ async function captureScreenFallback() {
370
+ const { exec: exec5 } = await import("child_process");
371
+ const { promisify: promisify5 } = await import("util");
372
+ const { tmpdir } = await import("os");
373
+ const { join } = await import("path");
374
+ const { readFile, unlink } = await import("fs/promises");
375
+ const execAsync5 = promisify5(exec5);
376
+ const tempFile = join(tmpdir(), `cnapse-screen-${Date.now()}.png`);
377
+ try {
378
+ const platform = process.platform;
379
+ if (platform === "win32") {
380
+ await execAsync5(`
381
+ Add-Type -AssemblyName System.Windows.Forms
382
+ $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
383
+ $bitmap = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height)
384
+ $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
385
+ $graphics.CopyFromScreen($screen.Location, [System.Drawing.Point]::Empty, $screen.Size)
386
+ $bitmap.Save("${tempFile.replace(/\\/g, "\\\\")}")
387
+ $graphics.Dispose()
388
+ $bitmap.Dispose()
389
+ `, { shell: "powershell.exe" });
390
+ } else if (platform === "darwin") {
391
+ await execAsync5(`screencapture -x "${tempFile}"`);
392
+ } else {
393
+ await execAsync5(`gnome-screenshot -f "${tempFile}" 2>/dev/null || scrot "${tempFile}" 2>/dev/null || import -window root "${tempFile}"`);
394
+ }
395
+ const imageBuffer = await readFile(tempFile);
396
+ await unlink(tempFile).catch(() => {
397
+ });
398
+ return imageBuffer.toString("base64");
399
+ } catch {
400
+ return null;
401
+ }
402
+ }
403
+ async function analyzeWithVision(base64Image, provider) {
404
+ const prompt = `Look at this screenshot and describe:
405
+ 1. What application or window is visible
406
+ 2. Key UI elements you can see (buttons, text fields, menus)
407
+ 3. What the user appears to be doing or could do next
408
+ 4. Any notable content or state
409
+
410
+ Be concise but helpful.`;
411
+ switch (provider) {
412
+ case "ollama":
413
+ return analyzeWithOllama(base64Image, prompt);
414
+ case "openrouter":
415
+ return analyzeWithOpenRouter(base64Image, prompt);
416
+ case "anthropic":
417
+ return analyzeWithAnthropic(base64Image, prompt);
418
+ case "openai":
419
+ return analyzeWithOpenAI(base64Image, prompt);
420
+ default:
421
+ throw new Error(`Vision not supported for provider: ${provider}`);
422
+ }
423
+ }
424
+ async function analyzeWithOllama(base64Image, prompt) {
425
+ const config = getConfig();
426
+ const ollamaHost = config.ollamaHost || "http://localhost:11434";
427
+ const visionModels = ["llava", "llama3.2-vision", "bakllava", "llava-llama3"];
428
+ const model = visionModels.find((m) => config.model.includes(m)) || "llava";
429
+ const response = await fetch(`${ollamaHost}/api/generate`, {
430
+ method: "POST",
431
+ headers: { "Content-Type": "application/json" },
432
+ body: JSON.stringify({
433
+ model,
434
+ prompt,
435
+ images: [base64Image],
436
+ stream: false
437
+ })
438
+ });
439
+ if (!response.ok) {
440
+ const text = await response.text();
441
+ throw new Error(`Ollama vision error: ${text}`);
442
+ }
443
+ const data = await response.json();
444
+ return data.response || "Unable to analyze image";
445
+ }
446
+ async function analyzeWithOpenRouter(base64Image, prompt) {
447
+ const apiKey = getApiKey("openrouter");
448
+ if (!apiKey) throw new Error("OpenRouter API key not configured");
449
+ const model = "anthropic/claude-3-5-sonnet";
450
+ const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
451
+ method: "POST",
452
+ headers: {
453
+ "Authorization": `Bearer ${apiKey}`,
454
+ "Content-Type": "application/json",
455
+ "HTTP-Referer": "https://c-napse.up.railway.app",
456
+ "X-Title": "C-napse"
457
+ },
458
+ body: JSON.stringify({
459
+ model,
460
+ messages: [
461
+ {
462
+ role: "user",
463
+ content: [
464
+ { type: "text", text: prompt },
465
+ {
466
+ type: "image_url",
467
+ image_url: { url: `data:image/png;base64,${base64Image}` }
468
+ }
469
+ ]
470
+ }
471
+ ],
472
+ max_tokens: 1e3
473
+ })
474
+ });
475
+ if (!response.ok) {
476
+ const text = await response.text();
477
+ throw new Error(`OpenRouter vision error: ${text}`);
478
+ }
479
+ const data = await response.json();
480
+ return data.choices?.[0]?.message?.content || "Unable to analyze image";
481
+ }
482
+ async function analyzeWithAnthropic(base64Image, prompt) {
483
+ const apiKey = getApiKey("anthropic");
484
+ if (!apiKey) throw new Error("Anthropic API key not configured");
485
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
486
+ method: "POST",
487
+ headers: {
488
+ "x-api-key": apiKey,
489
+ "anthropic-version": "2023-06-01",
490
+ "Content-Type": "application/json"
491
+ },
492
+ body: JSON.stringify({
493
+ model: "claude-3-5-sonnet-20241022",
494
+ max_tokens: 1e3,
495
+ messages: [
496
+ {
497
+ role: "user",
498
+ content: [
499
+ {
500
+ type: "image",
501
+ source: {
502
+ type: "base64",
503
+ media_type: "image/png",
504
+ data: base64Image
505
+ }
506
+ },
507
+ { type: "text", text: prompt }
508
+ ]
509
+ }
510
+ ]
511
+ })
512
+ });
513
+ if (!response.ok) {
514
+ const text = await response.text();
515
+ throw new Error(`Anthropic vision error: ${text}`);
516
+ }
517
+ const data = await response.json();
518
+ return data.content?.[0]?.text || "Unable to analyze image";
519
+ }
520
+ async function analyzeWithOpenAI(base64Image, prompt) {
521
+ const apiKey = getApiKey("openai");
522
+ if (!apiKey) throw new Error("OpenAI API key not configured");
523
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
524
+ method: "POST",
525
+ headers: {
526
+ "Authorization": `Bearer ${apiKey}`,
527
+ "Content-Type": "application/json"
528
+ },
529
+ body: JSON.stringify({
530
+ model: "gpt-4-vision-preview",
531
+ messages: [
532
+ {
533
+ role: "user",
534
+ content: [
535
+ { type: "text", text: prompt },
536
+ {
537
+ type: "image_url",
538
+ image_url: { url: `data:image/png;base64,${base64Image}` }
539
+ }
540
+ ]
541
+ }
542
+ ],
543
+ max_tokens: 1e3
544
+ })
545
+ });
546
+ if (!response.ok) {
547
+ const text = await response.text();
548
+ throw new Error(`OpenAI vision error: ${text}`);
549
+ }
550
+ const data = await response.json();
551
+ return data.choices?.[0]?.message?.content || "Unable to analyze image";
552
+ }
553
+
554
+ // src/services/telegram.ts
555
+ import { EventEmitter } from "events";
556
+
557
+ // src/tools/shell.ts
558
+ import { exec as exec4 } from "child_process";
559
+ import { promisify as promisify4 } from "util";
560
+
561
+ // src/tools/clipboard.ts
562
+ import clipboardy from "clipboardy";
563
+
564
+ // 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
+ import { exec as exec3 } from "child_process";
571
+ import { promisify as promisify3 } from "util";
572
+ var execAsync3 = promisify3(exec3);
573
+ async function clickMouse(button = "left") {
574
+ try {
575
+ if (process.platform === "win32") {
576
+ const script = `
577
+ Add-Type -MemberDefinition @"
578
+ [DllImport("user32.dll",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
579
+ public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
580
+ "@ -Name Mouse -Namespace Win32
581
+ ${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" });
583
+ } else if (process.platform === "darwin") {
584
+ await execAsync3(`cliclick c:.`);
585
+ } else {
586
+ const btn = button === "left" ? "1" : button === "right" ? "3" : "2";
587
+ await execAsync3(`xdotool click ${btn}`);
588
+ }
589
+ return ok(`Clicked ${button} button`);
590
+ } catch (error) {
591
+ return err(`Failed to click: ${error instanceof Error ? error.message : "Unknown error"}`);
592
+ }
593
+ }
594
+ async function typeText(text) {
595
+ try {
596
+ if (process.platform === "win32") {
597
+ 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" });
599
+ } else if (process.platform === "darwin") {
600
+ const escaped = text.replace(/'/g, "'\\''");
601
+ await execAsync3(`osascript -e 'tell application "System Events" to keystroke "${escaped}"'`);
602
+ } else {
603
+ const escaped = text.replace(/'/g, "'\\''");
604
+ await execAsync3(`xdotool type '${escaped}'`);
605
+ }
606
+ return ok(`Typed: ${text}`);
607
+ } catch (error) {
608
+ return err(`Failed to type: ${error instanceof Error ? error.message : "Unknown error"}`);
609
+ }
610
+ }
611
+ async function pressKey(key) {
612
+ try {
613
+ if (process.platform === "win32") {
614
+ const winKeyMap = {
615
+ "enter": "{ENTER}",
616
+ "return": "{ENTER}",
617
+ "escape": "{ESC}",
618
+ "esc": "{ESC}",
619
+ "tab": "{TAB}",
620
+ "space": " ",
621
+ "backspace": "{BACKSPACE}",
622
+ "delete": "{DELETE}",
623
+ "up": "{UP}",
624
+ "down": "{DOWN}",
625
+ "left": "{LEFT}",
626
+ "right": "{RIGHT}",
627
+ "home": "{HOME}",
628
+ "end": "{END}",
629
+ "pageup": "{PGUP}",
630
+ "pagedown": "{PGDN}",
631
+ "f1": "{F1}",
632
+ "f2": "{F2}",
633
+ "f3": "{F3}",
634
+ "f4": "{F4}",
635
+ "f5": "{F5}",
636
+ "f6": "{F6}",
637
+ "f7": "{F7}",
638
+ "f8": "{F8}",
639
+ "f9": "{F9}",
640
+ "f10": "{F10}",
641
+ "f11": "{F11}",
642
+ "f12": "{F12}"
643
+ };
644
+ 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" });
646
+ } else if (process.platform === "darwin") {
647
+ const macKeyMap = {
648
+ "return": 36,
649
+ "enter": 36,
650
+ "escape": 53,
651
+ "esc": 53,
652
+ "tab": 48,
653
+ "space": 49,
654
+ "backspace": 51,
655
+ "delete": 117,
656
+ "up": 126,
657
+ "down": 125,
658
+ "left": 123,
659
+ "right": 124
660
+ };
661
+ const keyCode = macKeyMap[key.toLowerCase()];
662
+ if (keyCode) {
663
+ await execAsync3(`osascript -e 'tell application "System Events" to key code ${keyCode}'`);
664
+ } else {
665
+ await execAsync3(`osascript -e 'tell application "System Events" to keystroke "${key}"'`);
666
+ }
667
+ } else {
668
+ await execAsync3(`xdotool key ${key}`);
669
+ }
670
+ return ok(`Pressed: ${key}`);
671
+ } catch (error) {
672
+ return err(`Failed to press key: ${error instanceof Error ? error.message : "Unknown error"}`);
673
+ }
674
+ }
675
+ async function keyCombo(keys) {
676
+ try {
677
+ if (process.platform === "win32") {
678
+ const hasWin = keys.some((k) => k.toLowerCase() === "meta" || k.toLowerCase() === "win");
679
+ const hasR = keys.some((k) => k.toLowerCase() === "r");
680
+ 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" });
682
+ return ok(`Pressed: ${keys.join("+")}`);
683
+ }
684
+ const modifierMap = {
685
+ "control": "^",
686
+ "ctrl": "^",
687
+ "alt": "%",
688
+ "shift": "+"
689
+ };
690
+ let combo = "";
691
+ const regularKeys = [];
692
+ for (const key of keys) {
693
+ const lower = key.toLowerCase();
694
+ if (modifierMap[lower]) {
695
+ combo += modifierMap[lower];
696
+ } else if (lower !== "meta" && lower !== "win") {
697
+ regularKeys.push(key.toLowerCase());
698
+ }
699
+ }
700
+ combo += regularKeys.join("");
701
+ await execAsync3(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${combo}')"`, { shell: "cmd.exe" });
702
+ } else if (process.platform === "darwin") {
703
+ const modifiers = keys.filter((k) => ["control", "ctrl", "alt", "shift", "command", "meta"].includes(k.toLowerCase()));
704
+ const regular = keys.filter((k) => !["control", "ctrl", "alt", "shift", "command", "meta"].includes(k.toLowerCase()));
705
+ let cmd = 'tell application "System Events" to keystroke "' + regular.join("") + '"';
706
+ if (modifiers.length > 0) {
707
+ const modMap = {
708
+ "control": "control down",
709
+ "ctrl": "control down",
710
+ "alt": "option down",
711
+ "shift": "shift down",
712
+ "command": "command down",
713
+ "meta": "command down"
714
+ };
715
+ cmd += " using {" + modifiers.map((m) => modMap[m.toLowerCase()]).join(", ") + "}";
716
+ }
717
+ await execAsync3(`osascript -e '${cmd}'`);
718
+ } else {
719
+ await execAsync3(`xdotool key ${keys.join("+")}`);
720
+ }
721
+ return ok(`Pressed: ${keys.join("+")}`);
722
+ } catch (error) {
723
+ return err(`Failed to press combo: ${error instanceof Error ? error.message : "Unknown error"}`);
724
+ }
725
+ }
726
+ async function focusWindow(title) {
727
+ try {
728
+ if (process.platform === "win32") {
729
+ const escaped = title.replace(/'/g, "''");
730
+ await execAsync3(`powershell -Command "$wshell = New-Object -ComObject wscript.shell; $wshell.AppActivate('${escaped}')"`, { shell: "cmd.exe" });
731
+ } else if (process.platform === "darwin") {
732
+ await execAsync3(`osascript -e 'tell application "${title}" to activate'`);
733
+ } else {
734
+ await execAsync3(`wmctrl -a "${title}"`);
735
+ }
736
+ return ok(`Focused window: ${title}`);
737
+ } catch (error) {
738
+ return err(`Failed to focus window: ${error instanceof Error ? error.message : "Unknown error"}`);
739
+ }
740
+ }
741
+
742
+ // src/tools/index.ts
743
+ function ok(output) {
744
+ return { success: true, output };
745
+ }
746
+ function err(error) {
747
+ return { success: false, output: "", error };
748
+ }
749
+
750
+ // src/tools/shell.ts
751
+ var execAsync4 = promisify4(exec4);
752
+ async function runCommand(cmd, timeout = 3e4) {
753
+ try {
754
+ const isWindows = process.platform === "win32";
755
+ const shell = isWindows ? "cmd.exe" : "/bin/sh";
756
+ const shellArg = isWindows ? "/C" : "-c";
757
+ const { stdout, stderr } = await execAsync4(cmd, {
758
+ shell,
759
+ timeout,
760
+ maxBuffer: 10 * 1024 * 1024
761
+ // 10MB
762
+ });
763
+ if (stderr && stderr.trim()) {
764
+ return ok(`${stdout}
765
+ [stderr]: ${stderr}`);
766
+ }
767
+ return ok(stdout || "(no output)");
768
+ } catch (error) {
769
+ if (error.killed) {
770
+ return err(`Command timed out after ${timeout}ms`);
771
+ }
772
+ const stderr = error.stderr || "";
773
+ const stdout = error.stdout || "";
774
+ return {
775
+ success: false,
776
+ output: stdout,
777
+ error: `Exit code: ${error.code || -1}
778
+ ${stderr}`
779
+ };
780
+ }
781
+ }
782
+
783
+ // src/services/telegram.ts
784
+ var TelegramBotService = class extends EventEmitter {
785
+ bot = null;
786
+ isRunning = false;
787
+ allowedChatIds = /* @__PURE__ */ new Set();
788
+ constructor() {
789
+ super();
790
+ }
791
+ /**
792
+ * Start the Telegram bot
793
+ */
794
+ async start() {
795
+ if (this.isRunning) {
796
+ return;
797
+ }
798
+ const botToken = getApiKey("telegram");
799
+ if (!botToken) {
800
+ throw new Error("Telegram bot token not configured. Use: cnapse auth telegram YOUR_BOT_TOKEN");
801
+ }
802
+ try {
803
+ const { Telegraf } = await import("telegraf");
804
+ this.bot = new Telegraf(botToken);
805
+ const config = getConfig();
806
+ if (config.telegram?.chatId) {
807
+ this.allowedChatIds.add(config.telegram.chatId);
808
+ }
809
+ this.setupHandlers();
810
+ await this.bot.launch();
811
+ this.isRunning = true;
812
+ this.emit("started");
813
+ } catch (error) {
814
+ throw new Error(`Failed to start Telegram bot: ${error instanceof Error ? error.message : "Unknown error"}`);
815
+ }
816
+ }
817
+ /**
818
+ * Stop the Telegram bot
819
+ */
820
+ async stop() {
821
+ if (!this.isRunning || !this.bot) {
822
+ return;
823
+ }
824
+ this.bot.stop("SIGTERM");
825
+ this.isRunning = false;
826
+ this.bot = null;
827
+ this.emit("stopped");
828
+ }
829
+ /**
830
+ * Check if bot is running
831
+ */
832
+ get running() {
833
+ return this.isRunning;
834
+ }
835
+ /**
836
+ * Setup message and command handlers
837
+ */
838
+ setupHandlers() {
839
+ if (!this.bot) return;
840
+ this.bot.command("start", async (ctx) => {
841
+ const chatId = ctx.chat.id;
842
+ this.allowedChatIds.add(chatId);
843
+ await ctx.reply(
844
+ `\u{1F916} C-napse connected!
845
+
846
+ Commands:
847
+ /screen - Take screenshot
848
+ /describe - Screenshot + AI description
849
+ /run <cmd> - Execute command
850
+ /status - System status
851
+
852
+ Your chat ID: ${chatId}`
853
+ );
854
+ });
855
+ this.bot.command("screen", async (ctx) => {
856
+ if (!this.isAllowed(ctx.chat.id)) {
857
+ await ctx.reply("\u26D4 Not authorized. Send /start first.");
858
+ return;
859
+ }
860
+ await ctx.reply("\u{1F4F8} Taking screenshot...");
861
+ try {
862
+ const screenshot = await captureScreenshot();
863
+ if (!screenshot) {
864
+ await ctx.reply("\u274C Failed to capture screenshot");
865
+ return;
866
+ }
867
+ const buffer = Buffer.from(screenshot, "base64");
868
+ await ctx.replyWithPhoto({ source: buffer }, { caption: "\u{1F4F8} Current screen" });
869
+ } catch (error) {
870
+ await ctx.reply(`\u274C Error: ${error instanceof Error ? error.message : "Unknown error"}`);
871
+ }
872
+ });
873
+ this.bot.command("describe", async (ctx) => {
874
+ if (!this.isAllowed(ctx.chat.id)) {
875
+ await ctx.reply("\u26D4 Not authorized. Send /start first.");
876
+ return;
877
+ }
878
+ await ctx.reply("\u{1F50D} Analyzing screen...");
879
+ try {
880
+ const result = await describeScreen();
881
+ const buffer = Buffer.from(result.screenshot, "base64");
882
+ const caption = `\u{1F5A5}\uFE0F Screen Analysis:
883
+
884
+ ${result.description}`.slice(0, 1024);
885
+ await ctx.replyWithPhoto({ source: buffer }, { caption });
886
+ if (result.description.length > 900) {
887
+ await ctx.reply(result.description);
888
+ }
889
+ } catch (error) {
890
+ await ctx.reply(`\u274C Error: ${error instanceof Error ? error.message : "Unknown error"}`);
891
+ }
892
+ });
893
+ this.bot.command("run", async (ctx) => {
894
+ if (!this.isAllowed(ctx.chat.id)) {
895
+ await ctx.reply("\u26D4 Not authorized. Send /start first.");
896
+ return;
897
+ }
898
+ const cmd = ctx.message.text.replace("/run ", "").trim();
899
+ if (!cmd) {
900
+ await ctx.reply("Usage: /run <command>\nExample: /run dir");
901
+ return;
902
+ }
903
+ await ctx.reply(`\u2699\uFE0F Running: ${cmd}`);
904
+ try {
905
+ const result = await runCommand(cmd, 3e4);
906
+ if (result.success) {
907
+ const output = result.output.slice(0, 4e3) || "(no output)";
908
+ await ctx.reply(`\u2705 Output:
909
+ \`\`\`
910
+ ${output}
911
+ \`\`\``, { parse_mode: "Markdown" });
912
+ } else {
913
+ await ctx.reply(`\u274C Error:
914
+ \`\`\`
915
+ ${result.error}
916
+ \`\`\``, { parse_mode: "Markdown" });
917
+ }
918
+ } catch (error) {
919
+ await ctx.reply(`\u274C Error: ${error instanceof Error ? error.message : "Unknown error"}`);
920
+ }
921
+ });
922
+ this.bot.command("status", async (ctx) => {
923
+ if (!this.isAllowed(ctx.chat.id)) {
924
+ await ctx.reply("\u26D4 Not authorized. Send /start first.");
925
+ return;
926
+ }
927
+ const config = getConfig();
928
+ const status = [
929
+ "\u{1F4CA} C-napse Status",
930
+ "",
931
+ `Provider: ${config.provider}`,
932
+ `Model: ${config.model}`,
933
+ `Platform: ${process.platform}`,
934
+ `Node: ${process.version}`
935
+ ].join("\n");
936
+ await ctx.reply(status);
937
+ });
938
+ this.bot.on("text", async (ctx) => {
939
+ if (!this.isAllowed(ctx.chat.id)) {
940
+ return;
941
+ }
942
+ if (ctx.message.text.startsWith("/")) {
943
+ return;
944
+ }
945
+ const message = {
946
+ chatId: ctx.chat.id,
947
+ text: ctx.message.text,
948
+ from: ctx.from.username || ctx.from.first_name || "User"
949
+ };
950
+ this.emit("message", message);
951
+ this.emit("command", "chat", ctx.message.text, ctx.chat.id);
952
+ });
953
+ this.bot.catch((err2) => {
954
+ this.emit("error", err2);
955
+ });
956
+ }
957
+ /**
958
+ * Check if chat is authorized
959
+ */
960
+ isAllowed(chatId) {
961
+ if (this.allowedChatIds.size === 0) {
962
+ return true;
963
+ }
964
+ return this.allowedChatIds.has(chatId);
965
+ }
966
+ /**
967
+ * Send a message to a specific chat
968
+ */
969
+ async sendMessage(chatId, text) {
970
+ if (!this.bot || !this.isRunning) {
971
+ throw new Error("Telegram bot is not running");
972
+ }
973
+ await this.bot.telegram.sendMessage(chatId, text);
974
+ }
975
+ /**
976
+ * Send a photo to a specific chat
977
+ */
978
+ async sendPhoto(chatId, base64Image, caption) {
979
+ if (!this.bot || !this.isRunning) {
980
+ throw new Error("Telegram bot is not running");
981
+ }
982
+ const buffer = Buffer.from(base64Image, "base64");
983
+ await this.bot.telegram.sendPhoto(chatId, { source: buffer }, { caption });
984
+ }
985
+ };
986
+ var instance = null;
987
+ function getTelegramBot() {
988
+ if (!instance) {
989
+ instance = new TelegramBotService();
990
+ }
991
+ return instance;
992
+ }
993
+
994
+ // 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.
997
+
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
1007
+
1008
+ Respond ONLY with a JSON array of steps, no other text:
1009
+ [
1010
+ { "description": "Human readable step", "action": "action_type:params" },
1011
+ ...
1012
+ ]
1013
+
1014
+ Example input: "open notepad and type hello world"
1015
+ Example output:
1016
+ [
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" }
1020
+ ]
1021
+
1022
+ Example input: "open vscode, go to folder E:\\Projects, then open terminal"
1023
+ Example output:
1024
+ [
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+\`" }
1035
+ ]`;
1036
+ const messages = [
1037
+ { role: "user", content: input }
1038
+ ];
1039
+ try {
1040
+ const response = await chat(messages, systemPrompt);
1041
+ const content = response.content || "[]";
1042
+ const jsonMatch = content.match(/\[[\s\S]*\]/);
1043
+ if (!jsonMatch) {
1044
+ throw new Error("Failed to parse task steps");
1045
+ }
1046
+ const parsedSteps = JSON.parse(jsonMatch[0]);
1047
+ const steps = parsedSteps.map((step, index) => ({
1048
+ id: `step-${index + 1}`,
1049
+ description: step.description,
1050
+ action: step.action,
1051
+ status: "pending"
1052
+ }));
1053
+ return {
1054
+ id: `task-${Date.now()}`,
1055
+ description: input,
1056
+ steps,
1057
+ status: "pending",
1058
+ createdAt: /* @__PURE__ */ new Date()
1059
+ };
1060
+ } catch (error) {
1061
+ return {
1062
+ id: `task-${Date.now()}`,
1063
+ description: input,
1064
+ steps: [{
1065
+ id: "step-1",
1066
+ description: input,
1067
+ action: `chat:${input}`,
1068
+ status: "pending"
1069
+ }],
1070
+ status: "pending",
1071
+ createdAt: /* @__PURE__ */ new Date()
1072
+ };
1073
+ }
1074
+ }
1075
+ async function executeStep(step) {
1076
+ const [actionType, ...paramParts] = step.action.split(":");
1077
+ const params = paramParts.join(":");
1078
+ switch (actionType) {
1079
+ case "open_app":
1080
+ await keyCombo(["meta", "r"]);
1081
+ await sleep(500);
1082
+ await typeText(params);
1083
+ await sleep(300);
1084
+ await pressKey("Return");
1085
+ step.result = `Opened ${params}`;
1086
+ break;
1087
+ case "type_text":
1088
+ await typeText(params);
1089
+ step.result = `Typed: ${params}`;
1090
+ break;
1091
+ case "press_key":
1092
+ await pressKey(params);
1093
+ step.result = `Pressed ${params}`;
1094
+ break;
1095
+ case "key_combo":
1096
+ const keys = params.split("+").map((k) => k.trim());
1097
+ await keyCombo(keys);
1098
+ step.result = `Pressed ${params}`;
1099
+ break;
1100
+ case "click":
1101
+ const button = params || "left";
1102
+ await clickMouse(button);
1103
+ step.result = `Clicked ${button}`;
1104
+ break;
1105
+ case "wait":
1106
+ const seconds = parseInt(params) || 1;
1107
+ await sleep(seconds * 1e3);
1108
+ step.result = `Waited ${seconds}s`;
1109
+ break;
1110
+ case "focus_window":
1111
+ await focusWindow(params);
1112
+ step.result = `Focused window: ${params}`;
1113
+ break;
1114
+ case "screenshot":
1115
+ const vision = await describeScreen();
1116
+ step.result = vision.description;
1117
+ break;
1118
+ case "chat":
1119
+ step.result = `Task noted: ${params}`;
1120
+ break;
1121
+ default:
1122
+ throw new Error(`Unknown action: ${actionType}`);
1123
+ }
1124
+ }
1125
+ async function executeTask(task, onProgress) {
1126
+ task.status = "running";
1127
+ for (const step of task.steps) {
1128
+ if (task.status === "failed") {
1129
+ step.status = "skipped";
1130
+ continue;
1131
+ }
1132
+ step.status = "running";
1133
+ onProgress?.(task, step);
1134
+ try {
1135
+ await executeStep(step);
1136
+ step.status = "completed";
1137
+ } catch (error) {
1138
+ step.status = "failed";
1139
+ step.error = error instanceof Error ? error.message : "Unknown error";
1140
+ task.status = "failed";
1141
+ }
1142
+ onProgress?.(task, step);
1143
+ }
1144
+ if (task.status !== "failed") {
1145
+ task.status = "completed";
1146
+ }
1147
+ task.completedAt = /* @__PURE__ */ new Date();
1148
+ return task;
1149
+ }
1150
+ function sleep(ms) {
1151
+ return new Promise((resolve) => setTimeout(resolve, ms));
1152
+ }
1153
+ function formatTask(task) {
1154
+ const statusEmoji = {
1155
+ pending: "\u23F3",
1156
+ running: "\u{1F504}",
1157
+ completed: "\u2705",
1158
+ failed: "\u274C"
1159
+ };
1160
+ const stepStatusEmoji = {
1161
+ pending: "\u25CB",
1162
+ running: "\u25D0",
1163
+ completed: "\u25CF",
1164
+ failed: "\u2717",
1165
+ skipped: "\u25CC"
1166
+ };
1167
+ let output = `${statusEmoji[task.status]} Task: ${task.description}
1168
+
1169
+ `;
1170
+ for (const step of task.steps) {
1171
+ output += ` ${stepStatusEmoji[step.status]} ${step.description}`;
1172
+ if (step.result) {
1173
+ output += ` \u2192 ${step.result}`;
1174
+ }
1175
+ if (step.error) {
1176
+ output += ` (Error: ${step.error})`;
1177
+ }
1178
+ output += "\n";
1179
+ }
1180
+ return output;
1181
+ }
1182
+
263
1183
  // src/components/App.tsx
264
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1184
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
265
1185
  function App() {
266
1186
  const { exit } = useApp();
267
- const [messages, setMessages] = useState([
1187
+ const [messages, setMessages] = useState2([
268
1188
  {
269
1189
  id: "0",
270
1190
  role: "system",
271
- content: "Welcome to C-napse! Type your message and press Enter.\n\nShortcuts:\n Ctrl+C - Exit\n /clear - Clear chat\n /help - Show help",
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",
272
1192
  timestamp: /* @__PURE__ */ new Date()
273
1193
  }
274
1194
  ]);
275
- const [input, setInput] = useState("");
276
- const [isProcessing, setIsProcessing] = useState(false);
277
- const [status, setStatus] = useState("Ready");
278
- const [error, setError] = useState(null);
279
- useInput((inputChar, key) => {
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
+ }
280
1222
  if (key.ctrl && inputChar === "c") {
281
1223
  exit();
282
1224
  }
@@ -284,6 +1226,27 @@ function App() {
284
1226
  setMessages([messages[0]]);
285
1227
  setError(null);
286
1228
  }
1229
+ if (key.ctrl && inputChar === "w") {
1230
+ setScreenWatch((prev) => {
1231
+ 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."
1234
+ );
1235
+ return newState;
1236
+ });
1237
+ }
1238
+ if (key.ctrl && inputChar === "h") {
1239
+ setShowHelpMenu(true);
1240
+ }
1241
+ 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
+ });
1249
+ }
287
1250
  });
288
1251
  const handleSubmit = async (value) => {
289
1252
  if (!value.trim() || isProcessing) return;
@@ -316,15 +1279,21 @@ function App() {
316
1279
  setStatus("Thinking...");
317
1280
  try {
318
1281
  const apiMessages = messages.filter((m) => m.role === "user" || m.role === "assistant").slice(-10).map((m) => ({ role: m.role, content: m.content }));
319
- apiMessages.push({ role: "user", content: userInput });
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 });
320
1289
  const response = await chat(apiMessages);
321
1290
  setMessages(
322
1291
  (prev) => prev.map(
323
1292
  (m) => m.id === assistantId ? { ...m, content: response.content || "(no response)", isStreaming: false } : m
324
1293
  )
325
1294
  );
326
- } catch (err) {
327
- const errorMsg = err instanceof Error ? err.message : "Unknown error";
1295
+ } catch (err2) {
1296
+ const errorMsg = err2 instanceof Error ? err2.message : "Unknown error";
328
1297
  setError(errorMsg);
329
1298
  setMessages(
330
1299
  (prev) => prev.map(
@@ -339,18 +1308,137 @@ function App() {
339
1308
  const handleCommand = (cmd) => {
340
1309
  const parts = cmd.slice(1).split(" ");
341
1310
  const command = parts[0];
1311
+ const args2 = parts.slice(1).join(" ");
342
1312
  switch (command) {
343
1313
  case "clear":
344
1314
  setMessages([messages[0]]);
345
1315
  addSystemMessage("Chat cleared.");
346
1316
  break;
347
1317
  case "help":
348
- addSystemMessage(
349
- "Commands:\n /clear - Clear chat history\n /help - Show this help\n\nJust type naturally to chat with the AI!"
350
- );
1318
+ setShowHelpMenu(true);
1319
+ break;
1320
+ case "watch":
1321
+ setScreenWatch((prev) => {
1322
+ const newState = !prev;
1323
+ addSystemMessage(
1324
+ newState ? "\u{1F5A5}\uFE0F Screen watching enabled." : "\u{1F5A5}\uFE0F Screen watching disabled."
1325
+ );
1326
+ return newState;
1327
+ });
1328
+ break;
1329
+ case "telegram":
1330
+ handleTelegramToggle();
1331
+ break;
1332
+ case "screen":
1333
+ handleScreenCommand();
1334
+ break;
1335
+ case "task":
1336
+ if (args2) {
1337
+ handleTaskCommand(args2);
1338
+ } else {
1339
+ addSystemMessage("Usage: /task <description>\nExample: /task open notepad and type hello");
1340
+ }
1341
+ 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>");
1350
+ break;
1351
+ case "quit":
1352
+ case "exit":
1353
+ exit();
351
1354
  break;
352
1355
  default:
353
- addSystemMessage(`Unknown command: ${command}`);
1356
+ addSystemMessage(`Unknown command: ${command}
1357
+ Type /help to see available commands.`);
1358
+ }
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);
1367
+ try {
1368
+ const result = await describeScreen();
1369
+ addSystemMessage(`\u{1F5A5}\uFE0F Screen Analysis:
1370
+
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
+ }
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
+ }
1421
+ } else {
1422
+ addSystemMessage("\u{1F4F1} Starting Telegram bot...");
1423
+ setStatus("Starting Telegram...");
1424
+ 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"
1435
+ );
1436
+ } catch (err2) {
1437
+ const errorMsg = err2 instanceof Error ? err2.message : "Failed to start bot";
1438
+ addSystemMessage(`\u274C Telegram error: ${errorMsg}`);
1439
+ } finally {
1440
+ setStatus("Ready");
1441
+ }
354
1442
  }
355
1443
  };
356
1444
  const addSystemMessage = (content) => {
@@ -365,11 +1453,20 @@ function App() {
365
1453
  ]);
366
1454
  };
367
1455
  const visibleMessages = messages.slice(-20);
368
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", height: "100%", children: [
369
- /* @__PURE__ */ jsx5(Header, {}),
370
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "gray", padding: 1, children: [
371
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: "gray", children: " Chat " }),
372
- visibleMessages.map((msg) => /* @__PURE__ */ jsx5(
1456
+ if (showHelpMenu) {
1457
+ return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", height: "100%", alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx6(
1458
+ HelpMenu,
1459
+ {
1460
+ onClose: () => setShowHelpMenu(false),
1461
+ onSelect: handleHelpMenuSelect
1462
+ }
1463
+ ) });
1464
+ }
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(
373
1470
  ChatMessage,
374
1471
  {
375
1472
  role: msg.role,
@@ -380,11 +1477,11 @@ function App() {
380
1477
  msg.id
381
1478
  ))
382
1479
  ] }),
383
- error && /* @__PURE__ */ jsx5(Box5, { marginY: 1, children: /* @__PURE__ */ jsxs4(Text5, { color: "red", children: [
1480
+ error && /* @__PURE__ */ jsx6(Box6, { marginY: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "red", children: [
384
1481
  "Error: ",
385
1482
  error
386
1483
  ] }) }),
387
- /* @__PURE__ */ jsx5(
1484
+ /* @__PURE__ */ jsx6(
388
1485
  ChatInput,
389
1486
  {
390
1487
  value: input,
@@ -393,12 +1490,12 @@ function App() {
393
1490
  isProcessing
394
1491
  }
395
1492
  ),
396
- /* @__PURE__ */ jsx5(StatusBar, { status })
1493
+ /* @__PURE__ */ jsx6(StatusBar, { status })
397
1494
  ] });
398
1495
  }
399
1496
 
400
1497
  // src/index.tsx
401
- import { jsx as jsx6 } from "react/jsx-runtime";
1498
+ import { jsx as jsx7 } from "react/jsx-runtime";
402
1499
  var args = process.argv.slice(2);
403
1500
  if (args.length > 0) {
404
1501
  const command = args[0];
@@ -441,15 +1538,15 @@ if (args.length > 0) {
441
1538
  process.exit(0);
442
1539
  }
443
1540
  if (subcommand === "show" || !subcommand) {
444
- const config2 = getConfig();
1541
+ const config = getConfig();
445
1542
  console.log("\nC-napse Configuration:");
446
- console.log(` Provider: ${config2.provider}`);
447
- console.log(` Model: ${config2.model}`);
448
- console.log(` Ollama Host: ${config2.ollamaHost}`);
1543
+ console.log(` Provider: ${config.provider}`);
1544
+ console.log(` Model: ${config.model}`);
1545
+ console.log(` Ollama Host: ${config.ollamaHost}`);
449
1546
  console.log(` API Keys configured:`);
450
- console.log(` - OpenRouter: ${config2.apiKeys.openrouter ? "\u2713" : "\u2717"}`);
451
- console.log(` - Anthropic: ${config2.apiKeys.anthropic ? "\u2713" : "\u2717"}`);
452
- console.log(` - OpenAI: ${config2.apiKeys.openai ? "\u2713" : "\u2717"}`);
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"}`);
453
1550
  console.log("");
454
1551
  process.exit(0);
455
1552
  }
@@ -464,18 +1561,22 @@ C-napse - Autonomous PC Intelligence
464
1561
 
465
1562
  Usage:
466
1563
  cnapse Start interactive chat
1564
+ cnapse init Interactive setup wizard
467
1565
  cnapse auth <provider> <key> Set API key
468
1566
  cnapse config Show configuration
469
1567
  cnapse config set <k> <v> Set config value
470
1568
  cnapse help Show this help
471
1569
 
472
1570
  Providers:
473
- ollama - Local AI (default)
474
- openrouter - OpenRouter API
1571
+ ollama - Local AI (default, free)
1572
+ openrouter - OpenRouter API (many models)
475
1573
  anthropic - Anthropic Claude
476
1574
  openai - OpenAI GPT
477
1575
 
478
- Examples:
1576
+ Quick Start:
1577
+ cnapse init # Interactive setup
1578
+
1579
+ Manual Setup:
479
1580
  cnapse auth openrouter sk-or-xxxxx
480
1581
  cnapse config set provider openrouter
481
1582
  cnapse config set model qwen/qwen-2.5-coder-32b-instruct
@@ -488,9 +1589,14 @@ Examples:
488
1589
  console.log("cnapse v0.2.0");
489
1590
  process.exit(0);
490
1591
  }
1592
+ case "init": {
1593
+ const { Setup } = await import("./Setup-Q32JPHGP.js");
1594
+ render(/* @__PURE__ */ jsx7(Setup, {}));
1595
+ process.exit(0);
1596
+ }
491
1597
  default: {
492
1598
  break;
493
1599
  }
494
1600
  }
495
1601
  }
496
- render(/* @__PURE__ */ jsx6(App, {}));
1602
+ render(/* @__PURE__ */ jsx7(App, {}));