opencode-sysinfo 1.0.0

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/README.md +54 -0
  2. package/dist/tui.js +150 -0
  3. package/package.json +34 -0
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # opencode-sysinfo
2
+
3
+ OpenCode TUI plugin that shows system info in the sidebar: hostname, project, RAM, CPU (with sparkline), battery status, and session duration.
4
+
5
+ ## Motivation
6
+
7
+ I work on multiple laptops β€” home, office, client sites β€” all accessed via SSH from a single terminal. Switching between them, especially on the same project, it's easy to lose track of where you are. A quick `uname -n` helps, but it breaks flow.
8
+
9
+ This plugin puts the hostname, project directory, resource usage, and battery level right in the OpenCode sidebar. At a glance, you know which machine you're on, how it's doing, and how long you've been connected. No more wondering "am I on the office laptop or the client one?"
10
+
11
+ ## Example
12
+
13
+ ```
14
+ β”Œβ”€ πŸ–₯ Env Info ──────────────┐
15
+ β”‚ Machine: thinkpad-x1 β”‚
16
+ β”‚ Project: your-project-nameβ”‚
17
+ β”‚ RAM: 4.2/15.6 GB β”‚
18
+ β”‚ BAT: 67% ⚑ β”‚
19
+ β”‚ CPU: 14% (12 cores) β”‚
20
+ β”‚ β–β–ƒβ–‚β–β–β–‚β–„β–†β–ˆβ–ˆβ–ˆβ–‡β–†β–„β–ƒβ–‚β–β–β–‚β–ƒβ–„ β”‚
21
+ β”‚ Session: 2h 15m β”‚
22
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
23
+ ```
24
+
25
+ ## Installation
26
+
27
+ Add to your `tui.json` (project root or `~/.config/opencode/tui.json`):
28
+
29
+ ```json
30
+ {
31
+ "plugin": ["opencode-sysinfo"]
32
+ }
33
+ ```
34
+
35
+ OpenCode will auto-install the package from npm on next startup.
36
+
37
+ ## Requirements
38
+
39
+ - Linux (tested). Battery reading requires `/sys/class/power_supply/BAT0`.
40
+ - OpenCode with TUI support.
41
+
42
+ ## Development
43
+
44
+ ```bash
45
+ git clone <repo>
46
+ cd opencode-sysinfo
47
+ bun install
48
+ bun test # run tests (20 tests)
49
+ bun run build # build dist/
50
+ ```
51
+
52
+ ## License
53
+
54
+ MIT
package/dist/tui.js ADDED
@@ -0,0 +1,150 @@
1
+ // src/tui.tsx
2
+ import { template as _$template } from "solid-js/web";
3
+ import { createComponent as _$createComponent } from "solid-js/web";
4
+ import { memo as _$memo } from "solid-js/web";
5
+ import { setAttribute as _$setAttribute } from "solid-js/web";
6
+ import { effect as _$effect } from "solid-js/web";
7
+ import { insert as _$insert } from "solid-js/web";
8
+ import { hostname, freemem, totalmem, cpus } from "os";
9
+ import path from "path";
10
+ import { readFileSync, existsSync } from "fs";
11
+ import { createSignal, createMemo, onCleanup } from "solid-js";
12
+
13
+ // src/utils.ts
14
+ var SPARK_CHARS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
15
+ function calcCpuPercent(prev, curr) {
16
+ const deltas = curr.map((s, i) => ({
17
+ idle: s.idle - prev[i].idle,
18
+ total: s.total - prev[i].total
19
+ }));
20
+ const totalIdle = deltas.reduce((s, d) => s + d.idle, 0);
21
+ const totalAll = deltas.reduce((s, d) => s + d.total, 0);
22
+ return totalAll === 0 ? 0 : Math.round((1 - totalIdle / totalAll) * 100);
23
+ }
24
+ function sparkline(values) {
25
+ return values.map((v) => {
26
+ const clamped = Math.max(0, Math.min(100, v));
27
+ return SPARK_CHARS[Math.round(clamped / 100 * (SPARK_CHARS.length - 1))];
28
+ }).join("");
29
+ }
30
+ function formatDuration(ms) {
31
+ const s = Math.floor(ms / 1e3);
32
+ if (s < 60) return `${s}s`;
33
+ const m = Math.floor(s / 60);
34
+ if (m < 60) return `${m}m ${s % 60}s`;
35
+ const h = Math.floor(m / 60);
36
+ return `${h}h ${m % 60}m`;
37
+ }
38
+
39
+ // src/tui.tsx
40
+ var _tmpl$ = /* @__PURE__ */ _$template(`<svg><text>Session: </svg>`, false, true, false);
41
+ var _tmpl$2 = /* @__PURE__ */ _$template(`<box borderstyle=single title="\u{1F5A5} Env Info"><text fg=cyan>Machine: </text><text fg=green>Project: </text><text fg=yellow>RAM: <!>/<!> GB</text><text fg=magenta>CPU: <!>% (<!> cores)</text><text fg=magenta>`);
42
+ var _tmpl$3 = /* @__PURE__ */ _$template(`<svg><text>BAT: <!>%</svg>`, false, true, false);
43
+ var HISTORY_SIZE = 20;
44
+ function sampleCpuTimes() {
45
+ return cpus().map((c) => ({
46
+ idle: c.times.idle,
47
+ total: Object.values(c.times).reduce((a, b) => a + b, 0)
48
+ }));
49
+ }
50
+ function SessionDuration(props) {
51
+ const [dur, setDur] = createSignal("");
52
+ const timer = setInterval(() => {
53
+ const session = props.api.state.session.get(props.session_id);
54
+ if (!session?.time?.created) return;
55
+ const ms = Date.now() - session.time.created;
56
+ setDur(formatDuration(ms));
57
+ }, 1e3);
58
+ onCleanup(() => clearInterval(timer));
59
+ const theme = () => props.api.theme.current;
60
+ return (() => {
61
+ var _el$ = _tmpl$(), _el$2 = _el$.firstChild;
62
+ _$insert(_el$, dur, null);
63
+ _$effect(() => _$setAttribute(_el$, "fg", theme().info));
64
+ return _el$;
65
+ })();
66
+ }
67
+ var tui = async (api) => {
68
+ const host = hostname();
69
+ const project = path.basename(process.cwd());
70
+ const cores = cpus().length;
71
+ const [cpuPct, setCpuPct] = createSignal(0);
72
+ const [history, setHistory] = createSignal([]);
73
+ const [ramUsed, setRamUsed] = createSignal("0");
74
+ const [ramTotal, setRamTotal] = createSignal("0");
75
+ const [batPct, setBatPct] = createSignal(null);
76
+ const [batStatus, setBatStatus] = createSignal(null);
77
+ const [blinkOn, setBlinkOn] = createSignal(false);
78
+ function readBattery() {
79
+ const bat = "/sys/class/power_supply/BAT0";
80
+ if (!existsSync(bat)) return;
81
+ try {
82
+ setBatPct(readFileSync(`${bat}/capacity`, "utf8").trim());
83
+ setBatStatus(readFileSync(`${bat}/status`, "utf8").trim());
84
+ } catch {
85
+ }
86
+ }
87
+ let prev = sampleCpuTimes();
88
+ readBattery();
89
+ const timer = setInterval(() => {
90
+ const curr = sampleCpuTimes();
91
+ const pct = calcCpuPercent(prev, curr);
92
+ setCpuPct(pct);
93
+ setHistory((h) => [...h.slice(-(HISTORY_SIZE - 1)), pct]);
94
+ setRamUsed(((totalmem() - freemem()) / 1024 ** 3).toFixed(1));
95
+ setRamTotal((totalmem() / 1024 ** 3).toFixed(1));
96
+ readBattery();
97
+ prev = curr;
98
+ }, 2e3);
99
+ onCleanup(() => clearInterval(timer));
100
+ const blinkTimer = setInterval(() => setBlinkOn((b) => !b), 500);
101
+ onCleanup(() => clearInterval(blinkTimer));
102
+ const border = createMemo(() => {
103
+ const pct = batPct();
104
+ if (!pct || parseInt(pct) >= 20) return void 0;
105
+ return blinkOn() ? "red" : void 0;
106
+ });
107
+ const spark = createMemo(() => sparkline(history()));
108
+ api.slots.register({
109
+ order: 10,
110
+ slots: {
111
+ sidebar_content(_ctx, props) {
112
+ return (() => {
113
+ var _el$3 = _tmpl$2(), _el$4 = _el$3.firstChild, _el$5 = _el$4.firstChild, _el$6 = _el$4.nextSibling, _el$7 = _el$6.firstChild, _el$8 = _el$6.nextSibling, _el$9 = _el$8.firstChild, _el$10 = _el$9.nextSibling, _el$0 = _el$10.nextSibling, _el$11 = _el$0.nextSibling, _el$1 = _el$11.nextSibling, _el$12 = _el$8.nextSibling, _el$13 = _el$12.firstChild, _el$16 = _el$13.nextSibling, _el$14 = _el$16.nextSibling, _el$17 = _el$14.nextSibling, _el$15 = _el$17.nextSibling, _el$18 = _el$12.nextSibling;
114
+ _$insert(_el$4, host, null);
115
+ _$insert(_el$6, project, null);
116
+ _$insert(_el$8, ramUsed, _el$10);
117
+ _$insert(_el$8, ramTotal, _el$11);
118
+ _$insert(_el$3, (() => {
119
+ var _c$ = _$memo(() => !!batPct());
120
+ return () => _c$() && (() => {
121
+ var _el$19 = _tmpl$3(), _el$20 = _el$19.firstChild, _el$22 = _el$20.nextSibling, _el$21 = _el$22.nextSibling;
122
+ _$insert(_el$19, batPct, _el$22);
123
+ _$insert(_el$19, () => batStatus() === "Charging" ? " \u26A1" : "", null);
124
+ _$effect(() => _$setAttribute(_el$19, "fg", batStatus() === "Charging" ? "green" : "yellow"));
125
+ return _el$19;
126
+ })();
127
+ })(), _el$12);
128
+ _$insert(_el$12, cpuPct, _el$16);
129
+ _$insert(_el$12, cores, _el$17);
130
+ _$insert(_el$18, spark);
131
+ _$insert(_el$3, _$createComponent(SessionDuration, {
132
+ api,
133
+ get session_id() {
134
+ return props.session_id;
135
+ }
136
+ }), null);
137
+ _$effect(() => _$setAttribute(_el$3, "bordercolor", border()));
138
+ return _el$3;
139
+ })();
140
+ }
141
+ }
142
+ });
143
+ };
144
+ var tui_default = {
145
+ id: "sidebar-info",
146
+ tui
147
+ };
148
+ export {
149
+ tui_default as default
150
+ };
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "opencode-sysinfo",
3
+ "version": "1.0.0",
4
+ "description": "OpenCode TUI plugin that shows system info (hostname, RAM, CPU, battery) in the sidebar",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": { "import": "./dist/tui.js" },
8
+ "./tui": { "import": "./dist/tui.js" }
9
+ },
10
+ "files": ["dist"],
11
+ "scripts": {
12
+ "build": "tsup",
13
+ "test": "bun test",
14
+ "prepublishOnly": "bun run build"
15
+ },
16
+ "peerDependencies": {
17
+ "@opencode-ai/plugin": ">=1.14.0",
18
+ "@opentui/core": ">=0.3.4",
19
+ "@opentui/solid": ">=0.3.4",
20
+ "solid-js": ">=1.9.0"
21
+ },
22
+ "devDependencies": {
23
+ "@opencode-ai/plugin": "^1.17.13",
24
+ "@opentui/core": "^0.4.3",
25
+ "@opentui/solid": "^0.4.3",
26
+ "bun-types": "latest",
27
+ "esbuild-plugin-solid": "^0.6.0",
28
+ "solid-js": "^1.9.0",
29
+ "tsup": "^8.0.0",
30
+ "typescript": "^5.8.0"
31
+ },
32
+ "keywords": ["opencode", "plugin", "tui", "sidebar", "system-info"],
33
+ "license": "MIT"
34
+ }