opencode-system-metrics-tui 0.1.6 → 0.1.7

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
@@ -3,10 +3,10 @@
3
3
  ## Install globally in OpenCode
4
4
 
5
5
  ```powershell
6
- opencode plugin -g opencode-system-metrics-tui@0.1.6 --force
6
+ opencode plugin -g opencode-system-metrics-tui@0.1.7 --force
7
7
  ```
8
8
 
9
- This is the primary installation path. The version must match `package.json`; `--force` asks OpenCode to refresh its package cache. Verify the actual installed artifact afterward:
9
+ This is the primary installation path. The version must match `package.json`; `--force` asks OpenCode to refresh its package cache. The helper below skips OpenCode when the expected artifact is already current. Verify the actual installed artifact afterward:
10
10
 
11
11
  ```powershell
12
12
  npm run install-plugin
@@ -91,7 +91,7 @@ npm publish --access public
91
91
  En otro equipo:
92
92
 
93
93
  ```bash
94
- opencode plugin opencode-system-metrics-tui@0.1.6
94
+ opencode plugin opencode-system-metrics-tui@0.1.7
95
95
  ```
96
96
 
97
97
  El paquete exporta tanto la raíz (`opencode-system-metrics-tui`) como `./tui`; ambas rutas cargan el mismo bundle TUI.
@@ -101,8 +101,8 @@ El instalador de OpenCode configura el plugin TUI automáticamente. Reinicia Ope
101
101
  Publicar en npm e instalar en OpenCode son pasos distintos:
102
102
 
103
103
  - `npm publish --access public` publica el paquete en npm.
104
- - `opencode plugin opencode-system-metrics-tui@0.1.6` lo instala solo en el proyecto actual.
105
- - `opencode plugin -g opencode-system-metrics-tui@0.1.6 --force` lo instala globalmente para todos los proyectos.
104
+ - `opencode plugin opencode-system-metrics-tui@0.1.7` lo instala solo en el proyecto actual.
105
+ - `opencode plugin -g opencode-system-metrics-tui@0.1.7 --force` lo instala globalmente para todos los proyectos.
106
106
 
107
107
  ### Verificación
108
108
 
@@ -184,7 +184,7 @@ npm publish --access public
184
184
  On another machine:
185
185
 
186
186
  ```bash
187
- opencode plugin opencode-system-metrics-tui@0.1.6
187
+ opencode plugin opencode-system-metrics-tui@0.1.7
188
188
  ```
189
189
 
190
190
  The package exports both the root (`opencode-system-metrics-tui`) and `./tui`; both paths load the same TUI bundle.
@@ -194,8 +194,8 @@ The OpenCode installer configures the TUI plugin automatically. Restart OpenCode
194
194
  Publishing to npm and installing in OpenCode are separate steps:
195
195
 
196
196
  - `npm publish --access public` publishes the package to npm.
197
- - `opencode plugin opencode-system-metrics-tui@0.1.6` installs it for the current project only.
198
- - `opencode plugin -g opencode-system-metrics-tui@0.1.6 --force` installs it globally for all projects.
197
+ - `opencode plugin opencode-system-metrics-tui@0.1.7` installs it for the current project only.
198
+ - `opencode plugin -g opencode-system-metrics-tui@0.1.7 --force` installs it globally for all projects.
199
199
 
200
200
  ### Verification
201
201
 
@@ -277,7 +277,7 @@ npm publish --access public
277
277
  Em outro computador:
278
278
 
279
279
  ```bash
