src/stats.ts 4.5 K raw
1
import { $ } from "bun";
2
3
export async function getSystemStats() {
4
	const stats: Record<string, string> = {};
5
6
	try {
7
		// OS Name
8
		const osName = await $`uname -s`.text();
9
		stats.os = osName.trim();
10
		const isLinux = stats.os === "Linux";
11
12
		// OS Version
13
		const osVersion = await $`uname -r`.text();
14
		stats.version = osVersion.trim();
15
16
		// Architecture
17
		const arch = await $`uname -m`.text();
18
		stats.arch = arch.trim();
19
20
		// Uptime
21
		if (isLinux) {
22
			const uptimeData = await $`cat /proc/uptime`.text();
23
			const uptimeSeconds = Math.floor(
24
				Number.parseFloat(uptimeData.split(" ")[0]),
25
			);
26
			const days = Math.floor(uptimeSeconds / 86400);
27
			const hours = Math.floor((uptimeSeconds % 86400) / 3600);
28
			const minutes = Math.floor((uptimeSeconds % 3600) / 60);
29
			stats.uptime = `${days}d ${hours}h ${minutes}m`;
30
		} else {
31
			const uptimeSeconds = await $`sysctl -n kern.boottime`.text();
32
			const bootMatch = uptimeSeconds.match(/sec = (\d+)/);
33
			if (bootMatch) {
34
				const bootTime = Number.parseInt(bootMatch[1]);
35
				const now = Math.floor(Date.now() / 1000);
36
				const uptime = now - bootTime;
37
				const days = Math.floor(uptime / 86400);
38
				const hours = Math.floor((uptime % 86400) / 3600);
39
				const minutes = Math.floor((uptime % 3600) / 60);
40
				stats.uptime = `${days}d ${hours}h ${minutes}m`;
41
			}
42
		}
43
44
		// CPU Info
45
		if (isLinux) {
46
			const cpuInfo = await $`grep "model name" /proc/cpuinfo`.text();
47
			const firstCpu = cpuInfo.split("\n")[0];
48
			let cpuBrand = firstCpu.split(":")[1]?.trim() || "Unknown";
49
			// Shorten common CPU names
50
			cpuBrand = cpuBrand.replace(/ w\/ Radeon.*$/i, ""); // Remove Radeon graphics info
51
			cpuBrand = cpuBrand.replace(/ with Radeon.*$/i, "");
52
			cpuBrand = cpuBrand.replace(/\(TM\)/g, "").replace(/\(R\)/g, ""); // Remove trademark symbols
53
			cpuBrand = cpuBrand.replace(/\s+/g, " ").trim(); // Clean up extra spaces
54
			stats.cpu = cpuBrand;
55
		} else {
56
			const cpuBrand = await $`sysctl -n machdep.cpu.brand_string`.text();
57
			stats.cpu = cpuBrand.trim();
58
		}
59
60
		// CPU Cores
61
		if (isLinux) {
62
			const cpuCores = await $`nproc`.text();
63
			stats.cores = cpuCores.trim();
64
		} else {
65
			const cpuCores = await $`sysctl -n hw.ncpu`.text();
66
			stats.cores = cpuCores.trim();
67
		}
68
69
		// Memory
70
		if (isLinux) {
71
			const memInfo = await $`grep MemTotal /proc/meminfo`.text();
72
			const memKB = Number.parseInt(memInfo.split(/\s+/)[1]);
73
			const memGB = (memKB / 1024 / 1024).toFixed(1);
74
			stats.memory = `${memGB} GB`;
75
		} else {
76
			const memBytes = await $`sysctl -n hw.memsize`.text();
77
			const memGB = (
78
				Number.parseInt(memBytes.trim()) /
79
				1024 /
80
				1024 /
81
				1024
82
			).toFixed(1);
83
			stats.memory = `${memGB} GB`;
84
		}
85
86
		// Shell
87
		const shell = process.env.SHELL || "unknown";
88
		stats.shell = shell.split("/").pop() || shell;
89
90
		// Bun version
91
		const bunVersion = Bun.version;
92
		stats.bun = bunVersion;
93
	} catch (error) {
94
		console.error("Error fetching system stats:", error);
95
	}
96
97
	return stats;
98
}
99
100
export function formatStats(stats: Record<string, string>): string {
101
	const labels: Record<string, string> = {
102
		os: "OS",
103
		version: "Kernel",
104
		arch: "Arch",
105
		uptime: "Uptime",
106
		cpu: "CPU",
107
		cores: "Cores",
108
		memory: "Memory",
109
		shell: "Shell",
110
		bun: "Bun",
111
	};
112
113
	// Calculate the actual content width
114
	const maxLabelLength = Math.max(
115
		...Object.values(labels).map((l) => l.length),
116
	);
117
	const maxValueLength = Math.max(...Object.values(stats).map((v) => v.length));
118
119
	const headerText = "message made possible thanks to";
120
	// Content width: "  LABEL : VALUE  " (2 spaces + label + space + colon + space + value + 2 spaces)
121
	const contentWidth = 2 + maxLabelLength + 3 + maxValueLength + 2;
122
	const boxWidth = Math.max(contentWidth, headerText.length + 4);
123
124
	const lines: string[] = [];
125
	lines.push(`     ╭${"─".repeat(boxWidth)}╮`);
126
127
	// Center the header
128
	const headerPadding = Math.floor((boxWidth - headerText.length) / 2);
129
	const headerRightPadding = boxWidth - headerPadding - headerText.length;
130
	lines.push(
131
		`     │${" ".repeat(headerPadding)}${headerText}${" ".repeat(headerRightPadding)}│`,
132
	);
133
	lines.push(`     ├${"─".repeat(boxWidth)}┤`);
134
135
	// Add stats rows
136
	for (const [key, value] of Object.entries(stats)) {
137
		const label = labels[key] || key;
138
		const paddedLabel = label.padEnd(maxLabelLength);
139
		const paddedValue = value.padEnd(maxValueLength);
140
		const content = `  ${paddedLabel} : ${paddedValue}  `;
141
		const rightPadding = boxWidth - content.length;
142
		lines.push(`     │${content}${" ".repeat(rightPadding)}│`);
143
	}
144
145
	lines.push(`     ╰${"─".repeat(boxWidth)}╯`);
146
147
	return lines.join("\n");
148
}