accelr8 3.0.3 → 3.0.5

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 = true ? "3.0.3" : "3.0.2";
4
+ var VERSION = true ? "3.0.5" : "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-SY2ASAWX.js";
10
+ } from "./chunk-CZENTCH6.js";
11
11
  import {
12
12
  DEFAULT_MODEL,
13
13
  addAccount,
@@ -1186,23 +1186,41 @@ var DRAIN_KILL_MS = 2e3;
1186
1186
  var MAX_PLAYERS = 8;
1187
1187
  var STDERR_TAIL = 400;
1188
1188
  var VERIFY_MIN_BYTES = SAMPLE_RATE * 2 / 10;
1189
- var PLAYBACK_ARGS = [
1190
- "-hide_banner",
1191
- "-loglevel",
1192
- "error",
1193
- "-nodisp",
1194
- "-sync",
1195
- "audio",
1196
- "-infbuf",
1197
- "-f",
1198
- "s16le",
1199
- "-ar",
1200
- String(SAMPLE_RATE),
1201
- "-ch_layout",
1202
- "mono",
1203
- "-i",
1204
- "-"
1205
- ];
1189
+ function playbackArgs(ffplayPath) {
1190
+ const ch = ffplayChannelFlag(ffplayPath);
1191
+ return [
1192
+ "-hide_banner",
1193
+ "-loglevel",
1194
+ "error",
1195
+ "-nodisp",
1196
+ "-sync",
1197
+ "audio",
1198
+ "-infbuf",
1199
+ "-f",
1200
+ "s16le",
1201
+ "-ar",
1202
+ String(SAMPLE_RATE),
1203
+ ...ch,
1204
+ "-i",
1205
+ "-"
1206
+ ];
1207
+ }
1208
+ var chFlagCache = null;
1209
+ function ffplayChannelFlag(ffplayPath) {
1210
+ if (chFlagCache) return chFlagCache;
1211
+ let modern = true;
1212
+ try {
1213
+ const out = spawnSync(ffplayPath, ["-hide_banner", "-version"], { encoding: "utf8", timeout: 3e3 });
1214
+ const m = ((out.stdout || "") + (out.stderr || "")).match(/version\s+n?(\d+)\.(\d+)/i);
1215
+ if (m) {
1216
+ const major = +m[1], minor = +m[2];
1217
+ modern = major > 5 || major === 5 && minor >= 1;
1218
+ }
1219
+ } catch {
1220
+ }
1221
+ chFlagCache = modern ? ["-ch_layout", "mono"] : ["-ac", "1"];
1222
+ return chFlagCache;
1223
+ }
1206
1224
  function detectVoiceTools() {
1207
1225
  const ffmpegPath = which("ffmpeg");
1208
1226
  const ffplayPath = which("ffplay");
@@ -1422,7 +1440,7 @@ var VoicePlayback = class {
1422
1440
  spawnPlayer(speakerId) {
1423
1441
  let child;
1424
1442
  try {
1425
- child = spawn(this.tools.ffplayPath, PLAYBACK_ARGS, { stdio: ["pipe", "ignore", "ignore"] });
1443
+ child = spawn(this.tools.ffplayPath, playbackArgs(this.tools.ffplayPath), { stdio: ["pipe", "ignore", "ignore"] });
1426
1444
  } catch {
1427
1445
  return null;
1428
1446
  }
@@ -1476,6 +1494,55 @@ var VoicePlayback = class {
1476
1494
  if (this.players.get(speakerId) === p) this.players.delete(speakerId);
1477
1495
  }
1478
1496
  };
1497
+ function listMicDevices() {
1498
+ try {
1499
+ if (process.platform === "darwin") {
1500
+ const text = runForOutput(["-hide_banner", "-f", "avfoundation", "-list_devices", "true", "-i", ""]);
1501
+ const out = [];
1502
+ let inAudio = false;
1503
+ for (const line of text.split("\n")) {
1504
+ if (/audio devices/i.test(line)) {
1505
+ inAudio = true;
1506
+ continue;
1507
+ }
1508
+ if (/video devices/i.test(line)) {
1509
+ inAudio = false;
1510
+ continue;
1511
+ }
1512
+ if (!inAudio) continue;
1513
+ const m = line.match(/\[(\d+)\]\s+(.+?)\s*$/);
1514
+ if (m) out.push({ index: m[1], name: m[2] });
1515
+ }
1516
+ return out;
1517
+ }
1518
+ if (process.platform === "linux") {
1519
+ const text = runForOutput(["-hide_banner", "-sources", "pulse"]);
1520
+ const out = [];
1521
+ for (const line of text.split("\n")) {
1522
+ if (/auto-detected/i.test(line)) continue;
1523
+ const m = line.match(/^\s*\*?\s*(\S+)\s+\[(.+)\]\s*$/);
1524
+ if (m) out.push({ index: m[1], name: m[2] });
1525
+ }
1526
+ return out;
1527
+ }
1528
+ if (process.platform === "win32") {
1529
+ const text = runForOutput(["-hide_banner", "-list_devices", "true", "-f", "dshow", "-i", "dummy"]);
1530
+ const out = [];
1531
+ for (const line of text.split("\n")) {
1532
+ const m = line.match(/"(.+)"\s+\(audio\)/);
1533
+ if (m) out.push({ index: m[1], name: m[1] });
1534
+ }
1535
+ return out;
1536
+ }
1537
+ return [];
1538
+ } catch {
1539
+ return [];
1540
+ }
1541
+ }
1542
+ function runForOutput(args) {
1543
+ const res = spawnSync("ffmpeg", args, { encoding: "utf8", timeout: 5e3 });
1544
+ return (res.stderr ?? "") + (res.stdout ?? "");
1545
+ }
1479
1546
  function errMsg(e) {
1480
1547
  return e instanceof Error ? e.message : String(e);
1481
1548
  }
@@ -1605,6 +1672,47 @@ var voiceOn = false;
1605
1672
  var voiceTransmit = false;
1606
1673
  var voiceCapture = null;
1607
1674
  var voicePlayback = null;
1675
+ var voiceDevice = process.env.ACCELR8_MIC || void 0;
1676
+ var voiceRetries = 0;
1677
+ function autoPickMic() {
1678
+ if (process.platform === "darwin") {
1679
+ const mics = listMicDevices();
1680
+ if (!mics.length) return void 0;
1681
+ const builtin = mics.find((m) => /macbook|built.?in|internal/i.test(m.name));
1682
+ if (builtin) return builtin.index;
1683
+ const notPhone = mics.find((m) => !/iphone|ipad|continuity|desk view/i.test(m.name));
1684
+ return notPhone?.index;
1685
+ }
1686
+ if (process.platform === "win32") {
1687
+ const mics = listMicDevices();
1688
+ return mics[0]?.index;
1689
+ }
1690
+ return void 0;
1691
+ }
1692
+ function startCapture() {
1693
+ if (!voiceCapture) return;
1694
+ voiceCapture.start({
1695
+ device: voiceDevice,
1696
+ onChunk: (b64) => {
1697
+ voiceRetries = 0;
1698
+ if (voiceTransmit) sendMsg({ t: "voiceFrame", data: b64 });
1699
+ },
1700
+ onError: (m) => {
1701
+ if (!voiceTransmit) return;
1702
+ voiceRetries++;
1703
+ if (voiceRetries <= 6) {
1704
+ state.status = `mic dropped \xB7 reconnecting ${voiceRetries}\u2026`;
1705
+ setTimeout(() => {
1706
+ if (voiceTransmit && voiceCapture && !voiceCapture.active) startCapture();
1707
+ }, 200);
1708
+ } else {
1709
+ voiceTransmit = false;
1710
+ voiceRetries = 0;
1711
+ state.status = "mic keeps dropping \xB7 /voice mics to pick another \xB7 " + m.slice(0, 30);
1712
+ }
1713
+ }
1714
+ });
1715
+ }
1608
1716
  function toggleVoice() {
1609
1717
  if (voiceOn) return leaveVoice();
1610
1718
  if (!state.project) {
@@ -1620,6 +1728,7 @@ function toggleVoice() {
1620
1728
  voiceCapture = new VoiceCapture(voiceTools);
1621
1729
  voiceOn = true;
1622
1730
  voiceTransmit = false;
1731
+ if (voiceDevice === void 0 && voiceTools.capture) voiceDevice = autoPickMic();
1623
1732
  sendMsg({ t: "voiceJoin" });
1624
1733
  state.status = voiceTools.capture ? "voice on \xB7 /talk (or ctrl-g) to speak" : "voice on \xB7 listen only (no mic found)";
1625
1734
  }
@@ -1642,21 +1751,15 @@ function toggleTalk() {
1642
1751
  return;
1643
1752
  }
1644
1753
  if (voiceTransmit) {
1645
- voiceCapture.stop();
1646
1754
  voiceTransmit = false;
1755
+ voiceRetries = 0;
1756
+ voiceCapture.stop();
1647
1757
  state.status = "muted \xB7 /talk (or ctrl-g) to speak";
1648
1758
  } else {
1649
1759
  voiceTransmit = true;
1760
+ voiceRetries = 0;
1650
1761
  state.status = "\u25CF live \xB7 /mute (or ctrl-g) to stop";
1651
- voiceCapture.start({
1652
- onChunk: (b64) => {
1653
- if (voiceTransmit) sendMsg({ t: "voiceFrame", data: b64 });
1654
- },
1655
- onError: (m) => {
1656
- voiceTransmit = false;
1657
- state.status = "mic error \xB7 " + m.slice(0, 44);
1658
- }
1659
- });
1762
+ startCapture();
1660
1763
  }
1661
1764
  }
1662
1765
  function openHelp() {
@@ -3037,7 +3140,7 @@ function renderAccounts() {
3037
3140
  puts(ix + 46, y, a.auth.kind === "claude_code_login" ? "claude login" : "api key", { color: C.dim });
3038
3141
  puts(ix + 60, y, status, { color: dotColor });
3039
3142
  const modeColor = a.mode === "auto" ? C.warn : a.mode === "plan" ? C.accent : C.good;
3040
- button(w - 22, y, ` ${a.mode} `, { bgColor: C.tabOff, color: modeColor, bold: true }, () => cycleMode(a));
3143
+ button(w - 22, y, ` ${a.mode} `, { bgColor: C.tabOff, color: readableFg(modeColor, C.tabOff), bold: true }, () => cycleMode(a));
3041
3144
  button(w - 11, y, " remove ", { bgColor: C.tabOff, color: C.text }, () => removeAgentAccount(a));
3042
3145
  y += 1;
3043
3146
  }
@@ -3536,6 +3639,19 @@ function slashCommand(text) {
3536
3639
  toggleVoice();
3537
3640
  } else if (cmd === "/talk" || cmd === "/mute") {
3538
3641
  toggleTalk();
3642
+ } else if (cmd === "/voice mics" || cmd === "/mics") {
3643
+ const mics = listMicDevices();
3644
+ sidecarBlock = { title: "microphones", at: Date.now(), lines: mics.length ? mics.map((m) => `${m.index} ${m.name}${voiceDevice === m.index ? " \xB7 selected" : ""}`).concat("use: /voice mic <index>") : ["none found (need ffmpeg)"] };
3645
+ state.status = `${mics.length} mics \xB7 /voice mic <index> to pick`;
3646
+ } else if (cmd.startsWith("/voice mic ")) {
3647
+ const idx = cmd.slice("/voice mic ".length).trim();
3648
+ voiceDevice = idx || void 0;
3649
+ state.status = `mic set to [${idx}]` + (voiceOn ? " \xB7 reconnecting" : "");
3650
+ if (voiceTransmit && voiceCapture) {
3651
+ voiceCapture.stop();
3652
+ voiceRetries = 0;
3653
+ startCapture();
3654
+ }
3539
3655
  } else {
3540
3656
  state.status = `unknown command: ${cmd.split(" ")[0]}`;
3541
3657
  }
@@ -6,7 +6,7 @@ import {
6
6
  LIMITS,
7
7
  PORT,
8
8
  PROTO
9
- } from "./chunk-SY2ASAWX.js";
9
+ } from "./chunk-CZENTCH6.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-SY2ASAWX.js";
6
+ } from "./chunks/chunk-CZENTCH6.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-IVR5UHTM.js");
55
+ const { runServer } = await import("./chunks/server-OGAKQFOU.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-HLMFYYAQ.js");
79
+ const { runClient } = await import("./chunks/client-DGDZ22XC.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.3",
3
+ "version": "3.0.5",
4
4
  "description": "Collaborative ascii canvas in your terminal. Real multiplayer, live cursors, mouse-driven TUI.",
5
5
  "type": "module",
6
6
  "license": "ISC",