accelr8 3.0.1 → 3.0.2

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/protocol.ts
4
- var VERSION = "3.0.0";
4
+ var VERSION = true ? "3.0.2" : "3.0.2";
5
5
  var PROTO = 3;
6
6
  var PORT = 8787;
7
7
  var DEFAULT_SERVER = "wss://accelr8-canvas.patonchain.workers.dev";
@@ -7,7 +7,7 @@ import {
7
7
  VERSION,
8
8
  decode,
9
9
  encode
10
- } from "./chunk-GXDRLI6N.js";
10
+ } from "./chunk-WRSSNAIF.js";
11
11
  import {
12
12
  DEFAULT_MODEL,
13
13
  addAccount,
@@ -1190,7 +1190,10 @@ var PLAYBACK_ARGS = [
1190
1190
  "-loglevel",
1191
1191
  "error",
1192
1192
  "-nodisp",
1193
- "-autoexit",
1193
+ "-fflags",
1194
+ "nobuffer",
1195
+ "-flags",
1196
+ "low_delay",
1194
1197
  "-f",
1195
1198
  "s16le",
1196
1199
  "-ar",
@@ -1360,8 +1363,9 @@ var VoicePlayback = class {
1360
1363
  }
1361
1364
  this.touch(speakerId, p);
1362
1365
  }
1363
- /** Graceful close for one speaker (voiceStop): end stdin so ffplay
1364
- * drains buffered audio and -autoexit's; SIGKILL backstop after 2s. */
1366
+ /** Graceful close for one speaker (voiceStop): end stdin so ffplay drains
1367
+ * its buffered audio, then a SIGKILL backstop after 2s (we dropped -autoexit,
1368
+ * so the backstop is what actually reaps the idle player). */
1365
1369
  end(speakerId) {
1366
1370
  const p = this.players.get(speakerId);
1367
1371
  if (!p) return;
@@ -1508,24 +1512,74 @@ var overlay = null;
1508
1512
  var accessAgentId = null;
1509
1513
  var overlayFocus = 0;
1510
1514
  var updateInfo = null;
1511
- var updating = null;
1512
- var cancelUpdate = null;
1515
+ var task = null;
1516
+ var cancelTask = null;
1513
1517
  function startUpdate() {
1514
- if (updating && !updating.done) return;
1515
- updating = { line: "starting npm\u2026", message: "" };
1518
+ if (task && task.phase === "running") return;
1519
+ task = { title: "updating accelr8", phase: "running", line: "starting npm\u2026", okLabel: "quit now \xB7 enter", onOk: () => shutdown() };
1516
1520
  import("./update-UEPNGB6Y.js").then(({ runUpdate }) => {
1517
- cancelUpdate = runUpdate({
1521
+ cancelTask = runUpdate({
1518
1522
  onLine: (l) => {
1519
- if (updating) updating.line = l.slice(0, 120);
1523
+ if (task) task.line = l.slice(0, 120);
1520
1524
  },
1521
1525
  onDone: (ok, message) => {
1522
- updating = { line: "", done: ok ? "ok" : "fail", message };
1523
- cancelUpdate = null;
1526
+ if (task) {
1527
+ task.phase = "done";
1528
+ task.ok = ok;
1529
+ task.line = message;
1530
+ }
1531
+ cancelTask = null;
1524
1532
  if (ok) updateInfo = null;
1525
1533
  }
1526
1534
  });
1527
1535
  }).catch(() => {
1528
- updating = { line: "", done: "fail", message: "could not load the updater \xB7 npm i -g accelr8@latest" };
1536
+ if (task) {
1537
+ task.phase = "done";
1538
+ task.ok = false;
1539
+ task.line = "could not load the updater \xB7 npm i -g accelr8@latest";
1540
+ }
1541
+ });
1542
+ }
1543
+ function startVoiceInstall() {
1544
+ import("./install-DL5WLVXN.js").then(({ ffmpegInstallPlan, runCommand }) => {
1545
+ const plan = ffmpegInstallPlan();
1546
+ if (!plan.auto) {
1547
+ state.status = `voice needs ffmpeg \xB7 ${plan.manual}`;
1548
+ return;
1549
+ }
1550
+ const auto = plan.auto;
1551
+ task = {
1552
+ title: "enable voice",
1553
+ phase: "confirm",
1554
+ line: `voice needs ffmpeg \xB7 ${auto.label} \xB7 press enter`,
1555
+ run: () => {
1556
+ if (!task) return;
1557
+ task.phase = "running";
1558
+ task.line = "installing ffmpeg\u2026";
1559
+ cancelTask = runCommand(auto.cmd, auto.args, {
1560
+ onLine: (l) => {
1561
+ if (task) task.line = l.slice(0, 120);
1562
+ },
1563
+ onDone: (ok, message) => {
1564
+ if (task) {
1565
+ task.phase = "done";
1566
+ task.ok = ok;
1567
+ task.line = ok ? "ffmpeg ready \xB7 voice is on" : message + " \xB7 " + auto.label;
1568
+ task.okLabel = ok ? "start voice \xB7 enter" : void 0;
1569
+ }
1570
+ cancelTask = null;
1571
+ }
1572
+ });
1573
+ },
1574
+ onOk: () => {
1575
+ voiceTools = null;
1576
+ task = null;
1577
+ toggleVoice();
1578
+ }
1579
+ // re-detect + join
1580
+ };
1581
+ }).catch(() => {
1582
+ state.status = "could not load the installer";
1529
1583
  });
1530
1584
  }
1531
1585
  var voiceTools = null;
@@ -1541,7 +1595,7 @@ function toggleVoice() {
1541
1595
  }
1542
1596
  voiceTools = voiceTools ?? detectVoiceTools();
1543
1597
  if (!voiceTools.capture && !voiceTools.playback) {
1544
- state.status = "voice needs ffmpeg \xB7 install it, then /voice";
1598
+ startVoiceInstall();
1545
1599
  return;
1546
1600
  }
1547
1601
  voicePlayback = new VoicePlayback(voiceTools);
@@ -1549,7 +1603,7 @@ function toggleVoice() {
1549
1603
  voiceOn = true;
1550
1604
  voiceTransmit = false;
1551
1605
  sendMsg({ t: "voiceJoin" });
1552
- state.status = voiceTools.capture ? "voice on \xB7 ctrl-g to talk" : "voice on \xB7 listen only (no mic found)";
1606
+ state.status = voiceTools.capture ? "voice on \xB7 /talk (or ctrl-g) to speak" : "voice on \xB7 listen only (no mic found)";
1553
1607
  }
1554
1608
  function leaveVoice() {
1555
1609
  if (!voiceOn) return;
@@ -1572,10 +1626,10 @@ function toggleTalk() {
1572
1626
  if (voiceTransmit) {
1573
1627
  voiceCapture.stop();
1574
1628
  voiceTransmit = false;
1575
- state.status = "muted \xB7 ctrl-g to talk";
1629
+ state.status = "muted \xB7 /talk (or ctrl-g) to speak";
1576
1630
  } else {
1577
1631
  voiceTransmit = true;
1578
- state.status = "\u25CF live \xB7 ctrl-g to mute";
1632
+ state.status = "\u25CF live \xB7 /mute (or ctrl-g) to stop";
1579
1633
  voiceCapture.start({
1580
1634
  onChunk: (b64) => {
1581
1635
  if (voiceTransmit) sendMsg({ t: "voiceFrame", data: b64 });
@@ -2203,6 +2257,9 @@ function __curTheme() {
2203
2257
  function __setUpdate(latest, current = "3.0.0") {
2204
2258
  updateInfo = latest ? { latest, current } : null;
2205
2259
  }
2260
+ function __taskDemo(phase, ok = true) {
2261
+ task = { title: "enable voice", phase, ok, line: phase === "confirm" ? "voice needs ffmpeg \xB7 brew install ffmpeg \xB7 press enter" : phase === "running" ? "installing ffmpeg\u2026" : ok ? "ffmpeg ready \xB7 voice is on" : "brew exited 1 \xB7 brew install ffmpeg", okLabel: "start voice \xB7 enter" };
2262
+ }
2206
2263
  function __closeOverlay() {
2207
2264
  overlay = null;
2208
2265
  }
@@ -2288,28 +2345,36 @@ function render() {
2288
2345
  overlayFocus = clickables.length ? Math.max(0, Math.min(overlayFocus, clickables.length - 1)) : 0;
2289
2346
  }
2290
2347
  if (state.modal && bigEnough) renderModal();
2291
- if (updating && bigEnough) renderUpdating();
2348
+ if (task && bigEnough) renderTask();
2292
2349
  if (screen.dst) screen.draw({ delta: true });
2293
2350
  }
2294
- function renderUpdating() {
2351
+ function renderTask() {
2295
2352
  clickables = [];
2296
2353
  const w = W(), h = H();
2297
2354
  const bw = Math.min(66, w - 6), bh = 7;
2298
2355
  const x0 = Math.floor((w - bw) / 2), y0 = Math.floor((h - bh) / 2);
2299
2356
  for (let yy = y0; yy < y0 + bh; yy++) puts(x0, yy, " ".repeat(bw), { bgColor: C.panel });
2300
- box(x0, y0, x0 + bw - 1, y0 + bh - 1, { color: C.accent, bgColor: C.panel }, "updating accelr8");
2301
- const u = updating;
2302
- if (!u.done) {
2303
- puts(x0 + 2, y0 + 2, `${SPINNER[frame % SPINNER.length]} installing accelr8@latest\u2026`, { color: C.bright, bgColor: C.panel, bold: true });
2304
- puts(x0 + 2, y0 + 4, ("\xB7 " + u.line).slice(0, bw - 4), { color: C.dim, bgColor: C.panel });
2305
- } else if (u.done === "ok") {
2306
- puts(x0 + 2, y0 + 2, "\u2713 " + u.message, { color: C.good, bgColor: C.panel, bold: true });
2307
- button(x0 + 2, y0 + 4, " quit now \xB7 enter ", { bgColor: C.good, color: onFill(C.good), bold: true }, () => shutdown());
2308
- puts(x0 + 22, y0 + 4, "then run: accelr8", { color: C.dim, bgColor: C.panel });
2357
+ box(x0, y0, x0 + bw - 1, y0 + bh - 1, { color: C.accent, bgColor: C.panel }, task.title);
2358
+ const t = task;
2359
+ if (t.phase === "confirm") {
2360
+ puts(x0 + 2, y0 + 2, t.line.slice(0, bw - 4), { color: C.bright, bgColor: C.panel, bold: true });
2361
+ button(x0 + 2, y0 + 4, " install \xB7 enter ", { bgColor: C.accent, color: onFill(C.accent), bold: true }, () => t.run?.());
2362
+ button(x0 + 20, y0 + 4, " not now \xB7 esc ", { bgColor: C.tabOff, color: C.text }, () => {
2363
+ task = null;
2364
+ });
2365
+ } else if (t.phase === "running") {
2366
+ puts(x0 + 2, y0 + 2, `${SPINNER[frame % SPINNER.length]} ${t.title}\u2026`, { color: C.bright, bgColor: C.panel, bold: true });
2367
+ puts(x0 + 2, y0 + 4, ("\xB7 " + t.line).slice(0, bw - 4), { color: C.dim, bgColor: C.panel });
2368
+ } else if (t.ok) {
2369
+ puts(x0 + 2, y0 + 2, "\u2713 " + t.line.slice(0, bw - 6), { color: C.good, bgColor: C.panel, bold: true });
2370
+ if (t.okLabel) button(x0 + 2, y0 + 4, ` ${t.okLabel} `, { bgColor: C.good, color: onFill(C.good), bold: true }, () => t.onOk?.());
2371
+ else button(x0 + 2, y0 + 4, " close \xB7 enter ", { bgColor: C.good, color: onFill(C.good), bold: true }, () => {
2372
+ task = null;
2373
+ });
2309
2374
  } else {
2310
- puts(x0 + 2, y0 + 2, "\u2715 " + u.message.slice(0, bw - 6), { color: C.danger, bgColor: C.panel, bold: true });
2375
+ puts(x0 + 2, y0 + 2, "\u2715 " + t.line.slice(0, bw - 6), { color: C.danger, bgColor: C.panel, bold: true });
2311
2376
  button(x0 + 2, y0 + 4, " close \xB7 esc ", { bgColor: C.tabOff, color: C.text }, () => {
2312
- updating = null;
2377
+ task = null;
2313
2378
  });
2314
2379
  }
2315
2380
  }
@@ -2591,7 +2656,7 @@ function renderHome(rung) {
2591
2656
  }
2592
2657
  y += 1;
2593
2658
  }
2594
- if (updateInfo && !updating) {
2659
+ if (updateInfo && !task) {
2595
2660
  puts(2, y, "\u21BB", { color: C.warn, bold: true });
2596
2661
  const w2 = W();
2597
2662
  const label = ` update to v${updateInfo.latest} \xB7 press enter `;
@@ -2652,7 +2717,7 @@ function renderStatusBar(rung) {
2652
2717
  const drawn = left.slice(0, w - 24);
2653
2718
  puts(0, sy, drawn, { bgColor: C.panel, color: C.dim });
2654
2719
  if (voiceOn) {
2655
- const label = voiceTransmit ? " \u25CF live \xB7 ctrl-g mute" : ` \u25CB voice ${state.voicePeers.length} \xB7 ctrl-g talk`;
2720
+ const label = voiceTransmit ? " \u25CF live \xB7 /mute" : ` \u25CB voice ${state.voicePeers.length} \xB7 /talk`;
2656
2721
  if (drawn.length + label.length < w - 24) puts(drawn.length, sy, label, { bgColor: C.panel, color: voiceTransmit ? C.good : C.accent, bold: voiceTransmit });
2657
2722
  }
2658
2723
  const hint = state.modal ? "[enter] ok \xB7 [esc] cancel " : "[tab] tabs \xB7 [^C] quit ";
@@ -3333,7 +3398,7 @@ function onMouse(name, data) {
3333
3398
  return;
3334
3399
  }
3335
3400
  if (state.modal) return;
3336
- if (updating) {
3401
+ if (task) {
3337
3402
  if (name === "MOUSE_LEFT_BUTTON_PRESSED") {
3338
3403
  for (const r of clickables) if (mx >= r.x0 && mx <= r.x1 && my >= r.y0 && my <= r.y1) {
3339
3404
  r.action();
@@ -3479,25 +3544,27 @@ function sendChat() {
3479
3544
  }
3480
3545
  function onKey(name) {
3481
3546
  if (name === "CTRL_C") return shutdown();
3482
- if (name === "CTRL_G" && voiceOn) return toggleTalk();
3547
+ if ((name === "CTRL_G" || name === "CTRL_ALT_G") && voiceOn) return toggleTalk();
3483
3548
  if (boot) return skipBoot();
3484
3549
  const rung = rungFor(W(), H());
3485
3550
  if (rung === "micro" || rung === "orb") return;
3486
3551
  const paused = rung === "ticker";
3487
- if (updating && !paused) {
3488
- if (!updating.done) {
3489
- if (name === "ESCAPE") {
3490
- cancelUpdate?.();
3491
- updating = null;
3492
- }
3552
+ if (task && !paused) {
3553
+ const enter = name === "ENTER" || name === "KP_ENTER";
3554
+ if (task.phase === "confirm") {
3555
+ if (enter) task.run?.();
3556
+ else if (name === "ESCAPE") task = null;
3493
3557
  return;
3494
3558
  }
3495
- if (updating.done === "ok") {
3496
- if (name === "ENTER" || name === "KP_ENTER" || name === "q") return shutdown();
3497
- if (name === "ESCAPE") updating = null;
3559
+ if (task.phase === "running") {
3560
+ if (name === "ESCAPE") {
3561
+ cancelTask?.();
3562
+ task = null;
3563
+ }
3498
3564
  return;
3499
3565
  }
3500
- if (name === "ESCAPE" || name === "ENTER" || name === "KP_ENTER") updating = null;
3566
+ if (enter && task.ok && task.onOk) return task.onOk();
3567
+ if (enter || name === "ESCAPE") task = null;
3501
3568
  return;
3502
3569
  }
3503
3570
  if (state.modal && !paused) {
@@ -3807,6 +3874,7 @@ export {
3807
3874
  __setMySid,
3808
3875
  __setNav,
3809
3876
  __setUpdate,
3877
+ __taskDemo,
3810
3878
  chatLog,
3811
3879
  cursors,
3812
3880
  editor,
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/install.ts
4
+ import { spawn } from "node:child_process";
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ function which(bin) {
8
+ const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ""] : [""];
9
+ for (const dir of (process.env.PATH ?? "").split(path.delimiter).filter(Boolean)) {
10
+ for (const ext of exts) {
11
+ const p = path.join(dir, bin + ext);
12
+ try {
13
+ fs.accessSync(p, fs.constants.X_OK);
14
+ if (fs.statSync(p).isFile()) return true;
15
+ } catch {
16
+ }
17
+ }
18
+ }
19
+ return false;
20
+ }
21
+ function ffmpegInstallPlan(platform = process.platform) {
22
+ if (platform === "darwin") {
23
+ if (which("brew")) return { auto: { cmd: "brew", args: ["install", "ffmpeg"], label: "brew install ffmpeg" }, manual: "brew install ffmpeg" };
24
+ return { manual: "install Homebrew from brew.sh, then: brew install ffmpeg", note: "Homebrew not found" };
25
+ }
26
+ if (platform === "win32") {
27
+ if (which("winget")) return { auto: { cmd: "winget", args: ["install", "--silent", "--accept-package-agreements", "--accept-source-agreements", "Gyan.FFmpeg"], label: "winget install ffmpeg" }, manual: "winget install Gyan.FFmpeg" };
28
+ if (which("choco")) return { auto: { cmd: "choco", args: ["install", "-y", "ffmpeg-full"], label: "choco install ffmpeg-full" }, manual: "choco install ffmpeg-full" };
29
+ return { manual: "install ffmpeg from ffmpeg.org and add it to PATH", note: "no winget/choco found" };
30
+ }
31
+ if (which("apt-get") || which("apt")) return { manual: "sudo apt install ffmpeg", note: "needs sudo \u2014 run it in your shell" };
32
+ if (which("dnf")) return { manual: "sudo dnf install ffmpeg", note: "needs sudo \u2014 run it in your shell" };
33
+ if (which("pacman")) return { manual: "sudo pacman -S ffmpeg", note: "needs sudo \u2014 run it in your shell" };
34
+ return { manual: "install ffmpeg with your package manager", note: "unknown package manager" };
35
+ }
36
+ function runCommand(cmd, args, cb) {
37
+ let child;
38
+ try {
39
+ child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
40
+ } catch (e) {
41
+ queueMicrotask(() => cb.onDone(false, `couldn't start ${cmd}`));
42
+ return () => {
43
+ };
44
+ }
45
+ let tail = "";
46
+ const feed = (chunk) => {
47
+ tail += chunk.toString();
48
+ let nl;
49
+ while ((nl = tail.indexOf("\n")) >= 0) {
50
+ const line = tail.slice(0, nl).trim();
51
+ tail = tail.slice(nl + 1);
52
+ if (line) cb.onLine(line);
53
+ }
54
+ };
55
+ child.stdout?.on("data", feed);
56
+ child.stderr?.on("data", feed);
57
+ child.on("error", (e) => cb.onDone(false, e.code === "ENOENT" ? `${cmd} not found` : e.message));
58
+ child.on("close", (code) => {
59
+ if (tail.trim()) cb.onLine(tail.trim());
60
+ cb.onDone(code === 0, code === 0 ? "installed" : `${cmd} exited ${code}`);
61
+ });
62
+ return () => {
63
+ try {
64
+ child.kill();
65
+ } catch {
66
+ }
67
+ };
68
+ }
69
+ export {
70
+ ffmpegInstallPlan,
71
+ runCommand
72
+ };
@@ -6,7 +6,7 @@ import {
6
6
  LIMITS,
7
7
  PORT,
8
8
  PROTO
9
- } from "./chunk-GXDRLI6N.js";
9
+ } from "./chunk-WRSSNAIF.js";
10
10
 
11
11
  // src/server.ts
12
12
  import { WebSocketServer, WebSocket } from "ws";
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  DEFAULT_SERVER,
4
4
  PORT,
5
5
  VERSION
6
- } from "./chunks/chunk-GXDRLI6N.js";
6
+ } from "./chunks/chunk-WRSSNAIF.js";
7
7
 
8
8
  // src/cli.ts
9
9
  var HELP = `
@@ -52,7 +52,7 @@ async function main() {
52
52
  console.error(`invalid port: ${argVal(args, "--port")}`);
53
53
  process.exit(1);
54
54
  }
55
- const { runServer } = await import("./chunks/server-NWTBQYCW.js");
55
+ const { runServer } = await import("./chunks/server-OQNHWL3Z.js");
56
56
  runServer(port);
57
57
  return;
58
58
  }
@@ -76,7 +76,7 @@ async function main() {
76
76
  const { cachedUpdate, refreshUpdateCache } = await import("./chunks/update-UEPNGB6Y.js");
77
77
  refreshUpdateCache();
78
78
  const update = cachedUpdate(VERSION);
79
- const { runClient } = await import("./chunks/client-36OXOMLV.js");
79
+ const { runClient } = await import("./chunks/client-W4563KDL.js");
80
80
  runClient({
81
81
  name: argVal(args, "--name"),
82
82
  server: argVal(args, "--server"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "accelr8",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "Collaborative ascii canvas in your terminal. Real multiplayer, live cursors, mouse-driven TUI.",
5
5
  "type": "module",
6
6
  "license": "ISC",
@@ -42,7 +42,7 @@
42
42
  "chattest": "tsx src/chattest.ts",
43
43
  "inputtest": "tsx src/inputtest.ts",
44
44
  "torturetest": "tsx src/torturetest.ts",
45
- "test": "for t in core chat input ladder boot orb crypto accounts executor agentcontext map voice glyph materialize update contrast; do tsx src/${t}test.ts || exit 1; done && tsx src/rendertest.ts > /dev/null && echo RENDER OK",
45
+ "test": "for t in core chat input ladder boot orb crypto accounts executor agentcontext map voice glyph materialize update install contrast; do tsx src/${t}test.ts || exit 1; done && tsx src/rendertest.ts > /dev/null && echo RENDER OK",
46
46
  "laddertest": "tsx src/laddertest.ts",
47
47
  "boottest": "tsx src/boottest.ts",
48
48
  "reconnecttest": "tsx src/reconnecttest.ts",