opencode-system-metrics-tui 0.1.1 → 0.1.3

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 CHANGED
@@ -15,11 +15,14 @@ The README is available in **Español**, **English**, and **Português**.
15
15
  - GPU: utilización y temperatura.
16
16
  - GPU VRAM: memoria usada, total y porcentaje.
17
17
  - Red: velocidad de descarga y subida en tiempo real.
18
- - Actualización cada 2 segundos.
18
+ - CPU, RAM y red se consultan con una cadencia general de 2 segundos; GPU, VRAM y temperaturas usan una caché expirable de 10 segundos para reducir el coste sin congelar los sensores.
19
19
  - Colores compatibles con el tema activo de OpenCode.
20
- - Iconos Hack Nerd Font con fallback Unicode.
20
+ - Iconos Unicode por defecto en Windows (la detección de fuentes instaladas no cambia la selección); Linux/macOS usan Nerd Font solo si está instalada y mantienen fallback Unicode.
21
21
  - En Windows usa iconos Unicode visibles de forma predeterminada, porque OpenCode no puede detectar la fuente activa de la terminal.
22
22
  - Soporte para Linux, macOS y Windows cuando el sistema expone las métricas.
23
+ - En Windows, la temperatura de CPU requiere LibreHardwareMonitor ejecutándose como administrador con **Remote Web Server** activo en `http://127.0.0.1:8085`.
24
+ - En Windows, la velocidad de red usa PowerShell y `Get-NetAdapterStatistics`; no agrega una dependencia npm.
25
+ - Las lecturas opcionales de Windows se hacen bajo demanda, con timeout y caché; no hay timers globales de PowerShell.
23
26
 
24
27
  ### Requisitos
25
28
 
@@ -36,7 +39,7 @@ npm run install-font
36
39
  opencode
37
40
  ```
38
41
 
39
- `npm run install-font` instala **Hack Nerd Font** únicamente para el usuario actual. No se ejecuta automáticamente durante `npm install` y solicita confirmación antes de descargar la fuente oficial.
42
+ `npm run install-font` ejecuta el instalador compilado incluido en `dist/`, instala **Hack Nerd Font** únicamente para el usuario actual, no se ejecuta automáticamente durante `npm install` y solicita confirmación antes de descargar la fuente oficial.
40
43
 
41
44
  Comandos adicionales:
42
45
 
@@ -55,7 +58,7 @@ La fuente se instala solo para el usuario actual:
55
58
  - macOS: `~/Library/Fonts`
56
59
  - Windows: `%LOCALAPPDATA%\\Microsoft\\Windows\\Fonts`
57
60
 
58
- Después de instalarla, selecciona **Hack Nerd Font Mono** en la terminal y reinicia la terminal y OpenCode. Si no se selecciona una Nerd Font, el plugin usa iconos Unicode de fallback.
61
+ Después de instalarla, selecciona **Hack Nerd Font Mono** en la terminal y reinicia la terminal y OpenCode. En Windows, para optar explícitamente por iconos Nerd Font define `OPENCODE_MONITOR_NERD_FONT=1`; no se activa solo por detectar archivos instalados.
59
62
 
60
63
  ### Publicación e instalación desde npm
61
64
 
@@ -72,6 +75,8 @@ En otro equipo:
72
75
  opencode plugin opencode-system-metrics-tui
73
76
  ```
74
77
 
78
+ El paquete exporta tanto la raíz (`opencode-system-metrics-tui`) como `./tui`; ambas rutas cargan el mismo bundle TUI.
79
+
75
80
  El instalador de OpenCode configura el plugin TUI automáticamente. Reinicia OpenCode después de instalarlo.
76
81
 
77
82
  Publicar en npm e instalar en OpenCode son pasos distintos:
@@ -99,11 +104,14 @@ npm run build
99
104
  - GPU: utilization and temperature.
100
105
  - GPU VRAM: used memory, total memory, and percentage.
101
106
  - Network: live download and upload speeds.
102
- - Refreshes every 2 seconds.
107
+ - CPU, RAM, and network use the 2-second general cadence; GPU, VRAM, and temperatures use an expiring 10-second cache to reduce cost without freezing sensors.
103
108
  - Uses the active OpenCode theme colors.
104
- - Hack Nerd Font icons with Unicode fallback.
109
+ - Windows uses Unicode icons by default (installed-font detection does not change selection); Linux/macOS use Nerd Font only when installed and retain Unicode fallback.
105
110
  - On Windows, visible Unicode icons are used by default because OpenCode cannot detect the terminal's active font.
106
111
  - Supports Linux, macOS, and Windows when the operating system exposes the metrics.
112
+ - On Windows, CPU temperature requires LibreHardwareMonitor running as administrator with **Remote Web Server** enabled at `http://127.0.0.1:8085`.
113
+ - On Windows, network speed uses PowerShell and `Get-NetAdapterStatistics`; it adds no npm dependency.
114
+ - Optional Windows reads are on demand, timeout-bounded, and cached; no global PowerShell timers are used.
107
115
 
108
116
  ### Requirements
109
117
 
@@ -120,7 +128,7 @@ npm run install-font
120
128
  opencode
121
129
  ```
122
130
 
123
- `npm run install-font` installs **Hack Nerd Font** for the current user only. It is not executed automatically by `npm install` and asks for confirmation before downloading the official font.
131
+ `npm run install-font` runs the compiled installer included in `dist/`, installs **Hack Nerd Font** for the current user only, is not executed automatically by `npm install`, and asks for confirmation before downloading the official font.
124
132
 
125
133
  Extra commands:
126
134
 
@@ -139,7 +147,7 @@ The font is installed for the current user only:
139
147
  - macOS: `~/Library/Fonts`
140
148
  - Windows: `%LOCALAPPDATA%\\Microsoft\\Windows\\Fonts`
141
149
 
142
- After installation, select **Hack Nerd Font Mono** in the terminal and restart the terminal and OpenCode. If a Nerd Font is not selected, the plugin uses Unicode fallback icons.
150
+ After installation, select **Hack Nerd Font Mono** in the terminal and restart the terminal and OpenCode. On Windows, explicitly opt into Nerd Font icons with `OPENCODE_MONITOR_NERD_FONT=1`; installed-font detection alone never enables them.
143
151
 
144
152
  ### Publishing and npm installation
145
153
 
@@ -156,6 +164,8 @@ On another machine:
156
164
  opencode plugin opencode-system-metrics-tui
157
165
  ```
158
166
 
167
+ The package exports both the root (`opencode-system-metrics-tui`) and `./tui`; both paths load the same TUI bundle.
168
+
159
169
  The OpenCode installer configures the TUI plugin automatically. Restart OpenCode after installation.
160
170
 
161
171
  Publishing to npm and installing in OpenCode are separate steps:
