@novaqore/atom 0.0.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.
@@ -0,0 +1,175 @@
1
+ import React from "react";
2
+ import { release, arch, cpus, totalmem } from "node:os";
3
+ import { Box, Text } from "ink";
4
+ import SelectInput from "ink-select-input";
5
+ import { Header } from "../components/Header.js";
6
+ import { RUNTIMES } from "../utils/runtimes.js";
7
+ import {
8
+ HOSTNAME,
9
+ USERNAME,
10
+ IS_ROOT,
11
+ DISK,
12
+ getGPUs,
13
+ getPackageManagers,
14
+ } from "../utils/system.js";
15
+
16
+ function Row({ label, value, color, dim }) {
17
+ const valueProps = dim
18
+ ? { dimColor: true }
19
+ : color
20
+ ? { color }
21
+ : null;
22
+ return React.createElement(
23
+ Box,
24
+ null,
25
+ React.createElement(
26
+ Box,
27
+ { width: 14 },
28
+ React.createElement(Text, { dimColor: true }, label)
29
+ ),
30
+ React.createElement(Text, valueProps, value)
31
+ );
32
+ }
33
+
34
+ function Section({ title, rows }) {
35
+ return React.createElement(
36
+ Box,
37
+ { flexDirection: "column", marginTop: 1 },
38
+ React.createElement(Text, { color: "cyan", bold: true }, title),
39
+ React.createElement(
40
+ Box,
41
+ { flexDirection: "column", paddingLeft: 2 },
42
+ rows
43
+ )
44
+ );
45
+ }
46
+
47
+ export function SystemScreen({ version, unhinged, onBack }) {
48
+ const platformStr = `${process.platform}/${arch()} (${release()})`;
49
+ const shell = (process.env.SHELL || "").split("/").pop() || "shell";
50
+ const cwd = process.cwd();
51
+
52
+ const gpus = getGPUs();
53
+ const pms = getPackageManagers();
54
+
55
+ const cpuList = cpus();
56
+ const cpuModel = cpuList?.[0]?.model?.trim() || "unknown";
57
+ const cpuCount = cpuList?.length || 0;
58
+ const cpuSpeed = cpuList?.[0]?.speed || 0;
59
+ const cpuStr = cpuSpeed
60
+ ? `${cpuCount} cores @ ${(cpuSpeed / 1000).toFixed(2)} GHz (${cpuModel})`
61
+ : `${cpuCount} cores (${cpuModel})`;
62
+ const ramStr = `${(totalmem() / 1024 ** 3).toFixed(1)} GB`;
63
+ const diskStr = DISK
64
+ ? `${(DISK.used / 1024 ** 3).toFixed(1)} GB used / ${(DISK.total / 1024 ** 3).toFixed(1)} GB total`
65
+ : "unknown";
66
+
67
+ const systemRows = [
68
+ React.createElement(Row, {
69
+ key: "host",
70
+ label: "Hostname:",
71
+ value: HOSTNAME,
72
+ }),
73
+ React.createElement(Row, {
74
+ key: "user",
75
+ label: "User:",
76
+ value: USERNAME + (IS_ROOT ? " (root)" : ""),
77
+ color: IS_ROOT ? "red" : undefined,
78
+ }),
79
+ React.createElement(Row, {
80
+ key: "platform",
81
+ label: "Platform:",
82
+ value: platformStr,
83
+ }),
84
+ React.createElement(Row, { key: "shell", label: "Shell:", value: shell }),
85
+ React.createElement(Row, {
86
+ key: "cwd",
87
+ label: "Working dir:",
88
+ value: cwd,
89
+ }),
90
+ ];
91
+
92
+ const pmRows = [];
93
+ if (pms.system.length > 0) {
94
+ pmRows.push(
95
+ React.createElement(Row, {
96
+ key: "pm-system",
97
+ label: "System:",
98
+ value: pms.system.join(", "),
99
+ })
100
+ );
101
+ }
102
+ if (pms.language.length > 0) {
103
+ pmRows.push(
104
+ React.createElement(Row, {
105
+ key: "pm-language",
106
+ label: "Language:",
107
+ value: pms.language.join(", "),
108
+ })
109
+ );
110
+ }
111
+ if (pms.container.length > 0) {
112
+ pmRows.push(
113
+ React.createElement(Row, {
114
+ key: "pm-container",
115
+ label: "Container:",
116
+ value: pms.container.join(", "),
117
+ })
118
+ );
119
+ }
120
+
121
+ const hardwareRows = [
122
+ React.createElement(Row, { key: "cpu", label: "CPU:", value: cpuStr }),
123
+ React.createElement(Row, { key: "mem", label: "Memory:", value: ramStr }),
124
+ ];
125
+ if (gpus.length > 0) {
126
+ hardwareRows.push(
127
+ React.createElement(Row, {
128
+ key: "gpu",
129
+ label: "GPU:",
130
+ value: gpus.join(", "),
131
+ })
132
+ );
133
+ }
134
+ if (DISK) {
135
+ hardwareRows.push(
136
+ React.createElement(Row, { key: "disk", label: "Disk:", value: diskStr })
137
+ );
138
+ }
139
+
140
+ const runtimeRows = Object.entries(RUNTIMES)
141
+ .filter(([, value]) => value !== "not installed")
142
+ .map(([key, value]) =>
143
+ React.createElement(Row, {
144
+ key,
145
+ label: `${key}:`,
146
+ value,
147
+ color: "green",
148
+ })
149
+ );
150
+
151
+ return React.createElement(
152
+ Box,
153
+ { flexDirection: "column", paddingBottom: 1 },
154
+ React.createElement(Header, { version, unhinged }),
155
+ React.createElement(
156
+ Box,
157
+ { flexDirection: "column", paddingX: 1 },
158
+ React.createElement(Section, { title: "System", rows: systemRows }),
159
+ React.createElement(Section, { title: "Hardware", rows: hardwareRows }),
160
+ React.createElement(Section, { title: "Runtimes", rows: runtimeRows }),
161
+ React.createElement(Section, {
162
+ title: "Package Managers",
163
+ rows: pmRows,
164
+ }),
165
+ React.createElement(
166
+ Box,
167
+ { marginTop: 1 },
168
+ React.createElement(SelectInput, {
169
+ items: [{ label: "Back", value: "back" }],
170
+ onSelect: onBack,
171
+ })
172
+ )
173
+ )
174
+ );
175
+ }
@@ -0,0 +1,45 @@
1
+ import React from "react";
2
+ import { Box, Text, useApp } from "ink";
3
+ import SelectInput from "ink-select-input";
4
+
5
+ const items = [
6
+ { label: "I understand, continue", value: true },
7
+ { label: "Cancel", value: false },
8
+ ];
9
+
10
+ export function UnhingedWarningScreen({ onConfirm }) {
11
+ const { exit } = useApp();
12
+
13
+ return React.createElement(
14
+ Box,
15
+ { flexDirection: "column", paddingX: 1, paddingBottom: 1 },
16
+ React.createElement(Text, { color: "red", bold: true }, "UNHINGED MODE"),
17
+ React.createElement(
18
+ Box,
19
+ { marginTop: 1, flexDirection: "column", gap: 1 },
20
+ React.createElement(
21
+ Text,
22
+ null,
23
+ "Atom will run shell commands on this machine without asking for confirmation, including destructive ones like rm and sudo."
24
+ ),
25
+ React.createElement(
26
+ Text,
27
+ null,
28
+ "Best used on isolated environments such as AWS instances, DigitalOcean droplets, or disposable containers."
29
+ ),
30
+ React.createElement(
31
+ Text,
32
+ { dimColor: true },
33
+ "Do not run on a personal machine with important files, production systems, or anywhere with credentials you care about."
34
+ )
35
+ ),
36
+ React.createElement(
37
+ Box,
38
+ { marginTop: 1 },
39
+ React.createElement(SelectInput, {
40
+ items,
41
+ onSelect: (item) => (item.value ? onConfirm() : exit()),
42
+ })
43
+ )
44
+ );
45
+ }
package/src/setup.js ADDED
@@ -0,0 +1,3 @@
1
+ if (!process.env.FORCE_COLOR) {
2
+ process.env.FORCE_COLOR = "3";
3
+ }
@@ -0,0 +1,74 @@
1
+ import { exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+
4
+ const execAsync = promisify(exec);
5
+ const PWD_MARKER = "__ATOM_PWD_MARKER__";
6
+
7
+ const DANGEROUS = ["rm"];
8
+
9
+ function isDangerous(command) {
10
+ return DANGEROUS.some((word) => new RegExp(`\\b${word}\\b`).test(command));
11
+ }
12
+
13
+ function parsePwd(text) {
14
+ const idx = text.lastIndexOf(PWD_MARKER);
15
+ if (idx === -1) return { output: text, newCwd: null };
16
+ const newCwd = text.slice(idx + PWD_MARKER.length).trim();
17
+ const output = text.slice(0, idx);
18
+ return { output, newCwd };
19
+ }
20
+
21
+ export default {
22
+ definition: {
23
+ type: "function",
24
+ function: {
25
+ name: "bash",
26
+ description:
27
+ "Execute a bash command on the user's machine to fulfill a request that requires running code, inspecting files, or interacting with the system. Only use this when the user is asking you to do something that genuinely needs a shell command. Never use it to greet, chat, echo a message, or produce conversational output. Talk to the user directly with a normal text response instead.",
28
+ parameters: {
29
+ type: "object",
30
+ properties: {
31
+ command: {
32
+ type: "string",
33
+ description: "The bash command to execute",
34
+ },
35
+ },
36
+ required: ["command"],
37
+ },
38
+ },
39
+ },
40
+ execute: async ({ command }, { askConfirm, unhinged, cwd, setCwd } = {}) => {
41
+ if (!unhinged && isDangerous(command) && askConfirm) {
42
+ const ok = await askConfirm(command);
43
+ if (!ok)
44
+ return "The user rejected this command and does not want it to run. Do not retry it or try a workaround. Acknowledge the rejection and ask the user what they would like to do instead.";
45
+ }
46
+
47
+ const startDir = cwd || process.cwd();
48
+ const wrapped = `${command}\nprintf '%s%s' '${PWD_MARKER}' "$(pwd)"`;
49
+
50
+ try {
51
+ const { stdout = "", stderr = "" } = await execAsync(wrapped, {
52
+ cwd: startDir,
53
+ });
54
+ const { output: cleanStdout, newCwd } = parsePwd(stdout);
55
+ if (newCwd && setCwd) setCwd(newCwd);
56
+ return [cleanStdout.trimEnd(), stderr.trimEnd()]
57
+ .filter(Boolean)
58
+ .join("\n")
59
+ .replace(/\s+$/, "");
60
+ } catch (err) {
61
+ const stdout = err.stdout || "";
62
+ const stderr = err.stderr || "";
63
+ const { output: cleanStdout, newCwd } = parsePwd(stdout);
64
+ if (newCwd && setCwd) setCwd(newCwd);
65
+ const body = [cleanStdout.trimEnd(), stderr.trimEnd()]
66
+ .filter(Boolean)
67
+ .join("\n");
68
+ return `Error (exit ${err.code ?? "?"}): ${err.message}\n${body}`.replace(
69
+ /\s+$/,
70
+ ""
71
+ );
72
+ }
73
+ },
74
+ };
@@ -0,0 +1,30 @@
1
+ import { readdirSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+
5
+ const here = dirname(fileURLToPath(import.meta.url));
6
+
7
+ const folders = readdirSync(here, { withFileTypes: true })
8
+ .filter((d) => d.isDirectory())
9
+ .map((d) => d.name);
10
+
11
+ const registry = {};
12
+ for (const folder of folders) {
13
+ const mod = await import(pathToFileURL(join(here, folder, "tool.js")).href);
14
+ const tool = mod.default;
15
+ registry[tool.definition.function.name] = tool;
16
+ }
17
+
18
+ export const tools = Object.values(registry).map((t) => t.definition);
19
+
20
+ export async function executeTool(name, rawArgs, context = {}) {
21
+ const tool = registry[name];
22
+ if (!tool) return `Error: unknown tool "${name}"`;
23
+ let args;
24
+ try {
25
+ args = typeof rawArgs === "string" ? JSON.parse(rawArgs || "{}") : rawArgs || {};
26
+ } catch (err) {
27
+ return `Error: invalid arguments JSON: ${err.message}`;
28
+ }
29
+ return tool.execute(args, context);
30
+ }
@@ -0,0 +1,13 @@
1
+ import { execSync } from "node:child_process";
2
+
3
+ export function tryRun(cmd, timeout = 1500) {
4
+ try {
5
+ return execSync(cmd, {
6
+ encoding: "utf8",
7
+ stdio: ["ignore", "pipe", "pipe"],
8
+ timeout,
9
+ }).trim();
10
+ } catch {
11
+ return null;
12
+ }
13
+ }
@@ -0,0 +1,47 @@
1
+ import { marked } from "marked";
2
+ import { markedTerminal } from "marked-terminal";
3
+ import chalk from "chalk";
4
+
5
+ const ext = markedTerminal({
6
+ codespan: chalk.bold.white,
7
+ text: chalk.white,
8
+ });
9
+
10
+ // Patch: marked-terminal v7's text renderer ignores nested inline tokens
11
+ // (codespan, strong, em, link, etc.) when they appear inside list items.
12
+ // Force it to parse them inline so styling works in lists too.
13
+ const origText = ext.renderer.text;
14
+ ext.renderer.text = function (token) {
15
+ if (
16
+ typeof token === "object" &&
17
+ Array.isArray(token.tokens) &&
18
+ token.tokens.length > 0
19
+ ) {
20
+ return this.parser.parseInline(token.tokens);
21
+ }
22
+ return origText.call(this, token);
23
+ };
24
+
25
+ marked.use(ext);
26
+
27
+ function balanceForStreaming(buffer) {
28
+ let out = buffer;
29
+
30
+ const fences = (out.match(/```/g) || []).length;
31
+ if (fences % 2 === 1) out += "\n```";
32
+
33
+ const bolds = (out.match(/\*\*/g) || []).length;
34
+ if (bolds % 2 === 1) out += "**";
35
+
36
+ const stripped = out.replace(/```[\s\S]*?```/g, "");
37
+ const ticks = (stripped.match(/`/g) || []).length;
38
+ if (ticks % 2 === 1) out += "`";
39
+
40
+ return out;
41
+ }
42
+
43
+ export function renderMarkdown(text, { streaming = false } = {}) {
44
+ if (!text) return "";
45
+ const input = streaming ? balanceForStreaming(text) : text;
46
+ return marked.parse(input).trim();
47
+ }
@@ -0,0 +1,5 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+
4
+ export const ATOM_DIR = join(homedir(), ".atom");
5
+ export const SERVICE_FILE = join(ATOM_DIR, "novaqore-ai-service.json");
@@ -0,0 +1,70 @@
1
+ import { tryRun } from "./exec.js";
2
+
3
+ function detectVersion(cmd, regex) {
4
+ const out = tryRun(cmd);
5
+ if (!out) return "not installed";
6
+ const match = out.match(regex);
7
+ return match ? match[1] : "not installed";
8
+ }
9
+
10
+ function detectPython() {
11
+ const v = detectVersion("python3 --version 2>&1", /Python\s+(\S+)/i);
12
+ if (v !== "not installed") return v;
13
+ return detectVersion("python --version 2>&1", /Python\s+(\S+)/i);
14
+ }
15
+
16
+ const lazySpecs = {
17
+ ruby: ["ruby --version 2>&1", /ruby\s+(\S+)/i],
18
+ go: ["go version 2>&1", /go(\d[\d.]+)/i],
19
+ rust: ["rustc --version 2>&1", /rustc\s+(\S+)/i],
20
+ java: ["java -version 2>&1", /version\s+"([^"]+)"/i],
21
+ php: ["php --version 2>&1", /PHP\s+(\S+)/i],
22
+ deno: ["deno --version 2>&1", /deno\s+(\S+)/i],
23
+ bun: ["bun --version 2>&1", /(\S+)/],
24
+ };
25
+
26
+ const cache = {
27
+ node: process.version,
28
+ python: detectPython(),
29
+ };
30
+
31
+ function readKey(key) {
32
+ if (key in cache) return cache[key];
33
+ if (key in lazySpecs) {
34
+ const [cmd, regex] = lazySpecs[key];
35
+ cache[key] = detectVersion(cmd, regex);
36
+ return cache[key];
37
+ }
38
+ return undefined;
39
+ }
40
+
41
+ const allKeys = ["node", "python", ...Object.keys(lazySpecs)];
42
+
43
+ export function peekRuntimes() {
44
+ return { ...cache };
45
+ }
46
+
47
+ export const RUNTIMES = new Proxy(
48
+ {},
49
+ {
50
+ get(_t, prop) {
51
+ return readKey(prop);
52
+ },
53
+ has(_t, prop) {
54
+ return allKeys.includes(prop);
55
+ },
56
+ ownKeys() {
57
+ return allKeys;
58
+ },
59
+ getOwnPropertyDescriptor(_t, prop) {
60
+ if (allKeys.includes(prop)) {
61
+ return {
62
+ enumerable: true,
63
+ configurable: true,
64
+ value: readKey(prop),
65
+ };
66
+ }
67
+ return undefined;
68
+ },
69
+ }
70
+ );
@@ -0,0 +1,89 @@
1
+ import { release, arch, cpus, totalmem } from "node:os";
2
+ import { peekRuntimes } from "./runtimes.js";
3
+ import {
4
+ HOSTNAME,
5
+ USERNAME,
6
+ IS_ROOT,
7
+ DISK,
8
+ peekGPUs,
9
+ peekPackageManagers,
10
+ } from "./system.js";
11
+
12
+ function cpuLine() {
13
+ const list = cpus();
14
+ if (!list || list.length === 0) return "CPU: unknown";
15
+ const model = list[0].model.trim();
16
+ const speed = list[0].speed;
17
+ if (!speed) return `CPU: ${list.length} cores (${model})`;
18
+ return `CPU: ${list.length} cores @ ${(speed / 1000).toFixed(2)} GHz (${model})`;
19
+ }
20
+
21
+ function memoryLine() {
22
+ const gb = totalmem() / 1024 ** 3;
23
+ return `Memory: ${gb.toFixed(1)} GB`;
24
+ }
25
+
26
+ function diskLine() {
27
+ if (!DISK) return null;
28
+ const totalGB = (DISK.total / 1024 ** 3).toFixed(1);
29
+ const usedGB = (DISK.used / 1024 ** 3).toFixed(1);
30
+ return `Disk: ${usedGB} GB used / ${totalGB} GB total`;
31
+ }
32
+
33
+ function gpuLine() {
34
+ const gpus = peekGPUs();
35
+ if (gpus === null) return null;
36
+ if (gpus.length === 0) return null;
37
+ return `GPU: ${gpus.join(", ")}`;
38
+ }
39
+
40
+ function runtimesLine() {
41
+ const cached = peekRuntimes();
42
+ const installed = Object.entries(cached)
43
+ .filter(([, v]) => v && v !== "not installed")
44
+ .map(([k, v]) => `${k} ${v}`);
45
+ if (installed.length === 0) return null;
46
+ return `Runtimes: ${installed.join(", ")}`;
47
+ }
48
+
49
+ function packageManagerLines() {
50
+ const pms = peekPackageManagers();
51
+ if (pms === null) return [];
52
+ const out = [];
53
+ if (pms.system.length > 0)
54
+ out.push(`Package managers (system): ${pms.system.join(", ")}`);
55
+ if (pms.language.length > 0)
56
+ out.push(`Package managers (language): ${pms.language.join(", ")}`);
57
+ if (pms.container.length > 0)
58
+ out.push(`Package managers (container): ${pms.container.join(", ")}`);
59
+ return out;
60
+ }
61
+
62
+ export function buildSystemPrompt() {
63
+ const env = [
64
+ `Hostname: ${HOSTNAME}`,
65
+ `User: ${USERNAME}${IS_ROOT ? " (root)" : ""}`,
66
+ `Platform: ${process.platform} (${release()}, ${arch()})`,
67
+ `Shell: ${process.env.SHELL || "unknown"}`,
68
+ `Working directory: ${process.cwd()}`,
69
+ cpuLine(),
70
+ memoryLine(),
71
+ diskLine(),
72
+ gpuLine(),
73
+ runtimesLine(),
74
+ ...packageManagerLines(),
75
+ ]
76
+ .filter(Boolean)
77
+ .join("\n");
78
+
79
+ return `You are Atom, a full-system code agent. You run on the server you are operating (typically an isolated server or VM), with shell access at the system level. You can read, write, and execute anywhere your process has permission, including system paths and service configuration.
80
+
81
+ You help with software engineering tasks across the whole machine: writing and debugging code, refactoring, explaining unfamiliar code, reviewing changes, configuring services, and operating on system files when the user asks. You favor clear, idiomatic solutions over clever ones, and you keep your responses concise.
82
+
83
+ You have access to tools when they help you complete a task. Use them when running a command or inspecting the environment is the most direct way to answer.
84
+
85
+ Always use structured sentences. Do not use em dashes or dashes in your writing.
86
+
87
+ Environment:
88
+ ${env}`;
89
+ }
@@ -0,0 +1,106 @@
1
+ import { hostname, userInfo } from "node:os";
2
+ import { tryRun } from "./exec.js";
3
+
4
+ export const HOSTNAME = hostname();
5
+ export const USERNAME = userInfo().username;
6
+ export const IS_ROOT =
7
+ typeof process.getuid === "function" && process.getuid() === 0;
8
+
9
+ function detectGPUs() {
10
+ if (process.platform === "darwin") {
11
+ const out = tryRun("system_profiler SPDisplaysDataType 2>/dev/null", 4000);
12
+ if (!out) return [];
13
+ return [...out.matchAll(/^\s*Chipset Model:\s+(.+)$/gm)].map((m) =>
14
+ m[1].trim()
15
+ );
16
+ }
17
+ if (process.platform === "linux") {
18
+ const nvidia = tryRun(
19
+ "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null",
20
+ 2000
21
+ );
22
+ if (nvidia) {
23
+ return nvidia
24
+ .split("\n")
25
+ .map((s) => s.trim())
26
+ .filter(Boolean);
27
+ }
28
+ const lspci = tryRun("lspci 2>/dev/null", 1500);
29
+ if (!lspci) return [];
30
+ return lspci
31
+ .split("\n")
32
+ .filter((l) => /VGA|3D controller|Display controller/i.test(l))
33
+ .map((l) => {
34
+ const m = l.match(
35
+ /(?:VGA compatible controller|3D controller|Display controller):\s+(.+)/i
36
+ );
37
+ return m ? m[1].trim() : l;
38
+ });
39
+ }
40
+ return [];
41
+ }
42
+
43
+ let _gpus = null;
44
+ export function getGPUs() {
45
+ if (_gpus === null) _gpus = detectGPUs();
46
+ return _gpus;
47
+ }
48
+ export function peekGPUs() {
49
+ return _gpus;
50
+ }
51
+
52
+ const PM_GROUPS = {
53
+ system: ["apt", "apt-get", "dnf", "yum", "pacman", "apk", "brew", "zypper"],
54
+ language: [
55
+ "npm",
56
+ "pnpm",
57
+ "yarn",
58
+ "pip3",
59
+ "pip",
60
+ "pipx",
61
+ "poetry",
62
+ "gem",
63
+ "bundler",
64
+ "cargo",
65
+ "composer",
66
+ "maven",
67
+ "gradle",
68
+ ],
69
+ container: ["docker", "podman"],
70
+ };
71
+
72
+ function detectInGroup(group) {
73
+ return group.filter((pm) => tryRun(`command -v ${pm}`));
74
+ }
75
+
76
+ let _pms = null;
77
+ export function getPackageManagers() {
78
+ if (_pms === null) {
79
+ _pms = {
80
+ system: detectInGroup(PM_GROUPS.system),
81
+ language: detectInGroup(PM_GROUPS.language),
82
+ container: detectInGroup(PM_GROUPS.container),
83
+ };
84
+ }
85
+ return _pms;
86
+ }
87
+ export function peekPackageManagers() {
88
+ return _pms;
89
+ }
90
+
91
+ function detectDisk() {
92
+ const out = tryRun("df -kP .");
93
+ if (!out) return null;
94
+ const lines = out.split("\n");
95
+ if (lines.length < 2) return null;
96
+ const fields = lines[lines.length - 1].split(/\s+/);
97
+ const totalKB = parseInt(fields[1], 10);
98
+ const usedKB = parseInt(fields[2], 10);
99
+ if (isNaN(totalKB) || isNaN(usedKB)) return null;
100
+ return {
101
+ total: totalKB * 1024,
102
+ used: usedKB * 1024,
103
+ };
104
+ }
105
+
106
+ export const DISK = detectDisk();