@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/Setup-Q32JPHGP.js +174 -0
- package/dist/chunk-COKO6V5J.js +50 -0
- package/dist/index.js +1203 -97
- package/package.json +4 -2
- package/src/agents/coder.ts +62 -0
- package/src/agents/computer.ts +61 -0
- package/src/agents/executor.ts +179 -0
- package/src/agents/filer.ts +56 -0
- package/src/agents/index.ts +12 -0
- package/src/agents/router.ts +160 -0
- package/src/agents/shell.ts +67 -0
- package/src/agents/types.ts +80 -0
- package/src/components/App.tsx +225 -8
- package/src/components/Header.tsx +11 -1
- package/src/components/HelpMenu.tsx +143 -0
- package/src/components/Setup.tsx +203 -0
- package/src/components/TaskProgress.tsx +68 -0
- package/src/index.tsx +14 -3
- package/src/lib/api.ts +2 -2
- package/src/lib/config.ts +21 -0
- package/src/lib/screen.ts +118 -0
- package/src/lib/tasks.ts +268 -0
- package/src/lib/vision.ts +254 -0
- package/src/services/telegram.ts +278 -0
- package/src/tools/clipboard.ts +55 -0
- package/src/tools/computer.ts +454 -0
- package/src/tools/filesystem.ts +272 -0
- package/src/tools/index.ts +35 -0
- package/src/tools/network.ts +204 -0
- package/src/tools/process.ts +194 -0
- package/src/tools/shell.ts +140 -0
- package/src/tools/vision.ts +65 -0
- package/src/types/screenshot-desktop.d.ts +10 -0
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
|
|
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
|
|
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
|
-
|
|
33
|
+
config.provider,
|
|
68
34
|
" \u2502 ",
|
|
69
|
-
|
|
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
|
|
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 (
|
|
207
|
+
switch (config.provider) {
|
|
147
208
|
case "openrouter":
|
|
148
|
-
return chatOpenRouter(allMessages,
|
|
209
|
+
return chatOpenRouter(allMessages, config.model);
|
|
149
210
|
case "ollama":
|
|
150
|
-
return chatOllama(allMessages,
|
|
211
|
+
return chatOllama(allMessages, config.model);
|
|
151
212
|
case "anthropic":
|
|
152
|
-
return chatAnthropic(allMessages,
|
|
213
|
+
return chatAnthropic(allMessages, config.model);
|
|
153
214
|
case "openai":
|
|
154
|
-
return chatOpenAI(allMessages,
|
|
215
|
+
return chatOpenAI(allMessages, config.model);
|
|
155
216
|
default:
|
|
156
|
-
throw new Error(`Unknown 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
|
|
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":
|
|
171
|
-
"X-Title":
|
|
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
|
|
190
|
-
const response = await fetch(`${
|
|
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
|
|
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] =
|
|
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] =
|
|
276
|
-
const [isProcessing, setIsProcessing] =
|
|
277
|
-
const [status, setStatus] =
|
|
278
|
-
const [error, setError] =
|
|
279
|
-
|
|
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
|
-
|
|
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 (
|
|
327
|
-
const errorMsg =
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
|
|
369
|
-
/* @__PURE__ */
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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__ */
|
|
1480
|
+
error && /* @__PURE__ */ jsx6(Box6, { marginY: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "red", children: [
|
|
384
1481
|
"Error: ",
|
|
385
1482
|
error
|
|
386
1483
|
] }) }),
|
|
387
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1493
|
+
/* @__PURE__ */ jsx6(StatusBar, { status })
|
|
397
1494
|
] });
|
|
398
1495
|
}
|
|
399
1496
|
|
|
400
1497
|
// src/index.tsx
|
|
401
|
-
import { jsx as
|
|
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
|
|
1541
|
+
const config = getConfig();
|
|
445
1542
|
console.log("\nC-napse Configuration:");
|
|
446
|
-
console.log(` Provider: ${
|
|
447
|
-
console.log(` Model: ${
|
|
448
|
-
console.log(` Ollama Host: ${
|
|
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: ${
|
|
451
|
-
console.log(` - Anthropic: ${
|
|
452
|
-
console.log(` - OpenAI: ${
|
|
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
|
-
|
|
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__ */
|
|
1602
|
+
render(/* @__PURE__ */ jsx7(App, {}));
|