@profullstack/qryptchat 0.6.1 → 0.6.3
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.
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/cli/update.js
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
var PACKAGE = "@profullstack/qryptchat";
|
|
5
|
+
function compareVersions(a, b) {
|
|
6
|
+
const parse = (v) => {
|
|
7
|
+
const [core, pre = ""] = String(v).replace(/^v/, "").split("-");
|
|
8
|
+
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
9
|
+
};
|
|
10
|
+
const x = parse(a);
|
|
11
|
+
const y = parse(b);
|
|
12
|
+
for (let i = 0;i < 3; i++) {
|
|
13
|
+
const d = (x.nums[i] ?? 0) - (y.nums[i] ?? 0);
|
|
14
|
+
if (d)
|
|
15
|
+
return Math.sign(d);
|
|
16
|
+
}
|
|
17
|
+
if (x.pre === y.pre)
|
|
18
|
+
return 0;
|
|
19
|
+
if (!x.pre)
|
|
20
|
+
return 1;
|
|
21
|
+
if (!y.pre)
|
|
22
|
+
return -1;
|
|
23
|
+
return x.pre < y.pre ? -1 : 1;
|
|
24
|
+
}
|
|
25
|
+
function installCommand(binPath = "") {
|
|
26
|
+
const p = binPath.replaceAll("\\", "/");
|
|
27
|
+
const spec = `${PACKAGE}@latest`;
|
|
28
|
+
if (p.includes("/_npx/"))
|
|
29
|
+
return { npx: true };
|
|
30
|
+
if (p.includes("/.bun/"))
|
|
31
|
+
return { cmd: "bun", args: ["add", "-g", spec] };
|
|
32
|
+
if (p.includes("/pnpm/"))
|
|
33
|
+
return { cmd: "pnpm", args: ["add", "-g", spec] };
|
|
34
|
+
return { cmd: "npm", args: ["install", "-g", spec] };
|
|
35
|
+
}
|
|
36
|
+
async function latestVersion(fetchImpl = fetch) {
|
|
37
|
+
const res = await fetchImpl(`https://registry.npmjs.org/${PACKAGE}/latest`, { headers: { accept: "application/json" } });
|
|
38
|
+
if (!res.ok)
|
|
39
|
+
throw new Error(`Could not reach the npm registry (HTTP ${res.status}).`);
|
|
40
|
+
const { version } = await res.json();
|
|
41
|
+
if (!version)
|
|
42
|
+
throw new Error("The npm registry did not say which version is latest.");
|
|
43
|
+
return version;
|
|
44
|
+
}
|
|
45
|
+
function run(cmd, args) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const child = spawn(cmd, args, { stdio: "inherit", shell: process.platform === "win32" });
|
|
48
|
+
child.on("error", (err) => reject(new Error(`Could not run ${cmd}: ${err.message}`)));
|
|
49
|
+
child.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`${cmd} ${args.join(" ")} exited with ${code}.`)));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function update({ current, check = false, binPath = process.argv[1], fetchImpl = fetch, runImpl = run }) {
|
|
53
|
+
const latest = await latestVersion(fetchImpl);
|
|
54
|
+
if (compareVersions(latest, current) <= 0)
|
|
55
|
+
return { updated: false, line: `qc ${current} is the latest.` };
|
|
56
|
+
let real = binPath || "";
|
|
57
|
+
try {
|
|
58
|
+
real = realpathSync(real);
|
|
59
|
+
} catch {}
|
|
60
|
+
const how = installCommand(real);
|
|
61
|
+
if (how.npx)
|
|
62
|
+
return { updated: false, line: `qc ${latest} is out (you have ${current}). Run: npx ${PACKAGE}@latest` };
|
|
63
|
+
const command = `${how.cmd} ${how.args.join(" ")}`;
|
|
64
|
+
if (check)
|
|
65
|
+
return { updated: false, line: `qc ${latest} is out (you have ${current}). Update with: qc update (runs ${command})` };
|
|
66
|
+
await runImpl(how.cmd, how.args);
|
|
67
|
+
return { updated: true, line: `qc ${current} -> ${latest}.` };
|
|
68
|
+
}
|
|
69
|
+
export {
|
|
70
|
+
update
|
|
71
|
+
};
|
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
loadSession,
|
|
6
6
|
unlockSession,
|
|
7
7
|
storeSession,
|
|
8
|
-
clearSession
|
|
9
|
-
|
|
8
|
+
clearSession,
|
|
9
|
+
notify
|
|
10
|
+
} from "./chunk-g9xn5036.js";
|
|
10
11
|
|
|
11
12
|
// src/cli/crypto.js
|
|
12
13
|
import { MlKem1024 as MlKem10242 } from "mlkem";
|
|
@@ -1166,6 +1167,7 @@ Usage:
|
|
|
1166
1167
|
qc agent join <link> join as an AI agent from an invite (makes its own keys) [--name --username]
|
|
1167
1168
|
qc login [--oob] sign in through your browser; --oob to paste a code (SSH)
|
|
1168
1169
|
qc logout forget this terminal's session and keys
|
|
1170
|
+
qc update [--check] update qc to the latest release (--check only says whether there is one)
|
|
1169
1171
|
qc whoami who this terminal is signed in as
|
|
1170
1172
|
qc profile [--emoji \uD83D\uDD2D] [--pronouns she/her] [--website URL] [--bio TEXT]
|
|
1171
1173
|
show or set your public profile (an empty value clears it);
|
|
@@ -1174,7 +1176,7 @@ Usage:
|
|
|
1174
1176
|
qc read <chat> [-n 20] print the last messages of a chat
|
|
1175
1177
|
qc send <chat> <text> send a message (text "-" reads stdin; --reply <message-id> to reply)
|
|
1176
1178
|
qc react <chat> <message-id> <emoji> [--remove] react to a message
|
|
1177
|
-
qc listen print new messages as they arrive (NDJSON with --json)
|
|
1179
|
+
qc listen print new messages as they arrive (NDJSON with --json; --notify alerts with it)
|
|
1178
1180
|
qc mcp run as an MCP server on stdio (list_chats, read_chat, send_message)
|
|
1179
1181
|
|
|
1180
1182
|
<chat> is a chat id or part of its name. Options: --json, --url <server> (or QC_URL).
|
|
@@ -1183,7 +1185,9 @@ OS keychain, or comes from a passphrase (QC_PASSPHRASE for scripts and qc mcp).
|
|
|
1183
1185
|
HD: emoji and icons are drawn as our OpenEmoji/OpenIcon images in Kitty, Ghostty, WezTerm and iTerm2.
|
|
1184
1186
|
Over SSH or in tmux (set -g allow-passthrough on) say which: QC_HD=1 (Kitty/Ghostty) or
|
|
1185
1187
|
QC_HD=wezterm (WezTerm/iTerm2). QC_HD=0 turns it off. Mosh carries only text, so behind mosh
|
|
1186
|
-
emoji stay characters: for images use ssh or WezTerm's multiplexer (wezterm connect)
|
|
1188
|
+
emoji stay characters: for images use ssh or WezTerm's multiplexer (wezterm connect).
|
|
1189
|
+
New messages raise a terminal notification (OSC 9, Kitty OSC 99) and a bell, naming the chat
|
|
1190
|
+
but never the text; in tmux set -g allow-passthrough on. QC_NOTIFY=bell rings only, QC_NOTIFY=0 is quiet.`;
|
|
1187
1191
|
function parseArgs2(argv) {
|
|
1188
1192
|
const args = { _: [], flags: {} };
|
|
1189
1193
|
for (let i = 0;i < argv.length; i++) {
|
|
@@ -1419,6 +1423,8 @@ ${info("Set: qc profile --emoji :telescope: --pronouns she/her --website https:/
|
|
|
1419
1423
|
if (seen.has(m.id) || m.id !== data.message.id)
|
|
1420
1424
|
continue;
|
|
1421
1425
|
seen.add(m.id);
|
|
1426
|
+
if (!m.mine && (flags.notify || !flags.json))
|
|
1427
|
+
notify("qrypt.chat", `${m.sender} in ${titles.get(id) ?? "a chat"}`);
|
|
1422
1428
|
if (flags.json)
|
|
1423
1429
|
out(JSON.stringify({ chat: id, title: titles.get(id), ...m }));
|
|
1424
1430
|
else
|
|
@@ -1428,6 +1434,12 @@ ${info("Set: qc profile --emoji :telescope: --pronouns she/her --website https:/
|
|
|
1428
1434
|
`) });
|
|
1429
1435
|
return;
|
|
1430
1436
|
}
|
|
1437
|
+
case "update":
|
|
1438
|
+
case "upgrade": {
|
|
1439
|
+
const { update } = await import("./chunk-1499e56f.js");
|
|
1440
|
+
const r = await update({ current: version, check: Boolean(flags.check) });
|
|
1441
|
+
return out(r.updated ? ok(r.line) : info(r.line));
|
|
1442
|
+
}
|
|
1431
1443
|
case "mcp": {
|
|
1432
1444
|
const { serveMcp } = await import("./mcp.js");
|
|
1433
1445
|
return serveMcp(await client(flags), { version });
|
|
@@ -187,4 +187,31 @@ function clearSession(env = process.env) {
|
|
|
187
187
|
rmSync(file);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
// src/cli/notify.js
|
|
191
|
+
var ESC = "\x1B";
|
|
192
|
+
var BEL = "\x07";
|
|
193
|
+
var ST = `${ESC}\\`;
|
|
194
|
+
var clean = (s) => String(s ?? "").replace(/[\x00-\x1f\x7f;]/g, " ").trim();
|
|
195
|
+
function notifySequence(title, body, env = process.env) {
|
|
196
|
+
const mode = String(env.QC_NOTIFY ?? "1").toLowerCase();
|
|
197
|
+
if (["0", "off", "false", "no"].includes(mode))
|
|
198
|
+
return "";
|
|
199
|
+
if (mode === "bell")
|
|
200
|
+
return BEL;
|
|
201
|
+
const t = clean(title);
|
|
202
|
+
const b = clean(body);
|
|
203
|
+
const kitty = env.KITTY_WINDOW_ID || env.TERM === "xterm-kitty";
|
|
204
|
+
let osc = kitty ? `${ESC}]99;i=qc:d=0:o=unfocused;${t}${ST}${ESC}]99;i=qc:d=1:p=body;${b}${ST}` : `${ESC}]9;${t}: ${b}${BEL}`;
|
|
205
|
+
if (env.TMUX)
|
|
206
|
+
osc = `${ESC}Ptmux;${osc.replaceAll(ESC, ESC + ESC)}${ST}`;
|
|
207
|
+
return osc + BEL;
|
|
208
|
+
}
|
|
209
|
+
function notify(title, body, { env = process.env, streams = [process.stdout, process.stderr] } = {}) {
|
|
210
|
+
const seq = notifySequence(title, body, env);
|
|
211
|
+
const tty = streams.find((s) => s?.isTTY);
|
|
212
|
+
if (seq && tty)
|
|
213
|
+
tty.write(seq);
|
|
214
|
+
return Boolean(seq && tty);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export { DEFAULT_URL, configDir, sessionPath, baseUrl, loadSession, unlockSession, storeSession, clearSession, notify };
|
package/dist/commands.js
CHANGED
package/dist/mcp.js
CHANGED
package/dist/tui.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
configDir
|
|
3
|
-
|
|
2
|
+
configDir,
|
|
3
|
+
notify
|
|
4
|
+
} from "./chunk-g9xn5036.js";
|
|
4
5
|
|
|
5
6
|
// src/cli/tui.js
|
|
6
7
|
import { createApp, createImageStore, drawIcon, drawRichText, editText, emojify, insertText, stringWidth, truncate, widgets, wrap } from "@profullstack/hqtui";
|
|
@@ -396,6 +397,10 @@ async function runTui(client, { initialChat } = {}) {
|
|
|
396
397
|
const id = data?.message?.conversation_id;
|
|
397
398
|
if (!id)
|
|
398
399
|
return;
|
|
400
|
+
if (data.message.sender_id !== client.me?.id) {
|
|
401
|
+
const chat = state.conversations.find((c) => c.id === id);
|
|
402
|
+
notify("qrypt.chat", `new message${chat ? ` in ${chat.title}` : ""}`);
|
|
403
|
+
}
|
|
399
404
|
if (id === state.activeId)
|
|
400
405
|
loadMessages(id);
|
|
401
406
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@profullstack/qryptchat",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
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": {
|