@@ -183,11 +193,14 @@ npm run build
183
193
  - GPU: utilização e temperatura.
184
194
  - GPU VRAM: memória usada, total e porcentagem.
185
195
  - Rede: velocidades de download e upload em tempo real.
186
- - Atualização a cada 2 segundos.
196
+ - CPU, RAM e rede usam a cadência geral de 2 segundos; GPU, VRAM e temperaturas usam uma cache expirável de 10 segundos para reduzir o custo sem congelar os sensores.
187
197
  - Usa as cores do tema ativo do OpenCode.
188
- - Ícones Hack Nerd Font com fallback Unicode.
198
+ - No Windows, os ícones Unicode são usados por padrão (detectar fontes instaladas não muda a seleção); Linux/macOS usam Nerd Font apenas quando instalada e mantêm fallback Unicode.
189
199
  - No Windows, ícones Unicode visíveis são usados por padrão porque o OpenCode não consegue detectar a fonte ativa do terminal.
190
200
  - Suporte para Linux, macOS e Windows quando o sistema disponibiliza as métricas.
201
+ - No Windows, a temperatura da CPU requer o LibreHardwareMonitor executado como administrador com o **Remote Web Server** ativo em `http://127.0.0.1:8085`.
202
+ - No Windows, a velocidade da rede usa o PowerShell e `Get-NetAdapterStatistics`; nenhuma dependência npm é adicionada.
203
+ - As leituras opcionais do Windows são sob demanda, têm timeout e cache; não há timers globais do PowerShell.
191
204
 
192
205
  ### Requisitos
193
206
 
@@ -204,7 +217,7 @@ npm run install-font
204
217
  opencode
205
218
  ```
206
219
 
207
- `npm run install-font` instala a **Hack Nerd Font** somente para o usuário atual. O comando não é executado automaticamente durante `npm install` e pede confirmação antes de baixar a fonte oficial.
220
+ `npm run install-font` executa o instalador compilado incluído em `dist/`, instala a **Hack Nerd Font** somente para o usuário atual, não é executado automaticamente durante `npm install` e pede confirmação antes de baixar a fonte oficial.
208
221
 
209
222
  Comandos adicionais:
210
223
 
@@ -223,7 +236,7 @@ A fonte é instalada somente para o usuário atual:
223
236
  - macOS: `~/Library/Fonts`
224
237
  - Windows: `%LOCALAPPDATA%\\Microsoft\\Windows\\Fonts`
225
238
 
226
- Depois da instalação, selecione **Hack Nerd Font Mono** no terminal e reinicie o terminal e o OpenCode. Se uma Nerd Font não estiver selecionada, o plugin usará ícones Unicode de fallback.
239
+ Depois da instalação, selecione **Hack Nerd Font Mono** no terminal e reinicie o terminal e o OpenCode. No Windows, opte explicitamente pelos ícones Nerd Font com `OPENCODE_MONITOR_NERD_FONT=1`; detectar arquivos instalados sozinho nunca os ativa.
227
240
 
228
241
  ### Publicação e instalação pelo npm
229
242
 
@@ -240,6 +253,8 @@ Em outro computador:
240
253
  opencode plugin opencode-system-metrics-tui
241
254
  ```
242
255
 
256
+ O pacote exporta tanto a raiz (`opencode-system-metrics-tui`) quanto `./tui`; os dois caminhos carregam o mesmo bundle TUI.
257
+
243
258
  O instalador do OpenCode configura o plugin TUI automaticamente. Reinicie o OpenCode após a instalação.
244
259
 
245
260
  Publicar no npm e instalar no OpenCode são etapas diferentes:
