@projectservan8n/cnapse 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -192,61 +192,152 @@ function HelpMenu({ onClose, onSelect }) {
192
192
  }
193
193
 
194
194
  // src/components/ProviderSelector.tsx
195
- import { useState as useState2 } from "react";
195
+ import { useState as useState2, useEffect } from "react";
196
196
  import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
197
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
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";
198
259
  var PROVIDERS = [
199
260
  {
200
261
  id: "ollama",
201
262
  name: "Ollama",
202
- description: "Local AI - Free, private, no API key",
203
- defaultModel: "qwen2.5:0.5b",
204
- models: ["qwen2.5:0.5b", "qwen2.5:1.5b", "qwen2.5:7b", "llama3.2:1b", "llama3.2:3b", "mistral:7b", "codellama:7b", "llava:7b"]
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
+ ]
205
274
  },
206
275
  {
207
276
  id: "openrouter",
208
277
  name: "OpenRouter",
209
278
  description: "Many models, pay-per-use",
210
- defaultModel: "qwen/qwen-2.5-coder-32b-instruct",
279
+ needsApiKey: true,
211
280
  models: [
212
- "qwen/qwen-2.5-coder-32b-instruct",
213
- "anthropic/claude-3.5-sonnet",
214
- "openai/gpt-4o",
215
- "openai/gpt-4o-mini",
216
- "google/gemini-pro-1.5",
217
- "meta-llama/llama-3.1-70b-instruct"
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" }
218
286
  ]
219
287
  },
220
288
  {
221
289
  id: "anthropic",
222
290
  name: "Anthropic",
223
- description: "Claude models - Best for coding",
224
- defaultModel: "claude-3-5-sonnet-20241022",
225
- models: ["claude-3-5-sonnet-20241022", "claude-3-opus-20240229", "claude-3-haiku-20240307"]
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
+ ]
226
298
  },
227
299
  {
228
300
  id: "openai",
229
301
  name: "OpenAI",
230
302
  description: "GPT models",
231
- defaultModel: "gpt-4o",
232
- models: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-3.5-turbo"]
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
+ ]
233
309
  }
234
310
  ];
235
311
  function ProviderSelector({ onClose, onSelect }) {
236
312
  const config = getConfig();
237
- const [mode, setMode] = useState2("provider");
313
+ const [step, setStep] = useState2("provider");
238
314
  const [providerIndex, setProviderIndex] = useState2(() => {
239
315
  const idx = PROVIDERS.findIndex((p) => p.id === config.provider);
240
316
  return idx >= 0 ? idx : 0;
241
317
  });
242
318
  const [modelIndex, setModelIndex] = useState2(0);
319
+ const [apiKeyInput, setApiKeyInput] = useState2("");
243
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]);
244
335
  useInput2((input, key) => {
245
336
  if (key.escape) {
246
337
  onClose();
247
338
  return;
248
339
  }
249
- if (mode === "provider") {
340
+ if (step === "provider") {
250
341
  if (key.upArrow) {
251
342
  setProviderIndex((prev) => prev > 0 ? prev - 1 : PROVIDERS.length - 1);
252
343
  } else if (key.downArrow) {
@@ -254,79 +345,194 @@ function ProviderSelector({ onClose, onSelect }) {
254
345
  } else if (key.return) {
255
346
  const provider = PROVIDERS[providerIndex];
256
347
  setSelectedProvider(provider);
257
- const currentModelIdx = provider.models.findIndex((m) => m === config.model);
258
- setModelIndex(currentModelIdx >= 0 ? currentModelIdx : 0);
259
- setMode("model");
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
+ }
260
361
  }
261
- } else if (mode === "model" && selectedProvider) {
362
+ } else if (step === "model" && selectedProvider) {
262
363
  if (key.upArrow) {
263
364
  setModelIndex((prev) => prev > 0 ? prev - 1 : selectedProvider.models.length - 1);
264
365
  } else if (key.downArrow) {
265
366
  setModelIndex((prev) => prev < selectedProvider.models.length - 1 ? prev + 1 : 0);
266
367
  } else if (key.return) {
267
368
  const model = selectedProvider.models[modelIndex];
369
+ if (selectedProvider.id === "ollama" && ollamaStatus && !hasModel(ollamaStatus, model.id)) {
370
+ }
268
371
  setProvider(selectedProvider.id);
269
- setModel(model);
270
- onSelect(selectedProvider.id, model);
271
- onClose();
372
+ setModel(model.id);
373
+ setStep("done");
374
+ onSelect(selectedProvider.id, model.id);
375
+ setTimeout(() => onClose(), 1500);
272
376
  } else if (key.leftArrow || input === "b") {
273
- setMode("provider");
377
+ setStep("provider");
378
+ setOllamaStatus(null);
379
+ }
380
+ } else if (step === "ollamaError") {
381
+ if (key.return || input === "b") {
382
+ setStep("provider");
383
+ setOllamaStatus(null);
274
384
  }
275
385
  }
276
386
  });
277
- if (mode === "provider") {
278
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, children: [
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: [
279
395
  /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: "Select Provider" }) }),
