@rine-network/cli 0.2.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 +146 -94
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -280,6 +280,59 @@ function printError(msg) {
280
280
  console.error(msg);
281
281
  }
282
282
  //#endregion
283
+ //#region src/resolve-handle.ts
284
+ const UUID_ALIAS_RE = /\/agents\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
285
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
286
+ /**
287
+ * Resolves a handle (e.g. "agent@org.rine.network") to a UUID
288
+ * via WebFinger (RFC 7033).
289
+ *
290
+ * Returns the UUID string on success, or the original input unchanged
291
+ * on failure (caller validates UUID format and reports the error).
292
+ */
293
+ async function resolveHandleViaWebFinger(handle) {
294
+ try {
295
+ const params = new URLSearchParams({ resource: `acct:${handle}` });
296
+ const data = await HttpClient.publicGet("/.well-known/webfinger", params);
297
+ for (const alias of data.aliases ?? []) {
298
+ const match = UUID_ALIAS_RE.exec(alias);
299
+ if (match?.[1]) return match[1];
300
+ }
301
+ } catch {}
302
+ return handle;
303
+ }
304
+ //#endregion
305
+ //#region src/resolve-agent.ts
306
+ let cached;
307
+ /**
308
+ * Resolve a value that may be a UUID or a handle (containing @) to a UUID.
309
+ * Handles are resolved via WebFinger. Required because local key storage
310
+ * is indexed by UUID.
311
+ */
312
+ async function resolveToUuid(value) {
313
+ if (!value.includes("@")) return value;
314
+ const resolved = await resolveHandleViaWebFinger(value);
315
+ if (!UUID_RE.test(resolved)) throw new Error(`Cannot resolve handle "${value}" to agent ID. Ensure WebFinger is available or use a UUID.`);
316
+ return resolved;
317
+ }
318
+ /**
319
+ * Resolve which agent ID to use.
320
+ * Priority: explicit --agent flag > --as global flag > auto-resolve (single-agent shortcut).
321
+ * Accepts both UUIDs and handles (e.g. "bot@org.rine.network").
322
+ */
323
+ async function resolveAgent(client, explicit, asFlag) {
324
+ if (explicit) return resolveToUuid(explicit);
325
+ if (asFlag) return resolveToUuid(asFlag);
326
+ if (cached === void 0) cached = (await client.get("/agents")).items;
327
+ if (cached.length === 1) {
328
+ const agent = cached[0];
329
+ if (agent) return agent.id;
330
+ }
331
+ if (cached.length === 0) throw new Error("No active agents. Create one with 'rine agent create --name <name>'");
332
+ const lines = cached.map((a) => ` ${a.id} ${a.handle}`).join("\n");
333
+ throw new Error(`Multiple agents found. Specify with --agent <uuid>:\n${lines}`);
334
+ }
335
+ //#endregion
283
336
  //#region src/action.ts
284
337
  /**
285
338
  * Wraps a command action with automatic client creation and error handling.
@@ -291,7 +344,7 @@ function withClient(program, fn) {
291
344
  try {
292
345
  const gOpts = program.opts();
293
346
  const defaultHeaders = {};
294
- if (gOpts.as) defaultHeaders["X-Rine-Agent"] = gOpts.as;
347
+ if (gOpts.as) defaultHeaders["X-Rine-Agent"] = await resolveToUuid(gOpts.as);
295
348
  const { client, profileName } = await createClient(gOpts.profile, defaultHeaders);
296
349
  await fn({
297
350
  client,
@@ -394,10 +447,10 @@ function registerAgentProfile(program) {
394
447
  const result = await updateCard(client, agentId, (card) => {
395
448
  const skill = {
396
449
  id: opts.skillId,
397
- name: opts.skillName
450
+ name: opts.skillName,
451
+ description: opts.skillDescription ?? "",
452
+ tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : []
398
453
  };
399
- if (opts.skillDescription) skill.description = opts.skillDescription;
400
- if (opts.tags) skill.tags = opts.tags.split(",").map((t) => t.trim());
401
454
  if (opts.examples) skill.examples = opts.examples.split(",").map((e) => e.trim());
402
455
  if (opts.inputModes) skill.inputModes = opts.inputModes.split(",").map((m) => m.trim());
403
456
  if (opts.outputModes) skill.outputModes = opts.outputModes.split(",").map((m) => m.trim());
@@ -766,28 +819,6 @@ async function showOrgStatus(client, gOpts) {
766
819
  ]);
767
820
  }
768
821
  //#endregion
769
- //#region src/resolve-handle.ts
770
- const UUID_ALIAS_RE = /\/agents\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
771
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
772
- /**
773
- * Resolves a handle (e.g. "agent@org.rine.network") to a UUID
774
- * via WebFinger (RFC 7033).
775
- *
776
- * Returns the UUID string on success, or the original input unchanged
777
- * on failure (caller validates UUID format and reports the error).
778
- */
779
- async function resolveHandleViaWebFinger(handle) {
780
- try {
781
- const params = new URLSearchParams({ resource: `acct:${handle}` });
782
- const data = await HttpClient.publicGet("/.well-known/webfinger", params);
783
- for (const alias of data.aliases ?? []) {
784
- const match = UUID_ALIAS_RE.exec(alias);
785
- if (match?.[1]) return match[1];
786
- }
787
- } catch {}
788
- return handle;
789
- }
790
- //#endregion
791
822
  //#region src/commands/discover-groups.ts
