pyre-cli 7.1.0 → 8.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 +42 -19
- package/dist/anomaliesCmd-TO52YY2B.js +117 -0
- package/dist/brewHealth-6NE37NAU.js +74 -0
- package/dist/chunk-GLH7EKAX.js +15 -0
- package/dist/chunk-KBKXU2ZV.js +859 -0
- package/dist/chunk-RVGKEJXT.js +121 -0
- package/dist/collectors-4VYRJVRO.js +33 -0
- package/dist/completions-VQMHQCXE.js +122 -0
- package/dist/config-4ZLF4HTS.js +22 -0
- package/dist/doctor-LJQAABZ4.js +184 -0
- package/dist/extensions-QCFSVCL5.js +74 -0
- package/dist/index.js +1279 -1191
- package/dist/updateCheck-52X6LJG5.js +84 -0
- package/package.json +1 -1
- package/parity.md +16 -9
- package/src/anomaliesCmd.ts +151 -0
- package/src/brewHealth.ts +88 -0
- package/src/doctor.ts +77 -1
- package/src/easy_features.test.ts +43 -0
- package/src/extensions.ts +83 -0
- package/src/formatters/html_template.ts +655 -0
- package/src/formatters/index.ts +1 -0
- package/src/formatters/render.ts +2 -2
- package/src/index.ts +280 -26
- package/src/live/input.ts +12 -0
- package/src/monitors/collectors.ts +110 -5
- package/src/monitors/types.ts +6 -0
- package/src/sparkline.ts +63 -2
- package/src/state/config.ts +36 -2
- package/src/updateCheck.ts +94 -0
package/src/sparkline.ts
CHANGED
|
@@ -8,8 +8,15 @@ const BLOCKS = ' ▂▃▄▅▆▇█';
|
|
|
8
8
|
* Unicode block characters to represent the range, making
|
|
9
9
|
* it suitable for inline terminal graphs.
|
|
10
10
|
*/
|
|
11
|
-
export function sparkline(values: number[], opts: { min?: number; max?: number } = {}): string {
|
|
11
|
+
export function sparkline(values: number[], opts: { min?: number; max?: number; plainText?: boolean } = {}): string {
|
|
12
12
|
if (!values.length) return '';
|
|
13
|
+
if (opts.plainText) {
|
|
14
|
+
const first = values[0];
|
|
15
|
+
const last = values[values.length - 1];
|
|
16
|
+
const diff = last - first;
|
|
17
|
+
const sign = diff >= 0 ? '+' : '';
|
|
18
|
+
return `[${first.toFixed(1)} -> ${last.toFixed(1)} (Δ:${sign}${diff.toFixed(1)})]`;
|
|
19
|
+
}
|
|
13
20
|
let min = opts.min ?? Infinity;
|
|
14
21
|
let max = opts.max ?? -Infinity;
|
|
15
22
|
if (opts.min === undefined || opts.max === undefined) {
|
|
@@ -29,6 +36,60 @@ export function sparkline(values: number[], opts: { min?: number; max?: number }
|
|
|
29
36
|
.join('');
|
|
30
37
|
}
|
|
31
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Render a multi-row unicode sparkline area graph.
|
|
41
|
+
* Returns an array of strings representing each row from top to bottom.
|
|
42
|
+
*/
|
|
43
|
+
export function multiRowBrailleGraph(
|
|
44
|
+
values: number[],
|
|
45
|
+
height: number,
|
|
46
|
+
opts: { min?: number; max?: number } = {}
|
|
47
|
+
): string[] {
|
|
48
|
+
if (!values.length || height <= 0) return Array(height).fill('');
|
|
49
|
+
let min = opts.min ?? Infinity;
|
|
50
|
+
let max = opts.max ?? -Infinity;
|
|
51
|
+
if (opts.min === undefined || opts.max === undefined) {
|
|
52
|
+
for (const v of values) {
|
|
53
|
+
if (v < min) min = v;
|
|
54
|
+
if (v > max) max = v;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const range = max - min || 1;
|
|
58
|
+
const levels = height * 4;
|
|
59
|
+
|
|
60
|
+
const dotsCol0 = [0x40, 0x04, 0x02, 0x01]; // bottom to top
|
|
61
|
+
const dotsCol1 = [0x80, 0x20, 0x10, 0x08]; // bottom to top
|
|
62
|
+
|
|
63
|
+
const rows: string[][] = Array.from({ length: height }, () => []);
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < values.length; i += 2) {
|
|
66
|
+
const v0 = values[i];
|
|
67
|
+
const v1 = i + 1 < values.length ? values[i + 1] : v0;
|
|
68
|
+
|
|
69
|
+
const rawSteps0 = Math.round(((v0 - min) / range) * levels);
|
|
70
|
+
const totalSteps0 = Math.min(levels, Math.max(0, rawSteps0));
|
|
71
|
+
|
|
72
|
+
const rawSteps1 = Math.round(((v1 - min) / range) * levels);
|
|
73
|
+
const totalSteps1 = Math.min(levels, Math.max(0, rawSteps1));
|
|
74
|
+
|
|
75
|
+
for (let r = 0; r < height; r++) {
|
|
76
|
+
const rowFromBottom = height - 1 - r;
|
|
77
|
+
const minForThisRow = rowFromBottom * 4;
|
|
78
|
+
|
|
79
|
+
const stepsInThisRow0 = Math.min(4, Math.max(0, totalSteps0 - minForThisRow));
|
|
80
|
+
const stepsInThisRow1 = Math.min(4, Math.max(0, totalSteps1 - minForThisRow));
|
|
81
|
+
|
|
82
|
+
let code = 0x2800;
|
|
83
|
+
for (let d = 0; d < stepsInThisRow0; d++) code |= dotsCol0[d];
|
|
84
|
+
for (let d = 0; d < stepsInThisRow1; d++) code |= dotsCol1[d];
|
|
85
|
+
|
|
86
|
+
rows[r].push(String.fromCharCode(code));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return rows.map(r => r.join(''));
|
|
91
|
+
}
|
|
92
|
+
|
|
32
93
|
/**
|
|
33
94
|
* Render a multi-row unicode sparkline area graph.
|
|
34
95
|
* Returns an array of strings representing each row from top to bottom.
|
|
@@ -64,7 +125,7 @@ export function multiRowSparkline(
|
|
|
64
125
|
if (stepsInThisRow === 0) {
|
|
65
126
|
rows[r].push(' ');
|
|
66
127
|
} else {
|
|
67
|
-
rows[r].push(BLOCKS[stepsInThisRow]);
|
|
128
|
+
rows[r].push(BLOCKS[Math.min(7, stepsInThisRow)]);
|
|
68
129
|
}
|
|
69
130
|
}
|
|
70
131
|
}
|
package/src/state/config.ts
CHANGED
|
@@ -26,14 +26,16 @@ export interface PyreConfig {
|
|
|
26
26
|
graphMode?: string;
|
|
27
27
|
showGraphs?: boolean;
|
|
28
28
|
autoLog?: boolean;
|
|
29
|
-
|
|
29
|
+
mouseEnabled?: boolean;
|
|
30
30
|
cpuAlertPct?: number;
|
|
31
31
|
tempAlertC?: number;
|
|
32
|
+
electricityRateKwH?: number;
|
|
32
33
|
notificationsEnabled?: boolean;
|
|
33
34
|
p2pPort?: number;
|
|
34
35
|
p2pPassword?: string;
|
|
35
36
|
splashEnabled?: boolean;
|
|
36
37
|
splashColorScheme?: string;
|
|
38
|
+
splashAnimation?: string;
|
|
37
39
|
watchdogProcess?: string;
|
|
38
40
|
watchdogCpu?: number;
|
|
39
41
|
watchdogMem?: number;
|
|
@@ -69,6 +71,7 @@ export const DEFAULT_CONFIG: Required<PyreConfig> = {
|
|
|
69
71
|
mouseEnabled: true,
|
|
70
72
|
cpuAlertPct: 90,
|
|
71
73
|
tempAlertC: 95,
|
|
74
|
+
electricityRateKwH: 0.15,
|
|
72
75
|
watchdogProcess: '',
|
|
73
76
|
watchdogCpu: 80,
|
|
74
77
|
watchdogMem: 80,
|
|
@@ -120,6 +123,37 @@ export function writeConfig(config: Partial<PyreConfig>): void {
|
|
|
120
123
|
}
|
|
121
124
|
}
|
|
122
125
|
|
|
126
|
+
const PROFILES_DIR = path.join(CONFIG_DIR, 'profiles');
|
|
127
|
+
|
|
128
|
+
export function saveProfile(name: string): void {
|
|
129
|
+
if (!name || name.includes('/') || name.includes('\\')) {
|
|
130
|
+
throw new Error('Invalid profile name.');
|
|
131
|
+
}
|
|
132
|
+
if (!fs.existsSync(PROFILES_DIR)) {
|
|
133
|
+
fs.mkdirSync(PROFILES_DIR, { recursive: true });
|
|
134
|
+
}
|
|
135
|
+
const currentConfig = readConfig();
|
|
136
|
+
const profileFile = path.join(PROFILES_DIR, `${name}.json`);
|
|
137
|
+
fs.writeFileSync(profileFile, JSON.stringify(currentConfig, null, 2) + '\n');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function loadProfile(name: string): void {
|
|
141
|
+
const profileFile = path.join(PROFILES_DIR, `${name}.json`);
|
|
142
|
+
if (!fs.existsSync(profileFile)) {
|
|
143
|
+
throw new Error(`Profile '${name}' does not exist.`);
|
|
144
|
+
}
|
|
145
|
+
const raw = fs.readFileSync(profileFile, 'utf-8');
|
|
146
|
+
const parsed = JSON.parse(raw);
|
|
147
|
+
// Atomic write to main config
|
|
148
|
+
writeConfig(parsed);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function listProfiles(): string[] {
|
|
152
|
+
if (!fs.existsSync(PROFILES_DIR)) return [];
|
|
153
|
+
const files = fs.readdirSync(PROFILES_DIR);
|
|
154
|
+
return files.filter(f => f.endsWith('.json')).map(f => f.replace(/\.json$/, ''));
|
|
155
|
+
}
|
|
156
|
+
|
|
123
157
|
function deepMerge(target: any, source: any): any {
|
|
124
158
|
const result = { ...target };
|
|
125
159
|
for (const key of Object.keys(source)) {
|
|
@@ -138,4 +172,4 @@ export function getConfigPath(): string {
|
|
|
138
172
|
return CONFIG_FILE;
|
|
139
173
|
}
|
|
140
174
|
|
|
141
|
-
export { CONFIG_FILE };
|
|
175
|
+
export { CONFIG_FILE, PROFILES_DIR };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import https from 'node:https';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
|
|
7
|
+
const UPDATE_CACHE_FILE = path.join(os.homedir(), '.config', 'pyre', 'update-check.json');
|
|
8
|
+
|
|
9
|
+
export interface UpdateCheckResult {
|
|
10
|
+
currentVersion: string;
|
|
11
|
+
latestVersion: string;
|
|
12
|
+
hasUpdate: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function checkForUpdates(currentVersion: string): Promise<UpdateCheckResult | null> {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
try {
|
|
18
|
+
if (fs.existsSync(UPDATE_CACHE_FILE)) {
|
|
19
|
+
const raw = fs.readFileSync(UPDATE_CACHE_FILE, 'utf-8');
|
|
20
|
+
const cached = JSON.parse(raw);
|
|
21
|
+
// Check once every 24 hours
|
|
22
|
+
if (Date.now() - (cached.lastChecked || 0) < 24 * 60 * 60 * 1000) {
|
|
23
|
+
return resolve({
|
|
24
|
+
currentVersion,
|
|
25
|
+
latestVersion: cached.latestVersion || currentVersion,
|
|
26
|
+
hasUpdate: isNewerVersion(currentVersion, cached.latestVersion),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// ignore cache errors
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const req = https.get('https://registry.npmjs.org/pyre-cli/latest', { timeout: 2000 }, (res) => {
|
|
35
|
+
let body = '';
|
|
36
|
+
res.on('data', chunk => body += chunk);
|
|
37
|
+
res.on('end', () => {
|
|
38
|
+
try {
|
|
39
|
+
if (res.statusCode === 200) {
|
|
40
|
+
const data = JSON.parse(body);
|
|
41
|
+
const latestVersion = data.version || currentVersion;
|
|
42
|
+
fs.mkdirSync(path.dirname(UPDATE_CACHE_FILE), { recursive: true });
|
|
43
|
+
fs.writeFileSync(UPDATE_CACHE_FILE, JSON.stringify({ lastChecked: Date.now(), latestVersion }));
|
|
44
|
+
return resolve({
|
|
45
|
+
currentVersion,
|
|
46
|
+
latestVersion,
|
|
47
|
+
hasUpdate: isNewerVersion(currentVersion, latestVersion),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
// ignore parse errors
|
|
52
|
+
}
|
|
53
|
+
resolve(null);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
req.on('error', () => resolve(null));
|
|
58
|
+
req.on('timeout', () => {
|
|
59
|
+
req.destroy();
|
|
60
|
+
resolve(null);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isNewerVersion(current: string, latest?: string): boolean {
|
|
66
|
+
if (!latest) return false;
|
|
67
|
+
const cParts = current.split('.').map(n => parseInt(n, 10));
|
|
68
|
+
const lParts = latest.split('.').map(n => parseInt(n, 10));
|
|
69
|
+
for (let i = 0; i < Math.max(cParts.length, lParts.length); i++) {
|
|
70
|
+
const c = cParts[i] || 0;
|
|
71
|
+
const l = lParts[i] || 0;
|
|
72
|
+
if (l > c) return true;
|
|
73
|
+
if (l < c) return false;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function printUpdateReport(currentVersion: string): Promise<void> {
|
|
79
|
+
console.log(chalk.bold('\n pyre update — Version Check\n'));
|
|
80
|
+
console.log(` Current Version: ${chalk.bold(currentVersion)}`);
|
|
81
|
+
|
|
82
|
+
const result = await checkForUpdates(currentVersion);
|
|
83
|
+
if (!result) {
|
|
84
|
+
console.log(chalk.yellow(' ⚠ Unable to reach npm registry for update check.\n'));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (result.hasUpdate) {
|
|
89
|
+
console.log(chalk.green.bold(` 🚀 A new version of pyre is available: v${result.latestVersion}`));
|
|
90
|
+
console.log(chalk.dim(` Run "npm install -g pyre-cli" to update.\n`));
|
|
91
|
+
} else {
|
|
92
|
+
console.log(chalk.green(` ✔ You are running the latest version of pyre (v${currentVersion}).\n`));
|
|
93
|
+
}
|
|
94
|
+
}
|