@profullstack/qryptchat 0.5.0 → 0.6.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.
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_URL,
|
|
2
3
|
sessionPath,
|
|
3
4
|
baseUrl,
|
|
4
5
|
loadSession,
|
|
5
6
|
unlockSession,
|
|
6
7
|
storeSession,
|
|
7
8
|
clearSession
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-menzxtw0.js";
|
|
9
10
|
|
|
10
11
|
// src/cli/crypto.js
|
|
11
12
|
import { MlKem1024 as MlKem10242 } from "mlkem";
|
|
@@ -1090,11 +1091,56 @@ async function login(options = {}) {
|
|
|
1090
1091
|
return { session, save };
|
|
1091
1092
|
}
|
|
1092
1093
|
|
|
1094
|
+
// src/cli/agent.js
|
|
1095
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
1096
|
+
function parseInvite(input) {
|
|
1097
|
+
const text = String(input ?? "").trim();
|
|
1098
|
+
let base = DEFAULT_URL;
|
|
1099
|
+
let token = text;
|
|
1100
|
+
if (/^https?:\/\//i.test(text)) {
|
|
1101
|
+
const url = new URL(text);
|
|
1102
|
+
base = url.origin;
|
|
1103
|
+
token = url.hash.replace(/^#/, "") || url.searchParams.get("token") || "";
|
|
1104
|
+
}
|
|
1105
|
+
if (!/^[A-Za-z0-9_-]{40,64}$/.test(token))
|
|
1106
|
+
throw new Error("That is not an agent invite link (https://qrypt.chat/agents/join#...).");
|
|
1107
|
+
return { base, token };
|
|
1108
|
+
}
|
|
1109
|
+
function usernameFor(name) {
|
|
1110
|
+
const stem = String(name || "agent").toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 20) || "agent";
|
|
1111
|
+
return `${stem.length >= 3 ? stem : `${stem}_agent`}_${randomBytes2(3).toString("hex")}`.slice(0, 30);
|
|
1112
|
+
}
|
|
1113
|
+
async function joinAsAgent(invite, { name, username, env = process.env, fetchImpl = fetch, vault } = {}) {
|
|
1114
|
+
const { base, token } = parseInvite(invite);
|
|
1115
|
+
const keys1024 = await ephemeralKeypair();
|
|
1116
|
+
const res = await fetchImpl(`${base}/api/agents/redeem`, {
|
|
1117
|
+
method: "POST",
|
|
1118
|
+
headers: { "Content-Type": "application/json" },
|
|
1119
|
+
body: JSON.stringify({ token, username: username || usernameFor(name), displayName: name, publicKey: keys1024.publicKey })
|
|
1120
|
+
});
|
|
1121
|
+
const body = await res.json().catch(() => ({}));
|
|
1122
|
+
if (!res.ok)
|
|
1123
|
+
throw new Error(body.error || `Could not join (HTTP ${res.status})`);
|
|
1124
|
+
const session = {
|
|
1125
|
+
base,
|
|
1126
|
+
access_token: body.access_token,
|
|
1127
|
+
refresh_token: body.refresh_token,
|
|
1128
|
+
expires_at: body.expires_at,
|
|
1129
|
+
user: body.user,
|
|
1130
|
+
keys: { keys1024 },
|
|
1131
|
+
agent: true,
|
|
1132
|
+
created_at: new Date().toISOString()
|
|
1133
|
+
};
|
|
1134
|
+
await storeSession(session, env, vault);
|
|
1135
|
+
return { session, conversationId: body.conversation_id, operator: body.operator };
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1093
1138
|
// src/cli/commands.js
|
|
1094
1139
|
var HELP2 = `qc: qrypt.chat in your terminal (end-to-end encrypted, ML-KEM-1024)
|
|
1095
1140
|
|
|
1096
1141
|
Usage:
|
|
1097
1142
|
qc open the chat client (signs you in first if needed)
|
|
1143
|
+
qc agent join <link> join as an AI agent from an invite (makes its own keys) [--name --username]
|
|
1098
1144
|
qc login [--oob] sign in through your browser; --oob to paste a code (SSH)
|
|
1099
1145
|
qc logout forget this terminal's session and keys
|
|
1100
1146
|
qc whoami who this terminal is signed in as
|
|
@@ -1113,7 +1159,8 @@ Keys and tokens are sealed (ChaCha20-Poly1305) in ${"$"}QC_HOME or ~/.config/qc;
|
|
|
1113
1159
|
OS keychain, or comes from a passphrase (QC_PASSPHRASE for scripts and qc mcp).
|
|
1114
1160
|
HD: emoji and icons are drawn as our OpenEmoji/OpenIcon images in Kitty, Ghostty, WezTerm and iTerm2.
|
|
1115
1161
|
Over SSH or in tmux (set -g allow-passthrough on) say which: QC_HD=1 (Kitty/Ghostty) or
|
|
1116
|
-
QC_HD=wezterm (WezTerm/iTerm2). QC_HD=0 turns it off
|
|
1162
|
+
QC_HD=wezterm (WezTerm/iTerm2). QC_HD=0 turns it off. Mosh carries only text, so behind mosh
|
|
1163
|
+
emoji stay characters: for images use ssh or WezTerm's multiplexer (wezterm connect).`;
|
|
1117
1164
|
function parseArgs2(argv) {
|
|
1118
1165
|
const args = { _: [], flags: {} };
|
|
1119
1166
|
for (let i = 0;i < argv.length; i++) {
|
|
@@ -1126,7 +1173,7 @@ function parseArgs2(argv) {
|
|
|
1126
1173
|
const [k, v] = a.slice(2).split("=", 2);
|
|
1127
1174
|
if (v !== undefined)
|
|
1128
1175
|
args.flags[k] = v;
|
|
1129
|
-
else if (["url", "n", "limit", "reply", "emoji", "pronouns", "website", "bio"].includes(k) && argv[i + 1] !== undefined && !argv[i + 1].startsWith("--"))
|
|
1176
|
+
else if (["url", "n", "limit", "reply", "emoji", "pronouns", "website", "bio", "name", "username"].includes(k) && argv[i + 1] !== undefined && !argv[i + 1].startsWith("--"))
|
|
1130
1177
|
args.flags[k] = argv[++i];
|
|
1131
1178
|
else
|
|
1132
1179
|
args.flags[k] = true;
|
|
@@ -1195,6 +1242,21 @@ async function main2(argv, { version = "0.0.0" } = {}) {
|
|
|
1195
1242
|
const { runTui } = await import("./tui.js");
|
|
1196
1243
|
return runTui(c, { initialChat: rest[0] });
|
|
1197
1244
|
}
|
|
1245
|
+
case "agent": {
|
|
1246
|
+
if (rest[0] !== "join" || !rest[1])
|
|
1247
|
+
throw new Error("Usage: qc agent join <invite link> [--name NAME] [--username NAME]");
|
|
1248
|
+
const { session, conversationId, operator } = await joinAsAgent(rest[1], {
|
|
1249
|
+
name: typeof flags.name === "string" ? flags.name : undefined,
|
|
1250
|
+
username: typeof flags.username === "string" ? flags.username : undefined
|
|
1251
|
+
});
|
|
1252
|
+
if (flags.json)
|
|
1253
|
+
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.`);
|
|
1256
|
+
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`);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1198
1260
|
case "login": {
|
|
1199
1261
|
const { session } = await login({ oob: !!flags.oob });
|
|
1200
1262
|
out(`Signed in as @${session.user?.username ?? "unknown"} on ${session.base}. Keys sealed (ChaCha20-Poly1305) in ${sessionPath()}.`);
|
|
@@ -187,4 +187,4 @@ function clearSession(env = process.env) {
|
|
|
187
187
|
rmSync(file);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
export { configDir, sessionPath, baseUrl, loadSession, unlockSession, storeSession, clearSession };
|
|
190
|
+
export { DEFAULT_URL, configDir, sessionPath, baseUrl, loadSession, unlockSession, storeSession, clearSession };
|
package/dist/commands.js
CHANGED
package/dist/mcp.js
CHANGED
package/dist/tui.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
configDir
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-menzxtw0.js";
|
|
4
4
|
|
|
5
5
|
// src/cli/tui.js
|
|
6
6
|
import { createApp, createImageStore, drawIcon, drawRichText, editText, emojify, insertText, stringWidth, truncate, widgets, wrap } from "@profullstack/hqtui";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@profullstack/qryptchat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@noble/ciphers": "^2.0.0",
|
|
20
20
|
"@profullstack/encrypt": "^0.1.0",
|
|
21
|
-
"@profullstack/hqtui": "^0.10.
|
|
21
|
+
"@profullstack/hqtui": "^0.10.2",
|
|
22
22
|
"mlkem": "^2.5.0"
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|