@@ -0,0 +1,156 @@
1
+ // scripts/install-hack-font.ts
2
+ import { cp, mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { basename, join } from "node:path";
5
+ import { execFile as execFileCallback } from "node:child_process";
6
+ import { promisify } from "node:util";
7
+
8
+ // src/font.ts
9
+ import { existsSync, readdirSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join as posixJoin, win32 } from "node:path";
12
+ var NERD_FONT_FILE_PATTERN = /NerdFont[A-Za-z0-9-]*[-_][A-Za-z0-9-]+\.(?:ttf|otf)$/i;
13
+ function getFontDirectories(platform, home = homedir(), env = process.env) {
14
+ if (platform === "darwin")
15
+ return [posixJoin(home, "Library", "Fonts"), "/Library/Fonts", "/System/Library/Fonts"];
16
+ if (platform === "win32") {
17
+ const localAppData = env.LOCALAPPDATA ?? win32.join(home, "AppData", "Local");
18
+ const windows = env.WINDIR ?? "C:\\Windows";
19
+ return [win32.join(localAppData, "Microsoft", "Windows", "Fonts"), win32.join(windows, "Fonts")];
20
+ }
21
+ return [posixJoin(home, ".local", "share", "fonts"), posixJoin(home, ".fonts"), "/usr/local/share/fonts", "/usr/share/fonts"];
22
+ }
23
+ function isNerdFontFile(fileName) {
24
+ return NERD_FONT_FILE_PATTERN.test(fileName);
25
+ }
26
+ var isHackNerdFontFile = isNerdFontFile;
27
+ function hasNerdFont(platform = process.platform, home = homedir(), env = process.env) {
28
+ const containsFont = (directory) => {
29
+ if (!existsSync(directory))
30
+ return false;
31
+ try {
32
+ return readdirSync(directory, { withFileTypes: true }).some((entry) => {
33
+ const path = platform === "win32" ? win32.join(directory, entry.name) : posixJoin(directory, entry.name);
34
+ return entry.isFile() ? isNerdFontFile(entry.name) : entry.isDirectory() && containsFont(path);
35
+ });
36
+ } catch {
37
+ return false;
38
+ }
39
+ };
40
+ return getFontDirectories(platform, home, env).some(containsFont);
41
+ }
42
+ var hasHackNerdFont = hasNerdFont;
43
+ var nerdFontIcons = {
44
+ title: "\uDB80\uDF79",
45
+ cpu: "\uDB83\uDEE0",
46
+ ram: "\uDB81\uDE1A",
47
+ gpu: "\uDB82\uDCAE",
48
+ vram: "\uDB80\uDF5B",
49
+ thermometer: "",
50
+ network: "\uDB81\uDEF3"
51
+ };
52
+ var unicodeIcons = { title: "▦", cpu: "▣", ram: "▤", gpu: "◆", vram: "◈", thermometer: "♨", network: "⇅" };
53
+ function getMetricIcons(useNerdFont) {
54
+ return useNerdFont ? nerdFontIcons : unicodeIcons;
55
+ }
56
+ function shouldUseNerdFont(platform, detected, env = process.env) {
57
+ if (platform === "win32")
58
+ return env.OPENCODE_MONITOR_NERD_FONT === "1";
59
+ return detected;
60
+ }
61
+
62
+ // scripts/install-hack-font.ts
63
+ var execFile = promisify(execFileCallback);
64
+ var RELEASE_URL = "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/Hack.zip";
65
+ function printHelp() {
66
+ console.log(`Install Hack Nerd Font for the current user.
67
+
68
+ Usage:
69
+ npm run install-font Ask before downloading and installing
70
+ npm run install-font --check Detect only; never downloads or changes files
71
+ npm run install-font --help Show this help
72
+ npm run install-font --yes Skip the download confirmation`);
73
+ }
74
+ async function confirm() {
75
+ if (!process.stdin.isTTY)
76
+ return false;
77
+ process.stdout.write("Download Hack Nerd Font from the official Nerd Fonts release? [y/N] ");
78
+ for await (const chunk of process.stdin)
79
+ return /^y(es)?$/i.test(String(chunk).trim());
80
+ return false;
81
+ }
82
+ async function findFontFiles(directory) {
83
+ const files = [];
84
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
85
+ const path = join(directory, entry.name);
86
+ if (entry.isDirectory())
87
+ files.push(...await findFontFiles(path));
88
+ else if (entry.isFile() && isHackNerdFontFile(entry.name))
89
+ files.push(path);
90
+ }
91
+ return files;
92
+ }
93
+ async function extractZip(zipPath, destination, platform) {
94
+ try {
95
+ if (platform === "win32") {
96
+ await execFile("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", "Expand-Archive -LiteralPath $args[0] -DestinationPath $args[1] -Force", zipPath, destination]);
97
+ } else {
98
+ await execFile("unzip", ["-q", "-o", zipPath, "-d", destination]);
99
+ }
100
+ } catch (error) {
101
+ throw new Error(`Could not extract the ZIP archive. Install unzip (Linux/macOS) or use PowerShell (Windows). ${error instanceof Error ? error.message : String(error)}`);
102
+ }
103
+ }
104
+ async function registerWindowsFonts(fontFiles) {
105
+ const script = "$key='HKCU:\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts'; New-Item -Path $key -Force | Out-Null; foreach ($font in $args) { New-ItemProperty -Path $key -Name ([IO.Path]::GetFileNameWithoutExtension($font)) -Value $font -PropertyType String -Force | Out-Null }";
106
+ await execFile("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script, ...fontFiles]);
107
+ }
108
+ async function install() {
109
+ const platform = process.platform;
110
+ const supportedPlatforms = ["linux", "darwin", "win32"];
111
+ if (!supportedPlatforms.includes(platform))
112
+ throw new Error(`Unsupported platform: ${process.platform}`);
113
+ if (hasHackNerdFont(platform))
114
+ return console.log("Hack Nerd Font is already installed; nothing to do.");
115
+ if (!(process.argv.includes("--yes") || await confirm()))
116
+ return console.log("Installation cancelled. Use --yes to confirm non-interactively.");
117
+ const temporaryDirectory = await mkdtemp(join(tmpdir(), "opencode-monitor-font-"));
118
+ try {
119
+ console.log(`Downloading ${RELEASE_URL}`);
120
+ const response = await fetch(RELEASE_URL);
121
+ if (!response.ok)
122
+ throw new Error(`Download failed with HTTP ${response.status} ${response.statusText}`);
123
+ const zipPath = join(temporaryDirectory, "Hack.zip");
124
+ await writeFile(zipPath, Buffer.from(await response.arrayBuffer()));
125
+ const extractedDirectory = join(temporaryDirectory, "extracted");
126
+ await mkdir(extractedDirectory);
127
+ await extractZip(zipPath, extractedDirectory, platform);
128
+ const fontFiles = await findFontFiles(extractedDirectory);
129
+ if (fontFiles.length === 0)
130
+ throw new Error("The downloaded archive did not contain Hack Nerd Font files.");
131
+ const targetDirectory = getFontDirectories(platform)[0];
132
+ await mkdir(targetDirectory, { recursive: true });
133
+ const installedFiles = fontFiles.map((file) => join(targetDirectory, basename(file)));
134
+ for (let index = 0;index < fontFiles.length; index += 1)
135
+ await cp(fontFiles[index], installedFiles[index], { force: true });
136
+ if (platform === "win32") {
137
+ try {
138
+ await registerWindowsFonts(installedFiles);
139
+ } catch {
140
+ console.warn("Fonts were copied, but Windows font registration failed. Select Hack Nerd Font in the terminal manually.");
141
+ }
142
+ }
143
+ console.log(`Installed ${fontFiles.length} font files in ${targetDirectory}. Select Hack Nerd Font in your terminal and restart OpenCode.`);
144
+ } finally {
145
+ await rm(temporaryDirectory, { recursive: true, force: true });
146
+ }
147
+ }
148
+ if (process.argv.includes("--help"))
149
+ printHelp();
150
+ else if (process.argv.includes("--check"))
151
+ console.log(hasHackNerdFont() ? "Hack Nerd Font is installed." : "Hack Nerd Font is not installed.");
152
+ else
153
+ install().catch((error) => {
154
+ console.error(`Hack Nerd Font installation could not be completed: ${error instanceof Error ? error.message : String(error)}`);
155
+ process.exitCode = 1;
156
+ });
package/dist/tui.js CHANGED
@@ -1,4 +1,3 @@
1
- // @bun
2
1
  // src/tui.tsx
3
2
  import { createComponent as _$createComponent } from "@opentui/solid";
4
3
  import { effect as _$effect } from "@opentui/solid";
@@ -13,19 +12,19 @@ import { TextAttributes } from "@opentui/core";
13
12
  // src/format.ts
14
13
  var BYTES_PER_GIB = 1024 ** 3;
15
14
  function formatPercent(value) {
16
- return value === null || !Number.isFinite(value) ? "\u2014" : `${Math.round(value)}%`;
15
+ return value === null || !Number.isFinite(value) ? "" : `${Math.round(value)}%`;
17
16
  }
18
17
  function formatTemperature(value) {
19
- return value === null || !Number.isFinite(value) ? "\u2014" : `${Math.round(value)}\xB0C`;
18
+ return value === null || !Number.isFinite(value) ? "" : `${Math.round(value)}°C`;
20
19
  }
21
20
  function formatGiB(bytes) {
22
21
  if (bytes === null || !Number.isFinite(bytes))
23
- return "\u2014";
22
+ return "";
24
23
  return `${(bytes / BYTES_PER_GIB).toFixed(1)} GiB`;
25
24
  }
26
25
  function formatRate(bytesPerSecond) {
27
26
  if (bytesPerSecond === null || !Number.isFinite(bytesPerSecond))
28
- return "\u2014";
27
+ return "";
29
28
  const units = ["B/s", "KiB/s", "MiB/s", "GiB/s"];
30
29
  let value = Math.max(0, bytesPerSecond);
31
30
  let unit = 0;
@@ -37,7 +36,51 @@ function formatRate(bytesPerSecond) {
37
36
  }
38
37
 
39
38
  // src/metrics.ts
39
+ import { execFile } from "node:child_process";
40
+ import { promisify } from "node:util";
40
41
  import si from "systeminformation";
42
+ var execFileAsync = promisify(execFile);
43
+ var WINDOWS_NETWORK_TIMEOUT_MS = 1500;
44
+ function parseMonitorTemperature(value) {
45
+ if (value === undefined)
46
+ return;
47
+ const parsed = Number(value.replace(",", ".").replace(/[^\d.-]/g, ""));
48
+ return Number.isFinite(parsed) ? parsed : undefined;
49
+ }
50
+ async function readLibreHardwareMonitorTemperature() {
51
+ const response = await fetch("http://127.0.0.1:8085/data.json", { signal: AbortSignal.timeout(2000) });
52
+ if (!response.ok)
53
+ return;
54
+ const root = await response.json();
55
+ const sensors = [];
56
+ const visit = (node) => {
57
+ if (node.Type === "Temperature" && node.Text !== undefined)
58
+ sensors.push(node);
59
+ for (const child of node.Children ?? [])
60
+ visit(child);
61
+ };
62
+ visit(root);
63
+ const cpuSensor = sensors.find((sensor) => sensor.Text === "CPU Package") ?? sensors.find((sensor) => sensor.Text === "Core Average") ?? sensors.find((sensor) => sensor.Text === "Core Max");
64
+ return parseMonitorTemperature(cpuSensor?.Value);
65
+ }
66
+ function parseWindowsNetworkOutput(stdout) {
67
+ const parsed = JSON.parse(stdout.trim());
68
+ const rows = Array.isArray(parsed) ? parsed : [parsed];
69
+ return rows.map((item) => ({
70
+ iface: item.Name,
71
+ rx_bytes: finite(item.ReceivedBytes) ?? 0,
72
+ tx_bytes: finite(item.SentBytes) ?? 0
73
+ }));
74
+ }
75
+ async function readWindowsNetworkSample() {
76
+ const { stdout } = await execFileAsync("powershell.exe", [
77
+ "-NoProfile",
78
+ "-NonInteractive",
79
+ "-Command",
80
+ "Get-NetAdapterStatistics | Select-Object Name,ReceivedBytes,SentBytes | ConvertTo-Json -Compress"
81
+ ], { windowsHide: true, timeout: WINDOWS_NETWORK_TIMEOUT_MS, maxBuffer: 1024 * 1024 });
82
+ return parseWindowsNetworkOutput(stdout);
83
+ }
41
84
  var previousNetwork;
42
85
  var unavailable = {
43
86
  cpuPercent: null,
@@ -70,62 +113,158 @@ async function withTimeout(promise, timeoutMs) {
70
113
  clearTimeout(timeout);
71
114
  }
72
115
  }
116
+ var SLOW_METRIC_CACHE_MS = 1e4;
117
+ var TEMPERATURE_CACHE_MS = 1e4;
118
+ var OPTIONAL_SOURCE_BACKOFF_MS = 30000;
119
+ var isWindows = process.platform === "win32";
120
+ var NETWORK_CACHE_MS = 2000;
121
+ function selectGpuMetrics(graphics) {
122
+ const empty = {
123
+ gpuPercent: null,
124
+ gpuTemperatureCelsius: null,
125
+ gpuMemoryUsedBytes: null,
126
+ gpuMemoryTotalBytes: null,
127
+ gpuMemoryPercent: null
128
+ };
129
+ if (graphics === undefined)
130
+ return empty;
131
+ const controller = graphics.controllers.map((item) => {
132
+ const utilization = finite(item.utilizationGpu);
133
+ const temperature = finite(item.temperatureGpu);
134
+ const memoryTotal = finite(item.memoryTotal);
135
+ const memoryFree = finite(item.memoryFree);
136
+ const memoryUsed = finite(item.memoryUsed) ?? (memoryTotal !== null && memoryFree !== null ? Math.max(0, memoryTotal - memoryFree) : null);
137
+ const hasMemory = memoryUsed !== null && memoryTotal !== null && memoryTotal > 0;
138
+ return { utilization, temperature, memoryUsed, memoryTotal, hasMemory };
139
+ }).filter(({ utilization, temperature, hasMemory }) => utilization !== null || temperature !== null || hasMemory).sort((left, right) => {
140
+ const score = (value) => (value.hasMemory ? 2 : 0) + (value.utilization !== null ? 1 : 0);
141
+ return score(right) - score(left) || (right.utilization ?? -Infinity) - (left.utilization ?? -Infinity);
142
+ })[0];
143
+ if (controller === undefined)
144
+ return empty;
145
+ const result = {
146
+ gpuPercent: controller.utilization ?? 0,
147
+ gpuTemperatureCelsius: controller.temperature,
148
+ gpuMemoryUsedBytes: null,
149
+ gpuMemoryTotalBytes: null,
150
+ gpuMemoryPercent: null
151
+ };
152
+ if (controller.hasMemory) {
153
+ result.gpuMemoryUsedBytes = controller.memoryUsed * 1024 ** 2;
154
+ result.gpuMemoryTotalBytes = controller.memoryTotal * 1024 ** 2;
155
+ result.gpuMemoryPercent = controller.memoryUsed / controller.memoryTotal * 100;
156
+ }
157
+ return result;
158
+ }
159
+ function readCachedMetric(state, reader, cacheMs = SLOW_METRIC_CACHE_MS, now = Date.now(), unavailableCacheMs = cacheMs) {
160
+ if (state.inFlight !== undefined)
161
+ return state.inFlight;
162
+ if (state.lastFailureAt !== undefined && now - state.lastFailureAt < unavailableCacheMs) {
163
+ return Promise.resolve(state.value);
164
+ }
165
+ if (now - state.lastAttemptAt < cacheMs)
166
+ return Promise.resolve(state.value);
167
+ state.lastAttemptAt = now;
168
+ state.inFlight = reader().then((value) => {
169
+ state.value = value;
170
+ state.lastFailureAt = undefined;
171
+ return value;
172
+ }).catch(() => {
173
+ state.lastFailureAt = now;
174
+ return state.value;
175
+ }).finally(() => {
176
+ state.inFlight = undefined;
177
+ });
178
+ return state.inFlight;
179
+ }
180
+ var cachedCpuTemperature = {
181
+ value: undefined,
182
+ lastAttemptAt: 0,
183
+ inFlight: undefined
184
+ };
185
+ var cachedLibreHardwareMonitorTemperature = {
186
+ value: undefined,
187
+ lastAttemptAt: 0,
188
+ inFlight: undefined
189
+ };
190
+ var cachedGraphics = {
191
+ value: undefined,
192
+ lastAttemptAt: 0,
193
+ inFlight: undefined
194
+ };
195
+ var cachedNetwork = {
196
+ value: undefined,
197
+ lastAttemptAt: 0,
198
+ inFlight: undefined
199
+ };
200
+ var previousWindowsNetwork;
201
+ async function readWindowsNetworkMetrics() {
202
+ const samples = await readWindowsNetworkSample();
203
+ const timestamp = Date.now();
204
+ const rx = samples.reduce((total, item) => total + (finite(item.rx_bytes) ?? 0), 0);
205
+ const tx = samples.reduce((total, item) => total + (finite(item.tx_bytes) ?? 0), 0);
206
+ const seconds = previousWindowsNetwork === undefined ? undefined : Math.max((timestamp - previousWindowsNetwork.timestamp) / 1000, 0.001);
207
+ const result = [{
208
+ rx_bytes: rx,
209
+ tx_bytes: tx,
210
+ rx_sec: seconds === undefined ? undefined : Math.max(0, rx - previousWindowsNetwork.rx) / seconds,
211
+ tx_sec: seconds === undefined ? undefined : Math.max(0, tx - previousWindowsNetwork.tx) / seconds
212
+ }];
213
+ previousWindowsNetwork = { timestamp, rx, tx };
214
+ return result;
215
+ }
216
+ async function readWindowsCpuTemperature() {
217
+ try {
218
+ const native = await si.cpuTemperature();
219
+ const value = finite(native.main) ?? finite(native.max) ?? undefined;
220
+ if (value !== undefined)
221
+ return { main: value };
222
+ } catch {}
223
+ const now = Date.now();
224
+ const value = await readCachedMetric(cachedLibreHardwareMonitorTemperature, async () => {
225
+ const temperature = await readLibreHardwareMonitorTemperature();
226
+ if (temperature === undefined)
227
+ throw new Error("LibreHardwareMonitor temperature unavailable");
228
+ return temperature;
229
+ }, TEMPERATURE_CACHE_MS, now, OPTIONAL_SOURCE_BACKOFF_MS);
230
+ if (cachedLibreHardwareMonitorTemperature.lastFailureAt !== undefined && now - cachedLibreHardwareMonitorTemperature.lastFailureAt < OPTIONAL_SOURCE_BACKOFF_MS) {
231
+ return;
232
+ }
233
+ return value === undefined ? undefined : { main: value };
234
+ }
73
235
  async function readMetrics() {
74
236
  const [load, memory] = await Promise.allSettled([
75
- si.currentLoad(),
76
- si.mem()
237
+ withTimeout(si.currentLoad(), 1500),
238
+ withTimeout(si.mem(), 1500)
77
239
  ]);
78
240
  const [temperature, graphics, network] = await Promise.allSettled([
79
- withTimeout(si.cpuTemperature(), 1500),
80
- withTimeout(si.graphics(), 1500),
81
- withTimeout(si.networkStats("*"), 1500)
241
+ withTimeout(readCachedMetric(cachedCpuTemperature, isWindows ? readWindowsCpuTemperature : () => si.cpuTemperature(), TEMPERATURE_CACHE_MS), 2500),
242
+ withTimeout(readCachedMetric(cachedGraphics, () => si.graphics(), SLOW_METRIC_CACHE_MS), 2500),
243
+ withTimeout(readCachedMetric(cachedNetwork, isWindows ? readWindowsNetworkMetrics : () => si.networkStats("*"), isWindows ? NETWORK_CACHE_MS : 2000, Date.now(), OPTIONAL_SOURCE_BACKOFF_MS), 1500)
82
244
  ]);
83
245
  const cpuPercent = load.status === "fulfilled" ? finite(load.value.currentLoad) : null;
84
246
  const memoryTotalBytes = memory.status === "fulfilled" ? finite(memory.value.total) : null;
85
247
  const memoryAvailableBytes = memory.status === "fulfilled" ? finite(memory.value.available) : null;
86
248
  const memoryUsedBytes = memoryTotalBytes !== null && memoryAvailableBytes !== null ? Math.max(0, memoryTotalBytes - memoryAvailableBytes) : memory.status === "fulfilled" ? finite(memory.value.used) : null;
87
249
  const memoryPercent = memoryUsedBytes !== null && memoryTotalBytes !== null && memoryTotalBytes > 0 ? memoryUsedBytes / memoryTotalBytes * 100 : null;
88
- let gpuPercent = null;
89
- let gpuTemperatureCelsius = null;
90
- let gpuMemoryUsedBytes = null;
91
- let gpuMemoryTotalBytes = null;
92
- let gpuMemoryPercent = null;
93
- if (graphics.status === "fulfilled") {
94
- const controller = graphics.value.controllers.map((item) => {
95
- const utilization = finite(item.utilizationGpu);
96
- const temperature = finite(item.temperatureGpu);
97
- const memoryUsed = finite(item.memoryUsed);
98
- const memoryTotal = finite(item.memoryTotal);
99
- const hasMemory = memoryUsed !== null && memoryTotal !== null && memoryTotal > 0;
100
- return { item, utilization, temperature, memoryUsed, memoryTotal, hasMemory };
101
- }).filter(({ utilization, temperature, hasMemory }) => utilization !== null || temperature !== null || hasMemory).sort((left, right) => {
102
- const score = (value) => (value.hasMemory ? 2 : 0) + (value.utilization !== null ? 1 : 0);
103
- const scoreDifference = score(right) - score(left);
104
- if (scoreDifference !== 0)
105
- return scoreDifference;
106
- return (right.utilization ?? -Infinity) - (left.utilization ?? -Infinity);
107
- })[0];
108
- if (controller !== undefined) {
109
- gpuPercent = controller.utilization ?? (controller.hasMemory ? 0 : null);
110
- gpuTemperatureCelsius = controller.temperature;
111
- if (controller.hasMemory) {
112
- gpuMemoryUsedBytes = controller.memoryUsed * 1024 ** 2;
113
- gpuMemoryTotalBytes = controller.memoryTotal * 1024 ** 2;
114
- gpuMemoryPercent = controller.memoryUsed / controller.memoryTotal * 100;
115
- }
116
- }
117
- }
250
+ const gpu = selectGpuMetrics(graphics.status === "fulfilled" ? graphics.value : undefined);
118
251
  let downloadBytesPerSecond = null;
119
252
  let uploadBytesPerSecond = null;
120
- if (network.status === "fulfilled") {
253
+ if (network.status === "fulfilled" && network.value !== undefined) {
121
254
  const samples = network.value;
122
255
  const timestamp = Date.now();
123
256
  const rx = samples.reduce((total, item) => total + (finite(item.rx_bytes) ?? 0), 0);
124
257
  const tx = samples.reduce((total, item) => total + (finite(item.tx_bytes) ?? 0), 0);
125
- if (previousNetwork !== undefined) {
258
+ if (isWindows) {
259
+ downloadBytesPerSecond = finite(samples[0]?.rx_sec) ?? null;
260
+ uploadBytesPerSecond = finite(samples[0]?.tx_sec) ?? null;
261
+ } else if (previousNetwork !== undefined) {
126
262
  const seconds = Math.max((timestamp - previousNetwork.timestamp) / 1000, 0.001);
127
263
  downloadBytesPerSecond = Math.max(0, rx - previousNetwork.rx) / seconds;
128
264
  uploadBytesPerSecond = Math.max(0, tx - previousNetwork.tx) / seconds;
265
+ } else {
266
+ downloadBytesPerSecond = finite(samples[0]?.rx_sec) ?? null;
267
+ uploadBytesPerSecond = finite(samples[0]?.tx_sec) ?? null;
129
268
  }
130
269
  previousNetwork = { timestamp, rx, tx };
131
270
  }
@@ -135,22 +274,22 @@ async function readMetrics() {
135
274
  memoryUsedBytes,
136
275
  memoryTotalBytes,
137
276
  memoryPercent,
138
- gpuPercent,
139
- cpuTemperatureCelsius: temperature.status === "fulfilled" ? finite(temperature.value.main) ?? finite(temperature.value.max) : null,
140
- gpuTemperatureCelsius,
141
- gpuMemoryUsedBytes,
142
- gpuMemoryTotalBytes,
143
- gpuMemoryPercent,
277
+ gpuPercent: gpu.gpuPercent,
278
+ cpuTemperatureCelsius: temperature.status === "fulfilled" && temperature.value !== undefined ? finite(temperature.value.main) ?? ("max" in temperature.value ? finite(temperature.value.max) : null) : null,
279
+ gpuTemperatureCelsius: gpu.gpuTemperatureCelsius,
280
+ gpuMemoryUsedBytes: gpu.gpuMemoryUsedBytes,
281
+ gpuMemoryTotalBytes: gpu.gpuMemoryTotalBytes,
282
+ gpuMemoryPercent: gpu.gpuMemoryPercent,
144
283
  downloadBytesPerSecond,
145
284
  uploadBytesPerSecond
146
285
  };
147
286
  }
148
287
 
149
288
  // src/font.ts
150
- import { existsSync, readdirSync } from "fs";
151
- import { homedir } from "os";
152
- import { join as posixJoin, win32 } from "path";
153
- var FONT_FILE_PATTERN = /^HackNerdFont(?:Mono|Propo|Console)?-[A-Za-z0-9-]+\.(?:ttf|otf)$/i;
289
+ import { existsSync, readdirSync } from "node:fs";
290
+ import { homedir } from "node:os";
291
+ import { join as posixJoin, win32 } from "node:path";
292
+ var NERD_FONT_FILE_PATTERN = /NerdFont[A-Za-z0-9-]*[-_][A-Za-z0-9-]+\.(?:ttf|otf)$/i;
154
293
  function getFontDirectories(platform, home = homedir(), env = process.env) {
155
294
  if (platform === "darwin")
156
295
  return [posixJoin(home, "Library", "Fonts"), "/Library/Fonts", "/System/Library/Fonts"];
@@ -161,17 +300,18 @@ function getFontDirectories(platform, home = homedir(), env = process.env) {
161
300
  }
162
301
  return [posixJoin(home, ".local", "share", "fonts"), posixJoin(home, ".fonts"), "/usr/local/share/fonts", "/usr/share/fonts"];
163
302
  }
164
- function isHackNerdFontFile(fileName) {
165
- return FONT_FILE_PATTERN.test(fileName);
303
+ function isNerdFontFile(fileName) {
304
+ return NERD_FONT_FILE_PATTERN.test(fileName);
166
305
  }
167
- function hasHackNerdFont(platform = process.platform, home = homedir(), env = process.env) {
306
+ var isHackNerdFontFile = isNerdFontFile;
307
+ function hasNerdFont(platform = process.platform, home = homedir(), env = process.env) {
168
308
  const containsFont = (directory) => {
169
309
  if (!existsSync(directory))
170
310
  return false;
171
311
  try {
172
312
  return readdirSync(directory, { withFileTypes: true }).some((entry) => {
173
313
  const path = platform === "win32" ? win32.join(directory, entry.name) : posixJoin(directory, entry.name);
174
- return entry.isFile() ? isHackNerdFontFile(entry.name) : entry.isDirectory() && containsFont(path);
314
+ return entry.isFile() ? isNerdFontFile(entry.name) : entry.isDirectory() && containsFont(path);
175
315
  });
176
316
  } catch {
177
317
  return false;
@@ -179,71 +319,87 @@ function hasHackNerdFont(platform = process.platform, home = homedir(), env = pr
179
319
  };
180
320
  return getFontDirectories(platform, home, env).some(containsFont);
181
321
  }
322
+ var hasHackNerdFont = hasNerdFont;
182
323
  var nerdFontIcons = {
183
324
  title: "\uDB80\uDF79",
184
325
  cpu: "\uDB83\uDEE0",
185
326
  ram: "\uDB81\uDE1A",
186
327
  gpu: "\uDB82\uDCAE",
187
328
  vram: "\uDB80\uDF5B",
188
- thermometer: "\uF2C7",
329
+ thermometer: "",
189
330
  network: "\uDB81\uDEF3"
190
331
  };
191
- var unicodeIcons = { title: "\u25A6", cpu: "\u25A3", ram: "\u25A4", gpu: "\u25C6", vram: "\u25C8", thermometer: "\u2668", network: "\u21C5" };
332
+ var unicodeIcons = { title: "", cpu: "", ram: "", gpu: "", vram: "", thermometer: "", network: "" };
192
333
  function getMetricIcons(useNerdFont) {
193
334
  return useNerdFont ? nerdFontIcons : unicodeIcons;
194
335
  }
336
+ function shouldUseNerdFont(platform, detected, env = process.env) {
337
+ if (platform === "win32")
338
+ return env.OPENCODE_MONITOR_NERD_FONT === "1";
339
+ return detected;
340
+ }
195
341
 
196
342
  // src/tui.tsx
197
- var REFRESH_INTERVAL_MS = process.platform === "win32" ? 5000 : 2000;
198
- var icons = getMetricIcons(process.platform !== "win32" && hasHackNerdFont());
343
+ var REFRESH_INTERVAL_MS = 2000;
344
+ var icons = getMetricIcons(shouldUseNerdFont(process.platform, hasNerdFont()));
345
+ var initialMetrics = {
346
+ cpuPercent: null,
347
+ memoryUsedBytes: null,
348
+ memoryTotalBytes: null,
349
+ memoryPercent: null,
350
+ gpuPercent: null,
351
+ cpuTemperatureCelsius: null,
352
+ gpuTemperatureCelsius: null,
353
+ gpuMemoryUsedBytes: null,
354
+ gpuMemoryTotalBytes: null,
355
+ gpuMemoryPercent: null,
356
+ downloadBytesPerSecond: null,
357
+ uploadBytesPerSecond: null
358
+ };
359
+ var [sharedMetrics, setSharedMetrics] = createSignal(initialMetrics);
360
+ var metricsTimer;
361
+ var metricsConsumers = 0;
362
+ var refreshing = false;
363
+ var refreshMetrics = async () => {
364
+ if (refreshing)
365
+ return;
366
+ refreshing = true;
367
+ try {
368
+ setSharedMetrics(await readMetrics());
369
+ } finally {
370
+ refreshing = false;
371
+ }
372
+ };
373
+ function startMetricsPolling() {
374
+ metricsConsumers += 1;
375
+ if (metricsConsumers !== 1)
376
+ return;
377
+ refreshMetrics();
378
+ metricsTimer = setInterval(() => void refreshMetrics(), REFRESH_INTERVAL_MS);
379
+ }
380
+ function stopMetricsPolling() {
381
+ metricsConsumers = Math.max(0, metricsConsumers - 1);
382
+ if (metricsConsumers !== 0 || metricsTimer === undefined)
383
+ return;
384
+ clearInterval(metricsTimer);
385
+ metricsTimer = undefined;
386
+ }
199
387
  function systemMetricsTitle() {
200
388
  const locale = process.env.LC_ALL ?? process.env.LANGUAGE?.split(":")[0] ?? process.env.LANG ?? Intl.DateTimeFormat().resolvedOptions().locale;
201
389
  const language = locale.split(/[-_.]/)[0].toLowerCase();
202
390
  return {
203
391
  de: "Systemmetriken",
204
- es: "M\xE9tricas del sistema",
205
- fr: "M\xE9triques syst\xE8me",
392
+ es: "Métricas del sistema",
393
+ fr: "Métriques système",
206
394
  it: "Metriche di sistema",
207
- pt: "M\xE9tricas do sistema"
395
+ pt: "Métricas do sistema"
208
396
  }[language] ?? "System metrics";
209
397
  }
210
398
  function MetricsPanel(props) {
211
- const [metrics, setMetrics] = createSignal({
212
- cpuPercent: null,
213
- memoryUsedBytes: null,
214
- memoryTotalBytes: null,
215
- memoryPercent: null,
216
- gpuPercent: null,
217
- cpuTemperatureCelsius: null,
218
- gpuTemperatureCelsius: null,
219
- gpuMemoryUsedBytes: null,
220
- gpuMemoryTotalBytes: null,
221
- gpuMemoryPercent: null,
222
- downloadBytesPerSecond: null,
223
- uploadBytesPerSecond: null
224
- });
225
- let refreshing = false;
226
- const refresh = async () => {
227
- if (refreshing)
228
- return;
229
- refreshing = true;
230
- try {
231
- setMetrics(await readMetrics());
232
- } finally {
233
- refreshing = false;
234
- }
235
- };
236
- let timer;
237
- onMount(() => {
238
- refresh();
239
- timer = setInterval(() => void refresh(), REFRESH_INTERVAL_MS);
240
- });
241
- onCleanup(() => {
242
- if (timer !== undefined)
243
- clearInterval(timer);
244
- });
399
+ onMount(startMetricsPolling);
400
+ onCleanup(stopMetricsPolling);
245
401
  return (() => {
246
- var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createTextNode(` `), _el$4 = _$createElement("box"), _el$5 = _$createElement("text"), _el$6 = _$createElement("text"), _el$8 = _$createElement("text"), _el$9 = _$createTextNode(` \xB7 `), _el$0 = _$createTextNode(` `), _el$1 = _$createElement("box"), _el$10 = _$createElement("text"), _el$11 = _$createElement("text"), _el$13 = _$createElement("text"), _el$14 = _$createTextNode(` / `), _el$15 = _$createTextNode(` (`), _el$16 = _$createTextNode(`)`), _el$17 = _$createElement("box"), _el$18 = _$createElement("text"), _el$19 = _$createElement("text"), _el$21 = _$createElement("text"), _el$22 = _$createTextNode(` \xB7 `), _el$23 = _$createTextNode(` `), _el$24 = _$createElement("box"), _el$25 = _$createElement("text"), _el$26 = _$createElement("text"), _el$28 = _$createElement("text"), _el$29 = _$createTextNode(` / `), _el$30 = _$createTextNode(` (`), _el$31 = _$createTextNode(`)`), _el$32 = _$createElement("box"), _el$33 = _$createElement("text"), _el$34 = _$createElement("text"), _el$36 = _$createElement("text"), _el$37 = _$createTextNode(`\u2193 `), _el$38 = _$createTextNode(` \u2191 `);
402
+ var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createTextNode(` `), _el$4 = _$createElement("box"), _el$5 = _$createElement("text"), _el$6 = _$createElement("text"), _el$8 = _$createElement("text"), _el$9 = _$createTextNode(` · `), _el$0 = _$createTextNode(` `), _el$1 = _$createElement("box"), _el$10 = _$createElement("text"), _el$11 = _$createElement("text"), _el$13 = _$createElement("text"), _el$14 = _$createTextNode(` / `), _el$15 = _$createTextNode(` (`), _el$16 = _$createTextNode(`)`), _el$17 = _$createElement("box"), _el$18 = _$createElement("text"), _el$19 = _$createElement("text"), _el$21 = _$createElement("text"), _el$22 = _$createTextNode(` · `), _el$23 = _$createTextNode(` `), _el$24 = _$createElement("box"), _el$25 = _$createElement("text"), _el$26 = _$createElement("text"), _el$28 = _$createElement("text"), _el$29 = _$createTextNode(` / `), _el$30 = _$createTextNode(` (`), _el$31 = _$createTextNode(`)`), _el$32 = _$createElement("box"), _el$33 = _$createElement("text"), _el$34 = _$createElement("text"), _el$36 = _$createElement("text"), _el$37 = _$createTextNode(`↓ `), _el$38 = _$createTextNode(` `);
247
403
  _$insertNode(_el$, _el$2);
248
404
  _$insertNode(_el$, _el$4);
249
405
  _$insertNode(_el$, _el$1);
@@ -264,9 +420,9 @@ function MetricsPanel(props) {
264
420
  _$insertNode(_el$6, _$createTextNode(` CPU `));
265
421
  _$insertNode(_el$8, _el$9);
266
422
  _$insertNode(_el$8, _el$0);
267
- _$insert(_el$8, () => formatPercent(metrics().cpuPercent), _el$9);
423
+ _$insert(_el$8, () => formatPercent(sharedMetrics().cpuPercent), _el$9);
268
424
  _$insert(_el$8, () => icons.thermometer, _el$0);
269
- _$insert(_el$8, () => formatTemperature(metrics().cpuTemperatureCelsius), null);
425
+ _$insert(_el$8, () => formatTemperature(sharedMetrics().cpuTemperatureCelsius), null);
270
426
  _$insertNode(_el$1, _el$10);
271
427
  _$insertNode(_el$1, _el$11);
272
428
  _$insertNode(_el$1, _el$13);
@@ -276,9 +432,9 @@ function MetricsPanel(props) {
276
432
  _$insertNode(_el$13, _el$14);
277
433
  _$insertNode(_el$13, _el$15);
278
434
  _$insertNode(_el$13, _el$16);
279
- _$insert(_el$13, () => formatGiB(metrics().memoryUsedBytes), _el$14);
280
- _$insert(_el$13, () => formatGiB(metrics().memoryTotalBytes), _el$15);
281
- _$insert(_el$13, () => formatPercent(metrics().memoryPercent), _el$16);
435
+ _$insert(_el$13, () => formatGiB(sharedMetrics().memoryUsedBytes), _el$14);
436
+ _$insert(_el$13, () => formatGiB(sharedMetrics().memoryTotalBytes), _el$15);
437
+ _$insert(_el$13, () => formatPercent(sharedMetrics().memoryPercent), _el$16);
282
438
  _$insertNode(_el$17, _el$18);
283
439
  _$insertNode(_el$17, _el$19);
284
440
  _$insertNode(_el$17, _el$21);
@@ -287,9 +443,9 @@ function MetricsPanel(props) {
287
443
  _$insertNode(_el$19, _$createTextNode(` GPU `));
288
444
  _$insertNode(_el$21, _el$22);
289
445
  _$insertNode(_el$21, _el$23);
290
- _$insert(_el$21, () => formatPercent(metrics().gpuPercent), _el$22);
446
+ _$insert(_el$21, () => formatPercent(sharedMetrics().gpuPercent), _el$22);
291
447
  _$insert(_el$21, () => icons.thermometer, _el$23);
292
- _$insert(_el$21, () => formatTemperature(metrics().gpuTemperatureCelsius), null);
448
+ _$insert(_el$21, () => formatTemperature(sharedMetrics().gpuTemperatureCelsius), null);
293
449
  _$insertNode(_el$24, _el$25);
294
450
  _$insertNode(_el$24, _el$26);
295
451
  _$insertNode(_el$24, _el$28);
@@ -299,9 +455,9 @@ function MetricsPanel(props) {
299
455
  _$insertNode(_el$28, _el$29);
300
456
  _$insertNode(_el$28, _el$30);
301
457
  _$insertNode(_el$28, _el$31);
302
- _$insert(_el$28, () => formatGiB(metrics().gpuMemoryUsedBytes), _el$29);
303
- _$insert(_el$28, () => formatGiB(metrics().gpuMemoryTotalBytes), _el$30);
304
- _$insert(_el$28, () => formatPercent(metrics().gpuMemoryPercent), _el$31);
458
+ _$insert(_el$28, () => formatGiB(sharedMetrics().gpuMemoryUsedBytes), _el$29);
459
+ _$insert(_el$28, () => formatGiB(sharedMetrics().gpuMemoryTotalBytes), _el$30);
460
+ _$insert(_el$28, () => formatPercent(sharedMetrics().gpuMemoryPercent), _el$31);
305
461
  _$insertNode(_el$32, _el$33);
306
462
  _$insertNode(_el$32, _el$34);
307
463
  _$insertNode(_el$32, _el$36);
@@ -310,8 +466,8 @@ function MetricsPanel(props) {
310
466
  _$insertNode(_el$34, _$createTextNode(` NET `));
311
467
  _$insertNode(_el$36, _el$37);
312
468
  _$insertNode(_el$36, _el$38);
313
- _$insert(_el$36, () => formatRate(metrics().downloadBytesPerSecond), _el$38);
314
- _$insert(_el$36, () => formatRate(metrics().uploadBytesPerSecond), null);
469
+ _$insert(_el$36, () => formatRate(sharedMetrics().downloadBytesPerSecond), _el$38);
470
+ _$insert(_el$36, () => formatRate(sharedMetrics().uploadBytesPerSecond), null);
315
471
  _$effect((_p$) => {
316
472
  var _v$ = props.theme.text, _v$2 = TextAttributes.BOLD, _v$3 = props.theme.success, _v$4 = props.theme.text, _v$5 = props.theme.textMuted, _v$6 = props.theme.success, _v$7 = props.theme.text, _v$8 = props.theme.textMuted, _v$9 = props.theme.success, _v$0 = props.theme.text, _v$1 = props.theme.textMuted, _v$10 = props.theme.success, _v$11 = props.theme.text, _v$12 = props.theme.textMuted, _v$13 = props.theme.success, _v$14 = props.theme.text, _v$15 = props.theme.textMuted;
317
473
  _v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "fg", _v$, _p$.e));
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "opencode-system-metrics-tui",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "OpenCode TUI sidebar plugin for live system metrics",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "exports": {
8
+ ".": {
9
+ "types": "./dist/tui.d.ts",
10
+ "import": "./dist/tui.js"
11
+ },
8
12
  "./tui": {
9
13
  "types": "./dist/tui.d.ts",
10
14
  "import": "./dist/tui.js"
@@ -17,9 +21,9 @@
17
21
  ],
18
22
  "scripts": {
19
23
  "build": "bun scripts/build.ts && tsc --emitDeclarationOnly --declaration --outDir dist",
20
- "test": "tsx --test test/format.test.ts",
24
+ "test": "tsx --test test/*.test.ts",
21
25
  "typecheck": "tsc --noEmit",
22
- "install-font": "tsx scripts/install-hack-font.ts"
26
+ "install-font": "node dist/install-hack-font.js"
23
27
  },
24
28
  "engines": {
25
29
  "node": ">=18"
package/scripts/build.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import solidPlugin from "@opentui/solid/bun-plugin"
2
2
 
3
3
  const result = await Bun.build({
4
- entrypoints: ["src/tui.tsx"],
4
+ entrypoints: ["src/tui.tsx", "scripts/install-hack-font.ts"],
5
5
  outdir: "dist",
6
- target: "bun",
6
+ target: "node",
7
7
  format: "esm",
8
8
  plugins: [solidPlugin],
9
9
  external: ["@opencode-ai/plugin/tui", "@opentui/core", "@opentui/solid", "solid-js", "systeminformation"],
10
- naming: "tui.js",
10
+ naming: "[name].js",
11
11
  })
12
12
 
13
13
  if (!result.success) {