accelr8 3.0.3 → 3.0.4

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.4" : "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-I5NIANY6.js";
11
11
  import {
12
12
  DEFAULT_MODEL,
13
13
  addAccount,
@@ -1476,6 +1476,55 @@ var VoicePlayback = class {
1476
1476
  if (this.players.get(speakerId) === p) this.players.delete(speakerId);
1477
1477
  }
1478
1478
  };
1479
+ function listMicDevices() {
1480
+ try {
1481
+ if (process.platform === "darwin") {
1482
+ const text = runForOutput(["-hide_banner", "-f", "avfoundation", "-list_devices", "true", "-i", ""]);
1483
+ const out = [];
1484
+ let inAudio = false;
1485
+ for (const line of text.split("\n")) {
1486
+ if (/audio devices/i.test(line)) {
1487
+ inAudio = true;
1488
+ continue;
1489
+ }
1490
+ if (/video devices/i.test(line)) {
1491
+ inAudio = false;
1492
+ continue;
1493
+ }
1494
+ if (!inAudio) continue;
1495
+ const m = line.match(/\[(\d+)\]\s+(.+?)\s*$/);
1496
+ if (m) out.push({ index: m[1], name: m[2] });
1497
+ }
1498
+ return out;
1499
+ }
1500
+ if (process.platform === "linux") {
1501
+ const text = runForOutput(["-hide_banner", "-sources", "pulse"]);
1502
+ const out = [];
1503
+ for (const line of text.split("\n")) {
1504
+ if (/auto-detected/i.test(line)) continue;
1505
+ const m = line.match(/^\s*\*?\s*(\S+)\s+\[(.+)\]\s*$/);
1506
+ if (m) out.push({ index: m[1], name: m[2] });
1507
+ }
1508
+ return out;
1509
+ }
1510
+ if (process.platform === "win32") {
1511
+ const text = runForOutput(["-hide_banner", "-list_devices", "true", "-f", "dshow", "-i", "dummy"]);
1512
+ const out = [];
1513
+ for (const line of text.split("\n")) {
1514
+ const m = line.match(/"(.+)"\s+\(audio\)/);
1515
+ if (m) out.push({ index: m[1], name: m[1] });
1516
+ }
1517
+ return out;
1518
+ }
1519
+ return [];
1520
+ } catch {
1521
+ return [];
1522
+ }
1523
+ }
1524
+ function runForOutput(args) {
1525
+ const res = spawnSync("ffmpeg", args, { encoding: "utf8", timeout: 5e3 });
1526
+ return (res.stderr ?? "") + (res.stdout ?? "");
1527
+ }
1479
1528
  function errMsg(e) {
1480
1529
  return e instanceof Error ? e.message : String(e);
1481
1530
  }
@@ -1605,6 +1654,41 @@ var voiceOn = false;
1605
1654
  var voiceTransmit = false;
1606
1655
  var voiceCapture = null;
1607
1656
  var voicePlayback = null;
