@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-s094t64g.js");
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-e39k2cyf.js";
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
- if (Date.now() - this.lastHuman > ALONE_LEAVE_MS)
221
- return this.close("everyone left");
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
@@ -5,7 +5,7 @@ import {
5
5
  parseArgs2,
6
6
  findChat2,
7
7
  main2
8
- } from "./chunk-e39k2cyf.js";
8
+ } from "./chunk-ew1xhfzy.js";
9
9
  import"./chunk-8gpysrd6.js";
10
10
  export {
11
11
  HELP2 as HELP,
package/dist/mcp.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import"./chunk-8gpysrd6.js";
2
2
  import {
3
3
  findChat2
4
- } from "./chunk-e39k2cyf.js";
4
+ } from "./chunk-ew1xhfzy.js";
5
5
 
6
6
  // src/cli/mcp.js
7
7
  import { createInterface } from "node:readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profullstack/qryptchat",
3
- "version": "0.9.0",
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": {