accelr8 3.0.2 → 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.
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
VERSION,
|
|
8
8
|
decode,
|
|
9
9
|
encode
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-I5NIANY6.js";
|
|
11
11
|
import {
|
|
12
12
|
DEFAULT_MODEL,
|
|
13
13
|
addAccount,
|
|
@@ -1181,6 +1181,7 @@ var SAMPLE_RATE = 16e3;
|
|
|
1181
1181
|
var CHUNK_BYTES = 1280;
|
|
1182
1182
|
var KILL_GRACE_MS = 300;
|
|
1183
1183
|
var SPEAKER_IDLE_MS = 2e3;
|
|
1184
|
+
var PREBUFFER_BYTES = 4800;
|
|
1184
1185
|
var DRAIN_KILL_MS = 2e3;
|
|
1185
1186
|
var MAX_PLAYERS = 8;
|
|
1186
1187
|
var STDERR_TAIL = 400;
|
|
@@ -1190,10 +1191,9 @@ var PLAYBACK_ARGS = [
|
|
|
1190
1191
|
"-loglevel",
|
|
1191
1192
|
"error",
|
|
1192
1193
|
"-nodisp",
|
|
1193
|
-
"-
|
|
1194
|
-
"
|
|
1195
|
-
"-
|
|
1196
|
-
"low_delay",
|
|
1194
|
+
"-sync",
|
|
1195
|
+
"audio",
|
|
1196
|
+
"-infbuf",
|
|
1197
1197
|
"-f",
|
|
1198
1198
|
"s16le",
|
|
1199
1199
|
"-ar",
|
|
@@ -1355,13 +1355,31 @@ var VoicePlayback = class {
|
|
|
1355
1355
|
if (!spawned) return;
|
|
1356
1356
|
p = spawned;
|
|
1357
1357
|
}
|
|
1358
|
-
if (!
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1358
|
+
if (!p.flowing) {
|
|
1359
|
+
p.pre.push(bytes);
|
|
1360
|
+
if (p.pre.reduce((n, b) => n + b.length, 0) >= PREBUFFER_BYTES) {
|
|
1361
|
+
const cushion = Buffer.concat(p.pre);
|
|
1362
|
+
p.pre = [];
|
|
1363
|
+
p.flowing = true;
|
|
1364
|
+
const np2 = this.writeOrRespawn(speakerId, p, cushion);
|
|
1365
|
+
if (np2) this.touch(speakerId, np2);
|
|
1366
|
+
} else {
|
|
1367
|
+
this.touch(speakerId, p);
|
|
1368
|
+
}
|
|
1369
|
+
return;
|
|
1363
1370
|
}
|
|
1364
|
-
this.
|
|
1371
|
+
const np = this.writeOrRespawn(speakerId, p, bytes);
|
|
1372
|
+
if (np) this.touch(speakerId, np);
|
|
1373
|
+
}
|
|
1374
|
+
/** Write to a player; on EPIPE (it died) respawn once and write there. Returns
|
|
1375
|
+
* the live player, or null if it couldn't be (re)spawned. */
|
|
1376
|
+
writeOrRespawn(speakerId, p, buf) {
|
|
1377
|
+
if (this.write(p, buf)) return p;
|
|
1378
|
+
this.reap(speakerId, p);
|
|
1379
|
+
const fresh = this.players.size < MAX_PLAYERS ? this.spawnPlayer(speakerId) : null;
|
|
1380
|
+
if (!fresh || !this.write(fresh, buf)) return null;
|
|
1381
|
+
fresh.flowing = true;
|
|
1382
|
+
return fresh;
|
|
1365
1383
|
}
|
|
1366
1384
|
/** Graceful close for one speaker (voiceStop): end stdin so ffplay drains
|
|
1367
1385
|
* its buffered audio, then a SIGKILL backstop after 2s (we dropped -autoexit,
|
|
@@ -1408,7 +1426,7 @@ var VoicePlayback = class {
|
|
|
1408
1426
|
} catch {
|
|
1409
1427
|
return null;
|
|
1410
1428
|
}
|
|
1411
|
-
const p = { child, alive: true, idleTimer: null };
|
|
1429
|
+
const p = { child, alive: true, idleTimer: null, pre: [], flowing: false };
|
|
1412
1430
|
child.stdin?.on("error", () => {
|
|
1413
1431
|
p.alive = false;
|
|
1414
1432
|
});
|
|
@@ -1458,6 +1476,55 @@ var VoicePlayback = class {
|
|
|
1458
1476
|
if (this.players.get(speakerId) === p) this.players.delete(speakerId);
|
|
1459
1477
|
}
|
|
1460
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
|
+
}
|
|
1461
1528
|
function errMsg(e) {
|
|
1462
1529
|
return e instanceof Error ? e.message : String(e);
|
|
1463
1530
|
}
|
|
@@ -1587,6 +1654,41 @@ var voiceOn = false;
|
|
|
1587
1654
|
var voiceTransmit = false;
|
|
1588
1655
|
var voiceCapture = null;
|
|
1589
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
|
+
}
|
|
1590
1692
|
function toggleVoice() {
|
|
1591
1693
|
if (voiceOn) return leaveVoice();
|
|
1592
1694
|
if (!state.project) {
|
|
@@ -1602,6 +1704,7 @@ function toggleVoice() {
|
|
|
1602
1704
|
voiceCapture = new VoiceCapture(voiceTools);
|
|
1603
1705
|
voiceOn = true;
|
|
1604
1706
|
voiceTransmit = false;
|
|
1707
|
+
if (voiceDevice === void 0 && voiceTools.capture) voiceDevice = autoPickMic();
|
|
1605
1708
|
sendMsg({ t: "voiceJoin" });
|
|
1606
1709
|
state.status = voiceTools.capture ? "voice on \xB7 /talk (or ctrl-g) to speak" : "voice on \xB7 listen only (no mic found)";
|
|
1607
1710
|
}
|
|
@@ -1624,21 +1727,15 @@ function toggleTalk() {
|
|
|
1624
1727
|
return;
|
|
1625
1728
|
}
|
|
1626
1729
|
if (voiceTransmit) {
|
|
1627
|
-
voiceCapture.stop();
|
|
1628
1730
|
voiceTransmit = false;
|
|
1731
|
+
voiceRetries = 0;
|
|
1732
|
+
voiceCapture.stop();
|
|
1629
1733
|
state.status = "muted \xB7 /talk (or ctrl-g) to speak";
|
|
1630
1734
|
} else {
|
|
1631
1735
|
voiceTransmit = true;
|
|
1736
|
+
voiceRetries = 0;
|
|
1632
1737
|
state.status = "\u25CF live \xB7 /mute (or ctrl-g) to stop";
|
|
1633
|
-
|
|
1634
|
-
onChunk: (b64) => {
|
|
1635
|
-
if (voiceTransmit) sendMsg({ t: "voiceFrame", data: b64 });
|
|
1636
|
-
},
|
|
1637
|
-
onError: (m) => {
|
|
1638
|
-
voiceTransmit = false;
|
|
1639
|
-
state.status = "mic error \xB7 " + m.slice(0, 44);
|
|
1640
|
-
}
|
|
1641
|
-
});
|
|
1738
|
+
startCapture();
|
|
1642
1739
|
}
|
|
1643
1740
|
}
|
|
1644
1741
|
function openHelp() {
|
|
@@ -3019,7 +3116,7 @@ function renderAccounts() {
|
|
|
3019
3116
|
puts(ix + 46, y, a.auth.kind === "claude_code_login" ? "claude login" : "api key", { color: C.dim });
|
|
3020
3117
|
puts(ix + 60, y, status, { color: dotColor });
|
|
3021
3118
|
const modeColor = a.mode === "auto" ? C.warn : a.mode === "plan" ? C.accent : C.good;
|
|
3022
|
-
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));
|
|
3023
3120
|
button(w - 11, y, " remove ", { bgColor: C.tabOff, color: C.text }, () => removeAgentAccount(a));
|
|
3024
3121
|
y += 1;
|
|
3025
3122
|
}
|
|
@@ -3518,6 +3615,19 @@ function slashCommand(text) {
|
|
|
3518
3615
|
toggleVoice();
|
|
3519
3616
|
} else if (cmd === "/talk" || cmd === "/mute") {
|
|
3520
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
|
+
}
|
|
3521
3631
|
} else {
|
|
3522
3632
|
state.status = `unknown command: ${cmd.split(" ")[0]}`;
|
|
3523
3633
|
}
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
DEFAULT_SERVER,
|
|
4
4
|
PORT,
|
|
5
5
|
VERSION
|
|
6
|
-
} from "./chunks/chunk-
|
|
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-
|
|
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-
|
|
79
|
+
const { runClient } = await import("./chunks/client-BVLUBSS7.js");
|
|
80
80
|
runClient({
|
|
81
81
|
name: argVal(args, "--name"),
|
|
82
82
|
server: argVal(args, "--server"),
|