280
- opencode plugin opencode-system-metrics-tui@0.1.6
280
+ opencode plugin opencode-system-metrics-tui@0.1.7
281
281
  ```
282
282
 
283
283
  O pacote exporta tanto a raiz (`opencode-system-metrics-tui`) quanto `./tui`; os dois caminhos carregam o mesmo bundle TUI.
@@ -287,8 +287,8 @@ O instalador do OpenCode configura o plugin TUI automaticamente. Reinicie o Open
287
287
  Publicar no npm e instalar no OpenCode são etapas diferentes:
288
288
 
289
289
  - `npm publish --access public` publica o pacote no npm.
290
- - `opencode plugin opencode-system-metrics-tui@0.1.6` instala o plugin apenas no projeto atual.
291
- - `opencode plugin -g opencode-system-metrics-tui@0.1.6 --force` instala o plugin globalmente para todos os projetos.
290
+ - `opencode plugin opencode-system-metrics-tui@0.1.7` instala o plugin apenas no projeto atual.
291
+ - `opencode plugin -g opencode-system-metrics-tui@0.1.7 --force` instala o plugin globalmente para todos os projetos.
292
292
 
293
293
  ### Verificação
294
294
 
@@ -8,19 +8,19 @@ import { promisify } from "node:util";
8
8
  // src/font.ts
9
9
  import { existsSync, readdirSync } from "node:fs";
10
10
  import { homedir } from "node:os";
11
- import { join as posixJoin, win32 } from "node:path";
11
+ import { posix, win32 } from "node:path";
12
12
  var NERD_FONT_FILE_PATTERN = /NerdFont[A-Za-z0-9-]*[-_][A-Za-z0-9-]+\.(?:ttf|otf)$/i;
13
13
  var MAX_FONT_SCAN_DEPTH = 2;
14
14
  var MAX_FONT_SCAN_ENTRIES = 256;
15
15
  function getFontDirectories(platform, home = homedir(), env = process.env) {
16
16
  if (platform === "darwin")
17
- return [posixJoin(home, "Library", "Fonts"), "/Library/Fonts", "/System/Library/Fonts"];
17
+ return [posix.join(home, "Library", "Fonts"), "/Library/Fonts", "/System/Library/Fonts"];
18
18
  if (platform === "win32") {
19
19
  const localAppData = env.LOCALAPPDATA ?? win32.join(home, "AppData", "Local");
20
20
  const windows = env.WINDIR ?? "C:\\Windows";
21
21
  return [win32.join(localAppData, "Microsoft", "Windows", "Fonts"), win32.join(windows, "Fonts")];
22
22
  }
23
- return [posixJoin(home, ".local", "share", "fonts"), posixJoin(home, ".fonts"), "/usr/local/share/fonts", "/usr/share/fonts"];
23
+ return [posix.join(home, ".local", "share", "fonts"), posix.join(home, ".fonts"), "/usr/local/share/fonts", "/usr/share/fonts"];
24
24
  }
25
25
  function isNerdFontFile(fileName) {
26
26
  return NERD_FONT_FILE_PATTERN.test(fileName);
@@ -36,7 +36,7 @@ function hasNerdFont(platform = process.platform, home = homedir(), env = proces
36
36
  return false;
37
37
  try {
38
38
  return readdirSync(directory, { withFileTypes: true }).slice(0, MAX_FONT_SCAN_ENTRIES).some((entry) => {
39
- const path = platform === "win32" ? win32.join(directory, entry.name) : posixJoin(directory, entry.name);
39
+ const path = platform === "win32" ? win32.join(directory, entry.name) : posix.join(directory, entry.name);
40
40
  return entry.isFile() ? isNerdFontFile(entry.name) : entry.isDirectory() && containsFont(path, depth + 1);
41
41
  });
42
42
  } catch {
package/dist/tui.js CHANGED
@@ -40,7 +40,10 @@ import { execFile } from "node:child_process";
40
40
  import { promisify } from "node:util";
41
41
  import si from "systeminformation";
42
42
  var execFileAsync = promisify(execFile);
43
- var WINDOWS_NETWORK_TIMEOUT_MS = 1500;
43
+ var DEFAULT_METRIC_TIMEOUT_MS = 1500;
44
+ var WINDOWS_MEMORY_TIMEOUT_MS = 5000;
45
+ var WINDOWS_NETWORK_PROCESS_TIMEOUT_MS = 4000;
46
+ var WINDOWS_NETWORK_OUTER_TIMEOUT_MS = 4500;
44
47
  function parseMonitorTemperature(value) {
45
48
  if (value === undefined)
46
49
  return;
@@ -87,7 +90,7 @@ async function readWindowsNetworkSample() {
87
90
  "-NonInteractive",
88
91
  "-Command",
89
92
  "Get-NetAdapterStatistics | Select-Object Name,ReceivedBytes,SentBytes | ConvertTo-Json -Compress"
90
- ], { windowsHide: true, timeout: WINDOWS_NETWORK_TIMEOUT_MS, maxBuffer: 1024 * 1024 });
93
+ ], { windowsHide: true, timeout: WINDOWS_NETWORK_PROCESS_TIMEOUT_MS, maxBuffer: 1024 * 1024 });
91
94
  return parseWindowsNetworkOutput(stdout);
92
95
  }
93
96
  var previousNetwork;
@@ -250,13 +253,13 @@ async function readWindowsCpuTemperature() {
250
253
  }
251
254
  async function readMetrics() {
252
255
  const [load, memory] = await Promise.allSettled([
253
- withTimeout(si.currentLoad(), 1500),
254
- withTimeout(si.mem(), 1500)
256
+ withTimeout(si.currentLoad(), DEFAULT_METRIC_TIMEOUT_MS),
257
+ withTimeout(si.mem(), isWindows ? WINDOWS_MEMORY_TIMEOUT_MS : DEFAULT_METRIC_TIMEOUT_MS)
255
258
  ]);
256
259
  const [temperature, graphics, network] = await Promise.allSettled([
257
260
  readCachedMetric(cachedCpuTemperature, isWindows ? readWindowsCpuTemperature : () => si.cpuTemperature(), TEMPERATURE_CACHE_MS, Date.now(), TEMPERATURE_CACHE_MS, 2500),
258
261
  readCachedMetric(cachedGraphics, () => si.graphics(), SLOW_METRIC_CACHE_MS, Date.now(), SLOW_METRIC_CACHE_MS, 2500),
259
- readCachedMetric(cachedNetwork, isWindows ? readWindowsNetworkMetrics : () => si.networkStats("*"), isWindows ? NETWORK_CACHE_MS : 2000, Date.now(), OPTIONAL_SOURCE_BACKOFF_MS, 1500)
262
+ readCachedMetric(cachedNetwork, isWindows ? readWindowsNetworkMetrics : () => si.networkStats("*"), isWindows ? NETWORK_CACHE_MS : 2000, Date.now(), OPTIONAL_SOURCE_BACKOFF_MS, isWindows ? WINDOWS_NETWORK_OUTER_TIMEOUT_MS : DEFAULT_METRIC_TIMEOUT_MS)
260
263
  ]);
261
264
  const cpuPercent = load.status === "fulfilled" ? finite(load.value.currentLoad) : null;
262
265
  const memoryTotalBytes = memory.status === "fulfilled" ? finite(memory.value.total) : null;
@@ -304,19 +307,19 @@ async function readMetrics() {
304
307
  // src/font.ts
305
308
  import { existsSync, readdirSync } from "node:fs";
306
309
  import { homedir } from "node:os";
307
- import { join as posixJoin, win32 } from "node:path";
310
+ import { posix, win32 } from "node:path";
308
311
  var NERD_FONT_FILE_PATTERN = /NerdFont[A-Za-z0-9-]*[-_][A-Za-z0-9-]+\.(?:ttf|otf)$/i;
309
312
  var MAX_FONT_SCAN_DEPTH = 2;
310
313
  var MAX_FONT_SCAN_ENTRIES = 256;
311
314
  function getFontDirectories(platform, home = homedir(), env = process.env) {
312
315
  if (platform === "darwin")
313
- return [posixJoin(home, "Library", "Fonts"), "/Library/Fonts", "/System/Library/Fonts"];
316
+ return [posix.join(home, "Library", "Fonts"), "/Library/Fonts", "/System/Library/Fonts"];
314
317
  if (platform === "win32") {
315
318
  const localAppData = env.LOCALAPPDATA ?? win32.join(home, "AppData", "Local");
316
319
  const windows = env.WINDIR ?? "C:\\Windows";
317
320
  return [win32.join(localAppData, "Microsoft", "Windows", "Fonts"), win32.join(windows, "Fonts")];
318
321
  }
319
- return [posixJoin(home, ".local", "share", "fonts"), posixJoin(home, ".fonts"), "/usr/local/share/fonts", "/usr/share/fonts"];
322
+ return [posix.join(home, ".local", "share", "fonts"), posix.join(home, ".fonts"), "/usr/local/share/fonts", "/usr/share/fonts"];
320
323
  }
321
324
  function isNerdFontFile(fileName) {
322
325
  return NERD_FONT_FILE_PATTERN.test(fileName);
@@ -332,7 +335,7 @@ function hasNerdFont(platform = process.platform, home = homedir(), env = proces
332
335
  return false;
333
336
  try {
334
337
  return readdirSync(directory, { withFileTypes: true }).slice(0, MAX_FONT_SCAN_ENTRIES).some((entry) => {
335
- const path = platform === "win32" ? win32.join(directory, entry.name) : posixJoin(directory, entry.name);
338
+ const path = platform === "win32" ? win32.join(directory, entry.name) : posix.join(directory, entry.name);
336
339
  return entry.isFile() ? isNerdFontFile(entry.name) : entry.isDirectory() && containsFont(path, depth + 1);
337
340
  });
338
341
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-system-metrics-tui",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "OpenCode TUI sidebar plugin for live system metrics",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,7 +1,8 @@
1
1
  import { execFile } from "node:child_process"
2
2
  import { readFile } from "node:fs/promises"
3
+ import { fileURLToPath } from "node:url"
3
4
  import { promisify } from "node:util"
4
- import { formatVerificationResult, verifyPluginInstallation } from "./plugin-installation.js"
5
+ import { formatVerificationResult, shouldInstallPlugin, verifyPluginInstallation } from "./plugin-installation.js"
5
6
 
6
7
  const execFileAsync = promisify(execFile)
7
8
  const manifest = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")) as {
@@ -10,21 +11,36 @@ const manifest = JSON.parse(await readFile(new URL("../package.json", import.met
10
11
  }
11
12
  const packageSpec = `${manifest.name}@${manifest.version}`
12
13
 
13
- console.log(`Installing ${packageSpec} globally through OpenCode`)
14
- try {
15
- const result = await execFileAsync("opencode", ["plugin", "-g", packageSpec, "--force"], { windowsHide: true })
16
- if (result.stdout.trim()) console.log(result.stdout.trim())
17
- if (result.stderr.trim()) console.error(result.stderr.trim())
18
- } catch (error) {
19
- const message = error instanceof Error ? error.message : String(error)
20
- console.error(`OpenCode plugin installation failed: ${message}`)
21
- process.exit(1)
14
+ export async function installPlugin(): Promise<void> {
15
+ const verificationOptions = {
16
+ packageName: manifest.name,
17
+ expectedVersion: manifest.version,
18
+ cacheDirectory: process.env.OPENCODE_CACHE_DIR,
19
+ }
20
+ const current = await verifyPluginInstallation(verificationOptions)
21
+ console.log(formatVerificationResult(current))
22
+ if (!shouldInstallPlugin(current)) {
23
+ console.log(`Installation already current for ${packageSpec}; OpenCode was not invoked.`)
24
+ return
25
+ }
26
+
27
+ console.log(`Installing ${packageSpec} globally through OpenCode`)
28
+ try {
29
+ const result = await execFileAsync("opencode", ["plugin", "-g", packageSpec, "--force"], { windowsHide: true })
30
+ if (result.stdout.trim()) console.log(result.stdout.trim())
31
+ if (result.stderr.trim()) console.error(result.stderr.trim())
32
+ } catch (error) {
33
+ const message = error instanceof Error ? error.message : String(error)
34
+ console.error(`OpenCode plugin installation failed: ${message}`)
35
+ process.exitCode = 1
36
+ return
37
+ }
38
+
39
+ const verification = await verifyPluginInstallation(verificationOptions)
40
+ console.log(formatVerificationResult(verification))
41
+ if (!verification.ok) process.exitCode = 1
22
42
  }
23
43
 
24
- const verification = await verifyPluginInstallation({
25
- packageName: manifest.name,
26
- expectedVersion: manifest.version,
27
- cacheDirectory: process.env.OPENCODE_CACHE_DIR,
28
- })
29
- console.log(formatVerificationResult(verification))
30
- if (!verification.ok) process.exitCode = 1
44
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
45
+ await installPlugin()
46
+ }
@@ -15,6 +15,7 @@ export type PluginVerification = {
15
15
  packagePath?: string
16
16
  actualVersion?: string
17
17
  staleEntries: string[]
18
+ warnings: string[]
18
19
  errors: string[]
19
20
  }
20
21
 
@@ -46,8 +47,11 @@ export async function verifyPluginInstallation(options: PluginVerificationOption
46
47
  const cacheDirectory = resolve(options.cacheDirectory ?? getDefaultOpenCodeCacheDirectory())
47
48
  const errors: string[] = []
48
49
  const staleEntries: string[] = []
50
+ const warnings: string[] = []
49
51
  let packagePath: string | undefined
50
52
  let actualVersion: string | undefined
53
+ let expectedArtifactFound = false
54
+ let expectedPackagePath: string | undefined
51
55
 
52
56
  let entries: string[]
53
57
  try {
@@ -55,7 +59,7 @@ export async function verifyPluginInstallation(options: PluginVerificationOption
55
59
  .filter((entry) => entry.isDirectory() && entry.name.startsWith(`${options.packageName}@`))
56
60
  .map((entry) => entry.name)
57
61
  } catch {
58
- return { ok: false, staleEntries, errors: [`OpenCode package cache not found: ${cacheDirectory}`] }
62
+ return { ok: false, staleEntries, warnings, errors: [`OpenCode package cache not found: ${cacheDirectory}`] }
59
63
  }
60
64
 
61
65
  for (const entry of entries) {
@@ -65,31 +69,42 @@ export async function verifyPluginInstallation(options: PluginVerificationOption
65
69
  staleEntries.push(join(cacheDirectory, entry))
66
70
  continue
67
71
  }
68
- if (manifest.version === options.expectedVersion && packagePath === undefined) {
69
- packagePath = candidatePath
70
- actualVersion = manifest.version
71
- } else if (manifest.version !== options.expectedVersion) {
72
+ if (manifest.version === options.expectedVersion) {
73
+ expectedArtifactFound = true
74
+ expectedPackagePath ??= candidatePath
75
+ if (await exists(join(candidatePath, options.distPath ?? "dist", "tui.js")) && packagePath === undefined) {
76
+ packagePath = candidatePath
77
+ actualVersion = manifest.version
78
+ }
79
+ } else {
72
80
  staleEntries.push(join(cacheDirectory, entry))
73
81
  }
74
82
  }
75
83
 
76
- if (packagePath === undefined) {
84
+ if (!expectedArtifactFound) {
77
85
  errors.push(`Installed ${options.packageName}@${options.expectedVersion} was not found in ${cacheDirectory}`)
86
+ } else if (packagePath === undefined) {
87
+ errors.push(`Required dist/tui.js is missing from an expected ${options.packageName}@${options.expectedVersion} artifact in ${cacheDirectory}`)
88
+ packagePath = expectedPackagePath
89
+ actualVersion = options.expectedVersion
78
90
  } else if (actualVersion !== options.expectedVersion) {
79
91
  errors.push(`Installed version is ${actualVersion ?? "unknown"}; expected ${options.expectedVersion}`)
80
92
  }
81
93
 
82
- if (packagePath !== undefined && !(await exists(join(packagePath, options.distPath ?? "dist", "tui.js")))) {
83
- errors.push(`Required dist/tui.js is missing from ${packagePath}`)
84
- }
85
- if (staleEntries.length > 0) errors.push(`Stale OpenCode cache entries detected: ${staleEntries.join(", ")}`)
94
+ if (staleEntries.length > 0) warnings.push(`Stale OpenCode cache entries detected: ${staleEntries.join(", ")}`)
95
+
96
+ return { ok: errors.length === 0, packagePath, actualVersion, staleEntries, warnings, errors }
97
+ }
86
98
 
87
- return { ok: errors.length === 0, packagePath, actualVersion, staleEntries, errors }
99
+ export function shouldInstallPlugin(result: PluginVerification): boolean {
100
+ return !result.ok
88
101
  }
89
102
 
90
103
  export function formatVerificationResult(result: PluginVerification): string {
91
- if (result.ok) return `Verified OpenCode plugin ${result.actualVersion} at ${result.packagePath}`
92
- return result.errors.join("\n")
104
+ const lines = result.ok
105
+ ? [`Verified OpenCode plugin ${result.actualVersion} at ${result.packagePath}`]
106
+ : result.errors
107
+ return [...lines, ...result.warnings].join("\n")
93
108
  }
94
109
 
95
110
  if (process.argv[1] !== undefined && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {