@rine-network/cli 0.3.0 → 0.4.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.
Files changed (2) hide show
  1. package/dist/main.js +82 -32
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -1700,6 +1700,31 @@ async function getOrCreateSenderKey(client, senderAgentId, groupHandle, extraHea
1700
1700
  groupId
1701
1701
  };
1702
1702
  }
1703
+ /**
1704
+ * Fetch pending sender key distributions from the inbox and ingest them.
1705
+ * Used by `read` and `stream` to auto-recover when a sender key is missing.
1706
+ *
1707
+ * Optionally short-circuits once `targetSenderKeyId` is ingested.
1708
+ * Returns the number of newly ingested keys.
1709
+ */
1710
+ async function fetchAndIngestPendingSKDistributions(client, agentId, targetSenderKeyId) {
1711
+ const inbox = await client.get(`/agents/${agentId}/messages`, {
1712
+ type: "rine.v1.sender_key_distribution",
1713
+ limit: 100
1714
+ });
1715
+ let ingested = 0;
1716
+ for (const msg of inbox.items) try {
1717
+ const full = await client.get(`/messages/${msg.id}`);
1718
+ const result = await decryptMessage(agentId, full.encrypted_payload, client);
1719
+ if (ingestSenderKeyDistribution(agentId, full.type, result)) {
1720
+ ingested++;
1721
+ if (targetSenderKeyId) {
1722
+ if (JSON.parse(result.plaintext).sender_key_id === targetSenderKeyId) break;
1723
+ }
1724
+ }
1725
+ } catch {}
1726
+ return ingested;
1727
+ }
1703
1728
  //#endregion
1704
1729
  //#region src/commands/messages.ts
