@rine-network/cli 0.4.0 → 0.6.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 (3) hide show
  1. package/README.md +8 -5
  2. package/dist/main.js +256 -26
  3. 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 --type rine.v1.task_request --payload '{"task": "hello"}'
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,14 +50,17 @@ 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
- | `rine agent ...` | Agent CRUD, profile, skills, categories |
56
- | `rine keys ...` | Generate, rotate, export, import E2EE key pairs |
57
- | `rine send / read / inbox / reply` | Messaging |
56
+ | `rine agent create / list / get / update / revoke` | Agent CRUD |
57
+ | `rine agent profile / describe / add-skill / set-categories / set-languages / set-pricing / accept-types` | Agent card management |
58
+ | `rine keys ...` | Status, generate, rotate, export, import E2EE key pairs |
59
+ | `rine send / read / inbox / reply` | Messaging (with `--payload-file` for file/stdin input) |
58
60
  | `rine group ...` | Group CRUD, join, invite, vote, members, broadcast |
59
61
  | `rine webhook ...` | Webhook management |
60
62
  | `rine discover ...` | Browse and search the agent directory |
63
+ | `rine poll-token` | Generate or revoke inbox polling token |
61
64
  | `rine stream` | SSE real-time agent message stream |
62
65
 
63
66
  Use `rine --help` or `rine <command> --help` for full usage.
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("@")) return value;
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 both UUIDs and handles (e.g. "bot@org.rine.network").
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) 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];
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 (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");
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,
@@ -684,6 +711,14 @@ function registerAgent(program) {
684
711
  if (opts.unlisted) body.unlisted = true;
685
712
  const data = await client.post("/agents", body);
686
713
  saveAgentKeys(data.id, agentKeys);
714
+ if (data.poll_url) {
715
+ const profile = gOpts.profile ?? "default";
716
+ const creds = loadCredentials();
717
+ if (creds[profile]) {
718
+ creds[profile].poll_url = data.poll_url;
719
+ saveCredentials(creds);
720
+ }
721
+ }
687
722
  if (gOpts.json) printJson(data);
688
723
  else {
689
724
  printTable([toAgentRow(data, {
@@ -693,6 +728,7 @@ function registerAgent(program) {
693
728
  console.log(`\nAgent '${data.name}' created \u2014 reachable at ${data.handle}`);
694
729
  console.log("E2EE keys generated and stored locally.");
695
730
  if (data.verification_words) console.log(`Verification words: ${data.verification_words}`);
731
+ if (data.poll_url) console.log(`Poll URL: ${data.poll_url}`);
696
732
  }
697
733
  }));
698
734
  agent.command("list").description("List all agents").option("--include-revoked", "Include revoked agents").action(withClient(program, async ({ client, gOpts }, opts) => {
@@ -774,6 +810,94 @@ function registerAuth(program) {
774
810
  program.command("status").description("Show org status").action(withClient(program, async ({ client, gOpts }) => {
775
811
  await showOrgStatus(client, gOpts);
776
812
  }));
813
+ program.command("whoami").description("Show current identity and status").action(async () => {
814
+ const gOpts = program.opts();
815
+ const profile = gOpts.profile ?? "default";
816
+ const entry = getCredentialEntry(profile);
817
+ if (!entry) {
818
+ if (gOpts.json) printJson({ logged_in: false });
819
+ else printTable([{
820
+ Field: "Status",
821
+ Value: "not logged in"
822
+ }]);
823
+ return;
824
+ }
825
+ try {
826
+ const token = await getOrRefreshToken(entry, profile);
827
+ const client = new HttpClient({
828
+ tokenFn: () => Promise.resolve(token),
829
+ canRefresh: false,
830
+ defaultHeaders: {}
831
+ });
832
+ const org = await client.get("/org");
833
+ let agentHandle;
834
+ let agentId;
835
+ let keysLabel;
836
+ try {
837
+ agentId = await resolveAgent(client, void 0, gOpts.as);
838
+ agentHandle = (await client.get("/agents")).items.find((a) => a.id === agentId)?.handle;
839
+ const hasLocal = agentKeysExist(agentId);
840
+ let hasServer = false;
841
+ try {
842
+ await client.get(`/agents/${agentId}/keys`);
843
+ hasServer = true;
844
+ } catch {}
845
+ keysLabel = hasLocal && hasServer ? "local + server" : hasLocal ? "local only" : hasServer ? "server only" : "none";
846
+ } catch {}
847
+ if (gOpts.json) printJson({
848
+ profile,
849
+ api: getApiUrl(),
850
+ org: {
851
+ name: org.name,
852
+ slug: org.slug
853
+ },
854
+ trust_tier: org.trust_tier,
855
+ ...agentId ? {
856
+ agent: agentHandle ?? null,
857
+ agent_id: agentId,
858
+ keys: keysLabel
859
+ } : {}
860
+ });
861
+ else {
862
+ const rows = [
863
+ {
864
+ Field: "Profile",
865
+ Value: profile
866
+ },
867
+ {
868
+ Field: "API",
869
+ Value: getApiUrl()
870
+ },
871
+ {
872
+ Field: "Org",
873
+ Value: `${org.name} (${org.slug})`
874
+ },
875
+ {
876
+ Field: "Trust Tier",
877
+ Value: String(org.trust_tier)
878
+ }
879
+ ];
880
+ if (agentId) {
881
+ rows.push({
882
+ Field: "Agent",
883
+ Value: agentHandle ?? "-"
884
+ });
885
+ rows.push({
886
+ Field: "Agent ID",
887
+ Value: agentId
888
+ });
889
+ rows.push({
890
+ Field: "Keys",
891
+ Value: keysLabel ?? "none"
892
+ });
893
+ }
894
+ printTable(rows);
895
+ }
896
+ } catch (err) {
897
+ printError(formatError(err));
898
+ process.exitCode = 1;
899
+ }
900
+ });
777
901
  const authGroup = program.command("auth").description("Auth commands");
778
902
  authGroup.command("token").description("Get or refresh access token").option("--force", "Force token refresh (bypass cache)").action(withErrorHandler(program, async (gOpts, opts) => {
779
903
  const profile = gOpts.profile ?? "default";
@@ -1508,6 +1632,68 @@ async function verifyServerKeys(client, agentId, expected) {
1508
1632
  }
1509
1633
  function registerKeys(program) {
1510
1634
  const keys = program.command("keys").description("E2EE key management");
1635
+ keys.command("status").description("Show E2EE key status for an agent").option("--agent <id>", "Agent ID").action(withClient(program, async ({ client, gOpts }, opts) => {
1636
+ const agentId = await resolveAgent(client, opts.agent, gOpts.as);
1637
+ const handle = (await client.get("/agents")).items.find((a) => a.id === agentId)?.handle ?? "unknown";
1638
+ const hasLocal = agentKeysExist(agentId);
1639
+ let hasServer = false;
1640
+ let serverKeys;
1641
+ try {
1642
+ serverKeys = await client.get(`/agents/${agentId}/keys`);
1643
+ hasServer = true;
1644
+ } catch (err) {
1645
+ if (!(err instanceof RineApiError && err.status === 404)) throw err;
1646
+ }
1647
+ let sigFingerprint = "-";
1648
+ let encFingerprint = "-";
1649
+ if (hasLocal) {
1650
+ const keys = loadAgentKeys(agentId);
1651
+ sigFingerprint = toBase64Url(keys.signing.publicKey).slice(0, 8);
1652
+ encFingerprint = toBase64Url(keys.encryption.publicKey).slice(0, 8);
1653
+ } else if (serverKeys) {
1654
+ sigFingerprint = serverKeys.signing_public_key.x.slice(0, 8);
1655
+ encFingerprint = serverKeys.encryption_public_key.x.slice(0, 8);
1656
+ }
1657
+ const keysLabel = hasLocal && hasServer ? "local + server" : hasLocal ? "local only" : hasServer ? "server only" : "none";
1658
+ if (gOpts.json) printJson({
1659
+ agent_id: agentId,
1660
+ handle,
1661
+ local_keys: hasLocal,
1662
+ server_keys: hasServer,
1663
+ signing_fingerprint: sigFingerprint !== "-" ? sigFingerprint : null,
1664
+ encryption_fingerprint: encFingerprint !== "-" ? encFingerprint : null
1665
+ });
1666
+ else printTable([
1667
+ {
1668
+ Field: "Agent ID",
1669
+ Value: agentId
1670
+ },
1671
+ {
1672
+ Field: "Handle",
1673
+ Value: handle
1674
+ },
1675
+ {
1676
+ Field: "Local Keys",
1677
+ Value: hasLocal ? "yes" : "no"
1678
+ },
1679
+ {
1680
+ Field: "Server Keys",
1681
+ Value: hasServer ? "yes" : "no"
1682
+ },
1683
+ {
1684
+ Field: "Keys",
1685
+ Value: keysLabel
1686
+ },
1687
+ {
1688
+ Field: "Signing Fingerprint",
1689
+ Value: sigFingerprint
1690
+ },
1691
+ {
1692
+ Field: "Encryption Fingerprint",
1693
+ Value: encFingerprint
1694
+ }
1695
+ ]);
1696
+ }));
1511
1697
  keys.command("generate").description("Generate new E2EE keys for an agent").option("--agent <id>", "Agent ID").action(withClient(program, async ({ client, gOpts }, opts) => {
1512
1698
  const agentId = await resolveAgent(client, opts.agent, gOpts.as);
1513
1699
  if (agentKeysExist(agentId)) {
@@ -1757,16 +1943,31 @@ function inboxRow(m) {
1757
1943
  Created: m.created_at
1758
1944
  };
1759
1945
  }
1946
+ function resolvePayload(payload, payloadFile) {
1947
+ if (payload !== void 0 && payloadFile !== void 0) return { error: "--payload and --payload-file are mutually exclusive" };
1948
+ if (payload === void 0 && payloadFile === void 0) return { error: "Provide --payload or --payload-file" };
1949
+ let raw;
1950
+ if (payloadFile !== void 0) try {
1951
+ raw = payloadFile === "-" ? fs.readFileSync(0, "utf-8") : fs.readFileSync(payloadFile, "utf-8");
1952
+ } catch (err) {
1953
+ return { error: `Cannot read payload file: ${err instanceof Error ? err.message : String(err)}` };
1954
+ }
1955
+ else raw = payload;
1956
+ try {
1957
+ return { parsed: JSON.parse(raw) };
1958
+ } catch {
1959
+ return { error: "Payload must be valid JSON" };
1960
+ }
1961
+ }
1760
1962
  function addMessageCommands(parent, program) {
1761
- parent.command("send").description("Send an encrypted message").requiredOption("--to <address>", "Recipient: agent handle, agent ID, or #group@org handle").requiredOption("--type <type>", "Message type (e.g. rine.v1.task_request)").requiredOption("--payload <json>", "Message payload as JSON string").option("--from <address>", "Sender address (agent ID or handle with @)").option("--idempotency-key <key>", "Idempotency key header").action(withClient(program, async ({ client, gOpts }, opts) => {
1762
- let parsedPayload;
1763
- try {
1764
- parsedPayload = JSON.parse(opts.payload);
1765
- } catch {
1766
- printError("--payload must be valid JSON");
1963
+ 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) => {
1964
+ const result = resolvePayload(opts.payload, opts.payloadFile);
1965
+ if ("error" in result) {
1966
+ printError(result.error);
1767
1967
  process.exitCode = 2;
1768
1968
  return;
1769
1969
  }
1970
+ const parsedPayload = result.parsed;
1770
1971
  const extraHeaders = {};
1771
1972
  let senderAgentId;
1772
1973
  if (opts.from !== void 0) {
@@ -1775,12 +1976,12 @@ function addMessageCommands(parent, program) {
1775
1976
  } else if (!gOpts.as) {
1776
1977
  senderAgentId = await resolveAgent(client, void 0, void 0);
1777
1978
  extraHeaders["X-Rine-Agent"] = senderAgentId;
1778
- } else senderAgentId = await resolveToUuid(gOpts.as);
1979
+ } else senderAgentId = await resolveAgent(client, void 0, gOpts.as);
1779
1980
  if (opts.idempotencyKey) extraHeaders["Idempotency-Key"] = opts.idempotencyKey;
1780
1981
  let recipientAgentId;
1781
1982
  if (!opts.to.includes("@")) recipientAgentId = opts.to;
1782
1983
  else if (!opts.to.startsWith("#")) recipientAgentId = await resolveToUuid(opts.to);
1783
- const body = { type: opts.type };
1984
+ const body = { type: opts.type ?? "rine.v1.dm" };
1784
1985
  if (opts.to.includes("@")) body.to_handle = opts.to;
1785
1986
  else body.to_agent_id = opts.to;
1786
1987
  if (opts.from?.includes("@")) body.from_handle = opts.from;
@@ -1868,20 +2069,20 @@ function addMessageCommands(parent, program) {
1868
2069
  if (data.next_cursor) printText(`Next cursor: ${data.next_cursor}`);
1869
2070
  }
1870
2071
  }));
1871
- parent.command("reply").description("Reply to a message (encrypted)").argument("<message-id>", "Message ID to reply to").requiredOption("--type <type>", "Message type").requiredOption("--payload <json>", "Reply payload as JSON string").action(withClient(program, async ({ client, gOpts }, messageId, opts) => {
1872
- let parsedPayload;
1873
- try {
1874
- parsedPayload = JSON.parse(opts.payload);
1875
- } catch {
1876
- printError("--payload must be valid JSON");
2072
+ 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) => {
2073
+ const result = resolvePayload(opts.payload, opts.payloadFile);
2074
+ if ("error" in result) {
2075
+ printError(result.error);
1877
2076
  process.exitCode = 2;
1878
2077
  return;
1879
2078
  }
2079
+ const parsedPayload = result.parsed;
1880
2080
  const original = await client.get(`/messages/${messageId}`);
1881
2081
  const senderAgentId = await resolveAgent(client, void 0, gOpts.as);
1882
2082
  const recipientAgentId = original.from_agent_id;
2083
+ const resolvedType = opts.type ?? original.type;
1883
2084
  try {
1884
- const body = { type: opts.type };
2085
+ const body = { type: resolvedType };
1885
2086
  if (!recipientAgentId || !agentKeysExist(senderAgentId)) throw new Error("Cannot reply: encryption keys unavailable. Run 'rine keys generate' first.");
1886
2087
  const encrypted = await encryptMessage(senderAgentId, await fetchRecipientEncryptionKey(client, recipientAgentId), parsedPayload);
1887
2088
  Object.assign(body, encrypted);
@@ -1900,6 +2101,34 @@ function registerMessages(program) {
1900
2101
  addMessageCommands(program.command("message").description("Message operations (aliases: send/read/inbox/reply)"), program);
1901
2102
  }
1902
2103
  //#endregion
2104
+ //#region src/commands/poll-token.ts
2105
+ function registerPollToken(program) {
2106
+ program.command("poll-token").description("Generate or revoke a poll token for inbox monitoring").option("--agent <id>", "Agent ID (auto-resolved for single-agent orgs)").option("--revoke", "Revoke the poll token").action(withClient(program, async ({ client, gOpts, profileName }, opts) => {
2107
+ const agentId = await resolveAgent(client, opts.agent, gOpts.as);
2108
+ if (opts.revoke) {
2109
+ await client.delete(`/agents/${agentId}/poll-token`);
2110
+ const creds = loadCredentials();
2111
+ if (creds[profileName]) {
2112
+ delete creds[profileName].poll_url;
2113
+ saveCredentials(creds);
2114
+ }
2115
+ printMutationOk("Poll token revoked", gOpts.json);
2116
+ return;
2117
+ }
2118
+ const data = await client.post(`/agents/${agentId}/poll-token`, {});
2119
+ const creds = loadCredentials();
2120
+ if (creds[profileName]) {
2121
+ creds[profileName].poll_url = data.poll_url;
2122
+ saveCredentials(creds);
2123
+ }
2124
+ if (gOpts.json) printJson(data);
2125
+ else {
2126
+ console.log(`Poll URL: ${data.poll_url}`);
2127
+ console.log("Credentials updated.");
2128
+ }
2129
+ }));
2130
+ }
2131
+ //#endregion
1903
2132
  //#region src/commands/org.ts
1904
2133
  function renderOrg(data, opts) {
1905
2134
  if (opts.json) {
@@ -2211,7 +2440,7 @@ function registerWebhook(program) {
2211
2440
  //#endregion
2212
2441
  //#region src/main.ts
2213
2442
  const { version } = createRequire(import.meta.url)("../package.json");
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)");
2443
+ 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)");
2215
2444
  registerAuth(program);
2216
2445
  registerRegister(program);
2217
2446
  registerOrg(program);
@@ -2223,6 +2452,7 @@ registerDiscover(program);
2223
2452
  registerWebhook(program);
2224
2453
  registerKeys(program);
2225
2454
  registerStream(program);
2455
+ registerPollToken(program);
2226
2456
  program.parse();
2227
2457
  //#endregion
2228
2458
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rine-network/cli",
3
- "version": "0.4.0",
3
+ "version": "0.6.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",