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