1705
1730
  function msgFrom(m) {
@@ -1775,32 +1800,48 @@ function addMessageCommands(parent, program) {
1775
1800
  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 }, messageId, opts) => {
1776
1801
  const data = await client.get(`/messages/${messageId}`);
1777
1802
  const agentId = await resolveAgent(client, opts.agent, gOpts.as);
1778
- if ((data.encryption_version === "hpke-v1" || data.encryption_version === "sender-key-v1") && agentKeysExist(agentId)) try {
1803
+ if ((data.encryption_version === "hpke-v1" || data.encryption_version === "sender-key-v1") && agentKeysExist(agentId)) {
1779
1804
  let result;
1780
- if (data.encryption_version === "sender-key-v1" && data.group_id) result = await decryptGroupMessage(agentId, data.group_id, data.encrypted_payload, client);
1781
- else result = await decryptMessage(agentId, data.encrypted_payload, client);
1782
- if (ingestSenderKeyDistribution(agentId, data.type, result)) printText("Sender key ingested");
1783
- if (gOpts.json) printJson({
1784
- ...data,
1785
- decrypted_payload: JSON.parse(result.plaintext),
1786
- signature_verified: result.verified,
1787
- verification_status: result.verificationStatus,
1788
- sender_kid: result.senderKid
1789
- });
1790
- else printTable([{
1791
- ID: data.id,
1792
- "Conversation ID": data.conversation_id,
1793
- From: msgFrom(data),
1794
- To: msgTo(data),
1795
- Group: msgGroup(data),
1796
- Type: data.type,
1797
- Verified: result.verificationStatus === "verified" ? "✓" : result.verificationStatus === "invalid" ? "✗ INVALID" : "?",
1798
- Payload: result.plaintext.slice(0, 200),
1799
- Created: data.created_at
1800
- }]);
1801
- return;
1802
- } catch (err) {
1803
- if (!gOpts.json) printError(`Decryption failed: ${err instanceof Error ? err.message : String(err)}`);
1805
+ if (data.encryption_version === "sender-key-v1" && data.group_id) try {
1806
+ result = await decryptGroupMessage(agentId, data.group_id, data.encrypted_payload, client);
1807
+ } catch (err) {
1808
+ const match = err instanceof Error && err.message.match(/^unknown sender key id: (.+)$/);
1809
+ if (match) {
1810
+ if (await fetchAndIngestPendingSKDistributions(client, agentId, match[1]) > 0) try {
1811
+ result = await decryptGroupMessage(agentId, data.group_id, data.encrypted_payload, client);
1812
+ } catch (retryErr) {
1813
+ if (!gOpts.json) printError(`Decryption failed after auto-ingest: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`);
1814
+ }
1815
+ else if (!gOpts.json) printError(`Decryption failed: ${err.message}`);
1816
+ } else if (!gOpts.json) printError(`Decryption failed: ${err instanceof Error ? err.message : String(err)}`);
1817
+ }
1818
+ else try {
1819
+ result = await decryptMessage(agentId, data.encrypted_payload, client);
1820
+ } catch (err) {
1821
+ if (!gOpts.json) printError(`Decryption failed: ${err instanceof Error ? err.message : String(err)}`);
1822
+ }
1823
+ if (result) {
1824
+ if (ingestSenderKeyDistribution(agentId, data.type, result)) printText("Sender key ingested");
1825
+ if (gOpts.json) printJson({
1826
+ ...data,
1827
+ decrypted_payload: JSON.parse(result.plaintext),
1828
+ signature_verified: result.verified,
1829
+ verification_status: result.verificationStatus,
1830
+ sender_kid: result.senderKid
1831
+ });
1832
+ else printTable([{
1833
+ ID: data.id,
1834
+ "Conversation ID": data.conversation_id,
1835
+ From: msgFrom(data),
1836
+ To: msgTo(data),
1837
+ Group: msgGroup(data),
1838
+ Type: data.type,
1839
+ Verified: result.verificationStatus === "verified" ? "✓" : result.verificationStatus === "invalid" ? "✗ INVALID" : "?",
1840
+ Payload: result.plaintext.slice(0, 200),
1841
+ Created: data.created_at
1842
+ }]);
1843
+ return;
1844
+ }
1804
1845
  }
1805
1846
  if (gOpts.json) printJson(data);
1806
1847
  else printTable([{
@@ -2004,16 +2045,25 @@ async function formatMessageLine(dataStr, agentId, client) {
2004
2045
  const msgType = data.type ?? "?";
2005
2046
  const target = data.group_handle ? `${data.group_handle} (${msgType})` : msgType;
2006
2047
  let preview = "[encrypted]";
2007
- if (data.encrypted_payload && agentKeysExist(agentId)) try {
2048
+ if (data.encrypted_payload && agentKeysExist(agentId)) {
2008
2049
  let result;
2009
- if (data.encryption_version === "sender-key-v1" && data.group_id) result = await decryptGroupMessage(agentId, data.group_id, data.encrypted_payload, client);
2010
- else if (data.encryption_version === "hpke-v1") {
2050
+ if (data.encryption_version === "sender-key-v1" && data.group_id) try {
2051
+ result = await decryptGroupMessage(agentId, data.group_id, data.encrypted_payload, client);
2052
+ } catch (err) {
2053
+ const match = err instanceof Error && err.message.match(/^unknown sender key id: (.+)$/);
2054
+ if (match) {
2055
+ if (await fetchAndIngestPendingSKDistributions(client, agentId, match[1]) > 0) try {
2056
+ result = await decryptGroupMessage(agentId, data.group_id, data.encrypted_payload, client);
2057
+ } catch {}
2058
+ }
2059
+ }
2060
+ else if (data.encryption_version === "hpke-v1") try {
2011
2061
  result = await decryptMessage(agentId, data.encrypted_payload, client);
2062
+ } catch {}
2063
+ if (result) {
2012
2064
  ingestSenderKeyDistribution(agentId, data.type ?? "", result);
2013
- } else return `[${ts}] ${sender} \u2192 ${target}: [encrypted]`;
2014
- preview = `[${result.verified ? "" : "?"}] ${result.plaintext.slice(0, 60)}`;
2015
- } catch {
2016
- preview = "[decrypt failed]";
2065
+ preview = `[${result.verified ? "✓" : "?"}] ${result.plaintext.slice(0, 60)}`;
2066
+ } else if (data.encryption_version === "hpke-v1" || data.encryption_version === "sender-key-v1") preview = "[decrypt failed]";
2017
2067
  }
2018
2068
  return `[${ts}] ${sender} \u2192 ${target}: ${preview}`;
2019
2069
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rine-network/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "CLI client for rine.network — EU-first messaging infrastructure for AI agents",
5
5
  "author": "mmmbs <mmmbs@proton.me>",
6
6
  "license": "EUPL-1.2",