@profullstack/qryptchat 0.4.2 → 0.4.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.
- package/dist/{chunk-7mn96363.js → chunk-jdt6p3k7.js} +22 -2
- package/dist/commands.js +1 -1
- package/dist/mcp.js +1 -1
- package/dist/tui.js +22 -7
- package/package.json +2 -2
|
@@ -992,6 +992,20 @@ class QcClient {
|
|
|
992
992
|
throw new QcError(data.error_description || data.error || `HTTP ${res.status}`, res.status);
|
|
993
993
|
return data;
|
|
994
994
|
}
|
|
995
|
+
async keyCheck() {
|
|
996
|
+
try {
|
|
997
|
+
const me = this.me?.id;
|
|
998
|
+
const mine = this.session.keys?.keys1024?.publicKey;
|
|
999
|
+
if (!me || !mine)
|
|
1000
|
+
return "unknown";
|
|
1001
|
+
const { public_keys: keys = {} } = await this.request("/api/crypto/public-keys", { method: "POST", body: { user_ids: [me] } });
|
|
1002
|
+
if (!keys[me])
|
|
1003
|
+
return "unknown";
|
|
1004
|
+
return keys[me] === mine ? "ok" : "mismatch";
|
|
1005
|
+
} catch {
|
|
1006
|
+
return "unknown";
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
995
1009
|
async conversations() {
|
|
996
1010
|
const { conversations = [] } = await this.request("/api/conversations/load", { method: "POST", body: {} });
|
|
997
1011
|
return conversations.map((c) => ({ ...c, title: conversationTitle(c, this.me) }));
|
|
@@ -1356,7 +1370,9 @@ Usage:
|
|
|
1356
1370
|
|
|
1357
1371
|
<chat> is a chat id or part of its name. Options: --json, --url <server> (or QC_URL).
|
|
1358
1372
|
Keys and tokens are sealed (ChaCha20-Poly1305) in ${"$"}QC_HOME or ~/.config/qc; the key is in your
|
|
1359
|
-
OS keychain, or comes from a passphrase (QC_PASSPHRASE for scripts and qc mcp)
|
|
1373
|
+
OS keychain, or comes from a passphrase (QC_PASSPHRASE for scripts and qc mcp).
|
|
1374
|
+
HD: emoji and icons are drawn as our OpenEmoji/OpenIcon images in Kitty and Ghostty. Over SSH or in
|
|
1375
|
+
tmux (set -g allow-passthrough on), run with QC_HD=1; QC_HD=0 turns it off.`;
|
|
1360
1376
|
function parseArgs2(argv) {
|
|
1361
1377
|
const args = { _: [], flags: {} };
|
|
1362
1378
|
for (let i = 0;i < argv.length; i++) {
|
|
@@ -1440,7 +1456,11 @@ async function main2(argv, { version = "0.0.0" } = {}) {
|
|
|
1440
1456
|
}
|
|
1441
1457
|
case "login": {
|
|
1442
1458
|
const { session } = await login({ oob: !!flags.oob });
|
|
1443
|
-
|
|
1459
|
+
out(`Signed in as @${session.user?.username ?? "unknown"} on ${session.base}. Keys sealed (ChaCha20-Poly1305) in ${sessionPath()}.`);
|
|
1460
|
+
if (await new QcClient(session).keyCheck() === "mismatch") {
|
|
1461
|
+
out("Warning: the browser handed over keys that are not your account's current keys, so messages will not decrypt here. Restore your keys in that browser (Settings > Keys) or approve from the device you chat on, then run qc login again.");
|
|
1462
|
+
}
|
|
1463
|
+
return;
|
|
1444
1464
|
}
|
|
1445
1465
|
case "logout":
|
|
1446
1466
|
clearSession();
|
package/dist/commands.js
CHANGED
package/dist/mcp.js
CHANGED
package/dist/tui.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-vfahhydf.js";
|
|
4
4
|
|
|
5
5
|
// src/cli/tui.js
|
|
6
|
-
import { createApp, editText, emojify, insertText, stringWidth, truncate, widgets, wrap } from "@profullstack/hqtui";
|
|
6
|
+
import { createApp, createImageStore, drawIcon, drawRichText, editText, emojify, insertText, stringWidth, truncate, widgets, wrap } from "@profullstack/hqtui";
|
|
7
7
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
var SIDEBAR = 30;
|
|
@@ -99,9 +99,9 @@ function drawTranscript(surface, state) {
|
|
|
99
99
|
if (line.spans) {
|
|
100
100
|
let x = 0;
|
|
101
101
|
for (const span of line.spans)
|
|
102
|
-
x += surface
|
|
102
|
+
x += drawRichText(surface, x, y, span.text, { fg: span.fg, attrs: span.bold ? 1 : 0 }, state.images);
|
|
103
103
|
} else {
|
|
104
|
-
surface
|
|
104
|
+
drawRichText(surface, 0, y, truncate(line.text, surface.width), { fg: line.fg }, state.images);
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
if (state.scrollBack > 0) {
|
|
@@ -116,7 +116,10 @@ function render({ ui, width, height, theme }, state, on = {}) {
|
|
|
116
116
|
bar.draw((s) => {
|
|
117
117
|
const t = s.theme;
|
|
118
118
|
s.fillRect(0, 0, s.width, 1, { bg: t.surface ?? t.background });
|
|
119
|
-
|
|
119
|
+
const brand = { fg: t.background, bg: t.primary, attrs: 1 };
|
|
120
|
+
let x = s.text(0, 0, " ", brand);
|
|
121
|
+
x += drawIcon(s, x, 0, "lock", state.images, "\uD83D\uDD12", brand);
|
|
122
|
+
x += s.text(x, 0, " qrypt.chat ", brand);
|
|
120
123
|
x += s.text(x, 0, ` @${state.me?.username ?? "?"}`, { fg: t.foreground, bg: t.surface ?? t.background });
|
|
121
124
|
if (state.notice)
|
|
122
125
|
s.text(x + 2, 0, truncate(state.notice, Math.max(0, s.width - x - 16)), { fg: t.warning, bg: t.surface ?? t.background });
|
|
@@ -203,16 +206,19 @@ function saveRecent(list) {
|
|
|
203
206
|
writeFileSync(recentFile(), JSON.stringify(list), { mode: 384 });
|
|
204
207
|
} catch {}
|
|
205
208
|
}
|
|
209
|
+
var KEY_MISMATCH = "Keys don't match your account, so messages can't decrypt. Run qc login and approve from a browser that can read your chats.";
|
|
206
210
|
async function runTui(client, { initialChat } = {}) {
|
|
207
211
|
const state = initialState(client.me);
|
|
208
212
|
state.recent = loadRecent();
|
|
209
213
|
const abort = new AbortController;
|
|
210
214
|
const app = await createApp({ quitKeys: ["ctrl+c"], focusNavigation: false });
|
|
211
215
|
const redraw = () => app.invalidate();
|
|
212
|
-
const
|
|
216
|
+
const env = process.env.QC_HD && !process.env.HQTUI_IMAGES ? { ...process.env, HQTUI_IMAGES: process.env.QC_HD } : process.env;
|
|
217
|
+
state.images = createImageStore({ write: (seq) => app.terminal.write(seq), onReady: redraw, env });
|
|
218
|
+
const note = (msg, { sticky = false } = {}) => {
|
|
213
219
|
state.notice = msg;
|
|
214
220
|
redraw();
|
|
215
|
-
if (msg)
|
|
221
|
+
if (msg && !sticky)
|
|
216
222
|
setTimeout(() => {
|
|
217
223
|
if (state.notice === msg) {
|
|
218
224
|
state.notice = "";
|
|
@@ -231,6 +237,7 @@ async function runTui(client, { initialChat } = {}) {
|
|
|
231
237
|
note(`Could not load messages: ${err.message}`);
|
|
232
238
|
}
|
|
233
239
|
}
|
|
240
|
+
let keysChecked = false;
|
|
234
241
|
async function loadConversations() {
|
|
235
242
|
try {
|
|
236
243
|
const before = state.activeId;
|
|
@@ -243,6 +250,11 @@ async function runTui(client, { initialChat } = {}) {
|
|
|
243
250
|
state.selected = Math.max(0, state.conversations.findIndex((c) => c.id === before));
|
|
244
251
|
}
|
|
245
252
|
redraw();
|
|
253
|
+
if (!keysChecked) {
|
|
254
|
+
keysChecked = true;
|
|
255
|
+
if (await client.keyCheck() === "mismatch")
|
|
256
|
+
note(KEY_MISMATCH, { sticky: true });
|
|
257
|
+
}
|
|
246
258
|
} catch (err) {
|
|
247
259
|
state.loading = false;
|
|
248
260
|
note(err.status === 401 ? "Session ended: run qc login" : `Could not load chats: ${err.message}`);
|
|
@@ -371,7 +383,10 @@ async function runTui(client, { initialChat } = {}) {
|
|
|
371
383
|
state.field = insertText(state.field, event.text);
|
|
372
384
|
redraw();
|
|
373
385
|
});
|
|
374
|
-
app.on("exit", () =>
|
|
386
|
+
app.on("exit", () => {
|
|
387
|
+
state.images?.clear();
|
|
388
|
+
abort.abort();
|
|
389
|
+
});
|
|
375
390
|
app.render((args) => render(args, state, handlers));
|
|
376
391
|
loadConversations();
|
|
377
392
|
client.events(({ type, data }) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@profullstack/qryptchat",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
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": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@noble/ciphers": "^2.0.0",
|
|
20
|
-
"@profullstack/hqtui": "^0.
|
|
20
|
+
"@profullstack/hqtui": "^0.9.0",
|
|
21
21
|
"mlkem": "^2.5.0"
|
|
22
22
|
},
|
|
23
23
|
"keywords": [
|