@rine-network/cli 0.4.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.
Files changed (3) hide show
  1. package/README.md +5 -4
  2. package/dist/main.js +218 -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,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 ...` | Generate, rotate, export, import E2EE key pairs |
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("@")) 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,
@@ -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)) {
@@ -1757,16 +1934,31 @@ function inboxRow(m) {
1757
1934
  Created: m.created_at
1758
1935
  };
1759
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
+ }
1760
1953
  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");
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);
1767
1958
  process.exitCode = 2;
1768
1959
  return;
1769
1960
  }
1961
+ const parsedPayload = result.parsed;
1770
1962
  const extraHeaders = {};
1771
1963
  let senderAgentId;
1772
1964
  if (opts.from !== void 0) {
@@ -1775,12 +1967,12 @@ function addMessageCommands(parent, program) {
1775
1967
  } else if (!gOpts.as) {
1776
1968
  senderAgentId = await resolveAgent(client, void 0, void 0);
1777
1969
  extraHeaders["X-Rine-Agent"] = senderAgentId;
1778
- } else senderAgentId = await resolveToUuid(gOpts.as);
1970
+ } else senderAgentId = await resolveAgent(client, void 0, gOpts.as);
1779
1971
  if (opts.idempotencyKey) extraHeaders["Idempotency-Key"] = opts.idempotencyKey;
1780
1972
  let recipientAgentId;
1781
1973
  if (!opts.to.includes("@")) recipientAgentId = opts.to;
1782
1974
  else if (!opts.to.startsWith("#")) recipientAgentId = await resolveToUuid(opts.to);
1783
- const body = { type: opts.type };
1975
+ const body = { type: opts.type ?? "rine.v1.dm" };
1784
1976
  if (opts.to.includes("@")) body.to_handle = opts.to;
1785
1977
  else body.to_agent_id = opts.to;
1786
1978
  if (opts.from?.includes("@")) body.from_handle = opts.from;
@@ -1868,20 +2060,20 @@ function addMessageCommands(parent, program) {
1868
2060
  if (data.next_cursor) printText(`Next cursor: ${data.next_cursor}`);
1869
2061
  }
1870
2062
  }));
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");
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);
1877
2067
  process.exitCode = 2;
1878
2068
  return;
1879
2069
  }
2070
+ const parsedPayload = result.parsed;
1880
2071
  const original = await client.get(`/messages/${messageId}`);
1881
2072
  const senderAgentId = await resolveAgent(client, void 0, gOpts.as);
1882
2073
  const recipientAgentId = original.from_agent_id;
2074
+ const resolvedType = opts.type ?? original.type;
1883
2075
  try {
1884
- const body = { type: opts.type };
2076
+ const body = { type: resolvedType };
1885
2077
  if (!recipientAgentId || !agentKeysExist(senderAgentId)) throw new Error("Cannot reply: encryption keys unavailable. Run 'rine keys generate' first.");
1886
2078
  const encrypted = await encryptMessage(senderAgentId, await fetchRecipientEncryptionKey(client, recipientAgentId), parsedPayload);
1887
2079
  Object.assign(body, encrypted);
@@ -2211,7 +2403,7 @@ function registerWebhook(program) {
2211
2403
  //#endregion
2212
2404
  //#region src/main.ts
2213
2405
  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)");
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)");
2215
2407
  registerAuth(program);
2216
2408
  registerRegister(program);
2217
2409
  registerOrg(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rine-network/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.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",