@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 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(`qc: ${err?.message ?? err}\n`);
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 @${session.user?.username} (agent), operated by @${operator?.username ?? "?"}.`);
1255
- out(`Keys made here and sealed in ${sessionPath()}; only the public key left this machine.`);
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(`Your chat with ${chatName} is open. Try: qc listen or qc send "${chatName}" "hello" or qc mcp`);
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 @${session.user?.username ?? "unknown"} on ${session.base}. Keys sealed (ChaCha20-Poly1305) in ${sessionPath()}.`);
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(`@${session.user?.username} (${session.user?.display_name ?? ""}) on ${session.base}`);
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(`${p.emoji ? `${p.emoji} ` : ""}${p.displayName ?? p.display_name ?? p.username} @${p.username}${p.pronouns ? ` (${p.pronouns})` : ""}`);
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 \uD83D\uDD2D --pronouns she/her --website https://you.example (an empty value clears)`);
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
- return out(`Sent to ${chat.title}${skipped ? ` (${skipped} participant(s) have no key yet)` : ""}.`);
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-074nrnc8.js";
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
@@ -1,7 +1,7 @@
1
1
  import"./chunk-menzxtw0.js";
2
2
  import {
3
3
  findChat2
4
- } from "./chunk-074nrnc8.js";
4
+ } from "./chunk-0f0frsar.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.6.0",
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": {