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 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
 
@@ -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.4";
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, tone) {
257
- if (tone === "error") {
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 = tone === "ok" ? color.green(text) : tone === "dim" ? color.dim(text) : text;
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, pick) => r.status === "fulfilled" ? String(pick(r.value)) : "?";
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 pick = pool[Math.floor(Math.random() * pool.length)];
2480
- const song = await getSong(pick.id);
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 pick = () => RAIN[Math.floor(Math.random() * RAIN.length)];
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 }, pick);
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] = pick();
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] = pick();
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 state = row.states[i];
2775
+ const state2 = row.states[i];
2776
2776
  const known = out.get(letter);
2777
- if (!known || RANK[state] > RANK[known]) out.set(letter, state);
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(state) {
2843
- lsSet2("round", state);
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(state) {
2849
- lsSet2("round:unlimited", state);
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 pick = 0;
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
- pick = 0;
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
- pick = 0;
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(pick, list.length - 1)];
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 === pick ? color.cyan(`\u203A ${text}`) : color.dim(` ${text}`));
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") pick = Math.min(Math.max(0, suggestions().length - 1), pick + 1);
3300
- else if (key.name === "up") pick = Math.max(0, pick - 1);
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
- pick = 0;
3307
+ pick2 = 0;
3308
3308
  } else if (key.ctrl && key.name === "u") {
3309
3309
  input = "";
3310
- pick = 0;
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
- pick = 0;
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 (_a, sh) => {
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 tone = row >= height * 0.85 ? hot : row >= height * 0.55 ? bright : row >= height * 0.25 ? mid : dark;
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 ? " " : `${tone}${BLOCKS[cell].repeat(2)}\x1B[0m`;
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, tone = "plain") {
5506
- if (tone === "error") this.errors++;
5635
+ print(text, tone2 = "plain") {
5636
+ if (tone2 === "error") this.errors++;
5507
5637
  const sink = this.sinks[this.sinks.length - 1];
5508
- if (sink && (tone !== "error" || sink.keepErrors)) sink.lines.push(...text.split("\n"));
5509
- else writeTone(text, tone);
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, tone = "dim") => {
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, tone);
5868
+ writeTone(text, tone2);
5739
5869
  return;
5740
5870
  }
5741
5871
  process.stdout.write("\r\x1B[2K");
5742
- writeTone(text, tone);
5872
+ writeTone(text, tone2);
5743
5873
  rl.prompt(true);
5744
5874
  };
5745
5875
  shell.player = new Player(notify);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unreleased-cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "The Unreleased site terminal as a command-line shell",
5
5
  "keywords": [
6
6
  "juice-wrld",