opencode-sysinfo 1.0.0 → 1.0.2

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.
Files changed (3) hide show
  1. package/dist/tui.tsx +104 -0
  2. package/dist/utils.ts +32 -0
  3. package/package.json +18 -6
package/dist/tui.tsx ADDED
@@ -0,0 +1,104 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui";
3
+ import { hostname, freemem, totalmem, cpus } from "node:os";
4
+ import path from "node:path";
5
+ import { readFileSync, existsSync } from "node:fs";
6
+ import { createSignal, createMemo, onCleanup } from "solid-js";
7
+ import { calcCpuPercent, sparkline, formatDuration } from "./utils";
8
+
9
+ const HISTORY_SIZE = 20;
10
+
11
+ function sampleCpuTimes() {
12
+ return cpus().map((c) => ({
13
+ idle: c.times.idle,
14
+ total: Object.values(c.times).reduce((a, b) => a + b, 0),
15
+ }));
16
+ }
17
+
18
+ function SessionDuration(props: { api: TuiPluginApi; session_id: string }) {
19
+ const [dur, setDur] = createSignal("");
20
+
21
+ const timer = setInterval(() => {
22
+ const session = props.api.state.session.get(props.session_id);
23
+ if (!session?.time?.created) return;
24
+ const ms = Date.now() - session.time.created;
25
+ setDur(formatDuration(ms));
26
+ }, 1000);
27
+
28
+ onCleanup(() => clearInterval(timer));
29
+
30
+ const theme = () => props.api.theme.current;
31
+ return <text fg={theme().info}>Session: {dur()}</text>;
32
+ }
33
+
34
+ const tui: TuiPlugin = async (api) => {
35
+ const host = hostname();
36
+ const project = path.basename(process.cwd());
37
+ const cores = cpus().length;
38
+
39
+ const [cpuPct, setCpuPct] = createSignal(0);
40
+ const [history, setHistory] = createSignal<number[]>([]);
41
+ const [ramUsed, setRamUsed] = createSignal("0");
42
+ const [ramTotal, setRamTotal] = createSignal("0");
43
+ const [batPct, setBatPct] = createSignal<string | null>(null);
44
+ const [batStatus, setBatStatus] = createSignal<string | null>(null);
45
+ const [blinkOn, setBlinkOn] = createSignal(false);
46
+
47
+ function readBattery() {
48
+ const bat = "/sys/class/power_supply/BAT0";
49
+ if (!existsSync(bat)) return;
50
+ try {
51
+ setBatPct(readFileSync(`${bat}/capacity`, "utf8").trim());
52
+ setBatStatus(readFileSync(`${bat}/status`, "utf8").trim());
53
+ } catch {}
54
+ }
55
+
56
+ let prev = sampleCpuTimes();
57
+
58
+ readBattery();
59
+
60
+ const timer = setInterval(() => {
61
+ const curr = sampleCpuTimes();
62
+ const pct = calcCpuPercent(prev, curr);
63
+ setCpuPct(pct);
64
+ setHistory((h) => [...h.slice(-(HISTORY_SIZE - 1)), pct]);
65
+ setRamUsed(((totalmem() - freemem()) / 1024 ** 3).toFixed(1));
66
+ setRamTotal((totalmem() / 1024 ** 3).toFixed(1));
67
+ readBattery();
68
+ prev = curr;
69
+ }, 2000);
70
+
71
+ onCleanup(() => clearInterval(timer));
72
+
73
+ const blinkTimer = setInterval(() => setBlinkOn((b) => !b), 500);
74
+ onCleanup(() => clearInterval(blinkTimer));
75
+
76
+ const border = createMemo(() => {
77
+ const pct = batPct();
78
+ if (!pct || parseInt(pct) >= 20) return undefined;
79
+ return blinkOn() ? "red" : undefined;
80
+ });
81
+
82
+ const spark = createMemo(() => sparkline(history()));
83
+
84
+ api.slots.register({
85
+ order: 10,
86
+ slots: {
87
+ sidebar_content(_ctx, props) {
88
+ return (
89
+ <box borderStyle="single" borderColor={border()} title="🖥 Env Info">
90
+ <text fg="cyan">Machine: {host}</text>
91
+ <text fg="green">Project: {project}</text>
92
+ <text fg="yellow">RAM: {ramUsed()}/{ramTotal()} GB</text>
93
+ {batPct() && <text fg={batStatus() === "Charging" ? "green" : "yellow"}>BAT: {batPct()}%{batStatus() === "Charging" ? " ⚡" : ""}</text>}
94
+ <text fg="magenta">CPU: {cpuPct()}% ({cores} cores)</text>
95
+ <text fg="magenta">{spark()}</text>
96
+ <SessionDuration api={api} session_id={props.session_id} />
97
+ </box>
98
+ );
99
+ },
100
+ },
101
+ });
102
+ };
103
+
104
+ export default { id: "sidebar-info", tui };
package/dist/utils.ts ADDED
@@ -0,0 +1,32 @@
1
+ export const SPARK_CHARS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
2
+
3
+ export function calcCpuPercent(
4
+ prev: { idle: number; total: number }[],
5
+ curr: { idle: number; total: number }[],
6
+ ): number {
7
+ const deltas = curr.map((s, i) => ({
8
+ idle: s.idle - prev[i].idle,
9
+ total: s.total - prev[i].total,
10
+ }));
11
+ const totalIdle = deltas.reduce((s, d) => s + d.idle, 0);
12
+ const totalAll = deltas.reduce((s, d) => s + d.total, 0);
13
+ return totalAll === 0 ? 0 : Math.round((1 - totalIdle / totalAll) * 100);
14
+ }
15
+
16
+ export function sparkline(values: number[]): string {
17
+ return values
18
+ .map((v) => {
19
+ const clamped = Math.max(0, Math.min(100, v));
20
+ return SPARK_CHARS[Math.round((clamped / 100) * (SPARK_CHARS.length - 1))];
21
+ })
22
+ .join("");
23
+ }
24
+
25
+ export function formatDuration(ms: number): string {
26
+ const s = Math.floor(ms / 1000);
27
+ if (s < 60) return `${s}s`;
28
+ const m = Math.floor(s / 60);
29
+ if (m < 60) return `${m}m ${s % 60}s`;
30
+ const h = Math.floor(m / 60);
31
+ return `${h}h ${m % 60}m`;
32
+ }
package/package.json CHANGED
@@ -1,15 +1,21 @@
1
1
  {
2
2
  "name": "opencode-sysinfo",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "OpenCode TUI plugin that shows system info (hostname, RAM, CPU, battery) in the sidebar",
5
5
  "type": "module",
6
6
  "exports": {
7
- ".": { "import": "./dist/tui.js" },
8
- "./tui": { "import": "./dist/tui.js" }
7
+ ".": {
8
+ "import": "./dist/tui.tsx"
9
+ },
10
+ "./tui": {
11
+ "import": "./dist/tui.tsx"
12
+ }
9
13
  },
10
- "files": ["dist"],
14
+ "files": [
15
+ "dist"
16
+ ],
11
17
  "scripts": {
12
- "build": "tsup",
18
+ "build": "tsup && cp src/tui.tsx src/utils.ts dist/",
13
19
  "test": "bun test",
14
20
  "prepublishOnly": "bun run build"
15
21
  },
@@ -29,6 +35,12 @@
29
35
  "tsup": "^8.0.0",
30
36
  "typescript": "^5.8.0"
31
37
  },
32
- "keywords": ["opencode", "plugin", "tui", "sidebar", "system-info"],
38
+ "keywords": [
39
+ "opencode",
40
+ "plugin",
41
+ "tui",
42
+ "sidebar",
43
+ "system-info"
44
+ ],
33
45
  "license": "MIT"
34
46
  }