@profullstack/qryptchat 0.6.0 → 0.6.1
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/bin/qc.js +2 -2
- package/dist/{chunk-074nrnc8.js → chunk-0f0frsar.js} +71 -15
- package/dist/commands.js +3 -1
- package/dist/mcp.js +1 -1
- package/package.json +1 -1
package/bin/qc.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// qc: qrypt.chat in the terminal. `qc` opens the chat client; `qc --help` lists the rest.
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
|
-
import { main } from '../dist/commands.js';
|
|
4
|
+
import { errorLine, main } from '../dist/commands.js';
|
|
5
5
|
|
|
6
6
|
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
7
7
|
|
|
8
8
|
main(process.argv.slice(2), { version: pkg.version }).then(
|
|
9
9
|
() => process.exit(0),
|
|
10
10
|
(err) => {
|
|
11
|
-
process.stderr.write(
|
|
11
|
+
process.stderr.write(`${errorLine(err)}\n`);
|
|
12
12
|
process.exit(1);
|
|
13
13
|
},
|
|
14
14
|
);
|
|
@@ -1135,11 +1135,34 @@ async function joinAsAgent(invite, { name, username, env = process.env, fetchImp
|
|
|
1135
1135
|
return { session, conversationId: body.conversation_id, operator: body.operator };
|
|
1136
1136
|
}
|
|
1137
1137
|
|
|
1138
|
+
// src/cli/style.js
|
|
1139
|
+
import { emojify, icon } from "@profullstack/hqtui";
|
|
1140
|
+
var mark = (name) => {
|
|
1141
|
+
const glyph = icon(name);
|
|
1142
|
+
return glyph ? `${glyph} ` : "";
|
|
1143
|
+
};
|
|
1144
|
+
var em = (text) => emojify(String(text ?? ""), { mode: "emoji" });
|
|
1145
|
+
var ok = (text) => `${mark("check-circle")}${em(text)}`;
|
|
1146
|
+
var info = (text) => `${mark("info")}${em(text)}`;
|
|
1147
|
+
var warn = (text) => `${mark("warning")}${em(text)}`;
|
|
1148
|
+
var fail = (text) => `${mark("error")}${em(text)}`;
|
|
1149
|
+
function who(user, { agent = false } = {}) {
|
|
1150
|
+
if (!user)
|
|
1151
|
+
return "";
|
|
1152
|
+
const face = user.emoji ? `${user.emoji} ` : agent || user.account_type === "agent" ? "\uD83E\uDD16 " : mark("user");
|
|
1153
|
+
const name = user.display_name || user.displayName || user.username || "";
|
|
1154
|
+
const handle = user.username && name !== user.username ? ` @${user.username}` : "";
|
|
1155
|
+
const pronouns = user.pronouns ? ` (${user.pronouns})` : "";
|
|
1156
|
+
return `${face}${name}${handle}${pronouns}`;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1138
1159
|
// src/cli/commands.js
|
|
1160
|
+
var errorLine2 = (err) => `qc: ${fail(err?.message ?? String(err))}`;
|
|
1139
1161
|
var HELP2 = `qc: qrypt.chat in your terminal (end-to-end encrypted, ML-KEM-1024)
|
|
1140
1162
|
|
|
1141
1163
|
Usage:
|
|
1142
1164
|
qc open the chat client (signs you in first if needed)
|
|
1165
|
+
qc fonts install|status|remove our OpenEmoji colour font as the terminal's emoji font
|
|
1143
1166
|
qc agent join <link> join as an AI agent from an invite (makes its own keys) [--name --username]
|
|
1144
1167
|
qc login [--oob] sign in through your browser; --oob to paste a code (SSH)
|
|
1145
1168
|
qc logout forget this terminal's session and keys
|
|
@@ -1251,15 +1274,46 @@ async function main2(argv, { version = "0.0.0" } = {}) {
|
|
|
1251
1274
|
});
|
|
1252
1275
|
if (flags.json)
|
|
1253
1276
|
return json({ user: session.user, conversation_id: conversationId, operator });
|
|
1254
|
-
out(`Joined qrypt.chat as
|
|
1255
|
-
out(
|
|
1277
|
+
out(ok(`Joined qrypt.chat as ${who(session.user, { agent: true })}, operated by ${who(operator)}.`));
|
|
1278
|
+
out(`${mark("key")}Keys made here and sealed in ${sessionPath()}; only the public key left this machine.`);
|
|
1256
1279
|
const chatName = operator?.display_name || operator?.username || "<chat>";
|
|
1257
|
-
out(
|
|
1280
|
+
out(`${mark("chat")}Your chat with ${chatName} is open. Try: qc listen or qc send "${chatName}" "hello" or qc mcp`);
|
|
1258
1281
|
return;
|
|
1259
1282
|
}
|
|
1283
|
+
case "fonts": {
|
|
1284
|
+
const { installEmojiFont, emojiFontStatus, removeEmojiFont } = await import("@profullstack/hqtui");
|
|
1285
|
+
const action = rest[0] || "status";
|
|
1286
|
+
if (action === "install") {
|
|
1287
|
+
const result = await installEmojiFont();
|
|
1288
|
+
for (const note of result.notes)
|
|
1289
|
+
out(info(note));
|
|
1290
|
+
if (result.snippets?.length) {
|
|
1291
|
+
out(`
|
|
1292
|
+
${mark("info")}Terminals that pick their own fonts need one line each:`);
|
|
1293
|
+
for (const sn of result.snippets)
|
|
1294
|
+
out(` ${sn.terminal} (${sn.file}):
|
|
1295
|
+
${sn.snippet}`);
|
|
1296
|
+
}
|
|
1297
|
+
return out(`
|
|
1298
|
+
${ok("Installed OpenEmoji. Restart the terminal, then run: qc fonts status")}`);
|
|
1299
|
+
}
|
|
1300
|
+
if (action === "status") {
|
|
1301
|
+
const st = await emojiFontStatus();
|
|
1302
|
+
out(`${st.installed ? ok("OpenEmoji installed") : warn("OpenEmoji not installed (qc fonts install)")}`);
|
|
1303
|
+
if (st.emojiFont !== undefined)
|
|
1304
|
+
out(`${mark("info")}Emoji font in use: ${st.emojiFont || "none"}`);
|
|
1305
|
+
return out(st.active ? ok("Active: emoji draw as OpenEmoji") : warn("Not active yet (restart the terminal)"));
|
|
1306
|
+
}
|
|
1307
|
+
if (action === "remove") {
|
|
1308
|
+
const done = await removeEmojiFont();
|
|
1309
|
+
return out(done.length ? ok("Removed the OpenEmoji font setup.") : info("Nothing to remove."));
|
|
1310
|
+
}
|
|
1311
|
+
throw new Error("Usage: qc fonts install|status|remove");
|
|
1312
|
+
}
|
|
1260
1313
|
case "login": {
|
|
1261
1314
|
const { session } = await login({ oob: !!flags.oob });
|
|
1262
|
-
out(`Signed in as
|
|
1315
|
+
out(ok(`Signed in as ${who(session.user)} on ${session.base}.`));
|
|
1316
|
+
out(`${mark("lock")}Keys sealed (ChaCha20-Poly1305) in ${sessionPath()}.`);
|
|
1263
1317
|
if (await new QcClient(session).keyCheck() === "mismatch") {
|
|
1264
1318
|
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.");
|
|
1265
1319
|
}
|
|
@@ -1267,12 +1321,12 @@ async function main2(argv, { version = "0.0.0" } = {}) {
|
|
|
1267
1321
|
}
|
|
1268
1322
|
case "logout":
|
|
1269
1323
|
clearSession();
|
|
1270
|
-
return out("Signed out. This terminal no longer holds your keys.");
|
|
1324
|
+
return out(ok("Signed out. This terminal no longer holds your keys."));
|
|
1271
1325
|
case "whoami": {
|
|
1272
1326
|
const session = loadSession();
|
|
1273
1327
|
if (!session)
|
|
1274
1328
|
throw new Error("Not signed in. Run qc login.");
|
|
1275
|
-
return flags.json ? json({ user: session.user, base: session.base }) : out(
|
|
1329
|
+
return flags.json ? json({ user: session.user, base: session.base }) : out(`${who(session.user)} on ${session.base}`);
|
|
1276
1330
|
}
|
|
1277
1331
|
case "profile": {
|
|
1278
1332
|
const c = await client(flags);
|
|
@@ -1285,14 +1339,14 @@ async function main2(argv, { version = "0.0.0" } = {}) {
|
|
|
1285
1339
|
const p = Object.keys(fields).length ? await c.updateProfile(fields) : await c.profile();
|
|
1286
1340
|
if (flags.json)
|
|
1287
1341
|
return json({ username: p.username, emoji: p.emoji ?? null, pronouns: p.pronouns ?? null, website: p.website ?? null, bio: p.bio ?? null });
|
|
1288
|
-
out(
|
|
1342
|
+
out(who(p));
|
|
1289
1343
|
if (p.website)
|
|
1290
|
-
out(p.website);
|
|
1344
|
+
out(`${mark("link")}${p.website}`);
|
|
1291
1345
|
if (p.bio)
|
|
1292
1346
|
out(p.bio);
|
|
1293
1347
|
if (!Object.keys(fields).length)
|
|
1294
1348
|
out(`
|
|
1295
|
-
Set: qc profile --emoji
|
|
1349
|
+
${info("Set: qc profile --emoji :telescope: --pronouns she/her --website https://you.example (an empty value clears)")}`);
|
|
1296
1350
|
return;
|
|
1297
1351
|
}
|
|
1298
1352
|
case "chats":
|
|
@@ -1301,7 +1355,7 @@ Set: qc profile --emoji \uD83D\uDD2D --pronouns she/her --website https://you.ex
|
|
|
1301
1355
|
if (flags.json)
|
|
1302
1356
|
return json(chats.map((c) => ({ id: c.id, title: c.title, type: c.type, updated_at: c.updated_at, participants: c.participants?.length ?? 0 })));
|
|
1303
1357
|
for (const c of chats)
|
|
1304
|
-
out(`${c.id} ${c.title}`);
|
|
1358
|
+
out(`${c.id} ${mark(c.type === "group" ? "users" : "chat")}${c.title}`);
|
|
1305
1359
|
return;
|
|
1306
1360
|
}
|
|
1307
1361
|
case "read": {
|
|
@@ -1317,7 +1371,7 @@ Set: qc profile --emoji \uD83D\uDD2D --pronouns she/her --website https://you.ex
|
|
|
1317
1371
|
if (m.replyTo)
|
|
1318
1372
|
out(` ┃ ${m.replyTo.name ? `${m.replyTo.name}: ` : ""}${m.replyTo.snippet}`);
|
|
1319
1373
|
const reactions = m.reactions?.length ? ` [${m.reactions.map((r) => `${r.emoji}${r.count > 1 ? r.count : ""}`).join(" ")}]` : "";
|
|
1320
|
-
out(`[${stamp(m.at)}] ${m.mine ? "you" : m.sender}: ${m.text}${reactions} (${m.id.slice(0, 8)})`);
|
|
1374
|
+
out(`[${stamp(m.at)}] ${m.emoji ? `${m.emoji} ` : ""}${m.mine ? "you" : m.sender}: ${em(m.text)}${reactions} (${m.id.slice(0, 8)})`);
|
|
1321
1375
|
}
|
|
1322
1376
|
return;
|
|
1323
1377
|
}
|
|
@@ -1332,7 +1386,7 @@ Set: qc profile --emoji \uD83D\uDD2D --pronouns she/her --website https://you.ex
|
|
|
1332
1386
|
if (target.length !== 1)
|
|
1333
1387
|
throw new Error(target.length ? `"${messageId}" matches ${target.length} messages` : `No message "${messageId}" in ${chat.title}`);
|
|
1334
1388
|
await c.react(chat.id, target[0].id, emoji, !!flags.remove);
|
|
1335
|
-
return out(`${flags.remove ? "Removed" : "Reacted"} ${emoji} on ${chat.title}.`);
|
|
1389
|
+
return out(ok(`${flags.remove ? "Removed" : "Reacted"} ${emoji} on ${chat.title}.`));
|
|
1336
1390
|
}
|
|
1337
1391
|
case "send": {
|
|
1338
1392
|
if (!rest[0] || rest.length < 2)
|
|
@@ -1345,7 +1399,9 @@ Set: qc profile --emoji \uD83D\uDD2D --pronouns she/her --website https://you.ex
|
|
|
1345
1399
|
const { message, skipped } = await c.send(chat.id, text, { replyTo: flags.reply || undefined });
|
|
1346
1400
|
if (flags.json)
|
|
1347
1401
|
return json({ id: message?.id, conversation: chat.id, skipped });
|
|
1348
|
-
|
|
1402
|
+
if (skipped)
|
|
1403
|
+
out(warn(`${skipped} participant(s) have no key yet and will not see it.`));
|
|
1404
|
+
return out(`${mark("send")}Sent to ${chat.title}.`);
|
|
1349
1405
|
}
|
|
1350
1406
|
case "listen": {
|
|
1351
1407
|
const c = await client(flags);
|
|
@@ -1366,7 +1422,7 @@ Set: qc profile --emoji \uD83D\uDD2D --pronouns she/her --website https://you.ex
|
|
|
1366
1422
|
if (flags.json)
|
|
1367
1423
|
out(JSON.stringify({ chat: id, title: titles.get(id), ...m }));
|
|
1368
1424
|
else
|
|
1369
|
-
out(`[${stamp(m.at)}] ${titles.get(id) ?? id} · ${m.mine ? "you" : m.sender}: ${m.text}`);
|
|
1425
|
+
out(`[${stamp(m.at)}] ${mark("chat")}${titles.get(id) ?? id} · ${m.emoji ? `${m.emoji} ` : ""}${m.mine ? "you" : m.sender}: ${em(m.text)}`);
|
|
1370
1426
|
}
|
|
1371
1427
|
}, { onStatus: (s) => process.stderr.write(`qc: ${s}
|
|
1372
1428
|
`) });
|
|
@@ -1383,4 +1439,4 @@ ${HELP2}`);
|
|
|
1383
1439
|
}
|
|
1384
1440
|
}
|
|
1385
1441
|
|
|
1386
|
-
export { HELP2, parseArgs2, findChat2, main2 };
|
|
1442
|
+
export { errorLine2, HELP2, parseArgs2, findChat2, main2 };
|
package/dist/commands.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
errorLine2,
|
|
2
3
|
HELP2,
|
|
3
4
|
parseArgs2,
|
|
4
5
|
findChat2,
|
|
5
6
|
main2
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-0f0frsar.js";
|
|
7
8
|
import"./chunk-menzxtw0.js";
|
|
8
9
|
export {
|
|
9
10
|
HELP2 as HELP,
|
|
11
|
+
errorLine2 as errorLine,
|
|
10
12
|
findChat2 as findChat,
|
|
11
13
|
main2 as main,
|
|
12
14
|
parseArgs2 as parseArgs
|
package/dist/mcp.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@profullstack/qryptchat",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
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": {
|