792
823
  function registerDiscoverGroups(discover, program) {
793
824
  discover.command("groups").description("List public groups in the directory").option("-q, --query <query>", "Search query").option("--limit <n>", "Max results").option("--cursor <cursor>", "Pagination cursor").action(withErrorHandler(program, async (gOpts, opts) => {
@@ -916,25 +947,6 @@ function registerDiscover(program) {
916
947
  registerDiscoverGroups(discover, program);
917
948
  }
918
949
  //#endregion
919
- //#region src/resolve-agent.ts
920
- let cached;
921
- /**
922
- * Resolve which agent ID to use.
923
- * Priority: explicit --agent flag > --as global flag > auto-resolve (single-agent shortcut).
924
- */
925
- async function resolveAgent(client, explicit, asFlag) {
926
- if (explicit) return explicit;
927
- if (asFlag) return asFlag;
928
- if (cached === void 0) cached = (await client.get("/agents")).items;
929
- if (cached.length === 1) {
930
- const agent = cached[0];
931
- if (agent) return agent.id;
932
- }
933
- if (cached.length === 0) throw new Error("No active agents. Create one with 'rine agent create --name <name>'");
934
- const lines = cached.map((a) => ` ${a.id} ${a.handle}`).join("\n");
935
- throw new Error(`Multiple agents found. Specify with --agent <uuid>:\n${lines}`);
936
- }
937
- //#endregion
938
950
  //#region src/commands/group.ts
939
951
  function groupRow(g) {
940
952
  return {
@@ -1688,6 +1700,31 @@ async function getOrCreateSenderKey(client, senderAgentId, groupHandle, extraHea
1688
1700
  groupId
1689
1701
  };
1690
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
+ }
1691
1728
  //#endregion
1692
1729
  //#region src/commands/messages.ts
1693
1730
  function msgFrom(m) {
@@ -1732,27 +1769,17 @@ function addMessageCommands(parent, program) {
1732
1769
  }
1733
1770
  const extraHeaders = {};
1734
1771
  let senderAgentId;
1735
- if (opts.from !== void 0) if (opts.from.includes("@")) {
1736
- const resolved = await resolveHandleViaWebFinger(opts.from);
1737
- if (!UUID_RE.test(resolved)) throw new Error("Cannot resolve sender handle to agent ID. Ensure WebFinger is available or use a UUID.");
1738
- senderAgentId = resolved;
1739
- extraHeaders["X-Rine-Agent"] = resolved;
1740
- } else {
1741
- senderAgentId = opts.from;
1742
- extraHeaders["X-Rine-Agent"] = opts.from;
1743
- }
1744
- else if (!gOpts.as) {
1772
+ if (opts.from !== void 0) {
1773
+ senderAgentId = await resolveToUuid(opts.from);
1774
+ extraHeaders["X-Rine-Agent"] = senderAgentId;
1775
+ } else if (!gOpts.as) {
1745
1776
  senderAgentId = await resolveAgent(client, void 0, void 0);
1746
1777
  extraHeaders["X-Rine-Agent"] = senderAgentId;
1747
- } else senderAgentId = gOpts.as;
1778
+ } else senderAgentId = await resolveToUuid(gOpts.as);
1748
1779
  if (opts.idempotencyKey) extraHeaders["Idempotency-Key"] = opts.idempotencyKey;
1749
1780
  let recipientAgentId;
1750
1781
  if (!opts.to.includes("@")) recipientAgentId = opts.to;
1751
- else if (!opts.to.startsWith("#")) {
1752
- const resolved = await resolveHandleViaWebFinger(opts.to);
1753
- if (!UUID_RE.test(resolved)) throw new Error("Cannot resolve recipient handle to agent ID. Ensure WebFinger is available or use a UUID.");
1754
- recipientAgentId = resolved;
1755
- }
1782
+ else if (!opts.to.startsWith("#")) recipientAgentId = await resolveToUuid(opts.to);
1756
1783
  const body = { type: opts.type };
1757
1784
  if (opts.to.includes("@")) body.to_handle = opts.to;
1758
1785
  else body.to_agent_id = opts.to;
@@ -1773,32 +1800,48 @@ function addMessageCommands(parent, program) {
1773
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) => {
1774
1801
  const data = await client.get(`/messages/${messageId}`);
1775
1802
  const agentId = await resolveAgent(client, opts.agent, gOpts.as);
1776
- 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)) {
1777
1804
  let result;
1778
- if (data.encryption_version === "sender-key-v1" && data.group_id) result = await decryptGroupMessage(agentId, data.group_id, data.encrypted_payload, client);
1779
- else result = await decryptMessage(agentId, data.encrypted_payload, client);
1780
- if (ingestSenderKeyDistribution(agentId, data.type, result)) printText("Sender key ingested");
1781
- if (gOpts.json) printJson({
1782
- ...data,
1783
- decrypted_payload: JSON.parse(result.plaintext),
1784
- signature_verified: result.verified,
1785
- verification_status: result.verificationStatus,
1786
- sender_kid: result.senderKid
1787
- });
1788
- else printTable([{
1789
- ID: data.id,
1790
- "Conversation ID": data.conversation_id,
1791
- From: msgFrom(data),
1792
- To: msgTo(data),
1793
- Group: msgGroup(data),
1794
- Type: data.type,
1795
- Verified: result.verificationStatus === "verified" ? "✓" : result.verificationStatus === "invalid" ? "✗ INVALID" : "?",
1796
- Payload: result.plaintext.slice(0, 200),
1797
- Created: data.created_at
1798
- }]);
1799
- return;
1800
- } catch (err) {
1801
- 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
+ }
1802
1845
  }
1803
1846
  if (gOpts.json) printJson(data);
1804
1847
  else printTable([{
@@ -2002,16 +2045,25 @@ async function formatMessageLine(dataStr, agentId, client) {
2002
2045
  const msgType = data.type ?? "?";
2003
2046
  const target = data.group_handle ? `${data.group_handle} (${msgType})` : msgType;
2004
2047
  let preview = "[encrypted]";
2005
- if (data.encrypted_payload && agentKeysExist(agentId)) try {
2048
+ if (data.encrypted_payload && agentKeysExist(agentId)) {
2006
2049
  let result;
2007
- if (data.encryption_version === "sender-key-v1" && data.group_id) result = await decryptGroupMessage(agentId, data.group_id, data.encrypted_payload, client);
2008
- 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 {
2009
2061
  result = await decryptMessage(agentId, data.encrypted_payload, client);
2062
+ } catch {}
2063
+ if (result) {
2010
2064
  ingestSenderKeyDistribution(agentId, data.type ?? "", result);
2011
- } else return `[${ts}] ${sender} \u2192 ${target}: [encrypted]`;
2012
- preview = `[${result.verified ? "" : "?"}] ${result.plaintext.slice(0, 60)}`;
2013
- } catch {
2014
- 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]";
2015
2067
  }
2016
2068
  return `[${ts}] ${sender} \u2192 ${target}: ${preview}`;
2017
2069
  } catch {
@@ -2159,7 +2211,7 @@ function registerWebhook(program) {
2159
2211
  //#endregion
2160
2212
  //#region src/main.ts
2161
2213
  const { version } = createRequire(import.meta.url)("../package.json");
2162
- const program = new Command("rine").version(version).description("rine.network CLI — messaging infrastructure for AI agents").option("--profile <name>", "credential profile to use", "default").option("--json", "output as JSON").option("--table", "output as table").option("--as <agent-uuid>", "act as a specific agent (sets X-Rine-Agent header)");
2214
+ const program = new Command("rine").version(version).description("rine.network CLI — messaging infrastructure for AI agents").option("--profile <name>", "credential profile to use", "default").option("--json", "output as JSON").option("--table", "output as table").option("--as <agent>", "act as a specific agent (UUID or handle, e.g. bot@org.rine.network)");
2163
2215
  registerAuth(program);
2164
2216
  registerRegister(program);
2165
2217
  registerOrg(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rine-network/cli",
3
- "version": "0.2.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",