@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 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 SYSTEM_PROMPT = `You are C-napse, a helpful AI assistant for PC automation running on the user's desktop.
539
- You can help with coding, file management, shell commands, and more. Be concise and helpful.
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: systemPrompt || SYSTEM_PROMPT },
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 exec2 } from "child_process";
670
- import { promisify as promisify2 } from "util";
671
- var execAsync2 = promisify2(exec2);
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 execAsync2(`
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 execAsync2(`system_profiler SPDisplaysDataType | grep Resolution | head -1`);
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 execAsync2(`xdpyinfo | grep dimensions | awk '{print $2}'`);
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: exec6 } = await import("child_process");
816
- const { promisify: promisify6 } = await import("util");
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 execAsync6 = promisify6(exec6);
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 execAsync6(`
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 execAsync6(`screencapture -x "${tempFile}"`);
917
+ await execAsync7(`screencapture -x "${tempFile}"`);
837
918
  } else {
838
- await execAsync6(`gnome-screenshot -f "${tempFile}" 2>/dev/null || scrot "${tempFile}" 2>/dev/null || import -window root "${tempFile}"`);
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 exec5 } from "child_process";
1038
- import { promisify as promisify5 } from "util";
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 execAsync4(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
1142
+ await execAsync5(`powershell -Command "${script.replace(/\n/g, " ")}"`, { shell: "cmd.exe" });
1062
1143
  } else if (process.platform === "darwin") {
1063
- await execAsync4(`cliclick c:.`);
1144
+ await execAsync5(`cliclick c:.`);
1064
1145
  } else {
1065
1146
  const btn = button === "left" ? "1" : button === "right" ? "3" : "2";
1066
- await execAsync4(`xdotool click ${btn}`);
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 execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${escapedText}')"`, { shell: "cmd.exe" });
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 execAsync4(`osascript -e 'tell application "System Events" to keystroke "${escaped}"'`);
1161
+ await execAsync5(`osascript -e 'tell application "System Events" to keystroke "${escaped}"'`);
1081
1162
  } else {
1082
1163
  const escaped = text.replace(/'/g, "'\\''");
1083
- await execAsync4(`xdotool type '${escaped}'`);
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 execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${winKey}')"`, { shell: "cmd.exe" });
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 execAsync4(`osascript -e 'tell application "System Events" to key code ${keyCode}'`);
1223
+ await execAsync5(`osascript -e 'tell application "System Events" to key code ${keyCode}'`);
1143
1224
  } else {
1144
- await execAsync4(`osascript -e 'tell application "System Events" to keystroke "${key}"'`);
1225
+ await execAsync5(`osascript -e 'tell application "System Events" to keystroke "${key}"'`);
1145
1226
  }
1146
1227
  } else {
1147
- await execAsync4(`xdotool key ${key}`);
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 execAsync4(`powershell -Command "$shell = New-Object -ComObject WScript.Shell; $shell.Run('explorer shell:::{2559a1f3-21d7-11d4-bdaf-00c04f60b9f0}')"`, { shell: "cmd.exe" });
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 execAsync4(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${combo}')"`, { shell: "cmd.exe" });
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 execAsync4(`osascript -e '${cmd}'`);
1277
+ await execAsync5(`osascript -e '${cmd}'`);
1197
1278
  } else {
1198
- await execAsync4(`xdotool key ${keys.join("+")}`);
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 execAsync4(`powershell -Command "$wshell = New-Object -ComObject wscript.shell; $wshell.AppActivate('${escaped}')"`, { shell: "cmd.exe" });
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 execAsync4(`osascript -e 'tell application "${title}" to activate'`);
1292
+ await execAsync5(`osascript -e 'tell application "${title}" to activate'`);
1212
1293
  } else {
1213
- await execAsync4(`wmctrl -a "${title}"`);
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 execAsync5 = promisify5(exec5);
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 execAsync5(cmd, {
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 os from "os";
1543
- var TASK_MEMORY_FILE = path.join(os.homedir(), ".cnapse", "task-memory.json");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectservan8n/cnapse",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Autonomous PC intelligence - AI assistant for desktop automation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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 SYSTEM_PROMPT = `You are C-napse, a helpful AI assistant for PC automation running on the user's desktop.
14
- You can help with coding, file management, shell commands, and more. Be concise and helpful.
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: systemPrompt || SYSTEM_PROMPT },
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
+ }