feat: added stats 11f3e4cd
Steve · 2025-11-13 20:55 2 file(s) · +122 −3
src/index.ts +10 −3
1 1
import { Hono } from "hono";
2 +
import { getSystemStats, formatStats } from "./stats";
2 3
3 4
const app = new Hono();
4 5
5 6
app.get("/", async (c) => {
6 7
	const txt = await Bun.file("./src/site.txt").text();
8 +
	const stats = await getSystemStats();
9 +
	const statsBox = formatStats(stats);
10 +
	const fullContent = `${txt}\n\n${statsBox}\n`;
7 11
	const userAgent = c.req.header("User-Agent") || "";
8 12
	console.log(userAgent);
9 13
	const isCrawler = /bot/i.test(userAgent);
28 32
<meta name="twitter:image" content="/og.png">
29 33
</head>
30 34
<body>
31 -
${txt}
35 +
${fullContent}
32 36
</body>
33 37
</html>
34 38
`);
35 39
	}
36 40
37 -
	return c.text(txt);
41 +
	return c.text(fullContent);
38 42
});
39 43
40 44
app.get("/og.png", async (c) => {
46 50
	});
47 51
});
48 52
49 -
export default app;
53 +
export default {
54 +
	port: 4321,
55 +
	fetch: app.fetch,
56 +
};
src/stats.ts (added) +112 −0
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 +
11 +
		// OS Version
12 +
		const osVersion = await $`uname -r`.text();
13 +
		stats.version = osVersion.trim();
14 +
15 +
		// Architecture
16 +
		const arch = await $`uname -m`.text();
17 +
		stats.arch = arch.trim();
18 +
19 +
		// Uptime
20 +
		const uptimeSeconds = await $`sysctl -n kern.boottime`.text();
21 +
		const bootMatch = uptimeSeconds.match(/sec = (\d+)/);
22 +
		if (bootMatch) {
23 +
			const bootTime = Number.parseInt(bootMatch[1]);
24 +
			const now = Math.floor(Date.now() / 1000);
25 +
			const uptime = now - bootTime;
26 +
			const days = Math.floor(uptime / 86400);
27 +
			const hours = Math.floor((uptime % 86400) / 3600);
28 +
			const minutes = Math.floor((uptime % 3600) / 60);
29 +
			stats.uptime = `${days}d ${hours}h ${minutes}m`;
30 +
		}
31 +
32 +
		// CPU Info
33 +
		const cpuBrand = await $`sysctl -n machdep.cpu.brand_string`.text();
34 +
		stats.cpu = cpuBrand.trim();
35 +
36 +
		// CPU Cores
37 +
		const cpuCores = await $`sysctl -n hw.ncpu`.text();
38 +
		stats.cores = cpuCores.trim();
39 +
40 +
		// Memory
41 +
		const memBytes = await $`sysctl -n hw.memsize`.text();
42 +
		const memGB = (
43 +
			Number.parseInt(memBytes.trim()) /
44 +
			1024 /
45 +
			1024 /
46 +
			1024
47 +
		).toFixed(1);
48 +
		stats.memory = `${memGB} GB`;
49 +
50 +
		// Shell
51 +
		const shell = process.env.SHELL || "unknown";
52 +
		stats.shell = shell.split("/").pop() || shell;
53 +
54 +
		// Bun version
55 +
		const bunVersion = Bun.version;
56 +
		stats.bun = bunVersion;
57 +
	} catch (error) {
58 +
		console.error("Error fetching system stats:", error);
59 +
	}
60 +
61 +
	return stats;
62 +
}
63 +
64 +
export function formatStats(stats: Record<string, string>): string {
65 +
	const labels: Record<string, string> = {
66 +
		os: "OS",
67 +
		version: "Kernel",
68 +
		arch: "Arch",
69 +
		uptime: "Uptime",
70 +
		cpu: "CPU",
71 +
		cores: "Cores",
72 +
		memory: "Memory",
73 +
		shell: "Shell",
74 +
		bun: "Bun",
75 +
	};
76 +
77 +
	// Calculate the actual content width
78 +
	const maxLabelLength = Math.max(
79 +
		...Object.values(labels).map((l) => l.length),
80 +
	);
81 +
	const maxValueLength = Math.max(...Object.values(stats).map((v) => v.length));
82 +
83 +
	const headerText = "message made possible thanks to";
84 +
	// Content width: "  LABEL : VALUE  " (2 spaces + label + space + colon + space + value + 2 spaces)
85 +
	const contentWidth = 2 + maxLabelLength + 3 + maxValueLength + 2;
86 +
	const boxWidth = Math.max(contentWidth, headerText.length + 4);
87 +
88 +
	const lines: string[] = [];
89 +
	lines.push(`         ╭${"─".repeat(boxWidth)}╮`);
90 +
91 +
	// Center the header
92 +
	const headerPadding = Math.floor((boxWidth - headerText.length) / 2);
93 +
	const headerRightPadding = boxWidth - headerPadding - headerText.length;
94 +
	lines.push(
95 +
		`         │${" ".repeat(headerPadding)}${headerText}${" ".repeat(headerRightPadding)}│`,
96 +
	);
97 +
	lines.push(`         ├${"─".repeat(boxWidth)}┤`);
98 +
99 +
	// Add stats rows
100 +
	for (const [key, value] of Object.entries(stats)) {
101 +
		const label = labels[key] || key;
102 +
		const paddedLabel = label.padEnd(maxLabelLength);
103 +
		const paddedValue = value.padEnd(maxValueLength);
104 +
		const content = `  ${paddedLabel} : ${paddedValue}  `;
105 +
		const rightPadding = boxWidth - content.length;
106 +
		lines.push(`         │${content}${" ".repeat(rightPadding)}│`);
107 +
	}
108 +
109 +
	lines.push(`         ╰${"─".repeat(boxWidth)}╯`);
110 +
111 +
	return lines.join("\n");
112 +
}