@profullstack/qryptchat 0.11.0 → 0.13.0
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.
- package/README.md +2 -0
- package/dist/{chunk-nsn86c8y.js → chunk-8frhjf08.js} +1 -1
- package/dist/{chunk-ywyhy3sp.js → chunk-94qf0rg1.js} +87 -3
- package/dist/{chunk-wkjs9anq.js → chunk-qcszcng8.js} +1 -1
- package/dist/commands.js +1 -1
- package/dist/mcp.js +21 -1
- package/dist/tui.js +50 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,12 +49,14 @@ The mouse works too: click a chat, scroll the transcript, click an emoji. Unread
|
|
|
49
49
|
```sh
|
|
50
50
|
qc chats # id and name of every chat
|
|
51
51
|
qc read alice -n 20 # the last 20 messages, decrypted
|
|
52
|
+
qc dm @alice "hey, it's me" # start (or reopen) a direct chat with anyone, by @username
|
|
52
53
|
qc send alice "on my way" # encrypted to every participant
|
|
53
54
|
qc send ops "@ri deploy is done" # an @partial matching one person is sent as @riotcoder
|
|
54
55
|
qc mentions ops @al # who @al completes to: @alice, @alina
|
|
55
56
|
echo "deploy done" | qc send ops -
|
|
56
57
|
qc listen --json # new messages as NDJSON, until you stop it
|
|
57
58
|
qc whoami
|
|
59
|
+
qc keys # are this terminal's keys your account's? (qc keys publish puts them back)
|
|
58
60
|
```
|
|
59
61
|
|
|
60
62
|
`<chat>` is a chat id or any unique part of its name. Add `--json` for machine output.
|
|
@@ -736,6 +736,13 @@ class QcClient {
|
|
|
736
736
|
return "unknown";
|
|
737
737
|
}
|
|
738
738
|
}
|
|
739
|
+
async publishKey() {
|
|
740
|
+
const mine = this.session.keys?.keys1024?.publicKey;
|
|
741
|
+
if (!mine)
|
|
742
|
+
throw new QcError("This terminal holds no keys. Run qc login.");
|
|
743
|
+
await this.request("/api/crypto/public-keys", { method: "PUT", body: { public_key: mine, key_type: "ML-KEM-1024" } });
|
|
744
|
+
return this.keyCheck();
|
|
745
|
+
}
|
|
739
746
|
async profile() {
|
|
740
747
|
const { user } = await this.request(`/api/users/by-username/${encodeURIComponent(this.me?.username ?? "")}`);
|
|
741
748
|
return user;
|
|
@@ -788,6 +795,24 @@ class QcClient {
|
|
|
788
795
|
}));
|
|
789
796
|
return { messages: out, hasMore };
|
|
790
797
|
}
|
|
798
|
+
async searchUsers(query) {
|
|
799
|
+
const q = String(query ?? "").trim().replace(/^@/, "");
|
|
800
|
+
if (!q)
|
|
801
|
+
return [];
|
|
802
|
+
const { users = [] } = await this.request(`/api/users/search?q=${encodeURIComponent(q)}`);
|
|
803
|
+
return users;
|
|
804
|
+
}
|
|
805
|
+
async startDirect(who) {
|
|
806
|
+
const user = pickUser(await this.searchUsers(who), who);
|
|
807
|
+
const { conversation_id: id } = await this.request("/api/chat/conversations", {
|
|
808
|
+
method: "POST",
|
|
809
|
+
body: { type: "direct", participant_ids: [user.id] }
|
|
810
|
+
});
|
|
811
|
+
if (!id)
|
|
812
|
+
throw new QcError(`Could not start a chat with @${user.username}`);
|
|
813
|
+
const chat = (await this.conversations()).find((c) => c.id === id);
|
|
814
|
+
return chat ?? { id, title: user.display_name || user.username, participants: [] };
|
|
815
|
+
}
|
|
791
816
|
async joinGroup(code) {
|
|
792
817
|
return this.request("/api/conversations/join", { method: "POST", body: { code } });
|
|
793
818
|
}
|
|
@@ -915,6 +940,18 @@ function sleep(ms, signal) {
|
|
|
915
940
|
});
|
|
916
941
|
});
|
|
917
942
|
}
|
|
943
|
+
function pickUser(users, query) {
|
|
944
|
+
const q = String(query ?? "").trim().replace(/^@/, "").toLowerCase();
|
|
945
|
+
const exact = users.filter((u) => u.username?.toLowerCase() === q || u.unique_identifier?.toLowerCase() === q);
|
|
946
|
+
if (exact.length === 1)
|
|
947
|
+
return exact[0];
|
|
948
|
+
if (users.length === 1)
|
|
949
|
+
return users[0];
|
|
950
|
+
if (!users.length)
|
|
951
|
+
throw new QcError(`Nobody on qrypt.chat matches "${query}"`, 404);
|
|
952
|
+
const names = users.slice(0, 8).map((u) => `@${u.username}${u.display_name && u.display_name !== u.username ? ` (${u.display_name})` : ""}`);
|
|
953
|
+
throw new QcError(`"${query}" matches ${users.length} people: ${names.join(", ")}${users.length > 8 ? ", …" : ""}. Use their @username.`, 409);
|
|
954
|
+
}
|
|
918
955
|
function conversationTitle(c, me) {
|
|
919
956
|
if (c.name)
|
|
920
957
|
return c.name;
|
|
@@ -1262,6 +1299,8 @@ Usage:
|
|
|
1262
1299
|
qc logout forget this terminal's session and keys
|
|
1263
1300
|
qc update [--check] update qc to the latest release (--check only says whether there is one)
|
|
1264
1301
|
qc whoami who this terminal is signed in as
|
|
1302
|
+
qc keys [publish] whether this terminal's keys are your account's; publish makes them the
|
|
1303
|
+
account's again (when another device published keys qc does not hold)
|
|
1265
1304
|
qc profile [--emoji \uD83D\uDD2D] [--pronouns she/her] [--website URL] [--bio TEXT]
|
|
1266
1305
|
show or set your public profile (an empty value clears it);
|
|
1267
1306
|
--openprofile prints it as OpenProfile.md
|
|
@@ -1276,10 +1315,12 @@ Usage:
|
|
|
1276
1315
|
qc read <chat> [-n 20] print the last messages of a chat
|
|
1277
1316
|
qc send <chat> <text> send a message (text "-" reads stdin; --reply <message-id> to reply);
|
|
1278
1317
|
an @partial that matches one person becomes their @username
|
|
1318
|
+
qc dm <@user> [text] start (or reopen) a direct chat with anyone on qrypt.chat and send
|
|
1319
|
+
text if given ("-" reads stdin); qc msg is the same. In the client: /msg @user
|
|
1279
1320
|
qc mentions <chat> [@partial] who @ completes to in a chat, best match first (one per line)
|
|
1280
1321
|
qc react <chat> <message-id> <emoji> [--remove] react to a message
|
|
1281
1322
|
qc listen print new messages as they arrive (NDJSON with --json; --notify alerts with it)
|
|
1282
|
-
qc mcp run as an MCP server on stdio (list_chats, read_chat, send_message, list_mentions)
|
|
1323
|
+
qc mcp run as an MCP server on stdio (list_chats, read_chat, send_message, start_direct_chat, list_mentions)
|
|
1283
1324
|
qc join <link> join a group chat from its invite link (https://qrypt.chat/join/...)
|
|
1284
1325
|
qc invite <chat> print a group's invite link (group admins) [--reset makes a new one, --off turns it off]
|
|
1285
1326
|
|
|
@@ -1379,7 +1420,7 @@ async function main2(argv, { version = "0.0.0" } = {}) {
|
|
|
1379
1420
|
}
|
|
1380
1421
|
case "agent": {
|
|
1381
1422
|
if (rest[0] === "call") {
|
|
1382
|
-
const { runVoiceAgent } = await import("./chunk-
|
|
1423
|
+
const { runVoiceAgent } = await import("./chunk-qcszcng8.js");
|
|
1383
1424
|
return runVoiceAgent(await client(flags), {
|
|
1384
1425
|
brain: typeof flags.brain === "string" ? flags.brain : undefined,
|
|
1385
1426
|
voice: typeof flags.voice === "string" ? flags.voice : undefined,
|
|
@@ -1442,6 +1483,35 @@ ${ok("Installed OpenEmoji. Restart the terminal, then run: qc fonts status")}`);
|
|
|
1442
1483
|
}
|
|
1443
1484
|
return;
|
|
1444
1485
|
}
|
|
1486
|
+
case "keys": {
|
|
1487
|
+
const c = await client(flags);
|
|
1488
|
+
const status = await c.keyCheck();
|
|
1489
|
+
if (rest[0] !== "publish") {
|
|
1490
|
+
if (flags.json)
|
|
1491
|
+
return json({ keys: status });
|
|
1492
|
+
if (status === "ok")
|
|
1493
|
+
return out(ok("This terminal's keys are your account's keys."));
|
|
1494
|
+
if (status === "mismatch")
|
|
1495
|
+
return out(warn("Your account publishes a different key, so messages sent to you since then won't decrypt here. If older messages read fine in qc read, run qc keys publish."));
|
|
1496
|
+
return out(info("Could not compare keys (no key on file, or the check failed)."));
|
|
1497
|
+
}
|
|
1498
|
+
if (status === "ok")
|
|
1499
|
+
return out(ok("Already published: nothing to do."));
|
|
1500
|
+
if (!flags.yes) {
|
|
1501
|
+
if (!process.stdin.isTTY)
|
|
1502
|
+
throw new Error("qc keys publish replaces the key everyone encrypts to. Add --yes to confirm.");
|
|
1503
|
+
const { createInterface } = await import("node:readline/promises");
|
|
1504
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1505
|
+
const answer = await rl.question("Publish this terminal's key? Devices holding the other key will stop decrypting new messages until they restore your keys. [y/N] ");
|
|
1506
|
+
rl.close();
|
|
1507
|
+
if (!/^y(es)?$/i.test(answer.trim()))
|
|
1508
|
+
return out(info("Left the published key alone."));
|
|
1509
|
+
}
|
|
1510
|
+
const after = await c.publishKey();
|
|
1511
|
+
if (flags.json)
|
|
1512
|
+
return json({ published: true, keys: after });
|
|
1513
|
+
return out(ok("Published. New messages are encrypted to this terminal's key again; other devices on the other key should restore from Settings > Keys."));
|
|
1514
|
+
}
|
|
1445
1515
|
case "logout":
|
|
1446
1516
|
clearSession();
|
|
1447
1517
|
return out(ok("Signed out. This terminal no longer holds your keys."));
|
|
@@ -1514,7 +1584,7 @@ ${info("Set: qc profile --emoji :telescope: --pronouns she/her --website https:/
|
|
|
1514
1584
|
throw new Error('Usage: qc call <number> (e.g. qc call "(415) 555-1234" or qc call +442079460958)');
|
|
1515
1585
|
const c = await client(flags);
|
|
1516
1586
|
const url = `${c.base}/phone?to=${encodeURIComponent(rest.join(" "))}`;
|
|
1517
|
-
const { openBrowser } = await import("./chunk-
|
|
1587
|
+
const { openBrowser } = await import("./chunk-8frhjf08.js");
|
|
1518
1588
|
const opened = openBrowser(url);
|
|
1519
1589
|
return out(`${mark("send")}${opened === false ? "Open" : "Opened"} ${url}
|
|
1520
1590
|
Calls to phones need your browser for the audio; tap Call there.`);
|
|
@@ -1615,6 +1685,20 @@ ${info("Set: qc profile --emoji :telescope: --pronouns she/her --website https:/
|
|
|
1615
1685
|
out(warn(`@${a.query} could be ${a.matches.join(", ")}: sent as typed.`));
|
|
1616
1686
|
return out(`${mark("send")}Sent to ${chat.title}.`);
|
|
1617
1687
|
}
|
|
1688
|
+
case "dm":
|
|
1689
|
+
case "msg": {
|
|
1690
|
+
if (!rest[0])
|
|
1691
|
+
throw new Error(`Usage: qc ${cmd} <@user> [text] (a username, or a name only one person has)`);
|
|
1692
|
+
const text = rest[1] === "-" && rest.length === 2 ? (await readStdin()).trim() : rest.slice(1).join(" ");
|
|
1693
|
+
const c = await client(flags);
|
|
1694
|
+
const chat = await c.startDirect(rest[0]);
|
|
1695
|
+
const sent = text ? await c.send(chat.id, text) : null;
|
|
1696
|
+
if (flags.json)
|
|
1697
|
+
return json({ conversation: chat.id, title: chat.title, ...sent ? { id: sent.message?.id, skipped: sent.skipped } : {} });
|
|
1698
|
+
if (sent?.skipped)
|
|
1699
|
+
out(warn(`${chat.title} has no key yet and will not see it until they sign in.`));
|
|
1700
|
+
return out(`${mark("send")}${sent ? `Sent to ${chat.title}` : `Chat with ${chat.title} is open (qc send "${chat.title}" <text>)`}.`);
|
|
1701
|
+
}
|
|
1618
1702
|
case "listen": {
|
|
1619
1703
|
const c = await client(flags);
|
|
1620
1704
|
const chats = await c.conversations();
|
package/dist/commands.js
CHANGED
package/dist/mcp.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
findChat2,
|
|
4
4
|
mentionMatches2,
|
|
5
5
|
withMentions2
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-94qf0rg1.js";
|
|
7
7
|
|
|
8
8
|
// src/cli/mcp.js
|
|
9
9
|
import { createInterface } from "node:readline";
|
|
@@ -40,6 +40,19 @@ var TOOLS = [
|
|
|
40
40
|
additionalProperties: false
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
|
+
{
|
|
44
|
+
name: "start_direct_chat",
|
|
45
|
+
description: "Start (or reopen) an end-to-end encrypted direct chat with anyone on qrypt.chat, by @username or a name only one person has, and optionally send the first message. Returns the chat id for read_chat/send_message.",
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: "object",
|
|
48
|
+
properties: {
|
|
49
|
+
user: { type: "string", description: "Their @username (or a name that matches only them)" },
|
|
50
|
+
text: { type: "string", description: "Optional: the first message" }
|
|
51
|
+
},
|
|
52
|
+
required: ["user"],
|
|
53
|
+
additionalProperties: false
|
|
54
|
+
}
|
|
55
|
+
},
|
|
43
56
|
{
|
|
44
57
|
name: "list_mentions",
|
|
45
58
|
description: "Who an @mention can complete to in a conversation, best match first (like typing @ali then Tab). Without partial: everyone you can mention.",
|
|
@@ -98,6 +111,13 @@ async function callTool(client, name, args = {}) {
|
|
|
98
111
|
const { message, skipped } = await client.send(chat.id, m.text, { replyTo: typeof args.reply_to === "string" ? args.reply_to : undefined });
|
|
99
112
|
return { sent: true, id: message?.id, chat: chat.title, skipped, ...m.mentions.length ? { mentions: m.mentions } : {}, ...m.ambiguous.length ? { ambiguous: m.ambiguous } : {} };
|
|
100
113
|
}
|
|
114
|
+
if (name === "start_direct_chat") {
|
|
115
|
+
if (!args.user || typeof args.user !== "string")
|
|
116
|
+
throw new Error("user is required");
|
|
117
|
+
const chat = await client.startDirect(args.user);
|
|
118
|
+
const sent = typeof args.text === "string" && args.text.trim() ? await client.send(chat.id, args.text.trim()) : null;
|
|
119
|
+
return { chat: chat.title, id: chat.id, ...sent ? { sent: true, message_id: sent.message?.id, skipped: sent.skipped } : {} };
|
|
120
|
+
}
|
|
101
121
|
if (name === "list_mentions") {
|
|
102
122
|
const chat = findChat2(await client.conversations(), args.chat);
|
|
103
123
|
const matches = mentionMatches2(chat, client.me, typeof args.partial === "string" ? args.partial : "");
|
package/dist/tui.js
CHANGED
|
@@ -74,7 +74,12 @@ function chatsWithNews(prev, next) {
|
|
|
74
74
|
const seen = new Map(prev.map((c) => [c.id, c.updated_at ?? ""]));
|
|
75
75
|
return next.filter((c) => !seen.has(c.id) || (c.updated_at ?? "") > seen.get(c.id)).map((c) => c.id);
|
|
76
76
|
}
|
|
77
|
-
var SLASH_COMMANDS = ["voice", "archive", "unarchive", "archived", "names", "list"];
|
|
77
|
+
var SLASH_COMMANDS = ["voice", "archive", "unarchive", "archived", "names", "list", "msg", "dm", "query", "new"];
|
|
78
|
+
var NEW_CHAT_COMMANDS = ["msg", "dm", "query", "new"];
|
|
79
|
+
function parseNewChat(args) {
|
|
80
|
+
const m = /^(\S+)(?:\s+([\s\S]*))?$/.exec(String(args ?? "").trim());
|
|
81
|
+
return m ? { who: m[1], text: (m[2] ?? "").trim() } : null;
|
|
82
|
+
}
|
|
78
83
|
var pad2 = (n) => String(n).padStart(2, "0");
|
|
79
84
|
var timeOf = (iso) => {
|
|
80
85
|
const d = new Date(iso);
|
|
@@ -132,7 +137,8 @@ function drawTranscript(surface, state) {
|
|
|
132
137
|
const theme = surface.theme;
|
|
133
138
|
const messages = state.messages[state.activeId] || [];
|
|
134
139
|
if (!state.activeId) {
|
|
135
|
-
|
|
140
|
+
const hint = state.loading ? "Loading your chats…" : state.conversations.length ? "Pick a chat on the left, or type /msg @name to start one." : "No chats yet. Type /msg @name to message someone.";
|
|
141
|
+
surface.text(1, 1, hint, { fg: theme.muted });
|
|
136
142
|
return;
|
|
137
143
|
}
|
|
138
144
|
if (!messages.length) {
|
|
@@ -217,7 +223,7 @@ function render({ ui, width, height, theme }, state, on = {}) {
|
|
|
217
223
|
p.textInput({
|
|
218
224
|
value: state.field.value,
|
|
219
225
|
cursor: state.field.cursor,
|
|
220
|
-
placeholder: active ? `Message ${active.title} (@name Tab, Ctrl+E emoji,
|
|
226
|
+
placeholder: active ? `Message ${active.title} (@name Tab, Ctrl+E emoji, /msg @name for a new chat)` : "/msg @name to start a chat",
|
|
221
227
|
focused: state.focus === "compose" && !state.picker,
|
|
222
228
|
size: 1
|
|
223
229
|
});
|
|
@@ -229,7 +235,7 @@ function render({ ui, width, height, theme }, state, on = {}) {
|
|
|
229
235
|
{ key: "Ctrl+E", label: "emoji" },
|
|
230
236
|
{ key: "Tab", label: state.focus === "list" ? "compose" : mention?.matches.length ? "complete @" : "chats" },
|
|
231
237
|
{ key: "PgUp", label: "scroll" },
|
|
232
|
-
...state.focus === "list" ? [{ key: "a", label: state.showArchived ? "unarchive" : "archive" }] : [],
|
|
238
|
+
...state.focus === "list" ? [{ key: "n", label: "new chat" }, { key: "a", label: state.showArchived ? "unarchive" : "archive" }] : [],
|
|
233
239
|
{ key: "Ctrl+O", label: state.showArchived ? "chats" : "archived" },
|
|
234
240
|
{ key: "Ctrl+C", label: "quit" }
|
|
235
241
|
],
|
|
@@ -262,7 +268,7 @@ function saveRecent(list) {
|
|
|
262
268
|
writeFileSync(recentFile(), JSON.stringify(list), { mode: 384 });
|
|
263
269
|
} catch {}
|
|
264
270
|
}
|
|
265
|
-
var KEY_MISMATCH = "Keys don't match your account, so messages can't decrypt.
|
|
271
|
+
var KEY_MISMATCH = "Keys don't match your account, so new messages can't decrypt. If your history reads here, run qc keys publish; else qc login from a browser that reads your chats.";
|
|
266
272
|
async function runTui(client, { initialChat } = {}) {
|
|
267
273
|
const state = initialState(client.me);
|
|
268
274
|
state.recent = loadRecent();
|
|
@@ -381,13 +387,43 @@ async function runTui(client, { initialChat } = {}) {
|
|
|
381
387
|
note(`Could not ${on ? "archive" : "unarchive"}: ${err.message}`);
|
|
382
388
|
}
|
|
383
389
|
}
|
|
390
|
+
function openById(id) {
|
|
391
|
+
const chat = state.conversations.find((c) => c.id === id);
|
|
392
|
+
if (!chat)
|
|
393
|
+
return;
|
|
394
|
+
state.showArchived = !!chat.is_archived;
|
|
395
|
+
openAt(listed(state).findIndex((c) => c.id === id));
|
|
396
|
+
state.focus = "compose";
|
|
397
|
+
}
|
|
398
|
+
async function startChat(args) {
|
|
399
|
+
const want = parseNewChat(args);
|
|
400
|
+
if (!want)
|
|
401
|
+
return note("Usage: /msg @name [message] (a username, or a name only one person has)");
|
|
402
|
+
note(`Looking up ${want.who}…`, { sticky: true });
|
|
403
|
+
try {
|
|
404
|
+
const chat = await client.startDirect(want.who);
|
|
405
|
+
if (!state.conversations.some((c) => c.id === chat.id))
|
|
406
|
+
state.conversations = [chat, ...state.conversations];
|
|
407
|
+
openById(chat.id);
|
|
408
|
+
note(`Chat with ${chat.title} is open`);
|
|
409
|
+
if (want.text) {
|
|
410
|
+
state.field = { value: want.text, cursor: want.text.length };
|
|
411
|
+
await send({ text: true });
|
|
412
|
+
}
|
|
413
|
+
} catch (err) {
|
|
414
|
+
note(err.message, { sticky: err.status === 409 });
|
|
415
|
+
}
|
|
416
|
+
redraw();
|
|
417
|
+
}
|
|
384
418
|
function runSlash(raw) {
|
|
385
419
|
const cmd = parseSlashCommand(raw, SLASH_COMMANDS);
|
|
386
420
|
if (!cmd)
|
|
387
421
|
return false;
|
|
388
422
|
const active = state.conversations.find((c) => c.id === state.activeId);
|
|
389
423
|
state.field = { value: "", cursor: 0 };
|
|
390
|
-
if (cmd.name
|
|
424
|
+
if (NEW_CHAT_COMMANDS.includes(cmd.name))
|
|
425
|
+
startChat(cmd.args);
|
|
426
|
+
else if (cmd.name === "archived")
|
|
391
427
|
toggleArchivedView();
|
|
392
428
|
else if (!active)
|
|
393
429
|
note("Pick a chat first");
|
|
@@ -407,8 +443,8 @@ async function runTui(client, { initialChat } = {}) {
|
|
|
407
443
|
redraw();
|
|
408
444
|
return true;
|
|
409
445
|
}
|
|
410
|
-
async function send({ raw = false } = {}) {
|
|
411
|
-
if (!raw && runSlash(state.field.value))
|
|
446
|
+
async function send({ raw = false, text: plain = false } = {}) {
|
|
447
|
+
if (!raw && !plain && runSlash(state.field.value))
|
|
412
448
|
return;
|
|
413
449
|
const text = raw ? state.field.value.trim() : emojify(state.field.value.trim(), { mode: "emoji" });
|
|
414
450
|
if (!text || !state.activeId)
|
|
@@ -492,7 +528,10 @@ async function runTui(client, { initialChat } = {}) {
|
|
|
492
528
|
state.selected = Math.min(listed(state).length - 1, state.selected + 1);
|
|
493
529
|
else if (event.key === "enter" || event.key === "right" || event.key === "l")
|
|
494
530
|
handlers.open(state.selected);
|
|
495
|
-
else if (event.key === "
|
|
531
|
+
else if (event.key === "n") {
|
|
532
|
+
state.field = { value: "/msg @", cursor: 6 };
|
|
533
|
+
state.focus = "compose";
|
|
534
|
+
} else if (event.key === "a") {
|
|
496
535
|
const c = listed(state)[state.selected];
|
|
497
536
|
if (c)
|
|
498
537
|
setArchived(c, !c.is_archived);
|
|
@@ -585,11 +624,13 @@ async function runTui(client, { initialChat } = {}) {
|
|
|
585
624
|
}
|
|
586
625
|
export {
|
|
587
626
|
CHAT_LIST_REFRESH_MS,
|
|
627
|
+
NEW_CHAT_COMMANDS,
|
|
588
628
|
SLASH_COMMANDS,
|
|
589
629
|
chatsWithNews,
|
|
590
630
|
initialState,
|
|
591
631
|
listed,
|
|
592
632
|
mentionSuggestions,
|
|
633
|
+
parseNewChat,
|
|
593
634
|
render,
|
|
594
635
|
runTui,
|
|
595
636
|
tabComplete,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@profullstack/qryptchat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
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": {
|