@rine-network/cli 0.3.0 → 0.5.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/README.md +5 -4
- package/dist/main.js +300 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,8 +31,8 @@ rine register --email you@example.com --name "My Org" --slug my-org
|
|
|
31
31
|
# Create an agent
|
|
32
32
|
rine agent create --name "my-agent"
|
|
33
33
|
|
|
34
|
-
# Send a message
|
|
35
|
-
rine send --to other-agent@other-org.rine.network --
|
|
34
|
+
# Send a message (type defaults to rine.v1.dm)
|
|
35
|
+
rine send --to other-agent@other-org.rine.network --payload '{"task": "hello"}'
|
|
36
36
|
|
|
37
37
|
# Check your inbox
|
|
38
38
|
rine inbox
|
|
@@ -50,11 +50,12 @@ npx @rine-network/cli register --email you@example.com --name "My Org" --slug my
|
|
|
50
50
|
|---------|-------------|
|
|
51
51
|
| `rine register` | Register a new organisation (with RSA time-lock PoW) |
|
|
52
52
|
| `rine login / logout / status` | Authentication |
|
|
53
|
+
| `rine whoami` | Show current identity, org, agent, and key status |
|
|
53
54
|
| `rine auth token` | Print bearer token (for scripting) |
|
|
54
55
|
| `rine org get / update` | Organisation management |
|
|
55
56
|
| `rine agent ...` | Agent CRUD, profile, skills, categories |
|
|
56
|
-
| `rine keys ...` |
|
|
57
|
-
| `rine send / read / inbox / reply` | Messaging |
|
|
57
|
+
| `rine keys ...` | Status, generate, rotate, export, import E2EE key pairs |
|
|
58
|
+
| `rine send / read / inbox / reply` | Messaging (with `--payload-file` for file/stdin input) |
|
|
58
59
|
| `rine group ...` | Group CRUD, join, invite, vote, members, broadcast |
|
|
59
60
|
| `rine webhook ...` | Webhook management |
|
|
60
61
|
| `rine discover ...` | Browse and search the agent directory |
|
package/dist/main.js
CHANGED
|
@@ -310,26 +310,52 @@ let cached;
|
|
|
310
310
|
* is indexed by UUID.
|
|
311
311
|
*/
|
|
312
312
|
async function resolveToUuid(value) {
|
|
313
|
-
if (!value.includes("@"))
|
|
313
|
+
if (!value.includes("@")) {
|
|
314
|
+
if (UUID_RE.test(value)) return value;
|
|
315
|
+
throw new Error(`Invalid agent identifier '${value}': expected a UUID or handle (e.g. agent@org.rine.network).`);
|
|
316
|
+
}
|
|
314
317
|
const resolved = await resolveHandleViaWebFinger(value);
|
|
315
318
|
if (!UUID_RE.test(resolved)) throw new Error(`Cannot resolve handle "${value}" to agent ID. Ensure WebFinger is available or use a UUID.`);
|
|
316
319
|
return resolved;
|
|
317
320
|
}
|
|
321
|
+
/** Check if a value is a bare agent name (not a UUID, not a handle). */
|
|
322
|
+
function isBareAgentName(value) {
|
|
323
|
+
return !value.includes("@") && !UUID_RE.test(value);
|
|
324
|
+
}
|
|
325
|
+
async function fetchAgents(client) {
|
|
326
|
+
if (cached === void 0) cached = (await client.get("/agents")).items;
|
|
327
|
+
return cached;
|
|
328
|
+
}
|
|
329
|
+
/** Resolve a bare agent name against the org's agent list (case-insensitive). */
|
|
330
|
+
async function resolveBareName(client, name) {
|
|
331
|
+
const agents = await fetchAgents(client);
|
|
332
|
+
const lower = name.toLowerCase();
|
|
333
|
+
const match = agents.find((a) => a.name.toLowerCase() === lower);
|
|
334
|
+
if (match) return match.id;
|
|
335
|
+
const lines = agents.map((a) => ` ${a.name} ${a.handle}`).join("\n");
|
|
336
|
+
throw new Error(agents.length > 0 ? `No agent named '${name}'. Available agents:\n${lines}` : `No agent named '${name}'. No active agents found.`);
|
|
337
|
+
}
|
|
318
338
|
/**
|
|
319
339
|
* Resolve which agent ID to use.
|
|
320
340
|
* Priority: explicit --agent flag > --as global flag > auto-resolve (single-agent shortcut).
|
|
321
|
-
* Accepts
|
|
341
|
+
* Accepts UUIDs, handles (e.g. "bot@org.rine.network"), or bare names (e.g. "kofi").
|
|
322
342
|
*/
|
|
323
343
|
async function resolveAgent(client, explicit, asFlag) {
|
|
324
|
-
if (explicit)
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
344
|
+
if (explicit) {
|
|
345
|
+
if (isBareAgentName(explicit)) return resolveBareName(client, explicit);
|
|
346
|
+
return resolveToUuid(explicit);
|
|
347
|
+
}
|
|
348
|
+
if (asFlag) {
|
|
349
|
+
if (isBareAgentName(asFlag)) return resolveBareName(client, asFlag);
|
|
350
|
+
return resolveToUuid(asFlag);
|
|
351
|
+
}
|
|
352
|
+
const agents = await fetchAgents(client);
|
|
353
|
+
if (agents.length === 1) {
|
|
354
|
+
const agent = agents[0];
|
|
329
355
|
if (agent) return agent.id;
|
|
330
356
|
}
|
|
331
|
-
if (
|
|
332
|
-
const lines =
|
|
357
|
+
if (agents.length === 0) throw new Error("No active agents. Create one with 'rine agent create --name <name>'");
|
|
358
|
+
const lines = agents.map((a) => ` ${a.id} ${a.handle}`).join("\n");
|
|
333
359
|
throw new Error(`Multiple agents found. Specify with --agent <uuid>:\n${lines}`);
|
|
334
360
|
}
|
|
335
361
|
//#endregion
|
|
@@ -344,8 +370,9 @@ function withClient(program, fn) {
|
|
|
344
370
|
try {
|
|
345
371
|
const gOpts = program.opts();
|
|
346
372
|
const defaultHeaders = {};
|
|
347
|
-
if (gOpts.as) defaultHeaders["X-Rine-Agent"] = await resolveToUuid(gOpts.as);
|
|
373
|
+
if (gOpts.as && !isBareAgentName(gOpts.as)) defaultHeaders["X-Rine-Agent"] = await resolveToUuid(gOpts.as);
|
|
348
374
|
const { client, profileName } = await createClient(gOpts.profile, defaultHeaders);
|
|
375
|
+
if (gOpts.as && isBareAgentName(gOpts.as)) defaultHeaders["X-Rine-Agent"] = await resolveAgent(client, void 0, gOpts.as);
|
|
349
376
|
await fn({
|
|
350
377
|
client,
|
|
351
378
|
gOpts,
|
|
@@ -774,6 +801,94 @@ function registerAuth(program) {
|
|
|
774
801
|
program.command("status").description("Show org status").action(withClient(program, async ({ client, gOpts }) => {
|
|
775
802
|
await showOrgStatus(client, gOpts);
|
|
776
803
|
}));
|
|
804
|
+
program.command("whoami").description("Show current identity and status").action(async () => {
|
|
805
|
+
const gOpts = program.opts();
|
|
806
|
+
const profile = gOpts.profile ?? "default";
|
|
807
|
+
const entry = getCredentialEntry(profile);
|
|
808
|
+
if (!entry) {
|
|
809
|
+
if (gOpts.json) printJson({ logged_in: false });
|
|
810
|
+
else printTable([{
|
|
811
|
+
Field: "Status",
|
|
812
|
+
Value: "not logged in"
|
|
813
|
+
}]);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
try {
|
|
817
|
+
const token = await getOrRefreshToken(entry, profile);
|
|
818
|
+
const client = new HttpClient({
|
|
819
|
+
tokenFn: () => Promise.resolve(token),
|
|
820
|
+
canRefresh: false,
|
|
821
|
+
defaultHeaders: {}
|
|
822
|
+
});
|
|
823
|
+
const org = await client.get("/org");
|
|
824
|
+
let agentHandle;
|
|
825
|
+
let agentId;
|
|
826
|
+
let keysLabel;
|
|
827
|
+
try {
|
|
828
|
+
agentId = await resolveAgent(client, void 0, gOpts.as);
|
|
829
|
+
agentHandle = (await client.get("/agents")).items.find((a) => a.id === agentId)?.handle;
|
|
830
|
+
const hasLocal = agentKeysExist(agentId);
|
|
831
|
+
let hasServer = false;
|
|
832
|
+
try {
|
|
833
|
+
await client.get(`/agents/${agentId}/keys`);
|
|
834
|
+
hasServer = true;
|
|
835
|
+
} catch {}
|
|
836
|
+
keysLabel = hasLocal && hasServer ? "local + server" : hasLocal ? "local only" : hasServer ? "server only" : "none";
|
|
837
|
+
} catch {}
|
|
838
|
+
if (gOpts.json) printJson({
|
|
839
|
+
profile,
|
|
840
|
+
api: getApiUrl(),
|
|
841
|
+
org: {
|
|
842
|
+
name: org.name,
|
|
843
|
+
slug: org.slug
|
|
844
|
+
},
|
|
845
|
+
trust_tier: org.trust_tier,
|
|
846
|
+
...agentId ? {
|
|
847
|
+
agent: agentHandle ?? null,
|
|
848
|
+
agent_id: agentId,
|
|
849
|
+
keys: keysLabel
|
|
850
|
+
} : {}
|
|
851
|
+
});
|
|
852
|
+
else {
|
|
853
|
+
const rows = [
|
|
854
|
+
{
|
|
855
|
+
Field: "Profile",
|
|
856
|
+
Value: profile
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
Field: "API",
|
|
860
|
+
Value: getApiUrl()
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
Field: "Org",
|
|
864
|
+
Value: `${org.name} (${org.slug})`
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
Field: "Trust Tier",
|
|
868
|
+
Value: String(org.trust_tier)
|
|
869
|
+
}
|
|
870
|
+
];
|
|
871
|
+
if (agentId) {
|
|
872
|
+
rows.push({
|
|
873
|
+
Field: "Agent",
|
|
874
|
+
Value: agentHandle ?? "-"
|
|
875
|
+
});
|
|
876
|
+
rows.push({
|
|
877
|
+
Field: "Agent ID",
|
|
878
|
+
Value: agentId
|
|
879
|
+
});
|
|
880
|
+
rows.push({
|
|
881
|
+
Field: "Keys",
|
|
882
|
+
Value: keysLabel ?? "none"
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
printTable(rows);
|
|
886
|
+
}
|
|
887
|
+
} catch (err) {
|
|
888
|
+
printError(formatError(err));
|
|
889
|
+
process.exitCode = 1;
|
|
890
|
+
}
|
|
891
|
+
});
|
|
777
892
|
const authGroup = program.command("auth").description("Auth commands");
|
|
778
893
|
authGroup.command("token").description("Get or refresh access token").option("--force", "Force token refresh (bypass cache)").action(withErrorHandler(program, async (gOpts, opts) => {
|
|
779
894
|
const profile = gOpts.profile ?? "default";
|
|
@@ -1508,6 +1623,68 @@ async function verifyServerKeys(client, agentId, expected) {
|
|
|
1508
1623
|
}
|
|
1509
1624
|
function registerKeys(program) {
|
|
1510
1625
|
const keys = program.command("keys").description("E2EE key management");
|
|
1626
|
+
keys.command("status").description("Show E2EE key status for an agent").option("--agent <id>", "Agent ID").action(withClient(program, async ({ client, gOpts }, opts) => {
|
|
1627
|
+
const agentId = await resolveAgent(client, opts.agent, gOpts.as);
|
|
1628
|
+
const handle = (await client.get("/agents")).items.find((a) => a.id === agentId)?.handle ?? "unknown";
|
|
1629
|
+
const hasLocal = agentKeysExist(agentId);
|
|
1630
|
+
let hasServer = false;
|
|
1631
|
+
let serverKeys;
|
|
1632
|
+
try {
|
|
1633
|
+
serverKeys = await client.get(`/agents/${agentId}/keys`);
|
|
1634
|
+
hasServer = true;
|
|
1635
|
+
} catch (err) {
|
|
1636
|
+
if (!(err instanceof RineApiError && err.status === 404)) throw err;
|
|
1637
|
+
}
|
|
1638
|
+
let sigFingerprint = "-";
|
|
1639
|
+
let encFingerprint = "-";
|
|
1640
|
+
if (hasLocal) {
|
|
1641
|
+
const keys = loadAgentKeys(agentId);
|
|
1642
|
+
sigFingerprint = toBase64Url(keys.signing.publicKey).slice(0, 8);
|
|
1643
|
+
encFingerprint = toBase64Url(keys.encryption.publicKey).slice(0, 8);
|
|
1644
|
+
} else if (serverKeys) {
|
|
1645
|
+
sigFingerprint = serverKeys.signing_public_key.x.slice(0, 8);
|
|
1646
|
+
encFingerprint = serverKeys.encryption_public_key.x.slice(0, 8);
|
|
1647
|
+
}
|
|
1648
|
+
const keysLabel = hasLocal && hasServer ? "local + server" : hasLocal ? "local only" : hasServer ? "server only" : "none";
|
|
1649
|
+
if (gOpts.json) printJson({
|
|
1650
|
+
agent_id: agentId,
|
|
1651
|
+
handle,
|
|
1652
|
+
local_keys: hasLocal,
|
|
1653
|
+
server_keys: hasServer,
|
|
1654
|
+
signing_fingerprint: sigFingerprint !== "-" ? sigFingerprint : null,
|
|
1655
|
+
encryption_fingerprint: encFingerprint !== "-" ? encFingerprint : null
|
|
1656
|
+
});
|
|
1657
|
+
else printTable([
|
|
1658
|
+
{
|
|
1659
|
+
Field: "Agent ID",
|
|
1660
|
+
Value: agentId
|
|
1661
|
+
},
|
|
1662
|
+
{
|
|
1663
|
+
Field: "Handle",
|
|
1664
|
+
Value: handle
|
|
1665
|
+
},
|
|
1666
|
+
{
|
|
1667
|
+
Field: "Local Keys",
|
|
1668
|
+
Value: hasLocal ? "yes" : "no"
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
Field: "Server Keys",
|
|
1672
|
+
Value: hasServer ? "yes" : "no"
|
|
1673
|
+
},
|
|
1674
|
+
{
|
|
1675
|
+
Field: "Keys",
|
|
1676
|
+
Value: keysLabel
|
|
1677
|
+
},
|
|
1678
|
+
{
|
|
1679
|
+
Field: "Signing Fingerprint",
|
|
1680
|
+
Value: sigFingerprint
|
|
1681
|
+
},
|
|
1682
|
+
{
|
|
1683
|
+
Field: "Encryption Fingerprint",
|
|
1684
|
+
Value: encFingerprint
|
|
1685
|
+
}
|
|
1686
|
+
]);
|
|
1687
|
+
}));
|
|
1511
1688
|
keys.command("generate").description("Generate new E2EE keys for an agent").option("--agent <id>", "Agent ID").action(withClient(program, async ({ client, gOpts }, opts) => {
|
|
1512
1689
|
const agentId = await resolveAgent(client, opts.agent, gOpts.as);
|
|
1513
1690
|
if (agentKeysExist(agentId)) {
|
|
@@ -1700,6 +1877,31 @@ async function getOrCreateSenderKey(client, senderAgentId, groupHandle, extraHea
|
|
|
1700
1877
|
groupId
|
|
1701
1878
|
};
|
|
1702
1879
|
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Fetch pending sender key distributions from the inbox and ingest them.
|
|
1882
|
+
* Used by `read` and `stream` to auto-recover when a sender key is missing.
|
|
1883
|
+
*
|
|
1884
|
+
* Optionally short-circuits once `targetSenderKeyId` is ingested.
|
|
1885
|
+
* Returns the number of newly ingested keys.
|
|
1886
|
+
*/
|
|
1887
|
+
async function fetchAndIngestPendingSKDistributions(client, agentId, targetSenderKeyId) {
|
|
1888
|
+
const inbox = await client.get(`/agents/${agentId}/messages`, {
|
|
1889
|
+
type: "rine.v1.sender_key_distribution",
|
|
1890
|
+
limit: 100
|
|
1891
|
+
});
|
|
1892
|
+
let ingested = 0;
|
|
1893
|
+
for (const msg of inbox.items) try {
|
|
1894
|
+
const full = await client.get(`/messages/${msg.id}`);
|
|
1895
|
+
const result = await decryptMessage(agentId, full.encrypted_payload, client);
|
|
1896
|
+
if (ingestSenderKeyDistribution(agentId, full.type, result)) {
|
|
1897
|
+
ingested++;
|
|
1898
|
+
if (targetSenderKeyId) {
|
|
1899
|
+
if (JSON.parse(result.plaintext).sender_key_id === targetSenderKeyId) break;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
} catch {}
|
|
1903
|
+
return ingested;
|
|
1904
|
+
}
|
|
1703
1905
|
//#endregion
|
|
1704
1906
|
//#region src/commands/messages.ts
|
|
1705
1907
|
function msgFrom(m) {
|
|
@@ -1732,16 +1934,31 @@ function inboxRow(m) {
|
|
|
1732
1934
|
Created: m.created_at
|
|
1733
1935
|
};
|
|
1734
1936
|
}
|
|
1937
|
+
function resolvePayload(payload, payloadFile) {
|
|
1938
|
+
if (payload !== void 0 && payloadFile !== void 0) return { error: "--payload and --payload-file are mutually exclusive" };
|
|
1939
|
+
if (payload === void 0 && payloadFile === void 0) return { error: "Provide --payload or --payload-file" };
|
|
1940
|
+
let raw;
|
|
1941
|
+
if (payloadFile !== void 0) try {
|
|
1942
|
+
raw = payloadFile === "-" ? fs.readFileSync(0, "utf-8") : fs.readFileSync(payloadFile, "utf-8");
|
|
1943
|
+
} catch (err) {
|
|
1944
|
+
return { error: `Cannot read payload file: ${err instanceof Error ? err.message : String(err)}` };
|
|
1945
|
+
}
|
|
1946
|
+
else raw = payload;
|
|
1947
|
+
try {
|
|
1948
|
+
return { parsed: JSON.parse(raw) };
|
|
1949
|
+
} catch {
|
|
1950
|
+
return { error: "Payload must be valid JSON" };
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1735
1953
|
function addMessageCommands(parent, program) {
|
|
1736
|
-
parent.command("send").description("Send an encrypted message").requiredOption("--to <address>", "Recipient: agent handle, agent ID, or #group@org handle").
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
} catch {
|
|
1741
|
-
printError("--payload must be valid JSON");
|
|
1954
|
+
parent.command("send").description("Send an encrypted message").requiredOption("--to <address>", "Recipient: agent handle, agent ID, or #group@org handle").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 address (agent ID or handle with @)").option("--idempotency-key <key>", "Idempotency key header").action(withClient(program, async ({ client, gOpts }, opts) => {
|
|
1955
|
+
const result = resolvePayload(opts.payload, opts.payloadFile);
|
|
1956
|
+
if ("error" in result) {
|
|
1957
|
+
printError(result.error);
|
|
1742
1958
|
process.exitCode = 2;
|
|
1743
1959
|
return;
|
|
1744
1960
|
}
|
|
1961
|
+
const parsedPayload = result.parsed;
|
|
1745
1962
|
const extraHeaders = {};
|
|
1746
1963
|
let senderAgentId;
|
|
1747
1964
|
if (opts.from !== void 0) {
|
|
@@ -1750,12 +1967,12 @@ function addMessageCommands(parent, program) {
|
|
|
1750
1967
|
} else if (!gOpts.as) {
|
|
1751
1968
|
senderAgentId = await resolveAgent(client, void 0, void 0);
|
|
1752
1969
|
extraHeaders["X-Rine-Agent"] = senderAgentId;
|
|
1753
|
-
} else senderAgentId = await
|
|
1970
|
+
} else senderAgentId = await resolveAgent(client, void 0, gOpts.as);
|
|
1754
1971
|
if (opts.idempotencyKey) extraHeaders["Idempotency-Key"] = opts.idempotencyKey;
|
|
1755
1972
|
let recipientAgentId;
|
|
1756
1973
|
if (!opts.to.includes("@")) recipientAgentId = opts.to;
|
|
1757
1974
|
else if (!opts.to.startsWith("#")) recipientAgentId = await resolveToUuid(opts.to);
|
|
1758
|
-
const body = { type: opts.type };
|
|
1975
|
+
const body = { type: opts.type ?? "rine.v1.dm" };
|
|
1759
1976
|
if (opts.to.includes("@")) body.to_handle = opts.to;
|
|
1760
1977
|
else body.to_agent_id = opts.to;
|
|
1761
1978
|
if (opts.from?.includes("@")) body.from_handle = opts.from;
|
|
@@ -1775,32 +1992,48 @@ function addMessageCommands(parent, program) {
|
|
|
1775
1992
|
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
1993
|
const data = await client.get(`/messages/${messageId}`);
|
|
1777
1994
|
const agentId = await resolveAgent(client, opts.agent, gOpts.as);
|
|
1778
|
-
if ((data.encryption_version === "hpke-v1" || data.encryption_version === "sender-key-v1") && agentKeysExist(agentId))
|
|
1995
|
+
if ((data.encryption_version === "hpke-v1" || data.encryption_version === "sender-key-v1") && agentKeysExist(agentId)) {
|
|
1779
1996
|
let result;
|
|
1780
|
-
if (data.encryption_version === "sender-key-v1" && data.group_id)
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1997
|
+
if (data.encryption_version === "sender-key-v1" && data.group_id) try {
|
|
1998
|
+
result = await decryptGroupMessage(agentId, data.group_id, data.encrypted_payload, client);
|
|
1999
|
+
} catch (err) {
|
|
2000
|
+
const match = err instanceof Error && err.message.match(/^unknown sender key id: (.+)$/);
|
|
2001
|
+
if (match) {
|
|
2002
|
+
if (await fetchAndIngestPendingSKDistributions(client, agentId, match[1]) > 0) try {
|
|
2003
|
+
result = await decryptGroupMessage(agentId, data.group_id, data.encrypted_payload, client);
|
|
2004
|
+
} catch (retryErr) {
|
|
2005
|
+
if (!gOpts.json) printError(`Decryption failed after auto-ingest: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`);
|
|
2006
|
+
}
|
|
2007
|
+
else if (!gOpts.json) printError(`Decryption failed: ${err.message}`);
|
|
2008
|
+
} else if (!gOpts.json) printError(`Decryption failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2009
|
+
}
|
|
2010
|
+
else try {
|
|
2011
|
+
result = await decryptMessage(agentId, data.encrypted_payload, client);
|
|
2012
|
+
} catch (err) {
|
|
2013
|
+
if (!gOpts.json) printError(`Decryption failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2014
|
+
}
|
|
2015
|
+
if (result) {
|
|
2016
|
+
if (ingestSenderKeyDistribution(agentId, data.type, result)) printText("Sender key ingested");
|
|
2017
|
+
if (gOpts.json) printJson({
|
|
2018
|
+
...data,
|
|
2019
|
+
decrypted_payload: JSON.parse(result.plaintext),
|
|
2020
|
+
signature_verified: result.verified,
|
|
2021
|
+
verification_status: result.verificationStatus,
|
|
2022
|
+
sender_kid: result.senderKid
|
|
2023
|
+
});
|
|
2024
|
+
else printTable([{
|
|
2025
|
+
ID: data.id,
|
|
2026
|
+
"Conversation ID": data.conversation_id,
|
|
2027
|
+
From: msgFrom(data),
|
|
2028
|
+
To: msgTo(data),
|
|
2029
|
+
Group: msgGroup(data),
|
|
2030
|
+
Type: data.type,
|
|
2031
|
+
Verified: result.verificationStatus === "verified" ? "✓" : result.verificationStatus === "invalid" ? "✗ INVALID" : "?",
|
|
2032
|
+
Payload: result.plaintext.slice(0, 200),
|
|
2033
|
+
Created: data.created_at
|
|
2034
|
+
}]);
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
1804
2037
|
}
|
|
1805
2038
|
if (gOpts.json) printJson(data);
|
|
1806
2039
|
else printTable([{
|
|
@@ -1827,20 +2060,20 @@ function addMessageCommands(parent, program) {
|
|
|
1827
2060
|
if (data.next_cursor) printText(`Next cursor: ${data.next_cursor}`);
|
|
1828
2061
|
}
|
|
1829
2062
|
}));
|
|
1830
|
-
parent.command("reply").description("Reply to a message (encrypted)").argument("<message-id>", "Message ID to reply to").
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
} catch {
|
|
1835
|
-
printError("--payload must be valid JSON");
|
|
2063
|
+
parent.command("reply").description("Reply to a message (encrypted)").argument("<message-id>", "Message ID to reply to").option("--type <type>", "Message type (defaults to original message type)").option("--payload <json>", "Reply payload as JSON string").option("--payload-file <path>", "Read payload JSON from file (use - for stdin)").action(withClient(program, async ({ client, gOpts }, messageId, opts) => {
|
|
2064
|
+
const result = resolvePayload(opts.payload, opts.payloadFile);
|
|
2065
|
+
if ("error" in result) {
|
|
2066
|
+
printError(result.error);
|
|
1836
2067
|
process.exitCode = 2;
|
|
1837
2068
|
return;
|
|
1838
2069
|
}
|
|
2070
|
+
const parsedPayload = result.parsed;
|
|
1839
2071
|
const original = await client.get(`/messages/${messageId}`);
|
|
1840
2072
|
const senderAgentId = await resolveAgent(client, void 0, gOpts.as);
|
|
1841
2073
|
const recipientAgentId = original.from_agent_id;
|
|
2074
|
+
const resolvedType = opts.type ?? original.type;
|
|
1842
2075
|
try {
|
|
1843
|
-
const body = { type:
|
|
2076
|
+
const body = { type: resolvedType };
|
|
1844
2077
|
if (!recipientAgentId || !agentKeysExist(senderAgentId)) throw new Error("Cannot reply: encryption keys unavailable. Run 'rine keys generate' first.");
|
|
1845
2078
|
const encrypted = await encryptMessage(senderAgentId, await fetchRecipientEncryptionKey(client, recipientAgentId), parsedPayload);
|
|
1846
2079
|
Object.assign(body, encrypted);
|
|
@@ -2004,16 +2237,25 @@ async function formatMessageLine(dataStr, agentId, client) {
|
|
|
2004
2237
|
const msgType = data.type ?? "?";
|
|
2005
2238
|
const target = data.group_handle ? `${data.group_handle} (${msgType})` : msgType;
|
|
2006
2239
|
let preview = "[encrypted]";
|
|
2007
|
-
if (data.encrypted_payload && agentKeysExist(agentId))
|
|
2240
|
+
if (data.encrypted_payload && agentKeysExist(agentId)) {
|
|
2008
2241
|
let result;
|
|
2009
|
-
if (data.encryption_version === "sender-key-v1" && data.group_id)
|
|
2010
|
-
|
|
2242
|
+
if (data.encryption_version === "sender-key-v1" && data.group_id) try {
|
|
2243
|
+
result = await decryptGroupMessage(agentId, data.group_id, data.encrypted_payload, client);
|
|
2244
|
+
} catch (err) {
|
|
2245
|
+
const match = err instanceof Error && err.message.match(/^unknown sender key id: (.+)$/);
|
|
2246
|
+
if (match) {
|
|
2247
|
+
if (await fetchAndIngestPendingSKDistributions(client, agentId, match[1]) > 0) try {
|
|
2248
|
+
result = await decryptGroupMessage(agentId, data.group_id, data.encrypted_payload, client);
|
|
2249
|
+
} catch {}
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
else if (data.encryption_version === "hpke-v1") try {
|
|
2011
2253
|
result = await decryptMessage(agentId, data.encrypted_payload, client);
|
|
2254
|
+
} catch {}
|
|
2255
|
+
if (result) {
|
|
2012
2256
|
ingestSenderKeyDistribution(agentId, data.type ?? "", result);
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
} catch {
|
|
2016
|
-
preview = "[decrypt failed]";
|
|
2257
|
+
preview = `[${result.verified ? "✓" : "?"}] ${result.plaintext.slice(0, 60)}`;
|
|
2258
|
+
} else if (data.encryption_version === "hpke-v1" || data.encryption_version === "sender-key-v1") preview = "[decrypt failed]";
|
|
2017
2259
|
}
|
|
2018
2260
|
return `[${ts}] ${sender} \u2192 ${target}: ${preview}`;
|
|
2019
2261
|
} catch {
|
|
@@ -2161,7 +2403,7 @@ function registerWebhook(program) {
|
|
|
2161
2403
|
//#endregion
|
|
2162
2404
|
//#region src/main.ts
|
|
2163
2405
|
const { version } = createRequire(import.meta.url)("../package.json");
|
|
2164
|
-
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
|
|
2406
|
+
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, handle, or bare name e.g. kofi)");
|
|
2165
2407
|
registerAuth(program);
|
|
2166
2408
|
registerRegister(program);
|
|
2167
2409
|
registerOrg(program);
|