@projectservan8n/cnapse 0.5.4 → 0.5.6
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 +129 -47
- package/package.json +1 -1
- package/src/components/App.tsx +4 -2
- package/src/lib/api.ts +22 -4
- package/src/lib/system.ts +105 -0
package/dist/index.js
CHANGED
|
@@ -534,18 +534,99 @@ function ProviderSelector({ onClose, onSelect }) {
|
|
|
534
534
|
// src/hooks/useChat.ts
|
|
535
535
|
import { useState as useState3, useCallback, useRef, useEffect as useEffect2 } from "react";
|
|
536
536
|
|
|
537
|
+
// src/lib/system.ts
|
|
538
|
+
import os from "os";
|
|
539
|
+
import { exec as exec2 } from "child_process";
|
|
540
|
+
import { promisify as promisify2 } from "util";
|
|
541
|
+
var execAsync2 = promisify2(exec2);
|
|
542
|
+
var cachedSystemInfo = null;
|
|
543
|
+
async function getSystemInfo() {
|
|
544
|
+
if (cachedSystemInfo) return cachedSystemInfo;
|
|
545
|
+
const platform = os.platform();
|
|
546
|
+
const cpus = os.cpus();
|
|
547
|
+
let osName = platform;
|
|
548
|
+
const osVersion = os.release();
|
|
549
|
+
if (platform === "win32") {
|
|
550
|
+
try {
|
|
551
|
+
const { stdout } = await execAsync2("wmic os get Caption /value", { timeout: 5e3 });
|
|
552
|
+
const match = stdout.match(/Caption=(.+)/);
|
|
553
|
+
if (match) osName = match[1].trim();
|
|
554
|
+
} catch {
|
|
555
|
+
osName = `Windows ${osVersion}`;
|
|
556
|
+
}
|
|
557
|
+
} else if (platform === "darwin") {
|
|
558
|
+
try {
|
|
559
|
+
const { stdout } = await execAsync2("sw_vers -productName && sw_vers -productVersion", { timeout: 5e3 });
|
|
560
|
+
const lines = stdout.trim().split("\n");
|
|
561
|
+
osName = `${lines[0]} ${lines[1]}`;
|
|
562
|
+
} catch {
|
|
563
|
+
osName = `macOS ${osVersion}`;
|
|
564
|
+
}
|
|
565
|
+
} else if (platform === "linux") {
|
|
566
|
+
try {
|
|
567
|
+
const { stdout } = await execAsync2("cat /etc/os-release | grep PRETTY_NAME", { timeout: 5e3 });
|
|
568
|
+
const match = stdout.match(/PRETTY_NAME="(.+)"/);
|
|
569
|
+
if (match) osName = match[1];
|
|
570
|
+
} catch {
|
|
571
|
+
osName = `Linux ${osVersion}`;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
cachedSystemInfo = {
|
|
575
|
+
platform,
|
|
576
|
+
osName,
|
|
577
|
+
osVersion,
|
|
578
|
+
arch: os.arch(),
|
|
579
|
+
cpuModel: cpus[0]?.model || "Unknown CPU",
|
|
580
|
+
cpuCores: cpus.length,
|
|
581
|
+
totalMemoryGB: Math.round(os.totalmem() / 1024 ** 3 * 10) / 10,
|
|
582
|
+
freeMemoryGB: Math.round(os.freemem() / 1024 ** 3 * 10) / 10,
|
|
583
|
+
username: os.userInfo().username,
|
|
584
|
+
hostname: os.hostname(),
|
|
585
|
+
homeDir: os.homedir(),
|
|
586
|
+
shell: process.env.SHELL || process.env.COMSPEC || "unknown"
|
|
587
|
+
};
|
|
588
|
+
return cachedSystemInfo;
|
|
589
|
+
}
|
|
590
|
+
async function getSystemContext() {
|
|
591
|
+
const info = await getSystemInfo();
|
|
592
|
+
return `SYSTEM INFO:
|
|
593
|
+
- OS: ${info.osName} (${info.arch})
|
|
594
|
+
- CPU: ${info.cpuModel} (${info.cpuCores} cores)
|
|
595
|
+
- RAM: ${info.totalMemoryGB}GB total, ${info.freeMemoryGB}GB free
|
|
596
|
+
- User: ${info.username}@${info.hostname}
|
|
597
|
+
- Home: ${info.homeDir}
|
|
598
|
+
- Shell: ${info.shell}`;
|
|
599
|
+
}
|
|
600
|
+
function getCwd() {
|
|
601
|
+
return process.cwd();
|
|
602
|
+
}
|
|
603
|
+
|
|
537
604
|
// src/lib/api.ts
|
|
538
|
-
var
|
|
539
|
-
You can help with coding, file management, shell commands, and more.
|
|
605
|
+
var BASE_PROMPT = `You are C-napse, an AI assistant for PC automation running on the user's desktop.
|
|
606
|
+
You have access to their system and can help with coding, file management, shell commands, and more.
|
|
540
607
|
|
|
541
608
|
When responding:
|
|
542
609
|
- Be direct and practical
|
|
543
610
|
- Use markdown formatting for code blocks
|
|
544
|
-
- If asked to do something, explain what you'll do first
|
|
611
|
+
- If asked to do something, explain what you'll do first
|
|
612
|
+
- Give commands specific to the user's OS (use the system info below)
|
|
613
|
+
- Be aware of the user's current working directory`;
|
|
614
|
+
var systemContextCache = null;
|
|
615
|
+
async function getSystemPrompt() {
|
|
616
|
+
if (!systemContextCache) {
|
|
617
|
+
systemContextCache = await getSystemContext();
|
|
618
|
+
}
|
|
619
|
+
const cwd = getCwd();
|
|
620
|
+
return `${BASE_PROMPT}
|
|
621
|
+
|
|
622
|
+
${systemContextCache}
|
|
623
|
+
- Current directory: ${cwd}`;
|
|
624
|
+
}
|
|
545
625
|
async function chat(messages, systemPrompt) {
|
|
546
626
|
const config = getConfig();
|
|
627
|
+
const finalPrompt = systemPrompt || await getSystemPrompt();
|
|
547
628
|
const allMessages = [
|
|
548
|
-
{ role: "system", content:
|
|
629
|
+
{ role: "system", content: finalPrompt },
|
|
549
630
|
...messages
|
|
550
631
|
];
|
|
551
632
|
switch (config.provider) {
|
|
@@ -666,24 +747,24 @@ async function chatOpenAI(messages, model) {
|
|
|
666
747
|
}
|
|
667
748
|
|
|
668
749
|
// src/lib/screen.ts
|
|
669
|
-
import { exec as
|
|
670
|
-
import { promisify as
|
|
671
|
-
var
|
|
750
|
+
import { exec as exec3 } from "child_process";
|
|
751
|
+
import { promisify as promisify3 } from "util";
|
|
752
|
+
var execAsync3 = promisify3(exec3);
|
|
672
753
|
async function getScreenDescription() {
|
|
673
754
|
try {
|
|
674
755
|
const platform = process.platform;
|
|
675
756
|
if (platform === "win32") {
|
|
676
|
-
const { stdout } = await
|
|
757
|
+
const { stdout } = await execAsync3(`
|
|
677
758
|
Add-Type -AssemblyName System.Windows.Forms
|
|
678
759
|
$screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
|
|
679
760
|
Write-Output "$($screen.Width)x$($screen.Height)"
|
|
680
761
|
`, { shell: "powershell.exe" });
|
|
681
762
|
return `Screen ${stdout.trim()} captured`;
|
|
682
763
|
} else if (platform === "darwin") {
|
|
683
|
-
const { stdout } = await
|
|
764
|
+
const { stdout } = await execAsync3(`system_profiler SPDisplaysDataType | grep Resolution | head -1`);
|
|
684
765
|
return `Screen ${stdout.trim()}`;
|
|
685
766
|
} else {
|
|
686
|
-
const { stdout } = await
|
|
767
|
+
const { stdout } = await execAsync3(`xdpyinfo | grep dimensions | awk '{print $2}'`);
|
|
687
768
|
return `Screen ${stdout.trim()} captured`;
|
|
688
769
|
}
|
|
689
770
|
} catch {
|
|
@@ -812,17 +893,17 @@ async function captureScreenshot() {
|
|
|
812
893
|
}
|
|
813
894
|
}
|
|
814
895
|
async function captureScreenFallback() {
|
|
815
|
-
const { exec:
|
|
816
|
-
const { promisify:
|
|
896
|
+
const { exec: exec7 } = await import("child_process");
|
|
897
|
+
const { promisify: promisify7 } = await import("util");
|
|
817
898
|
const { tmpdir } = await import("os");
|
|
818
899
|
const { join: join2 } = await import("path");
|
|
819
900
|
const { readFile, unlink } = await import("fs/promises");
|
|
820
|
-
const
|
|
901
|
+
const execAsync7 = promisify7(exec7);
|
|
821
902
|
const tempFile = join2(tmpdir(), `cnapse-screen-${Date.now()}.png`);
|
|
822
903
|
try {
|
|
823
904
|
const platform = process.platform;
|
|
824
905
|
if (platform === "win32") {
|
|
825
|
-
await
|
|
906
|
+
await execAsync7(`
|
|
826
907
|
Add-Type -AssemblyName System.Windows.Forms
|
|
827
908
|
$screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
|
|
828
909
|
$bitmap = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height)
|
|
@@ -833,9 +914,9 @@ async function captureScreenFallback() {
|
|
|
833
914
|
$bitmap.Dispose()
|
|
834
915
|
`, { shell: "powershell.exe" });
|
|
835
916
|
} else if (platform === "darwin") {
|
|
836
|
-
await
|
|
917
|
+
await execAsync7(`screencapture -x "${tempFile}"`);
|
|
837
918
|
} else {
|
|
838
|
-
await
|
|
919
|
+
await execAsync7(`gnome-screenshot -f "${tempFile}" 2>/dev/null || scrot "${tempFile}" 2>/dev/null || import -window root "${tempFile}"`);
|
|
839
920
|
}
|
|
840
921
|
const imageBuffer = await readFile(tempFile);
|
|
841
922
|
await unlink(tempFile).catch(() => {
|
|
@@ -1034,21 +1115,21 @@ import { useState as useState5, useCallback as useCallback3, useEffect as useEff
|
|
|
1034
1115
|
import { EventEmitter } from "events";
|
|
1035
1116
|
|
|
1036
1117
|
// src/tools/shell.ts
|
|
1037
|
-
import { exec as
|
|
1038
|
-
import { promisify as
|
|
1118
|
+
import { exec as exec6 } from "child_process";
|
|
1119
|
+
import { promisify as promisify6 } from "util";
|
|
1039
1120
|
|
|
1040
1121
|
// src/tools/clipboard.ts
|
|
1041
1122
|
import clipboardy from "clipboardy";
|
|
1042
1123
|
|
|
1043
1124
|
// src/tools/process.ts
|
|
1044
|
-
import { exec as exec3 } from "child_process";
|
|
1045
|
-
import { promisify as promisify3 } from "util";
|
|
1046
|
-
var execAsync3 = promisify3(exec3);
|
|
1047
|
-
|
|
1048
|
-
// src/tools/computer.ts
|
|
1049
1125
|
import { exec as exec4 } from "child_process";
|
|
1050
1126
|
import { promisify as promisify4 } from "util";
|
|
1051
1127
|
var execAsync4 = promisify4(exec4);
|
|
1128
|
+
|
|
1129
|
+
// src/tools/computer.ts
|
|
1130
|
+
import { exec as exec5 } from "child_process";
|
|
1131
|
+
import { promisify as promisify5 } from "util";
|
|
1132
|
+
var execAsync5 = promisify5(exec5);
|
|
1052
1133
|
async function clickMouse(button = "left") {
|
|
1053
1134
|
try {
|
|
1054
1135
|
if (process.platform === "win32") {
|
|
@@ -1058,12 +1139,12 @@ Add-Type -MemberDefinition @"
|
|
|
1058
1139
|
public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
|
|
1059
1140
|
"@ -Name Mouse -Namespace Win32
|
|
1060
1141
|
${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)"}`;
|
|
1061
|
-
await
|
|
1142
|
+
await execAsync5(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
|
|
1062
1143
|
} else if (process.platform === "darwin") {
|
|
1063
|
-
await
|
|
1144
|
+
await execAsync5(`cliclick c:.`);
|
|
1064
1145
|
} else {
|
|
1065
1146
|
const btn = button === "left" ? "1" : button === "right" ? "3" : "2";
|
|
1066
|
-
await
|
|
1147
|
+
await execAsync5(`xdotool click ${btn}`);
|
|
1067
1148
|
}
|
|
1068
1149
|
return ok(`Clicked ${button} button`);
|
|
1069
1150
|
} catch (error) {
|
|
@@ -1074,13 +1155,13 @@ async function typeText(text) {
|
|
|
1074
1155
|
try {
|
|
1075
1156
|
if (process.platform === "win32") {
|
|
1076
1157
|
const escapedText = text.replace(/'/g, "''").replace(/[+^%~(){}[\]]/g, "{$&}");
|
|
1077
|
-
await
|
|
1158
|
+
await execAsync5(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${escapedText}')"`, { shell: "cmd.exe" });
|
|
1078
1159
|
} else if (process.platform === "darwin") {
|
|
1079
1160
|
const escaped = text.replace(/'/g, "'\\''");
|
|
1080
|
-
await
|
|
1161
|
+
await execAsync5(`osascript -e 'tell application "System Events" to keystroke "${escaped}"'`);
|
|
1081
1162
|
} else {
|
|
1082
1163
|
const escaped = text.replace(/'/g, "'\\''");
|
|
1083
|
-
await
|
|
1164
|
+
await execAsync5(`xdotool type '${escaped}'`);
|
|
1084
1165
|
}
|
|
1085
1166
|
return ok(`Typed: ${text}`);
|
|
1086
1167
|
} catch (error) {
|
|
@@ -1121,7 +1202,7 @@ async function pressKey(key) {
|
|
|
1121
1202
|
"f12": "{F12}"
|
|
1122
1203
|
};
|
|
1123
1204
|
const winKey = winKeyMap[key.toLowerCase()] || key;
|
|
1124
|
-
await
|
|
1205
|
+
await execAsync5(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${winKey}')"`, { shell: "cmd.exe" });
|
|
1125
1206
|
} else if (process.platform === "darwin") {
|
|
1126
1207
|
const macKeyMap = {
|
|
1127
1208
|
"return": 36,
|
|
@@ -1139,12 +1220,12 @@ async function pressKey(key) {
|
|
|
1139
1220
|
};
|
|
1140
1221
|
const keyCode = macKeyMap[key.toLowerCase()];
|
|
1141
1222
|
if (keyCode) {
|
|
1142
|
-
await
|
|
1223
|
+
await execAsync5(`osascript -e 'tell application "System Events" to key code ${keyCode}'`);
|
|
1143
1224
|
} else {
|
|
1144
|
-
await
|
|
1225
|
+
await execAsync5(`osascript -e 'tell application "System Events" to keystroke "${key}"'`);
|
|
1145
1226
|
}
|
|
1146
1227
|
} else {
|
|
1147
|
-
await
|
|
1228
|
+
await execAsync5(`xdotool key ${key}`);
|
|
1148
1229
|
}
|
|
1149
1230
|
return ok(`Pressed: ${key}`);
|
|
1150
1231
|
} catch (error) {
|
|
@@ -1157,7 +1238,7 @@ async function keyCombo(keys) {
|
|
|
1157
1238
|
const hasWin = keys.some((k) => k.toLowerCase() === "meta" || k.toLowerCase() === "win");
|
|
1158
1239
|
const hasR = keys.some((k) => k.toLowerCase() === "r");
|
|
1159
1240
|
if (hasWin && hasR) {
|
|
1160
|
-
await
|
|
1241
|
+
await execAsync5(`powershell -Command "$shell = New-Object -ComObject WScript.Shell; $shell.Run('explorer shell:::{2559a1f3-21d7-11d4-bdaf-00c04f60b9f0}')"`, { shell: "cmd.exe" });
|
|
1161
1242
|
return ok(`Pressed: ${keys.join("+")}`);
|
|
1162
1243
|
}
|
|
1163
1244
|
const modifierMap = {
|
|
@@ -1177,7 +1258,7 @@ async function keyCombo(keys) {
|
|
|
1177
1258
|
}
|
|
1178
1259
|
}
|
|
1179
1260
|
combo += regularKeys.join("");
|
|
1180
|
-
await
|
|
1261
|
+
await execAsync5(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${combo}')"`, { shell: "cmd.exe" });
|
|
1181
1262
|
} else if (process.platform === "darwin") {
|
|
1182
1263
|
const modifiers = keys.filter((k) => ["control", "ctrl", "alt", "shift", "command", "meta"].includes(k.toLowerCase()));
|
|
1183
1264
|
const regular = keys.filter((k) => !["control", "ctrl", "alt", "shift", "command", "meta"].includes(k.toLowerCase()));
|
|
@@ -1193,9 +1274,9 @@ async function keyCombo(keys) {
|
|
|
1193
1274
|
};
|
|
1194
1275
|
cmd += " using {" + modifiers.map((m) => modMap[m.toLowerCase()]).join(", ") + "}";
|
|
1195
1276
|
}
|
|
1196
|
-
await
|
|
1277
|
+
await execAsync5(`osascript -e '${cmd}'`);
|
|
1197
1278
|
} else {
|
|
1198
|
-
await
|
|
1279
|
+
await execAsync5(`xdotool key ${keys.join("+")}`);
|
|
1199
1280
|
}
|
|
1200
1281
|
return ok(`Pressed: ${keys.join("+")}`);
|
|
1201
1282
|
} catch (error) {
|
|
@@ -1206,11 +1287,11 @@ async function focusWindow(title) {
|
|
|
1206
1287
|
try {
|
|
1207
1288
|
if (process.platform === "win32") {
|
|
1208
1289
|
const escaped = title.replace(/'/g, "''");
|
|
1209
|
-
await
|
|
1290
|
+
await execAsync5(`powershell -Command "$wshell = New-Object -ComObject wscript.shell; $wshell.AppActivate('${escaped}')"`, { shell: "cmd.exe" });
|
|
1210
1291
|
} else if (process.platform === "darwin") {
|
|
1211
|
-
await
|
|
1292
|
+
await execAsync5(`osascript -e 'tell application "${title}" to activate'`);
|
|
1212
1293
|
} else {
|
|
1213
|
-
await
|
|
1294
|
+
await execAsync5(`wmctrl -a "${title}"`);
|
|
1214
1295
|
}
|
|
1215
1296
|
return ok(`Focused window: ${title}`);
|
|
1216
1297
|
} catch (error) {
|
|
@@ -1227,13 +1308,13 @@ function err(error) {
|
|
|
1227
1308
|
}
|
|
1228
1309
|
|
|
1229
1310
|
// src/tools/shell.ts
|
|
1230
|
-
var
|
|
1311
|
+
var execAsync6 = promisify6(exec6);
|
|
1231
1312
|
async function runCommand(cmd, timeout = 3e4) {
|
|
1232
1313
|
try {
|
|
1233
1314
|
const isWindows = process.platform === "win32";
|
|
1234
1315
|
const shell = isWindows ? "cmd.exe" : "/bin/sh";
|
|
1235
1316
|
const shellArg = isWindows ? "/C" : "-c";
|
|
1236
|
-
const { stdout, stderr } = await
|
|
1317
|
+
const { stdout, stderr } = await execAsync6(cmd, {
|
|
1237
1318
|
shell,
|
|
1238
1319
|
timeout,
|
|
1239
1320
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -1539,8 +1620,8 @@ import { useState as useState6, useCallback as useCallback4 } from "react";
|
|
|
1539
1620
|
// src/lib/tasks.ts
|
|
1540
1621
|
import * as fs from "fs";
|
|
1541
1622
|
import * as path from "path";
|
|
1542
|
-
import * as
|
|
1543
|
-
var TASK_MEMORY_FILE = path.join(
|
|
1623
|
+
import * as os2 from "os";
|
|
1624
|
+
var TASK_MEMORY_FILE = path.join(os2.homedir(), ".cnapse", "task-memory.json");
|
|
1544
1625
|
function loadTaskMemory() {
|
|
1545
1626
|
try {
|
|
1546
1627
|
if (fs.existsSync(TASK_MEMORY_FILE)) {
|
|
@@ -1912,6 +1993,7 @@ function App() {
|
|
|
1912
1993
|
const [overlay, setOverlay] = useState7("none");
|
|
1913
1994
|
const [screenWatch, setScreenWatch] = useState7(false);
|
|
1914
1995
|
const [status, setStatus] = useState7("Ready");
|
|
1996
|
+
const [inputValue, setInputValue] = useState7("");
|
|
1915
1997
|
const chat2 = useChat(screenWatch);
|
|
1916
1998
|
const vision = useVision();
|
|
1917
1999
|
const telegram = useTelegram((msg) => {
|
|
@@ -2071,6 +2153,7 @@ ${tasks.format(task)}`);
|
|
|
2071
2153
|
}, [chat2, tasks]);
|
|
2072
2154
|
const handleSubmit = useCallback5(async (value) => {
|
|
2073
2155
|
if (!value.trim()) return;
|
|
2156
|
+
setInputValue("");
|
|
2074
2157
|
if (value.startsWith("/")) {
|
|
2075
2158
|
await handleCommand(value);
|
|
2076
2159
|
} else {
|
|
@@ -2127,9 +2210,8 @@ ${tasks.format(task)}`);
|
|
|
2127
2210
|
/* @__PURE__ */ jsx7(
|
|
2128
2211
|
ChatInput,
|
|
2129
2212
|
{
|
|
2130
|
-
value:
|
|
2131
|
-
onChange:
|
|
2132
|
-
},
|
|
2213
|
+
value: inputValue,
|
|
2214
|
+
onChange: setInputValue,
|
|
2133
2215
|
onSubmit: handleSubmit,
|
|
2134
2216
|
isProcessing
|
|
2135
2217
|
}
|
package/package.json
CHANGED
package/src/components/App.tsx
CHANGED
|
@@ -18,6 +18,7 @@ export function App() {
|
|
|
18
18
|
const [overlay, setOverlay] = useState<OverlayType>('none');
|
|
19
19
|
const [screenWatch, setScreenWatch] = useState(false);
|
|
20
20
|
const [status, setStatus] = useState('Ready');
|
|
21
|
+
const [inputValue, setInputValue] = useState('');
|
|
21
22
|
|
|
22
23
|
// Feature hooks
|
|
23
24
|
const chat = useChat(screenWatch);
|
|
@@ -204,6 +205,7 @@ export function App() {
|
|
|
204
205
|
// Submit handler
|
|
205
206
|
const handleSubmit = useCallback(async (value: string) => {
|
|
206
207
|
if (!value.trim()) return;
|
|
208
|
+
setInputValue(''); // Clear input immediately
|
|
207
209
|
|
|
208
210
|
if (value.startsWith('/')) {
|
|
209
211
|
await handleCommand(value);
|
|
@@ -270,8 +272,8 @@ export function App() {
|
|
|
270
272
|
)}
|
|
271
273
|
|
|
272
274
|
<ChatInput
|
|
273
|
-
value=
|
|
274
|
-
onChange={
|
|
275
|
+
value={inputValue}
|
|
276
|
+
onChange={setInputValue}
|
|
275
277
|
onSubmit={handleSubmit}
|
|
276
278
|
isProcessing={isProcessing}
|
|
277
279
|
/>
|
package/src/lib/api.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getConfig, getApiKey } from './config.js';
|
|
2
|
+
import { getSystemContext, getCwd } from './system.js';
|
|
2
3
|
|
|
3
4
|
export interface Message {
|
|
4
5
|
role: 'system' | 'user' | 'assistant';
|
|
@@ -10,19 +11,36 @@ export interface ChatResponse {
|
|
|
10
11
|
model: string;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const
|
|
14
|
-
You can help with coding, file management, shell commands, and more.
|
|
14
|
+
const BASE_PROMPT = `You are C-napse, an AI assistant for PC automation running on the user's desktop.
|
|
15
|
+
You have access to their system and can help with coding, file management, shell commands, and more.
|
|
15
16
|
|
|
16
17
|
When responding:
|
|
17
18
|
- Be direct and practical
|
|
18
19
|
- Use markdown formatting for code blocks
|
|
19
|
-
- If asked to do something, explain what you'll do first
|
|
20
|
+
- If asked to do something, explain what you'll do first
|
|
21
|
+
- Give commands specific to the user's OS (use the system info below)
|
|
22
|
+
- Be aware of the user's current working directory`;
|
|
23
|
+
|
|
24
|
+
// Cache system context to avoid repeated calls
|
|
25
|
+
let systemContextCache: string | null = null;
|
|
26
|
+
|
|
27
|
+
async function getSystemPrompt(): Promise<string> {
|
|
28
|
+
if (!systemContextCache) {
|
|
29
|
+
systemContextCache = await getSystemContext();
|
|
30
|
+
}
|
|
31
|
+
const cwd = getCwd();
|
|
32
|
+
return `${BASE_PROMPT}
|
|
33
|
+
|
|
34
|
+
${systemContextCache}
|
|
35
|
+
- Current directory: ${cwd}`;
|
|
36
|
+
}
|
|
20
37
|
|
|
21
38
|
export async function chat(messages: Message[], systemPrompt?: string): Promise<ChatResponse> {
|
|
22
39
|
const config = getConfig();
|
|
40
|
+
const finalPrompt = systemPrompt || await getSystemPrompt();
|
|
23
41
|
|
|
24
42
|
const allMessages: Message[] = [
|
|
25
|
-
{ role: 'system', content:
|
|
43
|
+
{ role: 'system', content: finalPrompt },
|
|
26
44
|
...messages,
|
|
27
45
|
];
|
|
28
46
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System information utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import { exec } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
|
|
11
|
+
export interface SystemInfo {
|
|
12
|
+
platform: string;
|
|
13
|
+
osName: string;
|
|
14
|
+
osVersion: string;
|
|
15
|
+
arch: string;
|
|
16
|
+
cpuModel: string;
|
|
17
|
+
cpuCores: number;
|
|
18
|
+
totalMemoryGB: number;
|
|
19
|
+
freeMemoryGB: number;
|
|
20
|
+
username: string;
|
|
21
|
+
hostname: string;
|
|
22
|
+
homeDir: string;
|
|
23
|
+
shell: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let cachedSystemInfo: SystemInfo | null = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get detailed system information
|
|
30
|
+
*/
|
|
31
|
+
export async function getSystemInfo(): Promise<SystemInfo> {
|
|
32
|
+
if (cachedSystemInfo) return cachedSystemInfo;
|
|
33
|
+
|
|
34
|
+
const platform = os.platform();
|
|
35
|
+
const cpus = os.cpus();
|
|
36
|
+
|
|
37
|
+
let osName: string = platform;
|
|
38
|
+
const osVersion = os.release();
|
|
39
|
+
|
|
40
|
+
// Get friendly OS name
|
|
41
|
+
if (platform === 'win32') {
|
|
42
|
+
try {
|
|
43
|
+
const { stdout } = await execAsync('wmic os get Caption /value', { timeout: 5000 });
|
|
44
|
+
const match = stdout.match(/Caption=(.+)/);
|
|
45
|
+
if (match) osName = match[1].trim();
|
|
46
|
+
} catch {
|
|
47
|
+
osName = `Windows ${osVersion}`;
|
|
48
|
+
}
|
|
49
|
+
} else if (platform === 'darwin') {
|
|
50
|
+
try {
|
|
51
|
+
const { stdout } = await execAsync('sw_vers -productName && sw_vers -productVersion', { timeout: 5000 });
|
|
52
|
+
const lines = stdout.trim().split('\n');
|
|
53
|
+
osName = `${lines[0]} ${lines[1]}`;
|
|
54
|
+
} catch {
|
|
55
|
+
osName = `macOS ${osVersion}`;
|
|
56
|
+
}
|
|
57
|
+
} else if (platform === 'linux') {
|
|
58
|
+
try {
|
|
59
|
+
const { stdout } = await execAsync('cat /etc/os-release | grep PRETTY_NAME', { timeout: 5000 });
|
|
60
|
+
const match = stdout.match(/PRETTY_NAME="(.+)"/);
|
|
61
|
+
if (match) osName = match[1];
|
|
62
|
+
} catch {
|
|
63
|
+
osName = `Linux ${osVersion}`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
cachedSystemInfo = {
|
|
68
|
+
platform,
|
|
69
|
+
osName,
|
|
70
|
+
osVersion,
|
|
71
|
+
arch: os.arch(),
|
|
72
|
+
cpuModel: cpus[0]?.model || 'Unknown CPU',
|
|
73
|
+
cpuCores: cpus.length,
|
|
74
|
+
totalMemoryGB: Math.round(os.totalmem() / (1024 ** 3) * 10) / 10,
|
|
75
|
+
freeMemoryGB: Math.round(os.freemem() / (1024 ** 3) * 10) / 10,
|
|
76
|
+
username: os.userInfo().username,
|
|
77
|
+
hostname: os.hostname(),
|
|
78
|
+
homeDir: os.homedir(),
|
|
79
|
+
shell: process.env.SHELL || process.env.COMSPEC || 'unknown',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return cachedSystemInfo;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get a formatted system context string for AI prompts
|
|
87
|
+
*/
|
|
88
|
+
export async function getSystemContext(): Promise<string> {
|
|
89
|
+
const info = await getSystemInfo();
|
|
90
|
+
|
|
91
|
+
return `SYSTEM INFO:
|
|
92
|
+
- OS: ${info.osName} (${info.arch})
|
|
93
|
+
- CPU: ${info.cpuModel} (${info.cpuCores} cores)
|
|
94
|
+
- RAM: ${info.totalMemoryGB}GB total, ${info.freeMemoryGB}GB free
|
|
95
|
+
- User: ${info.username}@${info.hostname}
|
|
96
|
+
- Home: ${info.homeDir}
|
|
97
|
+
- Shell: ${info.shell}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get current working directory
|
|
102
|
+
*/
|
|
103
|
+
export function getCwd(): string {
|
|
104
|
+
return process.cwd();
|
|
105
|
+
}
|