@rine-network/cli 0.9.5 → 0.10.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.
- package/dist/main.js +134 -16
- package/package.json +4 -4
package/dist/main.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { HttpClient, RineApiError, UUID_RE, agentKeysExist, cacheToken, decryptGroupMessage, decryptMessage, encryptGroupMessage, encryptMessage, fetchAgents, fetchAndIngestPendingSKDistributions, fetchOAuthToken,
|
|
3
|
+
import { HttpClient, RineApiError, UUID_RE, addMlsGroupMember, agentKeysExist, cacheToken, decryptGroupMessage, decryptMessage, encryptGroupMessage, encryptMessage, encryptMlsGroupMessage, externalJoinMlsGroup, fetchAgents, fetchAndIngestPendingSKDistributions, fetchOAuthToken, fetchRecipientKeys, formatError, fromBase64Url, generateAgentKeys, generatePqKeyPair, getAgentPublicKeys, getCredentialEntry, getOrCreateSenderKey, getOrRefreshToken, ingestSenderKeyDistribution, initMlsGroup, isBareAgentName, loadAgentKeys, loadCredentials, loadMlsState, loadTokenCache, normalizeHandle, performAgentCreation, performRegistration, processMlsWelcomes, resolveAgent, resolveApiUrl, resolveConfigDir, resolveHandleViaWebFinger, resolveToUuid, saveAgentKeys, saveCredentials, savePqEncryptionKey, saveTokenCache, syncMlsGroup, toBase64Url, validateEncryptionKey, validateSigningKey, validateSlug } from "@rine-network/core";
|
|
4
4
|
import readline from "node:readline";
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
@@ -686,6 +686,31 @@ function registerDiscover(program) {
|
|
|
686
686
|
}
|
|
687
687
|
//#endregion
|
|
688
688
|
//#region src/commands/group.ts
|
|
689
|
+
async function installWelcomes(client, configDir, agentId, json) {
|
|
690
|
+
try {
|
|
691
|
+
await processMlsWelcomes(configDir, agentId, client);
|
|
692
|
+
} catch (err) {
|
|
693
|
+
if (!json) printText(`Warning: MLS welcome install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async function selfJoinViaExternalCommit(client, configDir, groupId, agentId, json) {
|
|
697
|
+
try {
|
|
698
|
+
if (!(await client.get(`/groups/${groupId}`)).mls_group_id) return;
|
|
699
|
+
if (loadMlsState(configDir, agentId, groupId)) return;
|
|
700
|
+
await externalJoinMlsGroup(configDir, agentId, groupId, client, { "X-Rine-Agent": agentId });
|
|
701
|
+
} catch (err) {
|
|
702
|
+
if (!json) printText(`Warning: MLS self-join failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
async function addMemberToMls(client, configDir, apiUrl, groupId, memberAgentId, as, json) {
|
|
706
|
+
try {
|
|
707
|
+
if (!(await client.get(`/groups/${groupId}`)).mls_group_id) return;
|
|
708
|
+
const adminId = await resolveAgent(apiUrl, await fetchAgents(client), void 0, as);
|
|
709
|
+
await addMlsGroupMember(configDir, adminId, groupId, memberAgentId, client, { "X-Rine-Agent": adminId });
|
|
710
|
+
} catch (err) {
|
|
711
|
+
if (!json) printText(`Warning: MLS add-member failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
689
714
|
function groupRow(g) {
|
|
690
715
|
return {
|
|
691
716
|
ID: g.id,
|
|
@@ -731,7 +756,7 @@ function requestRow(r) {
|
|
|
731
756
|
}
|
|
732
757
|
function registerGroup(program) {
|
|
733
758
|
const group = program.command("group").description("Group management");
|
|
734
|
-
group.command("create").description("Create a new group").requiredOption("--name <name>", "Group name (DNS-safe slug)").option("--enrollment <policy>", "Enrollment policy (open|closed|majority|unanimity)").option("--visibility <vis>", "Visibility (public|private)").option("--isolated", "Isolate group communication").option("--vote-duration <hours>", "Vote duration in hours (1-72)").action(withClient(program, async ({ client, gOpts }, opts) => {
|
|
759
|
+
group.command("create").description("Create a new group").requiredOption("--name <name>", "Group name (DNS-safe slug)").option("--enrollment <policy>", "Enrollment policy (open|closed|majority|unanimity)").option("--visibility <vis>", "Visibility (public|private)").option("--isolated", "Isolate group communication").option("--vote-duration <hours>", "Vote duration in hours (1-72)").action(withClient(program, async ({ client, gOpts, configDir, apiUrl }, opts) => {
|
|
735
760
|
const body = { name: opts.name };
|
|
736
761
|
if (opts.enrollment) body.enrollment_policy = opts.enrollment;
|
|
737
762
|
if (opts.visibility) body.visibility = opts.visibility;
|
|
@@ -742,6 +767,12 @@ function registerGroup(program) {
|
|
|
742
767
|
if (opts.isolated) body.isolated = true;
|
|
743
768
|
if (opts.voteDuration) body.vote_duration_hours = Number(opts.voteDuration);
|
|
744
769
|
const data = await client.post("/groups", body);
|
|
770
|
+
try {
|
|
771
|
+
const actingAgentId = await resolveAgent(apiUrl, await fetchAgents(client), void 0, gOpts.as);
|
|
772
|
+
await initMlsGroup(configDir, actingAgentId, data.id, client, { "X-Rine-Agent": actingAgentId });
|
|
773
|
+
} catch (err) {
|
|
774
|
+
if (!gOpts.json) printText(`Warning: MLS init failed (group still created): ${err instanceof Error ? err.message : String(err)}`);
|
|
775
|
+
}
|
|
745
776
|
if (gOpts.json) printJson(data);
|
|
746
777
|
else {
|
|
747
778
|
printTable([groupDetailRow(data)]);
|
|
@@ -788,10 +819,14 @@ function registerGroup(program) {
|
|
|
788
819
|
if (gOpts.json) printJson(data);
|
|
789
820
|
else printTable(data.items.map(memberRow));
|
|
790
821
|
}));
|
|
791
|
-
group.command("join").description("Join a group").argument("<group-id>", "Group ID").option("--message <msg>", "Join request message").action(withClient(program, async ({ client, gOpts }, groupId, opts) => {
|
|
822
|
+
group.command("join").description("Join a group").argument("<group-id>", "Group ID").option("--message <msg>", "Join request message").action(withClient(program, async ({ client, gOpts, configDir }, groupId, opts) => {
|
|
792
823
|
const body = {};
|
|
793
824
|
if (opts.message) body.message = opts.message;
|
|
794
825
|
const data = await client.post(`/groups/${groupId}/join`, body);
|
|
826
|
+
if ("role" in data) {
|
|
827
|
+
await installWelcomes(client, configDir, data.agent_id, gOpts.json);
|
|
828
|
+
await selfJoinViaExternalCommit(client, configDir, groupId, data.agent_id, gOpts.json);
|
|
829
|
+
}
|
|
795
830
|
if (gOpts.json) printJson(data);
|
|
796
831
|
else if ("role" in data) {
|
|
797
832
|
printTable([memberRow(data)]);
|
|
@@ -811,10 +846,12 @@ function registerGroup(program) {
|
|
|
811
846
|
await client.delete(`/groups/${groupId}/members/${resolved}`);
|
|
812
847
|
printMutationOk("Member removed", gOpts.json);
|
|
813
848
|
}));
|
|
814
|
-
group.command("invite").description("Invite an agent to the group").argument("<group-id>", "Group ID").requiredOption("--agent <id>", "Agent ID to invite").option("--message <msg>", "Invitation message").action(withClient(program, async ({ client, gOpts, apiUrl }, groupId, opts) => {
|
|
815
|
-
const
|
|
849
|
+
group.command("invite").description("Invite an agent to the group").argument("<group-id>", "Group ID").requiredOption("--agent <id>", "Agent ID to invite").option("--message <msg>", "Invitation message").action(withClient(program, async ({ client, gOpts, configDir, apiUrl }, groupId, opts) => {
|
|
850
|
+
const agentId = await resolveToUuid(apiUrl, opts.agent);
|
|
851
|
+
const body = { agent_id: agentId };
|
|
816
852
|
if (opts.message) body.message = opts.message;
|
|
817
853
|
const data = await client.post(`/groups/${groupId}/invite`, body);
|
|
854
|
+
await addMemberToMls(client, configDir, apiUrl, groupId, agentId, gOpts.as, gOpts.json);
|
|
818
855
|
if (gOpts.json) printJson(data);
|
|
819
856
|
else {
|
|
820
857
|
printText("Invitation sent");
|
|
@@ -826,13 +863,16 @@ function registerGroup(program) {
|
|
|
826
863
|
if (gOpts.json) printJson(data);
|
|
827
864
|
else printTable(data.items.map(requestRow));
|
|
828
865
|
}));
|
|
829
|
-
group.command("vote").description("Vote on a join request").argument("<group-id>", "Group ID").argument("<request-id>", "Join request ID").requiredOption("--vote <vote>", "Vote: approve or deny").action(withClient(program, async ({ client, gOpts }, groupId, requestId, opts) => {
|
|
866
|
+
group.command("vote").description("Vote on a join request").argument("<group-id>", "Group ID").argument("<request-id>", "Join request ID").requiredOption("--vote <vote>", "Vote: approve or deny").action(withClient(program, async ({ client, gOpts, configDir, apiUrl }, groupId, requestId, opts) => {
|
|
830
867
|
if (opts.vote !== "approve" && opts.vote !== "deny") {
|
|
831
868
|
printError("--vote must be 'approve' or 'deny'");
|
|
832
869
|
process.exitCode = 2;
|
|
833
870
|
return;
|
|
834
871
|
}
|
|
872
|
+
let applicantId;
|
|
873
|
+
if (opts.vote === "approve") applicantId = (await client.get(`/groups/${groupId}/requests`)).items.find((r) => r.id === requestId)?.agent_id;
|
|
835
874
|
const data = await client.post(`/groups/${groupId}/requests/${requestId}/vote`, { vote: opts.vote });
|
|
875
|
+
if (data.status === "approved" && applicantId) await addMemberToMls(client, configDir, apiUrl, groupId, applicantId, gOpts.as, gOpts.json);
|
|
836
876
|
if (gOpts.json) printJson(data);
|
|
837
877
|
else printText(`Vote recorded. Request status: ${data.status}`);
|
|
838
878
|
}));
|
|
@@ -845,7 +885,11 @@ function getKeysDir(configDir, agentId) {
|
|
|
845
885
|
function backupKeys(configDir, agentId) {
|
|
846
886
|
const dir = getKeysDir(configDir, agentId);
|
|
847
887
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
848
|
-
for (const name of [
|
|
888
|
+
for (const name of [
|
|
889
|
+
"signing.key",
|
|
890
|
+
"encryption.key",
|
|
891
|
+
"pq_encryption.key"
|
|
892
|
+
]) {
|
|
849
893
|
const src = join(dir, name);
|
|
850
894
|
if (!fs.existsSync(src)) continue;
|
|
851
895
|
for (const f of fs.readdirSync(dir)) if (f.startsWith(`${name}.bak.`)) fs.unlinkSync(join(dir, f));
|
|
@@ -855,6 +899,7 @@ function backupKeys(configDir, agentId) {
|
|
|
855
899
|
async function verifyServerKeys(client, agentId, expected) {
|
|
856
900
|
const server = await client.get(`/agents/${agentId}/keys`);
|
|
857
901
|
if (server.signing_public_key.x !== expected.signing_public_key.x || server.encryption_public_key.x !== expected.encryption_public_key.x) throw new Error("Server key verification failed: uploaded keys do not match. Old keys preserved locally.");
|
|
902
|
+
if (expected.pq_encryption_public_key && server.pq_encryption_public_key?.x !== expected.pq_encryption_public_key.x) throw new Error("Server PQ key verification failed: uploaded PQ key does not match. Old keys preserved locally.");
|
|
858
903
|
}
|
|
859
904
|
function registerKeys(program) {
|
|
860
905
|
const keys = program.command("keys").description("E2EE key management");
|
|
@@ -873,13 +918,16 @@ function registerKeys(program) {
|
|
|
873
918
|
}
|
|
874
919
|
let sigFingerprint = "-";
|
|
875
920
|
let encFingerprint = "-";
|
|
921
|
+
let pqFingerprint = "-";
|
|
876
922
|
if (hasLocal) {
|
|
877
923
|
const agentKeys = loadAgentKeys(configDir, agentId);
|
|
878
924
|
sigFingerprint = toBase64Url(agentKeys.signing.publicKey).slice(0, 8);
|
|
879
925
|
encFingerprint = toBase64Url(agentKeys.encryption.publicKey).slice(0, 8);
|
|
926
|
+
if (agentKeys.pqEncryption) pqFingerprint = toBase64Url(agentKeys.pqEncryption.publicKey).slice(0, 8);
|
|
880
927
|
} else if (serverKeys) {
|
|
881
928
|
sigFingerprint = serverKeys.signing_public_key.x.slice(0, 8);
|
|
882
929
|
encFingerprint = serverKeys.encryption_public_key.x.slice(0, 8);
|
|
930
|
+
if (serverKeys.pq_encryption_public_key) pqFingerprint = serverKeys.pq_encryption_public_key.x.slice(0, 8);
|
|
883
931
|
}
|
|
884
932
|
const keysLabel = hasLocal && hasServer ? "local + server" : hasLocal ? "local only" : hasServer ? "server only" : "none";
|
|
885
933
|
if (gOpts.json) printJson({
|
|
@@ -888,7 +936,8 @@ function registerKeys(program) {
|
|
|
888
936
|
local_keys: hasLocal,
|
|
889
937
|
server_keys: hasServer,
|
|
890
938
|
signing_fingerprint: sigFingerprint !== "-" ? sigFingerprint : null,
|
|
891
|
-
encryption_fingerprint: encFingerprint !== "-" ? encFingerprint : null
|
|
939
|
+
encryption_fingerprint: encFingerprint !== "-" ? encFingerprint : null,
|
|
940
|
+
pq_encryption_fingerprint: pqFingerprint !== "-" ? pqFingerprint : null
|
|
892
941
|
});
|
|
893
942
|
else printTable([
|
|
894
943
|
{
|
|
@@ -918,6 +967,10 @@ function registerKeys(program) {
|
|
|
918
967
|
{
|
|
919
968
|
Field: "Encryption Fingerprint",
|
|
920
969
|
Value: encFingerprint
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
Field: "PQ Encryption Fingerprint",
|
|
973
|
+
Value: pqFingerprint
|
|
921
974
|
}
|
|
922
975
|
]);
|
|
923
976
|
}));
|
|
@@ -945,6 +998,30 @@ function registerKeys(program) {
|
|
|
945
998
|
saveAgentKeys(configDir, agentId, agentKeys);
|
|
946
999
|
printMutationOk(`E2EE keys rotated for agent ${agentId}`, gOpts.json);
|
|
947
1000
|
}));
|
|
1001
|
+
keys.command("add-pq").description("Add a post-quantum encryption key without rotating other keys").option("--agent <id>", "Agent ID").action(withClient(program, async ({ client, gOpts, configDir, apiUrl }, opts) => {
|
|
1002
|
+
const agentId = await resolveAgent(apiUrl, await fetchAgents(client), opts.agent, gOpts.as);
|
|
1003
|
+
if (!agentKeysExist(configDir, agentId)) {
|
|
1004
|
+
printError(`No keys found for agent ${agentId}. Use 'rine keys generate' first.`);
|
|
1005
|
+
process.exitCode = 1;
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
const existingKeys = loadAgentKeys(configDir, agentId);
|
|
1009
|
+
if (existingKeys.pqEncryption) {
|
|
1010
|
+
printError(`Agent ${agentId} already has a post-quantum key. Use 'rine keys rotate' to replace all keys.`);
|
|
1011
|
+
process.exitCode = 1;
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
backupKeys(configDir, agentId);
|
|
1015
|
+
const pqKeyPair = generatePqKeyPair();
|
|
1016
|
+
const pubKeys = getAgentPublicKeys(configDir, agentId, {
|
|
1017
|
+
...existingKeys,
|
|
1018
|
+
pqEncryption: pqKeyPair
|
|
1019
|
+
});
|
|
1020
|
+
await client.post(`/agents/${agentId}/keys`, pubKeys);
|
|
1021
|
+
await verifyServerKeys(client, agentId, pubKeys);
|
|
1022
|
+
savePqEncryptionKey(configDir, agentId, pqKeyPair);
|
|
1023
|
+
printMutationOk(`Post-quantum encryption key added for agent ${agentId}`, gOpts.json);
|
|
1024
|
+
}));
|
|
948
1025
|
keys.command("export").description("Export private keys to a file").option("--agent <id>", "Agent ID").requiredOption("--output <file>", "Output file path").action(withClient(program, async ({ client, gOpts, configDir, apiUrl }, opts) => {
|
|
949
1026
|
const agentId = await resolveAgent(apiUrl, await fetchAgents(client), opts.agent, gOpts.as);
|
|
950
1027
|
if (!agentKeysExist(configDir, agentId)) {
|
|
@@ -958,6 +1035,7 @@ function registerKeys(program) {
|
|
|
958
1035
|
signing_private_key: toBase64Url(agentKeys.signing.privateKey),
|
|
959
1036
|
encryption_private_key: toBase64Url(agentKeys.encryption.privateKey)
|
|
960
1037
|
};
|
|
1038
|
+
if (agentKeys.pqEncryption) exported.pq_encryption_private_key = toBase64Url(agentKeys.pqEncryption.privateKey);
|
|
961
1039
|
fs.writeFileSync(opts.output, JSON.stringify(exported, null, 2));
|
|
962
1040
|
fs.chmodSync(opts.output, 384);
|
|
963
1041
|
if (gOpts.json) printJson({
|
|
@@ -988,6 +1066,14 @@ function registerKeys(program) {
|
|
|
988
1066
|
process.exitCode = 2;
|
|
989
1067
|
return;
|
|
990
1068
|
}
|
|
1069
|
+
if (data.pq_encryption_private_key) {
|
|
1070
|
+
const pqBytes = fromBase64Url(data.pq_encryption_private_key);
|
|
1071
|
+
if (pqBytes.length !== 2400) {
|
|
1072
|
+
printError(`Invalid PQ encryption key: expected 2400 bytes, got ${pqBytes.length}`);
|
|
1073
|
+
process.exitCode = 2;
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
991
1077
|
const dir = join(resolveConfigDir(), "keys", data.agent_id);
|
|
992
1078
|
fs.mkdirSync(dir, {
|
|
993
1079
|
recursive: true,
|
|
@@ -999,6 +1085,11 @@ function registerKeys(program) {
|
|
|
999
1085
|
const encPath = join(dir, "encryption.key");
|
|
1000
1086
|
fs.writeFileSync(encPath, data.encryption_private_key, "utf-8");
|
|
1001
1087
|
fs.chmodSync(encPath, 384);
|
|
1088
|
+
if (data.pq_encryption_private_key) {
|
|
1089
|
+
const pqPath = join(dir, "pq_encryption.key");
|
|
1090
|
+
fs.writeFileSync(pqPath, data.pq_encryption_private_key, "utf-8");
|
|
1091
|
+
fs.chmodSync(pqPath, 384);
|
|
1092
|
+
}
|
|
1002
1093
|
printMutationOk(`Keys imported for agent ${data.agent_id}`, gOpts.json);
|
|
1003
1094
|
}));
|
|
1004
1095
|
}
|
|
@@ -1050,6 +1141,26 @@ function resolvePayload(payload, payloadFile) {
|
|
|
1050
1141
|
return { error: "Payload must be valid JSON" };
|
|
1051
1142
|
}
|
|
1052
1143
|
}
|
|
1144
|
+
async function resolveGroup(client, handle) {
|
|
1145
|
+
const group = (await client.get("/groups", { handle })).items?.[0];
|
|
1146
|
+
if (!group) throw new Error(`Group not found: ${handle}`);
|
|
1147
|
+
return group;
|
|
1148
|
+
}
|
|
1149
|
+
async function encryptMlsWithRecovery(client, configDir, senderAgentId, groupId, payload) {
|
|
1150
|
+
const hdrs = { "X-Rine-Agent": senderAgentId };
|
|
1151
|
+
await processMlsWelcomes(configDir, senderAgentId, client).catch(() => {});
|
|
1152
|
+
await syncMlsGroup(configDir, senderAgentId, groupId, client, hdrs).catch(() => {});
|
|
1153
|
+
try {
|
|
1154
|
+
return await encryptMlsGroupMessage(configDir, senderAgentId, groupId, payload);
|
|
1155
|
+
} catch (err) {
|
|
1156
|
+
if (err instanceof Error && err.message.startsWith("No MLS state for group")) {
|
|
1157
|
+
await processMlsWelcomes(configDir, senderAgentId, client);
|
|
1158
|
+
await syncMlsGroup(configDir, senderAgentId, groupId, client, hdrs);
|
|
1159
|
+
return await encryptMlsGroupMessage(configDir, senderAgentId, groupId, payload);
|
|
1160
|
+
}
|
|
1161
|
+
throw err;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1053
1164
|
function addMessageCommands(parent, program) {
|
|
1054
1165
|
parent.command("send").description("Send an encrypted message").requiredOption("--to <address>", "Recipient: agent handle (agent@org), agent ID, or group handle (#group@org)").option("--type <type>", "Message type (default: rine.v1.dm)").option("--payload <json>", "Message payload as JSON string").option("--payload-file <path>", "Read payload JSON from file (use - for stdin)").option("--from <address>", "Sender: agent handle (agent@org) or agent ID").option("--idempotency-key <key>", "Idempotency key header").action(withClient(program, async ({ client, gOpts, configDir, apiUrl }, opts) => {
|
|
1055
1166
|
const result = resolvePayload(opts.payload, opts.payloadFile);
|
|
@@ -1079,12 +1190,18 @@ function addMessageCommands(parent, program) {
|
|
|
1079
1190
|
else body.to_agent_id = to;
|
|
1080
1191
|
if (from?.includes("@")) body.from_handle = from;
|
|
1081
1192
|
if (to.startsWith("#") && senderAgentId) {
|
|
1082
|
-
const
|
|
1083
|
-
|
|
1084
|
-
|
|
1193
|
+
const group = await resolveGroup(client, to);
|
|
1194
|
+
if (group.mls_group_id) {
|
|
1195
|
+
const encResult = await encryptMlsWithRecovery(client, configDir, senderAgentId, group.id, parsedPayload);
|
|
1196
|
+
Object.assign(body, encResult);
|
|
1197
|
+
} else {
|
|
1198
|
+
const { state, groupId } = await getOrCreateSenderKey(client, configDir, senderAgentId, to, Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0);
|
|
1199
|
+
const { result: encResult } = await encryptGroupMessage(configDir, senderAgentId, groupId, state, parsedPayload);
|
|
1200
|
+
Object.assign(body, encResult);
|
|
1201
|
+
}
|
|
1085
1202
|
} else if (senderAgentId && recipientAgentId) {
|
|
1086
|
-
const
|
|
1087
|
-
const encrypted = await encryptMessage(configDir, senderAgentId,
|
|
1203
|
+
const recipientKeys = await fetchRecipientKeys(client, recipientAgentId);
|
|
1204
|
+
const encrypted = await encryptMessage(configDir, senderAgentId, recipientKeys.encryption, parsedPayload, recipientKeys.pqEncryption);
|
|
1088
1205
|
Object.assign(body, encrypted);
|
|
1089
1206
|
} else throw new Error("Cannot encrypt: unable to resolve sender or recipient. Use UUIDs or ensure WebFinger is available.");
|
|
1090
1207
|
const data = await client.post("/messages", body, Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0);
|
|
@@ -1094,9 +1211,9 @@ function addMessageCommands(parent, program) {
|
|
|
1094
1211
|
parent.command("read").description("Read and decrypt a message by ID").argument("<message-id>", "Message ID").option("--agent <id>", "Agent ID (for decryption key)").action(withClient(program, async ({ client, gOpts, configDir, apiUrl }, messageId, opts) => {
|
|
1095
1212
|
const data = await client.get(`/messages/${messageId}`);
|
|
1096
1213
|
const agentId = await resolveAgent(apiUrl, await fetchAgents(client), opts.agent, gOpts.as);
|
|
1097
|
-
if ((data.encryption_version === "hpke-v1" || data.encryption_version === "sender-key-v1") && agentKeysExist(configDir, agentId)) {
|
|
1214
|
+
if ((data.encryption_version === "hpke-v1" || data.encryption_version === "hpke-hybrid-v1" || data.encryption_version === "sender-key-v1" || data.encryption_version === "mls-v1") && agentKeysExist(configDir, agentId)) {
|
|
1098
1215
|
let decResult;
|
|
1099
|
-
if (data.encryption_version === "sender-key-v1" && data.group_id) try {
|
|
1216
|
+
if ((data.encryption_version === "sender-key-v1" || data.encryption_version === "mls-v1") && data.group_id) try {
|
|
1100
1217
|
decResult = await decryptGroupMessage(configDir, agentId, data.group_id, data.encrypted_payload, client);
|
|
1101
1218
|
} catch (err) {
|
|
1102
1219
|
const match = err instanceof Error && err.message.match(/^unknown sender key id: (.+)$/);
|
|
@@ -1190,7 +1307,8 @@ function addMessageCommands(parent, program) {
|
|
|
1190
1307
|
try {
|
|
1191
1308
|
const body = { type: resolvedType };
|
|
1192
1309
|
if (!recipientAgentId || !agentKeysExist(configDir, senderAgentId)) throw new Error("Cannot reply: encryption keys unavailable. Run 'rine keys generate' first.");
|
|
1193
|
-
const
|
|
1310
|
+
const recipientKeys = await fetchRecipientKeys(client, recipientAgentId);
|
|
1311
|
+
const encrypted = await encryptMessage(configDir, senderAgentId, recipientKeys.encryption, parsedPayload, recipientKeys.pqEncryption);
|
|
1194
1312
|
Object.assign(body, encrypted);
|
|
1195
1313
|
const data = await client.post(`/messages/${messageId}/reply`, body);
|
|
1196
1314
|
if (gOpts.json) printJson(data);
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rine-network/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "CLI client for rine.network \u2014 EU-first messaging infrastructure for AI agents",
|
|
5
5
|
"author": "mmmbs <mmmbs@proton.me>",
|
|
6
6
|
"license": "EUPL-1.2",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
|
-
"node": ">=
|
|
9
|
+
"node": ">=20"
|
|
10
10
|
},
|
|
11
11
|
"bin": {
|
|
12
12
|
"rine": "bin/rine.js"
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"dev": "tsx src/main.ts"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@rine-network/core": "^0.
|
|
31
|
+
"@rine-network/core": "^0.5.0",
|
|
32
32
|
"commander": "^12.0.0",
|
|
33
33
|
"eventsource-client": "^1.1.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@biomejs/biome": "^1.9.0",
|
|
37
|
-
"@types/node": "^
|
|
37
|
+
"@types/node": "^20.0.0",
|
|
38
38
|
"tsdown": "^0.12.0",
|
|
39
39
|
"tsx": "^4.19.0",
|
|
40
40
|
"typescript": "^5.8.0",
|