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 +11 -11
- package/dist/install-hack-font.js +4 -4
- package/dist/tui.js +12 -9
- package/package.json +1 -1
- package/scripts/install-plugin.ts +33 -17
- package/scripts/plugin-installation.ts +28 -13
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
|
+
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.
|
|
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.
|
|
105
|
-
- `opencode plugin -g opencode-system-metrics-tui@0.1.
|
|
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.
|
|
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.
|
|
198
|
-
- `opencode plugin -g opencode-system-metrics-tui@0.1.
|
|
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.
|
|
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.
|
|
291
|
-
- `opencode plugin -g opencode-system-metrics-tui@0.1.
|
|
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 {
|
|
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 [
|
|
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 [
|
|
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) :
|
|
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
|
|
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:
|
|
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(),
|
|
254
|
-
withTimeout(si.mem(),
|
|
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,
|
|
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 {
|
|
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 [
|
|
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 [
|
|
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) :
|
|
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,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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
const
|
|
20
|
-
console.
|
|
21
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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 (
|
|
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 (
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
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
|
-
|
|
99
|
+
export function shouldInstallPlugin(result: PluginVerification): boolean {
|
|
100
|
+
return !result.ok
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
export function formatVerificationResult(result: PluginVerification): string {
|
|
91
|
-
|
|
92
|
-
|
|
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))) {
|