280
- /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "Use arrows to navigate, Enter to select, Esc to cancel" }) }),
396
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", dimColor: true, children: "Arrows to navigate, Enter to select" }) }),
281
397
  PROVIDERS.map((provider, index) => {
282
398
  const isSelected = index === providerIndex;
283
399
  const isCurrent = provider.id === config.provider;
284
- return /* @__PURE__ */ jsxs5(Box6, { marginY: 0, children: [
400
+ const hasKey = provider.needsApiKey && provider.id !== "ollama" ? !!config.apiKeys[provider.id] : true;
401
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
285
402
  /* @__PURE__ */ jsxs5(Text6, { color: isSelected ? "cyan" : "white", children: [
286
403
  isSelected ? "\u276F " : " ",
287
404
  provider.name,
288
- isCurrent && /* @__PURE__ */ jsx6(Text6, { color: "green", children: " (current)" })
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)" })
289
408
  ] }),
290
409
  isSelected && /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
291
- " - ",
410
+ " ",
292
411
  provider.description
293
412
  ] })
294
413
  ] }, provider.id);
295
414
  }),
296
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
297
- "Current: ",
298
- config.provider,
299
- " / ",
300
- config.model
301
- ] }) })
415
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", dimColor: true, children: "Press Esc to cancel" }) })
302
416
  ] });
303
417
  }
304
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, children: [
305
- /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsxs5(Text6, { bold: true, color: "cyan", children: [
306
- "Select Model for ",
307
- selectedProvider?.name
308
- ] }) }),
309
- /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "Arrows to navigate, Enter to select, B/Left to go back" }) }),
310
- selectedProvider?.models.map((model, index) => {
311
- const isSelected = index === modelIndex;
312
- const isCurrent = model === config.model && selectedProvider.id === config.provider;
313
- const isDefault = model === selectedProvider.defaultModel;
314
- return /* @__PURE__ */ jsx6(Box6, { marginY: 0, children: /* @__PURE__ */ jsxs5(Text6, { color: isSelected ? "cyan" : "white", children: [
315
- isSelected ? "\u276F " : " ",
316
- model,
317
- isCurrent && /* @__PURE__ */ jsx6(Text6, { color: "green", children: " (current)" }),
318
- isDefault && !isCurrent && /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: " (default)" })
319
- ] }) }, model);
320
- }),
321
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
322
- "Provider: ",
323
- selectedProvider?.name
324
- ] }) })
325
- ] });
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;
326
532
  }
327
533
 
328
534
  // src/hooks/useChat.ts
329
- import { useState as useState3, useCallback, useRef, useEffect } from "react";
535
+ import { useState as useState3, useCallback, useRef, useEffect as useEffect2 } from "react";
330
536
 
331
537
  // src/lib/api.ts
332
538
  var SYSTEM_PROMPT = `You are C-napse, a helpful AI assistant for PC automation running on the user's desktop.
@@ -460,24 +666,24 @@ async function chatOpenAI(messages, model) {
460
666
  }
461
667
 
462
668
  // src/lib/screen.ts
463
- import { exec } from "child_process";
464
- import { promisify } from "util";
465
- var execAsync = promisify(exec);
669
+ import { exec as exec2 } from "child_process";
670
+ import { promisify as promisify2 } from "util";
671
+ var execAsync2 = promisify2(exec2);
466
672
  async function getScreenDescription() {
467
673
  try {
468
674
  const platform = process.platform;
469
675
  if (platform === "win32") {
470
- const { stdout } = await execAsync(`
676
+ const { stdout } = await execAsync2(`
471
677
  Add-Type -AssemblyName System.Windows.Forms
472
678
  $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
473
679
  Write-Output "$($screen.Width)x$($screen.Height)"
474
680
  `, { shell: "powershell.exe" });
475
681
  return `Screen ${stdout.trim()} captured`;
476
682
  } else if (platform === "darwin") {
477
- const { stdout } = await execAsync(`system_profiler SPDisplaysDataType | grep Resolution | head -1`);
683
+ const { stdout } = await execAsync2(`system_profiler SPDisplaysDataType | grep Resolution | head -1`);
478
684
  return `Screen ${stdout.trim()}`;
479
685
  } else {
480
- const { stdout } = await execAsync(`xdpyinfo | grep dimensions | awk '{print $2}'`);
686
+ const { stdout } = await execAsync2(`xdpyinfo | grep dimensions | awk '{print $2}'`);
481
687
  return `Screen ${stdout.trim()} captured`;
482
688
  }
483
689
  } catch {
@@ -497,7 +703,7 @@ function useChat(screenWatch = false) {
497
703
  const [isProcessing, setIsProcessing] = useState3(false);
498
704
  const [error, setError] = useState3(null);
499
705
  const screenContextRef = useRef(null);
500
- useEffect(() => {
706
+ useEffect2(() => {
501
707
  if (!screenWatch) {
502
708
  screenContextRef.current = null;
503
709
  return;
@@ -606,17 +812,17 @@ async function captureScreenshot() {
606
812
  }
607
813
  }
608
814
  async function captureScreenFallback() {
609
- const { exec: exec5 } = await import("child_process");
610
- const { promisify: promisify5 } = await import("util");
815
+ const { exec: exec6 } = await import("child_process");
816
+ const { promisify: promisify6 } = await import("util");
611
817
  const { tmpdir } = await import("os");
612
818
  const { join: join2 } = await import("path");
613
819
  const { readFile, unlink } = await import("fs/promises");
614
- const execAsync5 = promisify5(exec5);
820
+ const execAsync6 = promisify6(exec6);
615
821
  const tempFile = join2(tmpdir(), `cnapse-screen-${Date.now()}.png`);
616
822
  try {
617
823
  const platform = process.platform;
618
824
  if (platform === "win32") {
619
- await execAsync5(`
825
+ await execAsync6(`
620
826
  Add-Type -AssemblyName System.Windows.Forms
621
827
  $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
622
828
  $bitmap = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height)
@@ -627,9 +833,9 @@ async function captureScreenFallback() {
627
833
  $bitmap.Dispose()
628
834
  `, { shell: "powershell.exe" });
629
835
  } else if (platform === "darwin") {
630
- await execAsync5(`screencapture -x "${tempFile}"`);
836
+ await execAsync6(`screencapture -x "${tempFile}"`);
631
837
  } else {
632
- await execAsync5(`gnome-screenshot -f "${tempFile}" 2>/dev/null || scrot "${tempFile}" 2>/dev/null || import -window root "${tempFile}"`);
838
+ await execAsync6(`gnome-screenshot -f "${tempFile}" 2>/dev/null || scrot "${tempFile}" 2>/dev/null || import -window root "${tempFile}"`);
633
839
  }
634
840
  const imageBuffer = await readFile(tempFile);
635
841
  await unlink(tempFile).catch(() => {
@@ -822,27 +1028,27 @@ function useVision() {
822
1028
  }
823
1029
 
824
1030
  // src/hooks/useTelegram.ts
825
- import { useState as useState5, useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2 } from "react";
1031
+ import { useState as useState5, useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2 } from "react";
826
1032
 
827
1033
  // src/services/telegram.ts
828
1034
  import { EventEmitter } from "events";
829
1035
 
830
1036
  // src/tools/shell.ts
831
- import { exec as exec4 } from "child_process";
832
- import { promisify as promisify4 } from "util";
1037
+ import { exec as exec5 } from "child_process";
1038
+ import { promisify as promisify5 } from "util";
833
1039
 
834
1040
  // src/tools/clipboard.ts
835
1041
  import clipboardy from "clipboardy";
836
1042
 
837
1043
  // src/tools/process.ts
838
- import { exec as exec2 } from "child_process";
839
- import { promisify as promisify2 } from "util";
840
- var execAsync2 = promisify2(exec2);
841
-
842
- // src/tools/computer.ts
843
1044
  import { exec as exec3 } from "child_process";
844
1045
  import { promisify as promisify3 } from "util";
845
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);
846
1052
  async function clickMouse(button = "left") {
847
1053
  try {
848
1054
  if (process.platform === "win32") {
@@ -852,12 +1058,12 @@ Add-Type -MemberDefinition @"
852
1058
  public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
853
1059
  "@ -Name Mouse -Namespace Win32
854
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)"}`;
855
- await execAsync3(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
1061
+ await execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
856
1062
  } else if (process.platform === "darwin") {
857
- await execAsync3(`cliclick c:.`);
1063
+ await execAsync4(`cliclick c:.`);
858
1064
  } else {
859
1065
  const btn = button === "left" ? "1" : button === "right" ? "3" : "2";
860
- await execAsync3(`xdotool click ${btn}`);
1066
+ await execAsync4(`xdotool click ${btn}`);
861
1067
  }
862
1068
  return ok(`Clicked ${button} button`);
863
1069
  } catch (error) {
@@ -868,13 +1074,13 @@ async function typeText(text) {
868
1074
  try {
869
1075
  if (process.platform === "win32") {
870
1076
  const escapedText = text.replace(/'/g, "''").replace(/[+^%~(){}[\]]/g, "{$&}");
871
- await execAsync3(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${escapedText}')"`, { shell: "cmd.exe" });
1077
+ await execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${escapedText}')"`, { shell: "cmd.exe" });
872
1078
  } else if (process.platform === "darwin") {
873
1079
  const escaped = text.replace(/'/g, "'\\''");
874
- await execAsync3(`osascript -e 'tell application "System Events" to keystroke "${escaped}"'`);
1080
+ await execAsync4(`osascript -e 'tell application "System Events" to keystroke "${escaped}"'`);
875
1081
  } else {
876
1082
  const escaped = text.replace(/'/g, "'\\''");
877
- await execAsync3(`xdotool type '${escaped}'`);
1083
+ await execAsync4(`xdotool type '${escaped}'`);
878
1084
  }
879
1085
  return ok(`Typed: ${text}`);
880
1086
  } catch (error) {
@@ -915,7 +1121,7 @@ async function pressKey(key) {
915
1121
  "f12": "{F12}"
916
1122
  };
917
1123
  const winKey = winKeyMap[key.toLowerCase()] || key;
918
- await execAsync3(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${winKey}')"`, { shell: "cmd.exe" });
1124
+ await execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${winKey}')"`, { shell: "cmd.exe" });
919
1125
  } else if (process.platform === "darwin") {
920
1126
  const macKeyMap = {
921
1127
  "return": 36,
@@ -933,12 +1139,12 @@ async function pressKey(key) {
933
1139
  };
934
1140
  const keyCode = macKeyMap[key.toLowerCase()];
935
1141
  if (keyCode) {
936
- await execAsync3(`osascript -e 'tell application "System Events" to key code ${keyCode}'`);
1142
+ await execAsync4(`osascript -e 'tell application "System Events" to key code ${keyCode}'`);
937
1143
  } else {
938
- await execAsync3(`osascript -e 'tell application "System Events" to keystroke "${key}"'`);
1144
+ await execAsync4(`osascript -e 'tell application "System Events" to keystroke "${key}"'`);
939
1145
  }
940
1146
  } else {
941
- await execAsync3(`xdotool key ${key}`);
1147
+ await execAsync4(`xdotool key ${key}`);
942
1148
  }
943
1149
  return ok(`Pressed: ${key}`);
944
1150
  } catch (error) {
@@ -951,7 +1157,7 @@ async function keyCombo(keys) {
951
1157
  const hasWin = keys.some((k) => k.toLowerCase() === "meta" || k.toLowerCase() === "win");
952
1158
  const hasR = keys.some((k) => k.toLowerCase() === "r");
953
1159
  if (hasWin && hasR) {
954
- await execAsync3(`powershell -Command "$shell = New-Object -ComObject WScript.Shell; $shell.Run('explorer shell:::{2559a1f3-21d7-11d4-bdaf-00c04f60b9f0}')"`, { shell: "cmd.exe" });
1160
+ await execAsync4(`powershell -Command "$shell = New-Object -ComObject WScript.Shell; $shell.Run('explorer shell:::{2559a1f3-21d7-11d4-bdaf-00c04f60b9f0}')"`, { shell: "cmd.exe" });
955
1161
  return ok(`Pressed: ${keys.join("+")}`);
956
1162
  }
957
1163
  const modifierMap = {
@@ -971,7 +1177,7 @@ async function keyCombo(keys) {
971
1177
  }
972
1178
  }
973
1179
  combo += regularKeys.join("");
974
- await execAsync3(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${combo}')"`, { shell: "cmd.exe" });
1180
+ await execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${combo}')"`, { shell: "cmd.exe" });
975
1181
  } else if (process.platform === "darwin") {
976
1182
  const modifiers = keys.filter((k) => ["control", "ctrl", "alt", "shift", "command", "meta"].includes(k.toLowerCase()));
977
1183
  const regular = keys.filter((k) => !["control", "ctrl", "alt", "shift", "command", "meta"].includes(k.toLowerCase()));
@@ -987,9 +1193,9 @@ async function keyCombo(keys) {
987
1193
  };
988
1194
  cmd += " using {" + modifiers.map((m) => modMap[m.toLowerCase()]).join(", ") + "}";
989
1195
  }
990
- await execAsync3(`osascript -e '${cmd}'`);
1196
+ await execAsync4(`osascript -e '${cmd}'`);
991
1197
  } else {
992
- await execAsync3(`xdotool key ${keys.join("+")}`);
1198
+ await execAsync4(`xdotool key ${keys.join("+")}`);
993
1199
  }
994
1200
  return ok(`Pressed: ${keys.join("+")}`);
995
1201
  } catch (error) {
@@ -1000,11 +1206,11 @@ async function focusWindow(title) {
1000
1206
  try {
1001
1207
  if (process.platform === "win32") {
1002
1208
  const escaped = title.replace(/'/g, "''");
1003
- await execAsync3(`powershell -Command "$wshell = New-Object -ComObject wscript.shell; $wshell.AppActivate('${escaped}')"`, { shell: "cmd.exe" });
1209
+ await execAsync4(`powershell -Command "$wshell = New-Object -ComObject wscript.shell; $wshell.AppActivate('${escaped}')"`, { shell: "cmd.exe" });
1004
1210
  } else if (process.platform === "darwin") {
1005
- await execAsync3(`osascript -e 'tell application "${title}" to activate'`);
1211
+ await execAsync4(`osascript -e 'tell application "${title}" to activate'`);
1006
1212
  } else {
1007
- await execAsync3(`wmctrl -a "${title}"`);
1213
+ await execAsync4(`wmctrl -a "${title}"`);
1008
1214
  }
1009
1215
  return ok(`Focused window: ${title}`);
1010
1216
  } catch (error) {
@@ -1021,13 +1227,13 @@ function err(error) {
1021
1227
  }
1022
1228
 
1023
1229
  // src/tools/shell.ts
1024
- var execAsync4 = promisify4(exec4);
1230
+ var execAsync5 = promisify5(exec5);
1025
1231
  async function runCommand(cmd, timeout = 3e4) {
1026
1232
  try {
1027
1233
  const isWindows = process.platform === "win32";
1028
1234
  const shell = isWindows ? "cmd.exe" : "/bin/sh";
1029
1235
  const shellArg = isWindows ? "/C" : "-c";
1030
- const { stdout, stderr } = await execAsync4(cmd, {
1236
+ const { stdout, stderr } = await execAsync5(cmd, {
1031
1237
  shell,
1032
1238
  timeout,
1033
1239
  maxBuffer: 10 * 1024 * 1024
@@ -1271,7 +1477,7 @@ function useTelegram(onMessage) {
1271
1477
  const [error, setError] = useState5(null);
1272
1478
  const [lastMessage, setLastMessage] = useState5(null);
1273
1479
  const onMessageRef = useRef2(onMessage);
1274
- useEffect2(() => {
1480
+ useEffect3(() => {
1275
1481
  onMessageRef.current = onMessage;
1276
1482
  }, [onMessage]);
1277
1483
  const start = useCallback3(async () => {
@@ -1935,74 +2141,81 @@ ${tasks.format(task)}`);
1935
2141
  // src/index.tsx
1936
2142
  import { jsx as jsx8 } from "react/jsx-runtime";
1937
2143
  var args = process.argv.slice(2);
1938
- if (args.length > 0) {
1939
- const command = args[0];
1940
- switch (command) {
1941
- case "auth": {
1942
- const provider = args[1];
1943
- const key = args[2];
1944
- if (!provider || !key) {
1945
- console.log("Usage: cnapse auth <provider> <api-key>");
1946
- console.log("Providers: openrouter, anthropic, openai");
1947
- process.exit(1);
1948
- }
1949
- if (!["openrouter", "anthropic", "openai"].includes(provider)) {
1950
- console.log(`Invalid provider: ${provider}`);
1951
- console.log("Valid providers: openrouter, anthropic, openai");
1952
- process.exit(1);
1953
- }
1954
- setApiKey(provider, key);
1955
- console.log(`\u2713 ${provider} API key saved`);
1956
- process.exit(0);
1957
- }
1958
- case "config": {
1959
- const subcommand = args[1];
1960
- 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];
1961
2150
  const key = args[2];
1962
- const value = args[3];
1963
- if (key === "provider") {
1964
- if (!["openrouter", "ollama", "anthropic", "openai"].includes(value)) {
1965
- console.log("Valid providers: openrouter, ollama, anthropic, openai");
1966
- process.exit(1);
1967
- }
1968
- setProvider(value);
1969
- console.log(`\u2713 Provider set to: ${value}`);
1970
- } else if (key === "model") {
1971
- setModel(value);
1972
- console.log(`\u2713 Model set to: ${value}`);
1973
- } else {
1974
- 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);
1975
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);
2160
+ }
2161
+ setApiKey(provider, key);
2162
+ console.log(`\u2713 ${provider} API key saved`);
1976
2163
  process.exit(0);
1977
2164
  }
1978
- if (subcommand === "show" || !subcommand) {
1979
- const config = getConfig();
1980
- console.log("\nC-napse Configuration:");
1981
- console.log(` Provider: ${config.provider}`);
1982
- console.log(` Model: ${config.model}`);
1983
- console.log(` Ollama Host: ${config.ollamaHost}`);
1984
- console.log(` API Keys configured:`);
1985
- console.log(` - OpenRouter: ${config.apiKeys.openrouter ? "\u2713" : "\u2717"}`);
1986
- console.log(` - Anthropic: ${config.apiKeys.anthropic ? "\u2713" : "\u2717"}`);
1987
- console.log(` - OpenAI: ${config.apiKeys.openai ? "\u2713" : "\u2717"}`);
1988
- console.log("");
1989
- process.exit(0);
2165
+ case "config": {
2166
+ const subcommand = args[1];
2167
+ if (!subcommand) {
2168
+ const { ConfigUI } = await import("./ConfigUI-V5TM6KKS.js");
2169
+ render(/* @__PURE__ */ jsx8(ConfigUI, {}));
2170
+ return;
2171
+ }
2172
+ if (subcommand === "set") {
2173
+ const key = args[2];
2174
+ const value = args[3];
2175
+ if (key === "provider") {
2176
+ if (!["openrouter", "ollama", "anthropic", "openai"].includes(value)) {
2177
+ console.log("Valid providers: openrouter, ollama, anthropic, openai");
2178
+ process.exit(1);
2179
+ }
2180
+ setProvider(value);
2181
+ console.log(`\u2713 Provider set to: ${value}`);
2182
+ } else if (key === "model") {
2183
+ setModel(value);
2184
+ console.log(`\u2713 Model set to: ${value}`);
2185
+ } else {
2186
+ console.log("Usage: cnapse config set <provider|model> <value>");
2187
+ }
2188
+ process.exit(0);
2189
+ }
2190
+ if (subcommand === "show") {
2191
+ const config = getConfig();
2192
+ console.log("\nC-napse Configuration:");
2193
+ console.log(` Provider: ${config.provider}`);
2194
+ console.log(` Model: ${config.model}`);
2195
+ console.log(` Ollama Host: ${config.ollamaHost}`);
2196
+ console.log(` API Keys configured:`);
2197
+ console.log(` - OpenRouter: ${config.apiKeys.openrouter ? "\u2713" : "\u2717"}`);
2198
+ console.log(` - Anthropic: ${config.apiKeys.anthropic ? "\u2713" : "\u2717"}`);
2199
+ console.log(` - OpenAI: ${config.apiKeys.openai ? "\u2713" : "\u2717"}`);
2200
+ console.log("");
2201
+ process.exit(0);
2202
+ }
2203
+ console.log("Usage: cnapse config [show|set <key> <value>]");
2204
+ process.exit(1);
1990
2205
  }
1991
- console.log("Usage: cnapse config [show|set <key> <value>]");
1992
- process.exit(1);
1993
- }
1994
- case "help":
1995
- case "--help":
1996
- case "-h": {
1997
- console.log(`
2206
+ case "help":
2207
+ case "--help":
2208
+ case "-h": {
2209
+ console.log(`
1998
2210
  C-napse - Autonomous PC Intelligence
1999
2211
 
2000
2212
  Usage:
2001
2213
  cnapse Start interactive chat
2002
2214
  cnapse init Interactive setup wizard
2003
- cnapse auth <provider> <key> Set API key
2004
- cnapse config Show configuration
2215
+ cnapse config Interactive configuration
2216
+ cnapse config show Show current configuration
2005
2217
  cnapse config set <k> <v> Set config value
2218
+ cnapse auth <provider> <key> Set API key
2006
2219
  cnapse help Show this help
2007
2220
 
2008
2221
  Providers:
@@ -2013,28 +2226,31 @@ Providers:
2013
2226
 
2014
2227
  Quick Start:
2015
2228
  cnapse init # Interactive setup
2229
+ cnapse config # Change provider/model
2016
2230
 
2017
2231
  Manual Setup:
2018
2232
  cnapse auth openrouter sk-or-xxxxx
2019
2233
  cnapse config set provider openrouter
2020
2234
  cnapse config set model qwen/qwen-2.5-coder-32b-instruct
2021
2235
  `);
2022
- process.exit(0);
2023
- }
2024
- case "version":
2025
- case "--version":
2026
- case "-v": {
2027
- console.log("cnapse v0.2.0");
2028
- process.exit(0);
2029
- }
2030
- case "init": {
2031
- const { Setup } = await import("./Setup-Q32JPHGP.js");
2032
- render(/* @__PURE__ */ jsx8(Setup, {}));
2033
- process.exit(0);
2034
- }
2035
- default: {
2036
- break;
2236
+ process.exit(0);
2237
+ }
2238
+ case "version":
2239
+ case "--version":
2240
+ case "-v": {
2241
+ console.log("cnapse v0.5.0");
2242
+ process.exit(0);
2243
+ }
2244
+ case "init": {
2245
+ const { Setup } = await import("./Setup-Q32JPHGP.js");
2246
+ render(/* @__PURE__ */ jsx8(Setup, {}));
2247
+ return;
2248
+ }
2249
+ default: {
2250
+ break;
2251
+ }
2037
2252
  }
2038
2253
  }
2254
+ render(/* @__PURE__ */ jsx8(App, {}));
2039
2255
  }
2040
- render(/* @__PURE__ */ jsx8(App, {}));
2256
+ main();