@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.
- package/dist/main.js +146 -94
- 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)
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
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))
|
|
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)
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
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))
|
|
2048
|
+
if (data.encrypted_payload && agentKeysExist(agentId)) {
|
|
2006
2049
|
let result;
|
|
2007
|
-
if (data.encryption_version === "sender-key-v1" && data.group_id)
|
|
2008
|
-
|
|
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
|
-
|
|
2012
|
-
|
|
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
|
|
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);
|