unreleased-cli 0.1.4 → 0.1.6
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 +3 -2
- package/dist/unreleased.mjs +170 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ Commands (type `help <command>` in the shell for the details of each one):
|
|
|
54
54
|
- **Library:** `find`, `song`, `like`, `unlike`, `liked`, `playlists`, `playlist`, `stats`
|
|
55
55
|
- **Player:** `play`, `pause`, `toggle`, `next`, `prev`, `seek`, `volume`, `speed`, `eq`, `shuffle`, `repeat`, `queue`, `status`, `sleep`, `stop`
|
|
56
56
|
- **People:** `user`, `lookup`
|
|
57
|
-
- **Admin** (administrators only): `pending`, `proposals`, `comps`, `applications`, `inspect`, `approve`, `reject`, `reverse`, `users`, `sitebans`, `siteunban`
|
|
57
|
+
- **Admin** (administrators only): `pending`, `proposals`, `comps`, `applications`, `cdn`, `inspect`, `approve`, `reject`, `reverse`, `users`, `sitebans`, `siteunban`
|
|
58
58
|
- **Fun:** `neofetch`, `fortune`, `juicesay`, `matrix`, `visualizer`, `karaoke`, `wordle`, `heardle`
|
|
59
59
|
- **Settings:** `set`, `settings`, `termtheme`
|
|
60
60
|
- **Shell:** `source`, `alias`, `unalias`, `history`, `echo`, `watch`, `full`, `date`, `clear`, `exit`, `help`
|
|
@@ -97,7 +97,7 @@ Music plays through a hidden mpv that the interactive shell starts the first tim
|
|
|
97
97
|
- `playlist play <name>`, `playlist shuffle <name>`, `liked play` and `shuffle <era> [count]` replace the queue.
|
|
98
98
|
- `queue add` and `queue next` add to it. `queue` lists it, and `remove N` / `jump N` change it.
|
|
99
99
|
- Shuffle, repeat, previous and the end of the queue behave as they do on the site.
|
|
100
|
-
- `status` (or `now`) shows the song, a progress bar, the settings and the queue.
|
|
100
|
+
- `status` (or `now`) shows the song, a progress bar, the settings and the queue, and follows along live on a terminal (`q` leaves). `status -1` prints it once, and it does too when piped or run in a script.
|
|
101
101
|
- `like` and `unlike` with no song act on the one playing.
|
|
102
102
|
|
|
103
103
|
The music stops when you leave the shell, so `unreleased play …` on its own says to open the shell instead. If the shell is killed without a chance to stop mpv (for example the terminal window is closed), mpv quits by itself within about 10 seconds.
|
|
@@ -119,6 +119,7 @@ These cover the Admin page's review queues and site moderation, using the same e
|
|
|
119
119
|
- `reverse [song|comp] <id>` undoes an approved proposal.
|
|
120
120
|
- `users [role] [filter]` lists accounts. `user <name>` shows one person.
|
|
121
121
|
- `sitebans` lists active site-wide bans, mutes and timeouts. `siteunban <user | #id>` lifts them.
|
|
122
|
+
- `cdn` shows the CDN stats and every node, pending ones first. `cdn <node>` shows one in full, where a node is its id, the start of the id, or its name. `cdn approve|revoke|enable|disable|reset|restore|delete <node>` does what the Admin page's CDN nodes tab does. `restore` is for a node the server pulled for hash violations: it switches it back on and resets its trust and violations. `delete` asks first unless you pass `-y`.
|
|
122
123
|
|
|
123
124
|
`reverse` and `siteunban` ask before they act. Outside the interactive shell, such as in one-shot use or a script, they need `-y` instead.
|
|
124
125
|
|
package/dist/unreleased.mjs
CHANGED
|
@@ -82,7 +82,7 @@ function saveSettings(settings) {
|
|
|
82
82
|
|
|
83
83
|
// src/api.ts
|
|
84
84
|
var DEFAULT_API = "https://juicewrldapi.com/juicewrld";
|
|
85
|
-
var VERSION = "0.1.
|
|
85
|
+
var VERSION = "0.1.6";
|
|
86
86
|
function apiBase() {
|
|
87
87
|
return (process.env.UNRELEASED_API || loadConfig().api || DEFAULT_API).replace(/\/+$/, "");
|
|
88
88
|
}
|
|
@@ -253,12 +253,12 @@ var color = {
|
|
|
253
253
|
errRed: paint("red", process.stderr),
|
|
254
254
|
errDim: paint("dim", process.stderr)
|
|
255
255
|
};
|
|
256
|
-
function writeTone(text,
|
|
257
|
-
if (
|
|
256
|
+
function writeTone(text, tone2) {
|
|
257
|
+
if (tone2 === "error") {
|
|
258
258
|
process.stderr.write(color.errRed(text) + "\n");
|
|
259
259
|
return;
|
|
260
260
|
}
|
|
261
|
-
const painted =
|
|
261
|
+
const painted = tone2 === "ok" ? color.green(text) : tone2 === "dim" ? color.dim(text) : text;
|
|
262
262
|
process.stdout.write(painted + "\n");
|
|
263
263
|
}
|
|
264
264
|
function progressLine() {
|
|
@@ -1671,7 +1671,7 @@ var ADMIN_COMMANDS = [
|
|
|
1671
1671
|
listSiteModeration()
|
|
1672
1672
|
]);
|
|
1673
1673
|
for (const r of [song, comp, apps, mods]) if (r.status === "rejected" && r.reason?.name === "AbortError") throw r.reason;
|
|
1674
|
-
const n = (r,
|
|
1674
|
+
const n = (r, pick2) => r.status === "fulfilled" ? String(pick2(r.value)) : "?";
|
|
1675
1675
|
sh.print([
|
|
1676
1676
|
`song proposals ${n(song, (v) => v.pending)} pending`,
|
|
1677
1677
|
`comp proposals ${n(comp, (v) => v.pending)} pending`,
|
|
@@ -2476,8 +2476,8 @@ async function randomLyricLine() {
|
|
|
2476
2476
|
const pool = await loadPools(["released", "unreleased"]);
|
|
2477
2477
|
if (pool.length === 0) fail("fortune: no songs to pick from");
|
|
2478
2478
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
2479
|
-
const
|
|
2480
|
-
const song = await getSong(
|
|
2479
|
+
const pick2 = pool[Math.floor(Math.random() * pool.length)];
|
|
2480
|
+
const song = await getSong(pick2.id);
|
|
2481
2481
|
const lines = (song.lyrics ?? "").split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length >= 8 && l.length <= 120 && !/^[[(].*[\])]$/.test(l));
|
|
2482
2482
|
if (lines.length) return `${lines[Math.floor(Math.random() * lines.length)]}
|
|
2483
2483
|
- ${song.name}`;
|
|
@@ -2528,13 +2528,13 @@ function matrixScreen() {
|
|
|
2528
2528
|
let heads = [];
|
|
2529
2529
|
let age = new Uint16Array(0);
|
|
2530
2530
|
let glyph = [];
|
|
2531
|
-
const
|
|
2531
|
+
const pick2 = () => RAIN[Math.floor(Math.random() * RAIN.length)];
|
|
2532
2532
|
const fit = (s) => {
|
|
2533
2533
|
cols = s.cols;
|
|
2534
2534
|
rows = s.rows;
|
|
2535
2535
|
heads = Array.from({ length: cols }, () => -Math.floor(Math.random() * rows));
|
|
2536
2536
|
age = new Uint16Array(cols * rows).fill(999);
|
|
2537
|
-
glyph = Array.from({ length: cols * rows },
|
|
2537
|
+
glyph = Array.from({ length: cols * rows }, pick2);
|
|
2538
2538
|
};
|
|
2539
2539
|
const frame = (s) => {
|
|
2540
2540
|
if (s.cols !== cols || s.rows !== rows) fit(s);
|
|
@@ -2545,12 +2545,12 @@ function matrixScreen() {
|
|
|
2545
2545
|
const r = heads[c];
|
|
2546
2546
|
if (r >= 0 && r < rows) {
|
|
2547
2547
|
age[r * cols + c] = 0;
|
|
2548
|
-
glyph[r * cols + c] =
|
|
2548
|
+
glyph[r * cols + c] = pick2();
|
|
2549
2549
|
}
|
|
2550
2550
|
}
|
|
2551
2551
|
for (let k = 0; k < cols / 4; k++) {
|
|
2552
2552
|
const i = Math.floor(Math.random() * glyph.length);
|
|
2553
|
-
if (age[i] < TRAIL) glyph[i] =
|
|
2553
|
+
if (age[i] < TRAIL) glyph[i] = pick2();
|
|
2554
2554
|
}
|
|
2555
2555
|
const [dark, mid, bright, hot] = accentRamp().map((p) => `\x1B[0;${p}m`);
|
|
2556
2556
|
const lines = [];
|
|
@@ -2772,9 +2772,9 @@ function letterHints(rows) {
|
|
|
2772
2772
|
for (const row of rows) {
|
|
2773
2773
|
for (let i = 0; i < row.key.length; i++) {
|
|
2774
2774
|
const letter = row.key[i];
|
|
2775
|
-
const
|
|
2775
|
+
const state2 = row.states[i];
|
|
2776
2776
|
const known = out.get(letter);
|
|
2777
|
-
if (!known || RANK[
|
|
2777
|
+
if (!known || RANK[state2] > RANK[known]) out.set(letter, state2);
|
|
2778
2778
|
}
|
|
2779
2779
|
}
|
|
2780
2780
|
return out;
|
|
@@ -2839,14 +2839,14 @@ function loadRound(dayKey, answerId) {
|
|
|
2839
2839
|
if (!saved || saved.day !== dayKey || saved.answerId !== answerId) return null;
|
|
2840
2840
|
return saved;
|
|
2841
2841
|
}
|
|
2842
|
-
function saveRound(
|
|
2843
|
-
lsSet2("round",
|
|
2842
|
+
function saveRound(state2) {
|
|
2843
|
+
lsSet2("round", state2);
|
|
2844
2844
|
}
|
|
2845
2845
|
function loadPracticeRound2() {
|
|
2846
2846
|
return lsGet2("round:unlimited");
|
|
2847
2847
|
}
|
|
2848
|
-
function savePracticeRound2(
|
|
2849
|
-
lsSet2("round:unlimited",
|
|
2848
|
+
function savePracticeRound2(state2) {
|
|
2849
|
+
lsSet2("round:unlimited", state2);
|
|
2850
2850
|
}
|
|
2851
2851
|
function loadStats() {
|
|
2852
2852
|
const saved = lsGet2("stats");
|
|
@@ -3170,7 +3170,7 @@ function heardleScreen(sh, pausedMusic) {
|
|
|
3170
3170
|
let status = "playing";
|
|
3171
3171
|
let startAt = 0;
|
|
3172
3172
|
let input = "";
|
|
3173
|
-
let
|
|
3173
|
+
let pick2 = 0;
|
|
3174
3174
|
let failure = "";
|
|
3175
3175
|
const render = () => {
|
|
3176
3176
|
if (screen && !screen.closed) draw(screen);
|
|
@@ -3193,7 +3193,7 @@ function heardleScreen(sh, pausedMusic) {
|
|
|
3193
3193
|
guesses = [];
|
|
3194
3194
|
status = "playing";
|
|
3195
3195
|
input = "";
|
|
3196
|
-
|
|
3196
|
+
pick2 = 0;
|
|
3197
3197
|
startAt = clipStart(next, ladder[ladder.length - 1], settings.startPoint, null);
|
|
3198
3198
|
save();
|
|
3199
3199
|
};
|
|
@@ -3201,7 +3201,7 @@ function heardleScreen(sh, pausedMusic) {
|
|
|
3201
3201
|
guesses = [...guesses, guess];
|
|
3202
3202
|
status = correct ? "won" : guesses.length >= ladder.length ? "lost" : "playing";
|
|
3203
3203
|
input = "";
|
|
3204
|
-
|
|
3204
|
+
pick2 = 0;
|
|
3205
3205
|
if (status !== "playing") clip2.stop();
|
|
3206
3206
|
save();
|
|
3207
3207
|
};
|
|
@@ -3212,7 +3212,7 @@ function heardleScreen(sh, pausedMusic) {
|
|
|
3212
3212
|
return;
|
|
3213
3213
|
}
|
|
3214
3214
|
const list = suggestions();
|
|
3215
|
-
const song = list[Math.min(
|
|
3215
|
+
const song = list[Math.min(pick2, list.length - 1)];
|
|
3216
3216
|
if (!song) return;
|
|
3217
3217
|
const correct = isCorrectGuess(song, answer, versions);
|
|
3218
3218
|
record({
|
|
@@ -3254,7 +3254,7 @@ function heardleScreen(sh, pausedMusic) {
|
|
|
3254
3254
|
suggestions().forEach((song, i) => {
|
|
3255
3255
|
const alias = normalizeTitle(song.name).includes(normalizeTitle(input)) ? "" : " \xB7 alias";
|
|
3256
3256
|
const text = `${song.name}${song.era ? ` (${song.era})` : ""}${alias}`;
|
|
3257
|
-
lines.push(i ===
|
|
3257
|
+
lines.push(i === pick2 ? color.cyan(`\u203A ${text}`) : color.dim(` ${text}`));
|
|
3258
3258
|
});
|
|
3259
3259
|
}
|
|
3260
3260
|
s.draw(lines);
|
|
@@ -3296,23 +3296,23 @@ function heardleScreen(sh, pausedMusic) {
|
|
|
3296
3296
|
} else if (answer) void clip2.play(streamUrl(answer.path), startAt, unlocked());
|
|
3297
3297
|
return;
|
|
3298
3298
|
}
|
|
3299
|
-
if (key.name === "down")
|
|
3300
|
-
else if (key.name === "up")
|
|
3299
|
+
if (key.name === "down") pick2 = Math.min(Math.max(0, suggestions().length - 1), pick2 + 1);
|
|
3300
|
+
else if (key.name === "up") pick2 = Math.max(0, pick2 - 1);
|
|
3301
3301
|
else if (key.name === "return") {
|
|
3302
3302
|
if (finished()) {
|
|
3303
3303
|
if (pool) deal(pool);
|
|
3304
3304
|
} else submit();
|
|
3305
3305
|
} else if (key.name === "backspace") {
|
|
3306
3306
|
input = input.slice(0, -1);
|
|
3307
|
-
|
|
3307
|
+
pick2 = 0;
|
|
3308
3308
|
} else if (key.ctrl && key.name === "u") {
|
|
3309
3309
|
input = "";
|
|
3310
|
-
|
|
3310
|
+
pick2 = 0;
|
|
3311
3311
|
} else {
|
|
3312
3312
|
const ch = typed(str, key);
|
|
3313
3313
|
if (ch && !finished() && input.length < 80) {
|
|
3314
3314
|
input += ch;
|
|
3315
|
-
|
|
3315
|
+
pick2 = 0;
|
|
3316
3316
|
}
|
|
3317
3317
|
}
|
|
3318
3318
|
render();
|
|
@@ -3813,10 +3813,14 @@ var PLAYER_COMMANDS = [
|
|
|
3813
3813
|
name: "status",
|
|
3814
3814
|
aliases: ["now", "np"],
|
|
3815
3815
|
group: "Player",
|
|
3816
|
-
usage: "status",
|
|
3817
|
-
description: "What is playing, plus volume, speed, shuffle, repeat and the queue",
|
|
3818
|
-
run: async (
|
|
3816
|
+
usage: "status [-1]",
|
|
3817
|
+
description: "What is playing, plus volume, speed, shuffle, repeat and the queue. Live on a terminal (q leaves); -1 prints it once",
|
|
3818
|
+
run: async (args, sh) => {
|
|
3819
3819
|
const p = playerOf(sh);
|
|
3820
|
+
if (!sh.scripted && canOpenScreen() && args.trim() !== "-1") {
|
|
3821
|
+
await sh.execLine("watch -n 1 status -1");
|
|
3822
|
+
return;
|
|
3823
|
+
}
|
|
3820
3824
|
const t = p.current;
|
|
3821
3825
|
const [paused, pos, dur] = await Promise.all([p.paused(), p.position(), p.duration()]);
|
|
3822
3826
|
sh.print([
|
|
@@ -4560,6 +4564,131 @@ var UPDATE_COMMANDS = [
|
|
|
4560
4564
|
}
|
|
4561
4565
|
];
|
|
4562
4566
|
|
|
4567
|
+
// src/cdn.ts
|
|
4568
|
+
var DEFAULT_TRUST = 100;
|
|
4569
|
+
var VIOLATION_LIMIT = 5;
|
|
4570
|
+
var ACTIONS = ["approve", "revoke", "enable", "disable", "reset", "restore", "delete"];
|
|
4571
|
+
var BASE2 = "/cdn/admin";
|
|
4572
|
+
var listNodes = async () => (await apiFetch(`${BASE2}/nodes/`))?.nodes ?? [];
|
|
4573
|
+
var nodePath = (n) => `${BASE2}/nodes/${encodeURIComponent(n.node_id)}/`;
|
|
4574
|
+
var patch = (n, body) => apiFetch(nodePath(n), {}, { method: "PATCH", body });
|
|
4575
|
+
var autoDisabled = (n) => n.trust_score <= 0 || n.hash_violations >= VIOLATION_LIMIT;
|
|
4576
|
+
var mbps = (v) => v ? `${Math.round(v * 10) / 10} Mbps` : "\u2014";
|
|
4577
|
+
function state(n) {
|
|
4578
|
+
if (!n.is_approved) return autoDisabled(n) ? "auto-disabled" : "pending";
|
|
4579
|
+
if (!n.is_active) return "disabled";
|
|
4580
|
+
return n.online ? "online" : "offline";
|
|
4581
|
+
}
|
|
4582
|
+
var tone = (s) => s === "online" ? color.green(s) : s === "offline" ? color.dim(s) : color.red(s);
|
|
4583
|
+
function pick(nodes, arg) {
|
|
4584
|
+
const q = arg.trim().replace(/^#/, "").toLowerCase();
|
|
4585
|
+
if (!q) fail("say which node (see: cdn)");
|
|
4586
|
+
const exact = nodes.filter((n) => n.node_id.toLowerCase() === q || n.name.toLowerCase() === q);
|
|
4587
|
+
const hits = exact.length ? exact : nodes.filter((n) => n.node_id.toLowerCase().startsWith(q) || n.name.toLowerCase().includes(q));
|
|
4588
|
+
if (hits.length === 0) fail(`no node matches "${arg.trim()}" (see: cdn)`);
|
|
4589
|
+
if (hits.length > 1) fail(`"${arg.trim()}" matches ${hits.length} nodes: ${hits.slice(0, 5).map((n) => n.name || n.node_id.slice(0, 8)).join(", ")}`);
|
|
4590
|
+
return hits[0];
|
|
4591
|
+
}
|
|
4592
|
+
function describeNode(n, latest) {
|
|
4593
|
+
const pct = n.max_storage_bytes > 0 ? ` of ${formatBytes(n.max_storage_bytes)} (${Math.min(100, Math.round(n.current_storage_bytes / n.max_storage_bytes * 100))}%)` : "";
|
|
4594
|
+
const newest = n.manifest_version ?? latest;
|
|
4595
|
+
const synced = n.synced_manifest_version === void 0 ? "" : `
|
|
4596
|
+
synced manifest ${n.synced_manifest_version == null ? "never" : `v${n.synced_manifest_version}`}${newest != null ? ` (latest v${newest})` : ""}`;
|
|
4597
|
+
const notes = [];
|
|
4598
|
+
if (!n.is_approved && autoDisabled(n)) notes.push("pulled for hash violations - restore it to reset trust and violations too");
|
|
4599
|
+
else if (!n.is_approved) notes.push("waiting for approval - it serves nothing until approved");
|
|
4600
|
+
else if (!n.is_active) notes.push("switched off by an admin");
|
|
4601
|
+
return [
|
|
4602
|
+
`${color.bold(n.name || n.node_id)} ${tone(state(n))}`,
|
|
4603
|
+
`node id ${n.node_id}`,
|
|
4604
|
+
`owner ${n.owner_username || "unclaimed"}`,
|
|
4605
|
+
`region ${n.region || "\u2014"} \xB7 ${n.is_public ? "public" : "private"}`,
|
|
4606
|
+
`address ${n.ip_address ? n.port ? `${n.ip_address}:${n.port}` : n.ip_address : "\u2014"}${n.public_base_url ? ` ${n.public_base_url}` : ""}`,
|
|
4607
|
+
`storage ${formatBytes(n.current_storage_bytes)}${pct} \xB7 ${n.file_count.toLocaleString()} files`,
|
|
4608
|
+
`speed up ${mbps(n.upload_speed_mbps)} \xB7 down ${mbps(n.download_speed_mbps)} \xB7 to listeners ${mbps(n.observed_download_speed_mbps)}`,
|
|
4609
|
+
`served ${formatBytes(n.total_bytes_served)} in ${n.total_requests.toLocaleString()} requests`,
|
|
4610
|
+
`trust ${Math.round(n.trust_score)} \xB7 ${plural(n.hash_violations, "violation")}`,
|
|
4611
|
+
`heartbeat ${relativeTime(n.last_heartbeat)} \xB7 registered ${new Date(n.created_at).toLocaleDateString()}${synced}`,
|
|
4612
|
+
...notes.map((t) => color.yellow(`! ${t}`))
|
|
4613
|
+
].join("\n");
|
|
4614
|
+
}
|
|
4615
|
+
async function act(action, n, sh, yes) {
|
|
4616
|
+
const name = n.name || n.node_id;
|
|
4617
|
+
switch (action) {
|
|
4618
|
+
case "approve":
|
|
4619
|
+
if (n.is_approved) fail(`${name} is already approved`);
|
|
4620
|
+
if (autoDisabled(n)) fail(`${name} was pulled for hash violations - use: cdn restore ${name}`);
|
|
4621
|
+
await patch(n, { is_approved: true });
|
|
4622
|
+
break;
|
|
4623
|
+
case "revoke":
|
|
4624
|
+
if (!n.is_approved) fail(`${name} isn't approved`);
|
|
4625
|
+
await patch(n, { is_approved: false });
|
|
4626
|
+
break;
|
|
4627
|
+
case "enable":
|
|
4628
|
+
if (n.is_active) fail(`${name} is already enabled`);
|
|
4629
|
+
if (autoDisabled(n)) fail(`${name} was pulled for hash violations - use: cdn restore ${name}`);
|
|
4630
|
+
await patch(n, { is_active: true });
|
|
4631
|
+
break;
|
|
4632
|
+
case "disable":
|
|
4633
|
+
if (!n.is_active) fail(`${name} is already disabled`);
|
|
4634
|
+
await patch(n, { is_active: false });
|
|
4635
|
+
break;
|
|
4636
|
+
case "reset":
|
|
4637
|
+
if (n.hash_violations === 0 && n.trust_score >= DEFAULT_TRUST) fail(`${name} has nothing to reset`);
|
|
4638
|
+
await patch(n, { trust_score: DEFAULT_TRUST, hash_violations: 0 });
|
|
4639
|
+
break;
|
|
4640
|
+
case "restore":
|
|
4641
|
+
await patch(n, { is_approved: true, is_active: true, trust_score: DEFAULT_TRUST, hash_violations: 0 });
|
|
4642
|
+
break;
|
|
4643
|
+
case "delete":
|
|
4644
|
+
if (!await confirm(sh, `Permanently delete ${name}? Its file list, history and API key go with it.`, yes, "cdn")) return;
|
|
4645
|
+
await apiFetch(nodePath(n), {}, { method: "DELETE" });
|
|
4646
|
+
sh.print(`deleted ${name}`, "ok");
|
|
4647
|
+
return;
|
|
4648
|
+
}
|
|
4649
|
+
sh.print(`${action} ${name}: done`, "ok");
|
|
4650
|
+
}
|
|
4651
|
+
var CDN_COMMANDS = [
|
|
4652
|
+
{
|
|
4653
|
+
name: "cdn",
|
|
4654
|
+
group: "Admin",
|
|
4655
|
+
usage: "cdn [<node>] \xB7 cdn approve|revoke|enable|disable|reset|restore|delete [-y] <node>",
|
|
4656
|
+
description: "The CDN node roster and stats; one node in full; or approve, revoke, enable, disable, reset trust, restore or delete a node (a node is its id, the start of it, or its name)",
|
|
4657
|
+
complete: kindComplete(ACTIONS),
|
|
4658
|
+
run: admin(async (args, sh) => {
|
|
4659
|
+
const words = args.trim().split(/\s+/).filter(Boolean);
|
|
4660
|
+
const first = words[0]?.toLowerCase();
|
|
4661
|
+
if (first && ACTIONS.includes(first)) {
|
|
4662
|
+
words.shift();
|
|
4663
|
+
const yes = words[0] === "-y" ? (words.shift(), true) : false;
|
|
4664
|
+
const nodes2 = await listNodes();
|
|
4665
|
+
await act(first, pick(nodes2, words.join(" ")), sh, yes);
|
|
4666
|
+
return;
|
|
4667
|
+
}
|
|
4668
|
+
const [stats, nodes] = await Promise.all([apiFetch(`${BASE2}/stats/`), listNodes()]);
|
|
4669
|
+
if (words.length) {
|
|
4670
|
+
sh.print(describeNode(pick(nodes, words.join(" ")), stats.manifest_version));
|
|
4671
|
+
return;
|
|
4672
|
+
}
|
|
4673
|
+
const lines = [
|
|
4674
|
+
`${plural(stats.total_nodes, "node")} \xB7 ${stats.online_nodes} online \xB7 ${stats.pending_nodes} awaiting approval \xB7 ${formatBytes(stats.total_bytes_served)} served in ${stats.total_requests.toLocaleString()} requests`,
|
|
4675
|
+
`master copy: ${stats.master_files.toLocaleString()} files, ${formatBytes(stats.master_bytes)} \xB7 manifest v${stats.manifest_version}`
|
|
4676
|
+
];
|
|
4677
|
+
if (nodes.length === 0) {
|
|
4678
|
+
sh.print([...lines, "", color.dim("no nodes registered")].join("\n"));
|
|
4679
|
+
return;
|
|
4680
|
+
}
|
|
4681
|
+
const order = ["pending", "auto-disabled", "online", "offline", "disabled"];
|
|
4682
|
+
const sorted = [...nodes].sort((a, b) => order.indexOf(state(a)) - order.indexOf(state(b)) || (a.name || a.node_id).localeCompare(b.name || b.node_id));
|
|
4683
|
+
const rows = sorted.map((n) => {
|
|
4684
|
+
const s = state(n);
|
|
4685
|
+
return `${(n.name || n.node_id.slice(0, 8)).slice(0, 24).padEnd(26)}${tone(s)}${" ".repeat(Math.max(1, 15 - s.length))}${(n.owner_username || "unclaimed").slice(0, 16).padEnd(18)}${formatBytes(n.current_storage_bytes).padEnd(11)}${formatBytes(n.total_bytes_served).padEnd(11)}trust ${String(Math.round(n.trust_score)).padEnd(5)}${relativeTime(n.last_heartbeat)}`;
|
|
4686
|
+
});
|
|
4687
|
+
sh.print([...lines, "", ...rows].join("\n"));
|
|
4688
|
+
})
|
|
4689
|
+
}
|
|
4690
|
+
];
|
|
4691
|
+
|
|
4563
4692
|
// src/eq.ts
|
|
4564
4693
|
var WORDS = ["on", "off", "list", "reset", "reverb", "boost", "balance", "speed", "mono", "pitch", ...EQ_PRESETS.map((p) => p.id)];
|
|
4565
4694
|
function describe(speed, pitch) {
|
|
@@ -4967,12 +5096,12 @@ function visualizerScreen(sh, binary) {
|
|
|
4967
5096
|
const time = track ? `${clock(now)}${track.duration ? ` / ${clock(track.duration)}` : ""}` : "";
|
|
4968
5097
|
lines.push(`${color.bold(title)} ${color.dim(time)}${status ? ` ${color.dim(status)}` : ""}`, "");
|
|
4969
5098
|
for (let row = height - 1; row >= 0; row--) {
|
|
4970
|
-
const
|
|
5099
|
+
const tone2 = row >= height * 0.85 ? hot : row >= height * 0.55 ? bright : row >= height * 0.25 ? mid : dark;
|
|
4971
5100
|
let line = " ";
|
|
4972
5101
|
for (let i = 0; i < bars; i++) {
|
|
4973
5102
|
const filled = levels[i] * height;
|
|
4974
5103
|
const cell = Math.max(0, Math.min(8, Math.round((filled - row) * 8)));
|
|
4975
|
-
line += cell === 0 ? " " : `${
|
|
5104
|
+
line += cell === 0 ? " " : `${tone2}${BLOCKS[cell].repeat(2)}\x1B[0m`;
|
|
4976
5105
|
}
|
|
4977
5106
|
lines.push(line);
|
|
4978
5107
|
}
|
|
@@ -5216,6 +5345,7 @@ ${color.dim(`local ${sh.localDir}`)}`)
|
|
|
5216
5345
|
...EQ_COMMANDS,
|
|
5217
5346
|
...PEOPLE_COMMANDS,
|
|
5218
5347
|
...ADMIN_COMMANDS,
|
|
5348
|
+
...CDN_COMMANDS,
|
|
5219
5349
|
...FUN_COMMANDS,
|
|
5220
5350
|
...VISUALIZER_COMMANDS,
|
|
5221
5351
|
...KARAOKE_COMMANDS,
|
|
@@ -5502,11 +5632,11 @@ var Shell = class {
|
|
|
5502
5632
|
const user2 = getToken() ? loadConfig().user?.name ?? "user" : "guest";
|
|
5503
5633
|
return `${color.user(`${user2}@unreleased`)}:${color.blue(filesPathString(this.cwd))}$ `;
|
|
5504
5634
|
}
|
|
5505
|
-
print(text,
|
|
5506
|
-
if (
|
|
5635
|
+
print(text, tone2 = "plain") {
|
|
5636
|
+
if (tone2 === "error") this.errors++;
|
|
5507
5637
|
const sink = this.sinks[this.sinks.length - 1];
|
|
5508
|
-
if (sink && (
|
|
5509
|
-
else writeTone(text,
|
|
5638
|
+
if (sink && (tone2 !== "error" || sink.keepErrors)) sink.lines.push(...text.split("\n"));
|
|
5639
|
+
else writeTone(text, tone2);
|
|
5510
5640
|
}
|
|
5511
5641
|
/** A progress note ("loading…"): straight to stderr, never into a pipe or
|
|
5512
5642
|
* a one-shot command's stdout - and not at all over a full screen. */
|
|
@@ -5729,17 +5859,17 @@ async function startRepl() {
|
|
|
5729
5859
|
const flushHeld = () => {
|
|
5730
5860
|
for (const n of held.splice(0)) writeTone(n.text, n.tone);
|
|
5731
5861
|
};
|
|
5732
|
-
const notify = (text,
|
|
5862
|
+
const notify = (text, tone2 = "dim") => {
|
|
5733
5863
|
if (screenActive() || historySearchActive()) {
|
|
5734
|
-
held.push({ text, tone });
|
|
5864
|
+
held.push({ text, tone: tone2 });
|
|
5735
5865
|
return;
|
|
5736
5866
|
}
|
|
5737
5867
|
if (running) {
|
|
5738
|
-
writeTone(text,
|
|
5868
|
+
writeTone(text, tone2);
|
|
5739
5869
|
return;
|
|
5740
5870
|
}
|
|
5741
5871
|
process.stdout.write("\r\x1B[2K");
|
|
5742
|
-
writeTone(text,
|
|
5872
|
+
writeTone(text, tone2);
|
|
5743
5873
|
rl.prompt(true);
|
|
5744
5874
|
};
|
|
5745
5875
|
shell.player = new Player(notify);
|