1657
+ var voiceDevice = process.env.ACCELR8_MIC || void 0;
1658
+ var voiceRetries = 0;
1659
+ function autoPickMic() {
1660
+ if (process.platform !== "darwin") return void 0;
1661
+ const mics = listMicDevices();
1662
+ if (!mics.length) return void 0;
1663
+ const builtin = mics.find((m) => /macbook|built.?in|internal/i.test(m.name));
1664
+ if (builtin) return builtin.index;
1665
+ const notPhone = mics.find((m) => !/iphone|ipad|continuity|desk view/i.test(m.name));
1666
+ return notPhone?.index;
1667
+ }
1668
+ function startCapture() {
1669
+ if (!voiceCapture) return;
1670
+ voiceCapture.start({
1671
+ device: voiceDevice,
1672
+ onChunk: (b64) => {
1673
+ voiceRetries = 0;
1674
+ if (voiceTransmit) sendMsg({ t: "voiceFrame", data: b64 });
1675
+ },
1676
+ onError: (m) => {
1677
+ if (!voiceTransmit) return;
1678
+ voiceRetries++;
1679
+ if (voiceRetries <= 6) {
1680
+ state.status = `mic dropped \xB7 reconnecting ${voiceRetries}\u2026`;
1681
+ setTimeout(() => {
1682
+ if (voiceTransmit && voiceCapture && !voiceCapture.active) startCapture();
1683
+ }, 200);
1684
+ } else {
1685
+ voiceTransmit = false;
1686
+ voiceRetries = 0;
1687
+ state.status = "mic keeps dropping \xB7 /voice mics to pick another \xB7 " + m.slice(0, 30);
1688
+ }
1689
+ }
1690
+ });
1691
+ }
1608
1692
  function toggleVoice() {
1609
1693
  if (voiceOn) return leaveVoice();
1610
1694
  if (!state.project) {
@@ -1620,6 +1704,7 @@ function toggleVoice() {
1620
1704
  voiceCapture = new VoiceCapture(voiceTools);
1621
1705
  voiceOn = true;
1622
1706
  voiceTransmit = false;
1707
+ if (voiceDevice === void 0 && voiceTools.capture) voiceDevice = autoPickMic();
1623
1708
  sendMsg({ t: "voiceJoin" });
1624
1709
  state.status = voiceTools.capture ? "voice on \xB7 /talk (or ctrl-g) to speak" : "voice on \xB7 listen only (no mic found)";
1625
1710
  }
@@ -1642,21 +1727,15 @@ function toggleTalk() {
1642
1727
  return;
1643
1728
  }
1644
1729
  if (voiceTransmit) {
1645
- voiceCapture.stop();
1646
1730
  voiceTransmit = false;
1731
+ voiceRetries = 0;
1732
+ voiceCapture.stop();
1647
1733
  state.status = "muted \xB7 /talk (or ctrl-g) to speak";
1648
1734
  } else {
1649
1735
  voiceTransmit = true;
1736
+ voiceRetries = 0;
1650
1737
  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
- });
1738
+ startCapture();
1660
1739
  }
1661
1740
  }
1662
1741
  function openHelp() {
@@ -3037,7 +3116,7 @@ function renderAccounts() {
3037
3116
  puts(ix + 46, y, a.auth.kind === "claude_code_login" ? "claude login" : "api key", { color: C.dim });
3038
3117
  puts(ix + 60, y, status, { color: dotColor });
3039
3118
  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));
3119
+ button(w - 22, y, ` ${a.mode} `, { bgColor: C.tabOff, color: readableFg(modeColor, C.tabOff), bold: true }, () => cycleMode(a));
3041
3120
  button(w - 11, y, " remove ", { bgColor: C.tabOff, color: C.text }, () => removeAgentAccount(a));
3042
3121
  y += 1;
3043
3122
  }
@@ -3536,6 +3615,19 @@ function slashCommand(text) {
3536
3615
  toggleVoice();
3537
3616
  } else if (cmd === "/talk" || cmd === "/mute") {
3538
3617
  toggleTalk();
3618
+ } else if (cmd === "/voice mics" || cmd === "/mics") {
3619
+ const mics = listMicDevices();
3620
+ 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)"] };
3621
+ state.status = `${mics.length} mics \xB7 /voice mic <index> to pick`;
3622
+ } else if (cmd.startsWith("/voice mic ")) {
3623
+ const idx = cmd.slice("/voice mic ".length).trim();
3624
+ voiceDevice = idx || void 0;
3625
+ state.status = `mic set to [${idx}]` + (voiceOn ? " \xB7 reconnecting" : "");
3626
+ if (voiceTransmit && voiceCapture) {
3627
+ voiceCapture.stop();
3628
+ voiceRetries = 0;
3629
+ startCapture();
3630
+ }
3539
3631
  } else {
3540
3632
  state.status = `unknown command: ${cmd.split(" ")[0]}`;
3541
3633
  }
@@ -6,7 +6,7 @@ import {
6
6
  LIMITS,
7
7
  PORT,
8
8
  PROTO
9
- } from "./chunk-SY2ASAWX.js";
9
+ } from "./chunk-I5NIANY6.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-I5NIANY6.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-QZIOVRDR.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-BVLUBSS7.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.4",
4
4
  "description": "Collaborative ascii canvas in your terminal. Real multiplayer, live cursors, mouse-driven TUI.",
5
5
  "type": "module",
6
6
  "license": "ISC",