@profullstack/qryptchat 0.9.0 → 0.9.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.
|
@@ -1251,7 +1251,7 @@ Usage:
|
|
|
1251
1251
|
qc open the chat client (signs you in first if needed)
|
|
1252
1252
|
qc fonts install|status|remove our OpenEmoji colour font as the terminal's emoji font
|
|
1253
1253
|
qc agent join <link> join as an AI agent from an invite (makes its own keys) [--name --username]
|
|
1254
|
-
qc agent call --brain "<cmd>" [--voice orion] [--post] [--no-greet]
|
|
1254
|
+
qc agent call --brain "<cmd>" [--voice orion] [--tts <url>] [--post] [--no-greet]
|
|
1255
1255
|
as an agent, join voice calls in your chats: hear people, ask <cmd> what
|
|
1256
1256
|
to say (JSON on stdin; it prints PASS or SAY: text), speak it
|
|
1257
1257
|
(needs: npm i -g @livekit/rtc-node)
|
|
@@ -1296,7 +1296,7 @@ function parseArgs2(argv) {
|
|
|
1296
1296
|
const [k, v] = a.slice(2).split("=", 2);
|
|
1297
1297
|
if (v !== undefined)
|
|
1298
1298
|
args.flags[k] = v;
|
|
1299
|
-
else if (["url", "n", "limit", "reply", "emoji", "pronouns", "website", "bio", "name", "username", "brain", "voice"].includes(k) && argv[i + 1] !== undefined && !argv[i + 1].startsWith("--"))
|
|
1299
|
+
else if (["url", "n", "limit", "reply", "emoji", "pronouns", "website", "bio", "name", "username", "brain", "voice", "tts"].includes(k) && argv[i + 1] !== undefined && !argv[i + 1].startsWith("--"))
|
|
1300
1300
|
args.flags[k] = argv[++i];
|
|
1301
1301
|
else
|
|
1302
1302
|
args.flags[k] = true;
|
|
@@ -1369,10 +1369,11 @@ async function main2(argv, { version = "0.0.0" } = {}) {
|
|
|
1369
1369
|
}
|
|
1370
1370
|
case "agent": {
|
|
1371
1371
|
if (rest[0] === "call") {
|
|
1372
|
-
const { runVoiceAgent } = await import("./chunk-
|
|
1372
|
+
const { runVoiceAgent } = await import("./chunk-r98rnjvx.js");
|
|
1373
1373
|
return runVoiceAgent(await client(flags), {
|
|
1374
1374
|
brain: typeof flags.brain === "string" ? flags.brain : undefined,
|
|
1375
1375
|
voice: typeof flags.voice === "string" ? flags.voice : undefined,
|
|
1376
|
+
tts: typeof flags.tts === "string" ? flags.tts : undefined,
|
|
1376
1377
|
post: Boolean(flags.post),
|
|
1377
1378
|
greet: !flags["no-greet"],
|
|
1378
1379
|
hearAgents: Boolean(flags["hear-agents"])
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CALL_TYPE,
|
|
3
3
|
parseCall
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ew1xhfzy.js";
|
|
5
5
|
|
|
6
6
|
// src/cli/voice-agent.js
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
@@ -106,6 +106,7 @@ var e2eeOptions = (key) => ({
|
|
|
106
106
|
});
|
|
107
107
|
var JOIN_RECENT_MS = 30 * 60 * 1000;
|
|
108
108
|
var ALONE_LEAVE_MS = 30 * 1000;
|
|
109
|
+
var FIRST_WAIT_MS = 10 * 60 * 1000;
|
|
109
110
|
var MAX_CALL_MS = 3 * 60 * 60 * 1000;
|
|
110
111
|
var RATE = 16000;
|
|
111
112
|
var SPEAK_RATE = 24000;
|
|
@@ -172,13 +173,14 @@ function askBrain(cmd, input, { timeoutMs = 90000, env = process.env } = {}) {
|
|
|
172
173
|
clearTimeout(timer);
|
|
173
174
|
resolve({ say: parseBrain(out), error: code ? `brain exited ${code}: ${err.trim().slice(-200)}` : "" });
|
|
174
175
|
});
|
|
176
|
+
child.stdin.on("error", () => {});
|
|
175
177
|
child.stdin.end(JSON.stringify(input));
|
|
176
178
|
});
|
|
177
179
|
}
|
|
178
180
|
|
|
179
181
|
class CallSession {
|
|
180
|
-
constructor({ client, rtc, conversation, call, brain, voice, post, log, isHuman }) {
|
|
181
|
-
Object.assign(this, { client, rtc, conversation, call, brain, voice, post, log, isHuman });
|
|
182
|
+
constructor({ client, rtc, conversation, call, brain, voice, tts, post, log, isHuman }) {
|
|
183
|
+
Object.assign(this, { client, rtc, conversation, call, brain, voice, ttsUrl: tts, post, log, isHuman });
|
|
182
184
|
this.transcript = [];
|
|
183
185
|
this.queue = Promise.resolve();
|
|
184
186
|
this.speakToken = 0;
|
|
@@ -186,6 +188,7 @@ class CallSession {
|
|
|
186
188
|
this.closed = false;
|
|
187
189
|
this.started = Date.now();
|
|
188
190
|
this.lastHuman = Date.now();
|
|
191
|
+
this.humanSeen = false;
|
|
189
192
|
}
|
|
190
193
|
get id() {
|
|
191
194
|
return this.conversation.id;
|
|
@@ -203,8 +206,7 @@ class CallSession {
|
|
|
203
206
|
await room.connect(url, token, { autoSubscribe: true, e2ee: e2eeOptions(this.call.key) });
|
|
204
207
|
this.source = new AudioSource(SPEAK_RATE, 1);
|
|
205
208
|
const track = LocalAudioTrack.createAudioTrack(`${this.client.me?.username || "agent"}-voice`, this.source);
|
|
206
|
-
const opts = new TrackPublishOptions;
|
|
207
|
-
opts.source = TrackSource.SOURCE_MICROPHONE;
|
|
209
|
+
const opts = new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE, dtx: false, red: true, audioEncoding: { maxBitrate: 64000n } });
|
|
208
210
|
await room.localParticipant.publishTrack(track, opts);
|
|
209
211
|
this.log(`joined the call in ${this.conversation.title}`);
|
|
210
212
|
this.watchdog = setInterval(() => this.checkAlone(), 5000);
|
|
@@ -215,10 +217,13 @@ class CallSession {
|
|
|
215
217
|
return [...this.room?.remoteParticipants?.values() ?? []].some((p) => this.isHuman(userIdOf(p.identity)));
|
|
216
218
|
}
|
|
217
219
|
checkAlone() {
|
|
218
|
-
if (this.humansPresent())
|
|
220
|
+
if (this.humansPresent()) {
|
|
219
221
|
this.lastHuman = Date.now();
|
|
220
|
-
|
|
221
|
-
|
|
222
|
+
this.humanSeen = true;
|
|
223
|
+
}
|
|
224
|
+
const limit = this.humanSeen ? ALONE_LEAVE_MS : FIRST_WAIT_MS;
|
|
225
|
+
if (Date.now() - this.lastHuman > limit)
|
|
226
|
+
return this.close(this.humanSeen ? "everyone left" : "nobody came");
|
|
222
227
|
if (Date.now() - this.started > MAX_CALL_MS)
|
|
223
228
|
return this.close("call too long");
|
|
224
229
|
}
|
|
@@ -327,6 +332,20 @@ class CallSession {
|
|
|
327
332
|
}
|
|
328
333
|
}
|
|
329
334
|
async tts(sentence) {
|
|
335
|
+
if (this.ttsUrl) {
|
|
336
|
+
try {
|
|
337
|
+
const res = await this.client.fetch(this.ttsUrl, {
|
|
338
|
+
method: "POST",
|
|
339
|
+
headers: { "Content-Type": "application/json" },
|
|
340
|
+
body: JSON.stringify({ text: sentence, voice: this.voice })
|
|
341
|
+
});
|
|
342
|
+
if (!res.ok)
|
|
343
|
+
throw new Error(`HTTP ${res.status}`);
|
|
344
|
+
return wavSamples(new Uint8Array(await res.arrayBuffer()));
|
|
345
|
+
} catch (e) {
|
|
346
|
+
this.log(`--tts failed (${e.message}); using qrypt.chat's voice`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
330
349
|
const res = await this.client.fetch(`${this.client.base}/api/voice/speak`, {
|
|
331
350
|
method: "POST",
|
|
332
351
|
headers: { Authorization: `Bearer ${await this.client.token()}`, "Content-Type": "application/json" },
|
|
@@ -373,7 +392,7 @@ async function latestCall(client, conversationId) {
|
|
|
373
392
|
}
|
|
374
393
|
return null;
|
|
375
394
|
}
|
|
376
|
-
async function runVoiceAgent(client, { brain, voice = "orion", post = false, greet = true, hearAgents = false, log = (s) => process.stderr.write(`qc agent call: ${s}
|
|
395
|
+
async function runVoiceAgent(client, { brain, voice = "orion", tts, post = false, greet = true, hearAgents = false, log = (s) => process.stderr.write(`qc agent call: ${s}
|
|
377
396
|
`), rtc, signal } = {}) {
|
|
378
397
|
if (!brain)
|
|
379
398
|
throw new Error('Usage: qc agent call --brain "<command>" [--voice orion] [--post] [--no-greet]');
|
|
@@ -389,7 +408,7 @@ async function runVoiceAgent(client, { brain, voice = "orion", post = false, gre
|
|
|
389
408
|
const join = async (conversation, call) => {
|
|
390
409
|
if (sessions.has(conversation.id) || Date.now() - Date.parse(call.at) > MAX_CALL_MS)
|
|
391
410
|
return;
|
|
392
|
-
const session = new CallSession({ client, rtc, conversation, call, brain, voice, post, log, isHuman });
|
|
411
|
+
const session = new CallSession({ client, rtc, conversation, call, brain, voice, tts, post, log, isHuman });
|
|
393
412
|
sessions.set(conversation.id, session);
|
|
394
413
|
session.onClose = () => sessions.delete(conversation.id);
|
|
395
414
|
try {
|
package/dist/commands.js
CHANGED
package/dist/mcp.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@profullstack/qryptchat",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "qc: qrypt.chat in your terminal. A full-screen end-to-end encrypted chat client (ML-KEM-1024), plus a scriptable CLI and an MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|