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.
- package/README.md +54 -0
- package/dist/tui.js +150 -0
- 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
|
+
}
|