pyre-cli 4.15.0 → 4.17.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 +112 -0
- package/dist/index.js +341 -67
- package/package.json +1 -1
- package/src/index.ts +39 -6
- package/src/live/input.ts +32 -13
- package/src/live/render.ts +22 -15
- package/src/live/state.ts +52 -42
- package/src/splash.ts +137 -36
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@ Mac system monitoring CLI: temps, CPU, memory, disk, battery, live dashboard, pa
|
|
|
9
9
|
|
|
10
10
|
> Note : things may not work as intended if your firewall settings do not allow inbound connections for this program
|
|
11
11
|
|
|
12
|
+
> Additionally, you can skip init with enter but you may encounter some UI bugs. I am working on the issue at the moment.
|
|
13
|
+
|
|
12
14
|
## Features
|
|
13
15
|
|
|
14
16
|
- **P2P live data streaming** — send live system stats to another system over TCP with password authentication, TLS encryption, rate limiting, IP allow/deny lists, audit logging, and HMAC message signing
|
|
@@ -81,6 +83,116 @@ pyre live --interval 5 --log # Custom interval with auto-logging
|
|
|
81
83
|
| `--log` | Start continuous CSV logging immediately when live mode starts |
|
|
82
84
|
| `--packets` | Include packet monitor panel in static output |
|
|
83
85
|
|
|
86
|
+
## Commands
|
|
87
|
+
|
|
88
|
+
### Static snapshot
|
|
89
|
+
|
|
90
|
+
| Command | Description |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `pyre` | Show all system stats in a formatted table |
|
|
93
|
+
| `pyre --detailed` | Include detailed sensor and powermetrics readings |
|
|
94
|
+
| `pyre --json` | Output stats as JSON |
|
|
95
|
+
| `pyre --csv` | Output stats as CSV |
|
|
96
|
+
| `pyre --tsv` | Output stats as TSV |
|
|
97
|
+
| `pyre --once` | Show a single static snapshot (same as default without live) |
|
|
98
|
+
| `pyre --out <file>` | Write snapshot output to a file |
|
|
99
|
+
| `pyre --packets` | Include packet monitor panel in static output |
|
|
100
|
+
|
|
101
|
+
### Live dashboard
|
|
102
|
+
|
|
103
|
+
| Command | Description |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `pyre live` | Start the interactive live dashboard |
|
|
106
|
+
| `pyre live --interval <seconds>` | Set refresh interval for live mode (default: `2`) |
|
|
107
|
+
| `pyre live --detailed` | Live mode with detailed sensor readings |
|
|
108
|
+
| `pyre live --export-dir <dir>` | Custom directory for snapshot exports and logs |
|
|
109
|
+
| `pyre live --log` | Start continuous CSV logging immediately |
|
|
110
|
+
|
|
111
|
+
### Quick P2P setup
|
|
112
|
+
|
|
113
|
+
| Command | Description |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `pyre server` | Detect the local IP and print ready-to-paste commands for starting a P2P server and connecting a client on the same LAN |
|
|
116
|
+
|
|
117
|
+
`pyre server` automatically detects the machine's non-loopback IP address and outputs two copy-paste commands — one to start the server and one for a client on the same network to connect. Default port is `9876` and default password is `mysecret`.
|
|
118
|
+
|
|
119
|
+
### P2P server
|
|
120
|
+
|
|
121
|
+
| Command | Description |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `pyre p2p server` | Start a P2P server that streams live stats to authenticated peers |
|
|
124
|
+
| `pyre p2p server --p2p-host <host>` | Bind address (default: `0.0.0.0`) |
|
|
125
|
+
| `pyre p2p server --p2p-port <port>` | Port number (default: `9876`) |
|
|
126
|
+
| `pyre p2p server --p2p-password <password>` | Password for authentication (required) |
|
|
127
|
+
| `pyre p2p server --interval <seconds>` | Data refresh interval (default: `2`) |
|
|
128
|
+
| `pyre p2p server --detailed` | Include detailed sensor readings |
|
|
129
|
+
| `pyre p2p server --p2p-cert <file>` | TLS certificate file (PEM) for encrypted connections |
|
|
130
|
+
| `pyre p2p server --p2p-key <file>` | TLS private key file (PEM) for the server |
|
|
131
|
+
| `pyre p2p server --p2p-ca <file>` | TLS CA certificate file (PEM) — required by clients using `--p2p-tls` |
|
|
132
|
+
| `pyre p2p server --p2p-rate-limit <n>` | Max auth attempts per IP per minute (default: `5`) |
|
|
133
|
+
| `pyre p2p server --p2p-allow <ips>` | Comma-separated list of allowed IPs (empty = all) |
|
|
134
|
+
| `pyre p2p server --p2p-deny <ips>` | Comma-separated list of denied IPs |
|
|
135
|
+
| `pyre p2p server --p2p-audit-log <dir>` | Directory for P2P audit logs |
|
|
136
|
+
| `pyre p2p server --p2p-hmac-key <key>` | HMAC key for message signing (default: derived from password) |
|
|
137
|
+
|
|
138
|
+
### P2P client
|
|
139
|
+
|
|
140
|
+
| Command | Description |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `pyre p2p connect` | Connect to a P2P server and display live stats |
|
|
143
|
+
| `pyre p2p connect --p2p-host <host>` | Server address (default: `127.0.0.1`) |
|
|
144
|
+
| `pyre p2p connect --p2p-port <port>` | Server port (default: `9876`) |
|
|
145
|
+
| `pyre p2p connect --p2p-password <password>` | Password for authentication (required) |
|
|
146
|
+
| `pyre p2p connect --p2p-tls` | Enable TLS encryption for the connection |
|
|
147
|
+
| `pyre p2p connect --p2p-ca <file>` | TLS CA certificate file (PEM) — required when using `--p2p-tls` |
|
|
148
|
+
| `pyre p2p connect --p2p-insecure` | Skip TLS certificate verification (client only) |
|
|
149
|
+
| `pyre p2p connect --p2p-audit-log <dir>` | Directory for P2P client audit logs |
|
|
150
|
+
| `pyre p2p connect --p2p-hmac-key <key>` | HMAC key for message signing (must match server) |
|
|
151
|
+
|
|
152
|
+
### Options (shared)
|
|
153
|
+
|
|
154
|
+
| Option | Description |
|
|
155
|
+
|---|---|
|
|
156
|
+
| `-j, --json` | Output as JSON |
|
|
157
|
+
| `-c, --csv` | Output as CSV |
|
|
158
|
+
| `-t, --tsv` | Output as TSV |
|
|
159
|
+
| `--detailed` | Include detailed system info and sensor readings |
|
|
160
|
+
| `--interval <seconds>` | Refresh interval for live mode (default: `2`) |
|
|
161
|
+
| `--once` | Show a single static snapshot instead of live feed |
|
|
162
|
+
| `--out <file>` | Write snapshot output to a file |
|
|
163
|
+
| `--export-dir <dir>` | Directory for live-mode snapshot exports and logs (default: `./pyre-exports`) |
|
|
164
|
+
| `--log` | Start continuous CSV logging immediately when live mode starts |
|
|
165
|
+
| `--packets` | Include packet monitor panel in static output |
|
|
166
|
+
|
|
167
|
+
### Live dashboard keyboard controls
|
|
168
|
+
|
|
169
|
+
| Key | Action |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `q` | Quit the dashboard |
|
|
172
|
+
| `p` | Pause / resume the refresh cycle |
|
|
173
|
+
| `d` | Toggle detailed sensor mode |
|
|
174
|
+
| `g` | Show / hide sparkline graphs |
|
|
175
|
+
| `s` | Cycle sort order (CPU → memory → PID) |
|
|
176
|
+
| `f` | Cycle export format (JSON → CSV → TSV) |
|
|
177
|
+
| `e` | Export a snapshot to the export directory |
|
|
178
|
+
| `l` | Toggle continuous CSV logging |
|
|
179
|
+
| `c` | Open the UI customizer |
|
|
180
|
+
| `/` | Open process filter |
|
|
181
|
+
| `k` | Open process kill (enter PID, then Enter to confirm) |
|
|
182
|
+
| `+` | Increase refresh interval by 1 second |
|
|
183
|
+
| `-` | Decrease refresh interval by 1 second |
|
|
184
|
+
| `↑` / `k` | Navigate customizer options |
|
|
185
|
+
| `↓` / `j` | Navigate customizer options |
|
|
186
|
+
| `Enter` / `Space` | Toggle visibility or select theme in customizer |
|
|
187
|
+
| `Esc` | Close customizer or clear filter/kill input |
|
|
188
|
+
|
|
189
|
+
### P2P server controls
|
|
190
|
+
|
|
191
|
+
| Command | Action |
|
|
192
|
+
|---|---|
|
|
193
|
+
| `status` | Print the number of currently connected peers |
|
|
194
|
+
| `q` / `quit` / `exit` | Shut down the server gracefully |
|
|
195
|
+
|
|
84
196
|
## P2P Live Data Streaming
|
|
85
197
|
|
|
86
198
|
Send live system stats to another system over a TCP connection with password authentication. The server streams `StatsData` snapshots to authenticated peers at the configured interval.
|
package/dist/index.js
CHANGED
|
@@ -1550,7 +1550,10 @@ var state = {
|
|
|
1550
1550
|
"Toggle Tasks",
|
|
1551
1551
|
"Toggle Disk",
|
|
1552
1552
|
"Toggle Processes",
|
|
1553
|
-
"Toggle Tree View"
|
|
1553
|
+
"Toggle Tree View",
|
|
1554
|
+
"Splash Screen",
|
|
1555
|
+
"Splash Color",
|
|
1556
|
+
"Splash Animation"
|
|
1554
1557
|
],
|
|
1555
1558
|
CPU_ALERT_PCT: 90,
|
|
1556
1559
|
TEMP_ALERT_C: 95,
|
|
@@ -1576,7 +1579,10 @@ var state = {
|
|
|
1576
1579
|
p2pServer: null,
|
|
1577
1580
|
p2pServerRunning: false,
|
|
1578
1581
|
p2pPort: 9876,
|
|
1579
|
-
p2pPassword: "pyre"
|
|
1582
|
+
p2pPassword: "pyre",
|
|
1583
|
+
splashEnabled: true,
|
|
1584
|
+
splashColorScheme: "fire",
|
|
1585
|
+
splashAnimation: "classic"
|
|
1580
1586
|
};
|
|
1581
1587
|
function setStatus(msg, ms = 3e3) {
|
|
1582
1588
|
state.statusMessage = msg;
|
|
@@ -1741,6 +1747,13 @@ function renderCustomizerOverlay() {
|
|
|
1741
1747
|
lines.push(`${prefix}${opt}: ${chalk5.bold.green(state.currentTheme)} [${themesList.join(", ")}]`);
|
|
1742
1748
|
} else if (opt === "Graph Mode") {
|
|
1743
1749
|
lines.push(`${prefix}${opt}: ${chalk5.bold.green(state.graphMode)} [spark, bar]`);
|
|
1750
|
+
} else if (opt === "Splash Screen") {
|
|
1751
|
+
const status = state.splashEnabled ? chalk5.green("[ON]") : chalk5.red("[OFF]");
|
|
1752
|
+
lines.push(`${prefix}${opt}: ${status}`);
|
|
1753
|
+
} else if (opt === "Splash Color") {
|
|
1754
|
+
lines.push(`${prefix}${opt}: ${chalk5.bold.green(state.splashColorScheme)} [fire, ocean, forest, purple, monochrome]`);
|
|
1755
|
+
} else if (opt === "Splash Animation") {
|
|
1756
|
+
lines.push(`${prefix}${opt}: ${chalk5.bold.green(state.splashAnimation)} [classic, wave, sparks]`);
|
|
1744
1757
|
} else {
|
|
1745
1758
|
const toggleKey = getToggleKey(opt);
|
|
1746
1759
|
const isVisible = toggleKey ? toggleKey === "tree" ? state.treeView : state.visiblePanels[toggleKey] !== false : true;
|
|
@@ -2591,6 +2604,19 @@ function handleInputModeKey(str, key) {
|
|
|
2591
2604
|
state.currentTheme = themesList[nextIdx];
|
|
2592
2605
|
} else if (selected === "Graph Mode") {
|
|
2593
2606
|
state.graphMode = state.graphMode === "spark" ? "bar" : "spark";
|
|
2607
|
+
} else if (selected === "Splash Screen") {
|
|
2608
|
+
state.splashEnabled = !state.splashEnabled;
|
|
2609
|
+
setStatus(state.splashEnabled ? "Splash screen enabled" : "Splash screen disabled");
|
|
2610
|
+
} else if (selected === "Splash Color") {
|
|
2611
|
+
const schemes = ["fire", "ocean", "forest", "purple", "monochrome"];
|
|
2612
|
+
const curIdx = schemes.indexOf(state.splashColorScheme);
|
|
2613
|
+
state.splashColorScheme = schemes[(curIdx + 1) % schemes.length];
|
|
2614
|
+
setStatus(`Splash color: ${state.splashColorScheme}`);
|
|
2615
|
+
} else if (selected === "Splash Animation") {
|
|
2616
|
+
const animations = ["classic", "wave", "sparks"];
|
|
2617
|
+
const curIdx = animations.indexOf(state.splashAnimation);
|
|
2618
|
+
state.splashAnimation = animations[(curIdx + 1) % animations.length];
|
|
2619
|
+
setStatus(`Splash animation: ${state.splashAnimation}`);
|
|
2594
2620
|
} else {
|
|
2595
2621
|
const toggleKey = getToggleKey(selected);
|
|
2596
2622
|
if (toggleKey) {
|
|
@@ -3001,6 +3027,12 @@ async function startLive(opts2, splashPromise) {
|
|
|
3001
3027
|
case "tab":
|
|
3002
3028
|
cycleTab(1);
|
|
3003
3029
|
break;
|
|
3030
|
+
case "escape":
|
|
3031
|
+
case "esc":
|
|
3032
|
+
state.activePanel = "grid";
|
|
3033
|
+
setStatus("Grid view");
|
|
3034
|
+
render();
|
|
3035
|
+
break;
|
|
3004
3036
|
default:
|
|
3005
3037
|
if (str && str.length === 1) {
|
|
3006
3038
|
const tabId = tabKeyToId(str);
|
|
@@ -3100,59 +3132,285 @@ function stopLive() {
|
|
|
3100
3132
|
import chalk8 from "chalk";
|
|
3101
3133
|
var MAXV = 36;
|
|
3102
3134
|
var CHAR_RAMP = [" ", ".", "`", ",", ":", ";", "+", "*", "?", "%", "#", "@"];
|
|
3103
|
-
var
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3135
|
+
var PALETTES = {
|
|
3136
|
+
fire: [
|
|
3137
|
+
"#0a0505",
|
|
3138
|
+
"#1a0806",
|
|
3139
|
+
"#2a0b06",
|
|
3140
|
+
"#3f1004",
|
|
3141
|
+
"#591404",
|
|
3142
|
+
"#791804",
|
|
3143
|
+
"#9e1c04",
|
|
3144
|
+
"#c92004",
|
|
3145
|
+
"#d63004",
|
|
3146
|
+
"#e34004",
|
|
3147
|
+
"#f05004",
|
|
3148
|
+
"#fd6004",
|
|
3149
|
+
"#ff7004",
|
|
3150
|
+
"#ff8004",
|
|
3151
|
+
"#ff9004",
|
|
3152
|
+
"#ffa004",
|
|
3153
|
+
"#ffb004",
|
|
3154
|
+
"#ffc004",
|
|
3155
|
+
"#ffd004",
|
|
3156
|
+
"#ffe004",
|
|
3157
|
+
"#fff004",
|
|
3158
|
+
"#fff820",
|
|
3159
|
+
"#ffff40",
|
|
3160
|
+
"#ffff60",
|
|
3161
|
+
"#ffff80",
|
|
3162
|
+
"#ffffa0",
|
|
3163
|
+
"#ffffc0",
|
|
3164
|
+
"#ffffe0",
|
|
3165
|
+
"#ffffff",
|
|
3166
|
+
"#ffffff",
|
|
3167
|
+
"#ffffff",
|
|
3168
|
+
"#ffffff",
|
|
3169
|
+
"#ffffff",
|
|
3170
|
+
"#ffffff",
|
|
3171
|
+
"#ffffff",
|
|
3172
|
+
"#ffffff"
|
|
3173
|
+
],
|
|
3174
|
+
ocean: [
|
|
3175
|
+
"#020a1a",
|
|
3176
|
+
"#041428",
|
|
3177
|
+
"#061e36",
|
|
3178
|
+
"#0a2844",
|
|
3179
|
+
"#0e3252",
|
|
3180
|
+
"#123c60",
|
|
3181
|
+
"#16466e",
|
|
3182
|
+
"#1a507c",
|
|
3183
|
+
"#1e5a8a",
|
|
3184
|
+
"#226498",
|
|
3185
|
+
"#266ea6",
|
|
3186
|
+
"#2a78b4",
|
|
3187
|
+
"#2e82c2",
|
|
3188
|
+
"#328cd0",
|
|
3189
|
+
"#3696de",
|
|
3190
|
+
"#3aa0ec",
|
|
3191
|
+
"#3eaae8",
|
|
3192
|
+
"#42b4e4",
|
|
3193
|
+
"#46bee0",
|
|
3194
|
+
"#4ac8dc",
|
|
3195
|
+
"#4fd2d8",
|
|
3196
|
+
"#53dcd4",
|
|
3197
|
+
"#57e6d0",
|
|
3198
|
+
"#5befcc",
|
|
3199
|
+
"#5ff6c8",
|
|
3200
|
+
"#63ffc4",
|
|
3201
|
+
"#67ffc0",
|
|
3202
|
+
"#6bffbc",
|
|
3203
|
+
"#6fffB8",
|
|
3204
|
+
"#73ffb4",
|
|
3205
|
+
"#77ffb0",
|
|
3206
|
+
"#7bffac",
|
|
3207
|
+
"#7fffA8",
|
|
3208
|
+
"#83ffa4",
|
|
3209
|
+
"#87ffa0",
|
|
3210
|
+
"#8bff9c"
|
|
3211
|
+
],
|
|
3212
|
+
forest: [
|
|
3213
|
+
"#020a02",
|
|
3214
|
+
"#041404",
|
|
3215
|
+
"#061e06",
|
|
3216
|
+
"#0a280a",
|
|
3217
|
+
"#0e320e",
|
|
3218
|
+
"#123c12",
|
|
3219
|
+
"#164616",
|
|
3220
|
+
"#1a501a",
|
|
3221
|
+
"#1e5a1e",
|
|
3222
|
+
"#226422",
|
|
3223
|
+
"#266e26",
|
|
3224
|
+
"#2a782a",
|
|
3225
|
+
"#2e822e",
|
|
3226
|
+
"#328c32",
|
|
3227
|
+
"#369636",
|
|
3228
|
+
"#3aa03a",
|
|
3229
|
+
"#3eaa3e",
|
|
3230
|
+
"#42b442",
|
|
3231
|
+
"#46be46",
|
|
3232
|
+
"#4ac84a",
|
|
3233
|
+
"#4fd24f",
|
|
3234
|
+
"#53dc53",
|
|
3235
|
+
"#57e657",
|
|
3236
|
+
"#5bf05b",
|
|
3237
|
+
"#5ff65f",
|
|
3238
|
+
"#63ff63",
|
|
3239
|
+
"#67ff67",
|
|
3240
|
+
"#6bff6b",
|
|
3241
|
+
"#6fff6f",
|
|
3242
|
+
"#73ff73",
|
|
3243
|
+
"#77ff77",
|
|
3244
|
+
"#7bff7b",
|
|
3245
|
+
"#7fff7f",
|
|
3246
|
+
"#83ff83",
|
|
3247
|
+
"#87ff87",
|
|
3248
|
+
"#8bff8b"
|
|
3249
|
+
],
|
|
3250
|
+
purple: [
|
|
3251
|
+
"#0a021a",
|
|
3252
|
+
"#140428",
|
|
3253
|
+
"#1e0636",
|
|
3254
|
+
"#280844",
|
|
3255
|
+
"#320a52",
|
|
3256
|
+
"#3c0c60",
|
|
3257
|
+
"#460e6e",
|
|
3258
|
+
"#50107c",
|
|
3259
|
+
"#5a128a",
|
|
3260
|
+
"#641498",
|
|
3261
|
+
"#6e16a6",
|
|
3262
|
+
"#7818b4",
|
|
3263
|
+
"#821ac2",
|
|
3264
|
+
"#8c1cd0",
|
|
3265
|
+
"#961ede",
|
|
3266
|
+
"#a020ec",
|
|
3267
|
+
"#aa22e8",
|
|
3268
|
+
"#b424e4",
|
|
3269
|
+
"#be26e0",
|
|
3270
|
+
"#c828dc",
|
|
3271
|
+
"#d22ad8",
|
|
3272
|
+
"#dc2cd4",
|
|
3273
|
+
"#e62ed0",
|
|
3274
|
+
"#f030cc",
|
|
3275
|
+
"#f432c8",
|
|
3276
|
+
"#f834c4",
|
|
3277
|
+
"#fc36c0",
|
|
3278
|
+
"#ff38bc",
|
|
3279
|
+
"#ff3ab8",
|
|
3280
|
+
"#ff3cb4",
|
|
3281
|
+
"#ff3eb0",
|
|
3282
|
+
"#ff40ac",
|
|
3283
|
+
"#ff42a8",
|
|
3284
|
+
"#ff44a4",
|
|
3285
|
+
"#ff46a0",
|
|
3286
|
+
"#ff489c"
|
|
3287
|
+
],
|
|
3288
|
+
monochrome: [
|
|
3289
|
+
"#0a0a0a",
|
|
3290
|
+
"#1a1a1a",
|
|
3291
|
+
"#2a2a2a",
|
|
3292
|
+
"#3a3a3a",
|
|
3293
|
+
"#4a4a4a",
|
|
3294
|
+
"#5a5a5a",
|
|
3295
|
+
"#6a6a6a",
|
|
3296
|
+
"#7a7a7a",
|
|
3297
|
+
"#8a8a8a",
|
|
3298
|
+
"#9a9a9a",
|
|
3299
|
+
"#aaaaaa",
|
|
3300
|
+
"#bAbAbA",
|
|
3301
|
+
"#cacaca",
|
|
3302
|
+
"#dAdAdA",
|
|
3303
|
+
"#eaeaea",
|
|
3304
|
+
"#f4f4f4",
|
|
3305
|
+
"#f8f8f8",
|
|
3306
|
+
"#fcfcfc",
|
|
3307
|
+
"#ffffff",
|
|
3308
|
+
"#ffffff",
|
|
3309
|
+
"#ffffff",
|
|
3310
|
+
"#ffffff",
|
|
3311
|
+
"#ffffff",
|
|
3312
|
+
"#ffffff",
|
|
3313
|
+
"#ffffff",
|
|
3314
|
+
"#ffffff",
|
|
3315
|
+
"#ffffff",
|
|
3316
|
+
"#ffffff",
|
|
3317
|
+
"#ffffff",
|
|
3318
|
+
"#ffffff",
|
|
3319
|
+
"#ffffff",
|
|
3320
|
+
"#ffffff",
|
|
3321
|
+
"#ffffff",
|
|
3322
|
+
"#ffffff",
|
|
3323
|
+
"#ffffff",
|
|
3324
|
+
"#ffffff"
|
|
3325
|
+
]
|
|
3326
|
+
};
|
|
3141
3327
|
function getRgb(hex) {
|
|
3142
3328
|
const r = parseInt(hex.slice(1, 3), 16);
|
|
3143
3329
|
const g = parseInt(hex.slice(3, 5), 16);
|
|
3144
3330
|
const b = parseInt(hex.slice(5, 7), 16);
|
|
3145
3331
|
return { r, g, b };
|
|
3146
3332
|
}
|
|
3147
|
-
|
|
3333
|
+
function getPalette(scheme) {
|
|
3334
|
+
return (PALETTES[scheme] ?? PALETTES.fire).map(getRgb);
|
|
3335
|
+
}
|
|
3148
3336
|
function getChar(val) {
|
|
3149
3337
|
const idx = Math.min(CHAR_RAMP.length - 1, Math.floor(val / (MAXV + 1) * CHAR_RAMP.length));
|
|
3150
3338
|
return CHAR_RAMP[idx];
|
|
3151
3339
|
}
|
|
3152
|
-
|
|
3340
|
+
function initFire(cols, rows) {
|
|
3341
|
+
return new Uint8Array(cols * rows);
|
|
3342
|
+
}
|
|
3343
|
+
function spreadFireClassic(fire, cols, rows) {
|
|
3344
|
+
for (let x = 0; x < cols; x++) {
|
|
3345
|
+
fire[(rows - 1) * cols + x] = MAXV - Math.floor(Math.random() * 4);
|
|
3346
|
+
}
|
|
3347
|
+
for (let y = rows - 1; y >= 1; y--) {
|
|
3348
|
+
for (let x = 0; x < cols; x++) {
|
|
3349
|
+
const src = y * cols + x;
|
|
3350
|
+
const val = fire[src];
|
|
3351
|
+
if (val === 0) {
|
|
3352
|
+
fire[(y - 1) * cols + x] = 0;
|
|
3353
|
+
continue;
|
|
3354
|
+
}
|
|
3355
|
+
const decay = Math.floor(Math.random() * 3);
|
|
3356
|
+
const drift = Math.floor(Math.random() * 3) - 1;
|
|
3357
|
+
let nx = x + drift;
|
|
3358
|
+
if (nx < 0) nx = 0;
|
|
3359
|
+
else if (nx >= cols) nx = cols - 1;
|
|
3360
|
+
fire[(y - 1) * cols + nx] = Math.max(0, val - decay);
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
function spreadFireWave(fire, cols, rows, time) {
|
|
3365
|
+
for (let x = 0; x < cols; x++) {
|
|
3366
|
+
const wave = Math.sin((x + time) * 0.3) * 0.5 + 0.5;
|
|
3367
|
+
fire[(rows - 1) * cols + x] = Math.floor(MAXV * wave);
|
|
3368
|
+
}
|
|
3369
|
+
for (let y = rows - 1; y >= 1; y--) {
|
|
3370
|
+
for (let x = 0; x < cols; x++) {
|
|
3371
|
+
const src = y * cols + x;
|
|
3372
|
+
const val = fire[src];
|
|
3373
|
+
if (val === 0) {
|
|
3374
|
+
fire[(y - 1) * cols + x] = 0;
|
|
3375
|
+
continue;
|
|
3376
|
+
}
|
|
3377
|
+
const decay = Math.floor(Math.random() * 2);
|
|
3378
|
+
const nx = x;
|
|
3379
|
+
fire[(y - 1) * cols + nx] = Math.max(0, val - decay);
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
function spreadFireSparks(fire, cols, rows) {
|
|
3384
|
+
for (let x = 0; x < cols; x++) {
|
|
3385
|
+
fire[(rows - 1) * cols + x] = Math.random() < 0.3 ? MAXV - Math.floor(Math.random() * 4) : 0;
|
|
3386
|
+
}
|
|
3387
|
+
for (let y = rows - 1; y >= 1; y--) {
|
|
3388
|
+
for (let x = 0; x < cols; x++) {
|
|
3389
|
+
const src = y * cols + x;
|
|
3390
|
+
const val = fire[src];
|
|
3391
|
+
if (val === 0) {
|
|
3392
|
+
fire[(y - 1) * cols + x] = 0;
|
|
3393
|
+
continue;
|
|
3394
|
+
}
|
|
3395
|
+
const decay = Math.floor(Math.random() * 4);
|
|
3396
|
+
const drift = Math.floor(Math.random() * 5) - 2;
|
|
3397
|
+
let nx = x + drift;
|
|
3398
|
+
if (nx < 0) nx = 0;
|
|
3399
|
+
else if (nx >= cols) nx = cols - 1;
|
|
3400
|
+
fire[(y - 1) * cols + nx] = Math.max(0, val - decay);
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
async function showSplash(config) {
|
|
3405
|
+
const enabled = config?.enabled ?? true;
|
|
3406
|
+
if (!enabled) return;
|
|
3407
|
+
const colorScheme = config?.colorScheme ?? "fire";
|
|
3408
|
+
const animation = config?.animation ?? "classic";
|
|
3409
|
+
const rgbPalette = getPalette(colorScheme);
|
|
3153
3410
|
const cols = 60;
|
|
3154
3411
|
const rows = 15;
|
|
3155
|
-
let fire =
|
|
3412
|
+
let fire = initFire(cols, rows);
|
|
3413
|
+
let time = 0;
|
|
3156
3414
|
process.stdout.write("\x1B[2J");
|
|
3157
3415
|
const termRows = process.stdout.rows || 24;
|
|
3158
3416
|
const termCols = process.stdout.columns || 80;
|
|
@@ -3160,24 +3418,13 @@ async function showSplash() {
|
|
|
3160
3418
|
const startCol = Math.max(0, Math.floor((termCols - cols) / 2));
|
|
3161
3419
|
return new Promise((resolve) => {
|
|
3162
3420
|
const interval = setInterval(() => {
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
if (val === 0) {
|
|
3171
|
-
fire[(y - 1) * cols + x] = 0;
|
|
3172
|
-
continue;
|
|
3173
|
-
}
|
|
3174
|
-
const decay = Math.floor(Math.random() * 3);
|
|
3175
|
-
const drift = Math.floor(Math.random() * 3) - 1;
|
|
3176
|
-
let nx = x + drift;
|
|
3177
|
-
if (nx < 0) nx = 0;
|
|
3178
|
-
else if (nx >= cols) nx = cols - 1;
|
|
3179
|
-
fire[(y - 1) * cols + nx] = Math.max(0, val - decay);
|
|
3180
|
-
}
|
|
3421
|
+
time++;
|
|
3422
|
+
if (animation === "wave") {
|
|
3423
|
+
spreadFireWave(fire, cols, rows, time);
|
|
3424
|
+
} else if (animation === "sparks") {
|
|
3425
|
+
spreadFireSparks(fire, cols, rows);
|
|
3426
|
+
} else {
|
|
3427
|
+
spreadFireClassic(fire, cols, rows);
|
|
3181
3428
|
}
|
|
3182
3429
|
let output = "";
|
|
3183
3430
|
for (let i = 0; i < startRow; i++) output += "\n";
|
|
@@ -3381,6 +3628,22 @@ async function runP2PServer() {
|
|
|
3381
3628
|
process.exit(0);
|
|
3382
3629
|
});
|
|
3383
3630
|
}
|
|
3631
|
+
var _prevFrameLines2 = [];
|
|
3632
|
+
function writeFrame2(lines) {
|
|
3633
|
+
if (_prevFrameLines2.length === 0 || lines.length !== _prevFrameLines2.length) {
|
|
3634
|
+
process.stdout.write("\x1B[?25l\x1B[H" + lines.map((l) => `\x1B[2K${l}`).join("\r\n") + "\x1B[0J");
|
|
3635
|
+
_prevFrameLines2 = lines;
|
|
3636
|
+
return;
|
|
3637
|
+
}
|
|
3638
|
+
let out = "";
|
|
3639
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3640
|
+
if (lines[i] !== _prevFrameLines2[i]) {
|
|
3641
|
+
out += `\x1B[${i + 1};1H\x1B[2K${lines[i]}`;
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
if (out) process.stdout.write(out);
|
|
3645
|
+
_prevFrameLines2 = lines;
|
|
3646
|
+
}
|
|
3384
3647
|
async function runP2PConnect() {
|
|
3385
3648
|
const host = sanitizeHost2(opts.p2pHost || "127.0.0.1");
|
|
3386
3649
|
const port = parseInt(opts.p2pPort, 10) || 9876;
|
|
@@ -3406,16 +3669,23 @@ async function runP2PConnect() {
|
|
|
3406
3669
|
process.stdout.write(`\r${chalk9.dim(msg)}${" ".repeat(40)}\r`);
|
|
3407
3670
|
});
|
|
3408
3671
|
client.setOnData((data) => {
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3672
|
+
const lines = [];
|
|
3673
|
+
lines.push(chalk9.bold(` pyre P2P Client \u2014 ${data.header.hostname}`));
|
|
3674
|
+
lines.push(chalk9.dim(` ${data.timestamp}`));
|
|
3675
|
+
lines.push("");
|
|
3676
|
+
lines.push(...formatTable(data, {
|
|
3414
3677
|
width: process.stdout.columns || 80,
|
|
3415
3678
|
sortBy: "cpu",
|
|
3416
3679
|
treeView: false,
|
|
3417
3680
|
visible: { packets: true }
|
|
3418
|
-
}));
|
|
3681
|
+
}).split("\n"));
|
|
3682
|
+
while (lines.length < process.stdout.rows) {
|
|
3683
|
+
lines.push("");
|
|
3684
|
+
}
|
|
3685
|
+
if (lines.length > process.stdout.rows) {
|
|
3686
|
+
lines.length = process.stdout.rows;
|
|
3687
|
+
}
|
|
3688
|
+
writeFrame2(lines);
|
|
3419
3689
|
});
|
|
3420
3690
|
const protocolLabel = useTLS ? "TLS" : "TCP";
|
|
3421
3691
|
console.log(chalk9.bold(`
|
|
@@ -3424,6 +3694,7 @@ async function runP2PConnect() {
|
|
|
3424
3694
|
console.log(chalk9.dim(` Connecting to ${host}:${port}...`));
|
|
3425
3695
|
console.log(chalk9.dim(` Press Ctrl+C to disconnect
|
|
3426
3696
|
`));
|
|
3697
|
+
process.stdout.write("\x1B[?25l");
|
|
3427
3698
|
try {
|
|
3428
3699
|
await client.connect();
|
|
3429
3700
|
} catch (err) {
|
|
@@ -3447,10 +3718,13 @@ async function runP2PConnect() {
|
|
|
3447
3718
|
console.log(chalk9.dim(" 3. Check connectivity: ping " + host));
|
|
3448
3719
|
console.log(chalk9.dim(" 4. Ensure both machines are on the same LAN (ping each other first)"));
|
|
3449
3720
|
}
|
|
3721
|
+
process.stdout.write("\x1B[?25h");
|
|
3450
3722
|
process.exit(1);
|
|
3451
3723
|
}
|
|
3452
3724
|
process.once("SIGINT", () => {
|
|
3725
|
+
_prevFrameLines2 = [];
|
|
3453
3726
|
client.disconnect();
|
|
3727
|
+
process.stdout.write("\x1B[?25h\x1B[2J\x1B[H");
|
|
3454
3728
|
process.exit(0);
|
|
3455
3729
|
});
|
|
3456
3730
|
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -253,6 +253,25 @@ async function runP2PServer(): Promise<void> {
|
|
|
253
253
|
});
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
+
let _prevFrameLines: string[] = [];
|
|
257
|
+
|
|
258
|
+
function writeFrame(lines: string[]) {
|
|
259
|
+
if (_prevFrameLines.length === 0 || lines.length !== _prevFrameLines.length) {
|
|
260
|
+
process.stdout.write('\x1b[?25l\x1b[H' + lines.map(l => `\x1b[2K${l}`).join('\r\n') + '\x1b[0J');
|
|
261
|
+
_prevFrameLines = lines;
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let out = '';
|
|
266
|
+
for (let i = 0; i < lines.length; i++) {
|
|
267
|
+
if (lines[i] !== _prevFrameLines[i]) {
|
|
268
|
+
out += `\x1b[${i + 1};1H\x1b[2K${lines[i]}`;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (out) process.stdout.write(out);
|
|
272
|
+
_prevFrameLines = lines;
|
|
273
|
+
}
|
|
274
|
+
|
|
256
275
|
async function runP2PConnect(): Promise<void> {
|
|
257
276
|
const host = sanitizeHost(opts.p2pHost || '127.0.0.1');
|
|
258
277
|
const port = parseInt(opts.p2pPort, 10) || 9876;
|
|
@@ -282,16 +301,25 @@ async function runP2PConnect(): Promise<void> {
|
|
|
282
301
|
});
|
|
283
302
|
|
|
284
303
|
client.setOnData((data) => {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
304
|
+
const lines: string[] = [];
|
|
305
|
+
lines.push(chalk.bold(` pyre P2P Client — ${data.header.hostname}`));
|
|
306
|
+
lines.push(chalk.dim(` ${data.timestamp}`));
|
|
307
|
+
lines.push('');
|
|
308
|
+
lines.push(...formatTable(data, {
|
|
290
309
|
width: process.stdout.columns || 80,
|
|
291
310
|
sortBy: 'cpu',
|
|
292
311
|
treeView: false,
|
|
293
312
|
visible: { packets: true },
|
|
294
|
-
}));
|
|
313
|
+
}).split('\n'));
|
|
314
|
+
|
|
315
|
+
while (lines.length < process.stdout.rows) {
|
|
316
|
+
lines.push('');
|
|
317
|
+
}
|
|
318
|
+
if (lines.length > process.stdout.rows) {
|
|
319
|
+
lines.length = process.stdout.rows;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
writeFrame(lines);
|
|
295
323
|
});
|
|
296
324
|
|
|
297
325
|
const protocolLabel = useTLS ? 'TLS' : 'TCP';
|
|
@@ -300,6 +328,8 @@ async function runP2PConnect(): Promise<void> {
|
|
|
300
328
|
console.log(chalk.dim(` Connecting to ${host}:${port}...`));
|
|
301
329
|
console.log(chalk.dim(` Press Ctrl+C to disconnect\n`));
|
|
302
330
|
|
|
331
|
+
process.stdout.write('\x1b[?25l');
|
|
332
|
+
|
|
303
333
|
try {
|
|
304
334
|
await client.connect();
|
|
305
335
|
} catch (err: any) {
|
|
@@ -323,11 +353,14 @@ async function runP2PConnect(): Promise<void> {
|
|
|
323
353
|
console.log(chalk.dim(' 3. Check connectivity: ping ' + host));
|
|
324
354
|
console.log(chalk.dim(' 4. Ensure both machines are on the same LAN (ping each other first)'));
|
|
325
355
|
}
|
|
356
|
+
process.stdout.write('\x1b[?25h');
|
|
326
357
|
process.exit(1);
|
|
327
358
|
}
|
|
328
359
|
|
|
329
360
|
process.once('SIGINT', () => {
|
|
361
|
+
_prevFrameLines = [];
|
|
330
362
|
client.disconnect();
|
|
363
|
+
process.stdout.write('\x1b[?25h\x1b[2J\x1b[H');
|
|
331
364
|
process.exit(0);
|
|
332
365
|
});
|
|
333
366
|
}
|
package/src/live/input.ts
CHANGED
|
@@ -10,7 +10,7 @@ import readline from 'node:readline';
|
|
|
10
10
|
import chalk from 'chalk';
|
|
11
11
|
import { collectAll, StatsData } from '../monitors/index.js';
|
|
12
12
|
import { THEMES, type ThemeName, type VisibleItems, getTabHitboxes, TAB_BAR_ROW } from '../formatters/index.js';
|
|
13
|
-
import { state, setStatus, getToggleKey } from './state.js';
|
|
13
|
+
import { state, setStatus, getToggleKey, type SplashColorScheme, type SplashAnimation } from './state.js';
|
|
14
14
|
import { exportSnapshot, startLogging, stopLogging, toggleLogging, writeLogRow } from './export.js';
|
|
15
15
|
import { render, footerLine, checkAlerts, invalidateTableCache, invalidateFrame } from './render.js';
|
|
16
16
|
import type { LiveOptions, ExportFormat, InputMode, SortMode, GraphMode } from './types.js';
|
|
@@ -51,18 +51,31 @@ function handleInputModeKey(str: string, key: readline.Key) {
|
|
|
51
51
|
state.currentTheme = themesList[nextIdx];
|
|
52
52
|
} else if (selected === 'Graph Mode') {
|
|
53
53
|
state.graphMode = state.graphMode === 'spark' ? 'bar' : 'spark';
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
54
|
+
} else if (selected === 'Splash Screen') {
|
|
55
|
+
state.splashEnabled = !state.splashEnabled;
|
|
56
|
+
setStatus(state.splashEnabled ? 'Splash screen enabled' : 'Splash screen disabled');
|
|
57
|
+
} else if (selected === 'Splash Color') {
|
|
58
|
+
const schemes: SplashColorScheme[] = ['fire', 'ocean', 'forest', 'purple', 'monochrome'];
|
|
59
|
+
const curIdx = schemes.indexOf(state.splashColorScheme);
|
|
60
|
+
state.splashColorScheme = schemes[(curIdx + 1) % schemes.length];
|
|
61
|
+
setStatus(`Splash color: ${state.splashColorScheme}`);
|
|
62
|
+
} else if (selected === 'Splash Animation') {
|
|
63
|
+
const animations: SplashAnimation[] = ['classic', 'wave', 'sparks'];
|
|
64
|
+
const curIdx = animations.indexOf(state.splashAnimation);
|
|
65
|
+
state.splashAnimation = animations[(curIdx + 1) % animations.length];
|
|
66
|
+
setStatus(`Splash animation: ${state.splashAnimation}`);
|
|
67
|
+
} else {
|
|
68
|
+
const toggleKey = getToggleKey(selected);
|
|
69
|
+
if (toggleKey) {
|
|
70
|
+
if (toggleKey === 'tree') {
|
|
71
|
+
state.treeView = !state.treeView;
|
|
72
|
+
state.visiblePanels.tree = state.treeView;
|
|
73
|
+
} else {
|
|
74
|
+
const isVisible = state.visiblePanels[toggleKey] !== false;
|
|
75
|
+
state.visiblePanels[toggleKey] = !isVisible;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
66
79
|
invalidateTableCache();
|
|
67
80
|
invalidateFrame();
|
|
68
81
|
}
|
|
@@ -492,6 +505,12 @@ function killProcess(pidStr: string, signal: string = 'SIGTERM') {
|
|
|
492
505
|
case 'tab':
|
|
493
506
|
cycleTab(1);
|
|
494
507
|
break;
|
|
508
|
+
case 'escape':
|
|
509
|
+
case 'esc':
|
|
510
|
+
state.activePanel = 'grid';
|
|
511
|
+
setStatus('Grid view');
|
|
512
|
+
render();
|
|
513
|
+
break;
|
|
495
514
|
default:
|
|
496
515
|
if (str && str.length === 1) {
|
|
497
516
|
const tabId = tabKeyToId(str);
|
package/src/live/render.ts
CHANGED
|
@@ -103,21 +103,28 @@ function renderCustomizerOverlay(): string {
|
|
|
103
103
|
chalk.bgCyan.black.bold(' UI CUSTOMIZER (Use ↑/↓ or J/K to navigate, Enter/Space to toggle, Esc to exit) '),
|
|
104
104
|
];
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
106
|
+
state.CUSTOMIZER_OPTIONS.forEach((opt, idx) => {
|
|
107
|
+
const isSelected = idx === state.customizerIndex;
|
|
108
|
+
const prefix = isSelected ? chalk.yellow('▶ ') : ' ';
|
|
109
|
+
|
|
110
|
+
if (opt === 'Theme') {
|
|
111
|
+
lines.push(`${prefix}${opt}: ${chalk.bold.green(state.currentTheme)} [${themesList.join(', ')}]`);
|
|
112
|
+
} else if (opt === 'Graph Mode') {
|
|
113
|
+
lines.push(`${prefix}${opt}: ${chalk.bold.green(state.graphMode)} [spark, bar]`);
|
|
114
|
+
} else if (opt === 'Splash Screen') {
|
|
115
|
+
const status = state.splashEnabled ? chalk.green('[ON]') : chalk.red('[OFF]');
|
|
116
|
+
lines.push(`${prefix}${opt}: ${status}`);
|
|
117
|
+
} else if (opt === 'Splash Color') {
|
|
118
|
+
lines.push(`${prefix}${opt}: ${chalk.bold.green(state.splashColorScheme)} [fire, ocean, forest, purple, monochrome]`);
|
|
119
|
+
} else if (opt === 'Splash Animation') {
|
|
120
|
+
lines.push(`${prefix}${opt}: ${chalk.bold.green(state.splashAnimation)} [classic, wave, sparks]`);
|
|
121
|
+
} else {
|
|
122
|
+
const toggleKey = getToggleKey(opt);
|
|
123
|
+
const isVisible = toggleKey ? (toggleKey === 'tree' ? state.treeView : state.visiblePanels[toggleKey] !== false) : true;
|
|
124
|
+
const status = isVisible ? chalk.green('[VISIBLE]') : chalk.red('[HIDDEN]');
|
|
125
|
+
lines.push(`${prefix}${opt}: ${status}`);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
121
128
|
|
|
122
129
|
return lines.map(l => ` ${l}`).join('\n');
|
|
123
130
|
}
|
package/src/live/state.ts
CHANGED
|
@@ -14,6 +14,10 @@ import type { ThemeName, VisibleItems } from '../formatters/types.js';
|
|
|
14
14
|
|
|
15
15
|
export const SIGNAL_OPTIONS = ['SIGTERM', 'SIGKILL', 'SIGINT', 'SIGHUP', 'SIGSTOP', 'SIGCONT'] as const;
|
|
16
16
|
|
|
17
|
+
import { type SplashColorScheme, type SplashAnimation } from '../splash.js';
|
|
18
|
+
|
|
19
|
+
export { type SplashColorScheme, type SplashAnimation };
|
|
20
|
+
|
|
17
21
|
const state = {
|
|
18
22
|
intervalHandle: null as NodeJS.Timeout | null,
|
|
19
23
|
uiIntervalHandle: null as NodeJS.Timeout | null,
|
|
@@ -59,48 +63,54 @@ const state = {
|
|
|
59
63
|
treeView: false,
|
|
60
64
|
SIGNAL_OPTIONS,
|
|
61
65
|
customizerIndex: 0,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
66
|
+
CUSTOMIZER_OPTIONS: [
|
|
67
|
+
'Theme',
|
|
68
|
+
'Graph Mode',
|
|
69
|
+
'Toggle CPU',
|
|
70
|
+
'Toggle Memory',
|
|
71
|
+
'Toggle GPU',
|
|
72
|
+
'Toggle Power',
|
|
73
|
+
'Toggle Battery',
|
|
74
|
+
'Toggle Thermal',
|
|
75
|
+
'Toggle Network',
|
|
76
|
+
'Toggle Packets',
|
|
77
|
+
'Toggle Tasks',
|
|
78
|
+
'Toggle Disk',
|
|
79
|
+
'Toggle Processes',
|
|
80
|
+
'Toggle Tree View',
|
|
81
|
+
'Splash Screen',
|
|
82
|
+
'Splash Color',
|
|
83
|
+
'Splash Animation',
|
|
84
|
+
],
|
|
85
|
+
CPU_ALERT_PCT: 90,
|
|
86
|
+
TEMP_ALERT_C: 95,
|
|
87
|
+
alerted: false,
|
|
88
|
+
activePanel: 'grid' as 'grid' | 'cpu' | 'mem' | 'gpu' | 'power' | 'battery' | 'thermal' | 'network' | 'packets' | 'tasks' | 'disk' | 'process' | 'p2p',
|
|
89
|
+
mouseEnabled: true,
|
|
90
|
+
PANEL_TABS: [
|
|
91
|
+
{ id: 'cpu', label: 'CPU', key: '1' },
|
|
92
|
+
{ id: 'mem', label: 'Memory', key: '2' },
|
|
93
|
+
{ id: 'gpu', label: 'GPU', key: '3' },
|
|
94
|
+
{ id: 'power', label: 'Power', key: '4' },
|
|
95
|
+
{ id: 'battery', label: 'Battery', key: '5' },
|
|
96
|
+
{ id: 'thermal', label: 'Thermal', key: '6' },
|
|
97
|
+
{ id: 'network', label: 'Network', key: '7' },
|
|
98
|
+
{ id: 'packets', label: 'Packets', key: '8' },
|
|
99
|
+
{ id: 'tasks', label: 'Tasks', key: '9' },
|
|
100
|
+
{ id: 'disk', label: 'Disk', key: '0' },
|
|
101
|
+
{ id: 'process', label: 'Process', key: 'P' },
|
|
102
|
+
{ id: 'p2p', label: 'P2P', key: 'R' },
|
|
103
|
+
] as const,
|
|
104
|
+
p2pEvents: [] as { type: string; detail: string; ts: number }[],
|
|
105
|
+
p2pBind: '',
|
|
106
|
+
p2pServer: null as { stop: () => void; peerEventHistory: { type: string; detail: string; ts: number }[] } | null,
|
|
107
|
+
p2pServerRunning: false,
|
|
108
|
+
p2pPort: 9876,
|
|
109
|
+
p2pPassword: 'pyre',
|
|
110
|
+
splashEnabled: true,
|
|
111
|
+
splashColorScheme: 'fire' as SplashColorScheme,
|
|
112
|
+
splashAnimation: 'classic' as SplashAnimation,
|
|
113
|
+
};
|
|
104
114
|
|
|
105
115
|
function setStatus(msg: string, ms = 3000) {
|
|
106
116
|
state.statusMessage = msg;
|
package/src/splash.ts
CHANGED
|
@@ -1,19 +1,60 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ASCII Splash Screen implementation based on the doom-fire effect.
|
|
3
|
+
*
|
|
4
|
+
* Supports configurable colour schemes and animation types via
|
|
5
|
+
* the {@link SplashConfig} interface.
|
|
3
6
|
*/
|
|
4
7
|
import chalk from 'chalk';
|
|
5
8
|
|
|
6
9
|
const MAXV = 36;
|
|
7
10
|
const CHAR_RAMP = [' ', '.', '`', ',', ':', ';', '+', '*', '?', '%', '#', '@'];
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
export type SplashColorScheme = 'fire' | 'ocean' | 'forest' | 'purple' | 'monochrome';
|
|
13
|
+
export type SplashAnimation = 'classic' | 'wave' | 'sparks';
|
|
14
|
+
|
|
15
|
+
export interface SplashConfig {
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
colorScheme?: SplashColorScheme;
|
|
18
|
+
animation?: SplashAnimation;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const PALETTES: Record<SplashColorScheme, string[]> = {
|
|
22
|
+
fire: [
|
|
23
|
+
'#0a0505', '#1a0806', '#2a0b06', '#3f1004', '#591404', '#791804', '#9e1c04', '#c92004',
|
|
24
|
+
'#d63004', '#e34004', '#f05004', '#fd6004', '#ff7004', '#ff8004', '#ff9004', '#ffa004',
|
|
25
|
+
'#ffb004', '#ffc004', '#ffd004', '#ffe004', '#fff004', '#fff820', '#ffff40', '#ffff60',
|
|
26
|
+
'#ffff80', '#ffffa0', '#ffffc0', '#ffffe0', '#ffffff', '#ffffff', '#ffffff', '#ffffff',
|
|
27
|
+
'#ffffff', '#ffffff', '#ffffff', '#ffffff',
|
|
28
|
+
],
|
|
29
|
+
ocean: [
|
|
30
|
+
'#020a1a', '#041428', '#061e36', '#0a2844', '#0e3252', '#123c60', '#16466e', '#1a507c',
|
|
31
|
+
'#1e5a8a', '#226498', '#266ea6', '#2a78b4', '#2e82c2', '#328cd0', '#3696de', '#3aa0ec',
|
|
32
|
+
'#3eaae8', '#42b4e4', '#46bee0', '#4ac8dc', '#4fd2d8', '#53dcd4', '#57e6d0', '#5befcc',
|
|
33
|
+
'#5ff6c8', '#63ffc4', '#67ffc0', '#6bffbc', '#6fffB8', '#73ffb4', '#77ffb0', '#7bffac',
|
|
34
|
+
'#7fffA8', '#83ffa4', '#87ffa0', '#8bff9c',
|
|
35
|
+
],
|
|
36
|
+
forest: [
|
|
37
|
+
'#020a02', '#041404', '#061e06', '#0a280a', '#0e320e', '#123c12', '#164616', '#1a501a',
|
|
38
|
+
'#1e5a1e', '#226422', '#266e26', '#2a782a', '#2e822e', '#328c32', '#369636', '#3aa03a',
|
|
39
|
+
'#3eaa3e', '#42b442', '#46be46', '#4ac84a', '#4fd24f', '#53dc53', '#57e657', '#5bf05b',
|
|
40
|
+
'#5ff65f', '#63ff63', '#67ff67', '#6bff6b', '#6fff6f', '#73ff73', '#77ff77', '#7bff7b',
|
|
41
|
+
'#7fff7f', '#83ff83', '#87ff87', '#8bff8b',
|
|
42
|
+
],
|
|
43
|
+
purple: [
|
|
44
|
+
'#0a021a', '#140428', '#1e0636', '#280844', '#320a52', '#3c0c60', '#460e6e', '#50107c',
|
|
45
|
+
'#5a128a', '#641498', '#6e16a6', '#7818b4', '#821ac2', '#8c1cd0', '#961ede', '#a020ec',
|
|
46
|
+
'#aa22e8', '#b424e4', '#be26e0', '#c828dc', '#d22ad8', '#dc2cd4', '#e62ed0', '#f030cc',
|
|
47
|
+
'#f432c8', '#f834c4', '#fc36c0', '#ff38bc', '#ff3ab8', '#ff3cb4', '#ff3eb0', '#ff40ac',
|
|
48
|
+
'#ff42a8', '#ff44a4', '#ff46a0', '#ff489c',
|
|
49
|
+
],
|
|
50
|
+
monochrome: [
|
|
51
|
+
'#0a0a0a', '#1a1a1a', '#2a2a2a', '#3a3a3a', '#4a4a4a', '#5a5a5a', '#6a6a6a', '#7a7a7a',
|
|
52
|
+
'#8a8a8a', '#9a9a9a', '#aaaaaa', '#bAbAbA', '#cacaca', '#dAdAdA', '#eaeaea', '#f4f4f4',
|
|
53
|
+
'#f8f8f8', '#fcfcfc', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff',
|
|
54
|
+
'#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff',
|
|
55
|
+
'#ffffff', '#ffffff', '#ffffff', '#ffffff',
|
|
56
|
+
],
|
|
57
|
+
};
|
|
17
58
|
|
|
18
59
|
function getRgb(hex: string) {
|
|
19
60
|
const r = parseInt(hex.slice(1, 3), 16);
|
|
@@ -22,19 +63,94 @@ function getRgb(hex: string) {
|
|
|
22
63
|
return { r, g, b };
|
|
23
64
|
}
|
|
24
65
|
|
|
25
|
-
|
|
66
|
+
function getPalette(scheme: SplashColorScheme) {
|
|
67
|
+
return (PALETTES[scheme] ?? PALETTES.fire).map(getRgb);
|
|
68
|
+
}
|
|
26
69
|
|
|
27
70
|
function getChar(val: number) {
|
|
28
71
|
const idx = Math.min(CHAR_RAMP.length - 1, Math.floor((val / (MAXV + 1)) * CHAR_RAMP.length));
|
|
29
72
|
return CHAR_RAMP[idx];
|
|
30
73
|
}
|
|
31
74
|
|
|
32
|
-
|
|
75
|
+
function initFire(cols: number, rows: number): Uint8Array {
|
|
76
|
+
return new Uint8Array(cols * rows);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function spreadFireClassic(fire: Uint8Array, cols: number, rows: number) {
|
|
80
|
+
for (let x = 0; x < cols; x++) {
|
|
81
|
+
fire[(rows - 1) * cols + x] = MAXV - Math.floor(Math.random() * 4);
|
|
82
|
+
}
|
|
83
|
+
for (let y = rows - 1; y >= 1; y--) {
|
|
84
|
+
for (let x = 0; x < cols; x++) {
|
|
85
|
+
const src = y * cols + x;
|
|
86
|
+
const val = fire[src];
|
|
87
|
+
if (val === 0) {
|
|
88
|
+
fire[(y - 1) * cols + x] = 0;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const decay = Math.floor(Math.random() * 3);
|
|
92
|
+
const drift = Math.floor(Math.random() * 3) - 1;
|
|
93
|
+
let nx = x + drift;
|
|
94
|
+
if (nx < 0) nx = 0; else if (nx >= cols) nx = cols - 1;
|
|
95
|
+
fire[(y - 1) * cols + nx] = Math.max(0, val - decay);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function spreadFireWave(fire: Uint8Array, cols: number, rows: number, time: number) {
|
|
101
|
+
for (let x = 0; x < cols; x++) {
|
|
102
|
+
const wave = Math.sin((x + time) * 0.3) * 0.5 + 0.5;
|
|
103
|
+
fire[(rows - 1) * cols + x] = Math.floor(MAXV * wave);
|
|
104
|
+
}
|
|
105
|
+
for (let y = rows - 1; y >= 1; y--) {
|
|
106
|
+
for (let x = 0; x < cols; x++) {
|
|
107
|
+
const src = y * cols + x;
|
|
108
|
+
const val = fire[src];
|
|
109
|
+
if (val === 0) {
|
|
110
|
+
fire[(y - 1) * cols + x] = 0;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const decay = Math.floor(Math.random() * 2);
|
|
114
|
+
const nx = x;
|
|
115
|
+
fire[(y - 1) * cols + nx] = Math.max(0, val - decay);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function spreadFireSparks(fire: Uint8Array, cols: number, rows: number) {
|
|
121
|
+
for (let x = 0; x < cols; x++) {
|
|
122
|
+
fire[(rows - 1) * cols + x] = Math.random() < 0.3 ? MAXV - Math.floor(Math.random() * 4) : 0;
|
|
123
|
+
}
|
|
124
|
+
for (let y = rows - 1; y >= 1; y--) {
|
|
125
|
+
for (let x = 0; x < cols; x++) {
|
|
126
|
+
const src = y * cols + x;
|
|
127
|
+
const val = fire[src];
|
|
128
|
+
if (val === 0) {
|
|
129
|
+
fire[(y - 1) * cols + x] = 0;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const decay = Math.floor(Math.random() * 4);
|
|
133
|
+
const drift = Math.floor(Math.random() * 5) - 2;
|
|
134
|
+
let nx = x + drift;
|
|
135
|
+
if (nx < 0) nx = 0; else if (nx >= cols) nx = cols - 1;
|
|
136
|
+
fire[(y - 1) * cols + nx] = Math.max(0, val - decay);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function showSplash(config?: SplashConfig): Promise<void> {
|
|
142
|
+
const enabled = config?.enabled ?? true;
|
|
143
|
+
if (!enabled) return;
|
|
144
|
+
|
|
145
|
+
const colorScheme = config?.colorScheme ?? 'fire';
|
|
146
|
+
const animation = config?.animation ?? 'classic';
|
|
147
|
+
const rgbPalette = getPalette(colorScheme);
|
|
148
|
+
|
|
33
149
|
const cols = 60;
|
|
34
150
|
const rows = 15;
|
|
35
|
-
let fire =
|
|
151
|
+
let fire = initFire(cols, rows);
|
|
152
|
+
let time = 0;
|
|
36
153
|
|
|
37
|
-
// Clear screen
|
|
38
154
|
process.stdout.write('\x1b[2J');
|
|
39
155
|
const termRows = process.stdout.rows || 24;
|
|
40
156
|
const termCols = process.stdout.columns || 80;
|
|
@@ -43,29 +159,16 @@ export async function showSplash() {
|
|
|
43
159
|
|
|
44
160
|
return new Promise<void>((resolve) => {
|
|
45
161
|
const interval = setInterval(() => {
|
|
46
|
-
|
|
47
|
-
for (let x = 0; x < cols; x++) {
|
|
48
|
-
fire[(rows - 1) * cols + x] = MAXV - Math.floor(Math.random() * 4);
|
|
49
|
-
}
|
|
162
|
+
time++;
|
|
50
163
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
fire[(y - 1) * cols + x] = 0;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
const decay = Math.floor(Math.random() * 3);
|
|
61
|
-
const drift = Math.floor(Math.random() * 3) - 1;
|
|
62
|
-
let nx = x + drift;
|
|
63
|
-
if (nx < 0) nx = 0; else if (nx >= cols) nx = cols - 1;
|
|
64
|
-
fire[(y - 1) * cols + nx] = Math.max(0, val - decay);
|
|
65
|
-
}
|
|
164
|
+
if (animation === 'wave') {
|
|
165
|
+
spreadFireWave(fire, cols, rows, time);
|
|
166
|
+
} else if (animation === 'sparks') {
|
|
167
|
+
spreadFireSparks(fire, cols, rows);
|
|
168
|
+
} else {
|
|
169
|
+
spreadFireClassic(fire, cols, rows);
|
|
66
170
|
}
|
|
67
171
|
|
|
68
|
-
// Draw
|
|
69
172
|
let output = '';
|
|
70
173
|
for (let i = 0; i < startRow; i++) output += '\n';
|
|
71
174
|
|
|
@@ -80,8 +183,7 @@ export async function showSplash() {
|
|
|
80
183
|
}
|
|
81
184
|
output += ' '.repeat(Math.max(0, startCol + (cols - 45) / 2)) + chalk.bold.white('🔥 pyre — system monitor (warming up sensors...)\n');
|
|
82
185
|
output += ' '.repeat(Math.max(0, startCol + (cols - 25) / 2)) + chalk.dim('[Press Enter to continue]');
|
|
83
|
-
|
|
84
|
-
// Clear viewport area and redraw
|
|
186
|
+
|
|
85
187
|
process.stdout.write(`\x1b[H${output}`);
|
|
86
188
|
}, 65);
|
|
87
189
|
|
|
@@ -96,7 +198,6 @@ export async function showSplash() {
|
|
|
96
198
|
process.stdin.resume();
|
|
97
199
|
process.stdin.on('data', onEnter);
|
|
98
200
|
|
|
99
|
-
// Auto-exit after 5s
|
|
100
201
|
setTimeout(onEnter, 5000);
|
|
101
202
|
});
|
|
102
|
-
}
|
|
203
|
+
}
|