@rine-network/cli 0.1.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 ADDED
@@ -0,0 +1,1346 @@
1
+ import { createRequire } from "node:module";
2
+ import { Command } from "commander";
3
+ import * as fs from "node:fs";
4
+ import { join } from "node:path";
5
+ import readline from "node:readline";
6
+ import { createEventSource } from "eventsource-client";
7
+ //#region src/errors.ts
8
+ var RineApiError = class extends Error {
9
+ constructor(status, detail, raw) {
10
+ super(`${status}: ${detail}`);
11
+ this.status = status;
12
+ this.detail = detail;
13
+ this.raw = raw;
14
+ this.name = "RineApiError";
15
+ }
16
+ };
17
+ function formatError(err) {
18
+ if (err instanceof RineApiError) return err.detail;
19
+ if (err instanceof Error) return err.message;
20
+ return String(err);
21
+ }
22
+ //#endregion
23
+ //#region src/config.ts
24
+ function getConfigDir() {
25
+ return process.env.RINE_CONFIG_DIR ?? join(process.cwd(), ".rine");
26
+ }
27
+ function getApiUrl() {
28
+ return (process.env.RINE_API_URL ?? "https://rine.network").replace(/\/$/, "");
29
+ }
30
+ function ensureDir(dir) {
31
+ fs.mkdirSync(dir, {
32
+ recursive: true,
33
+ mode: 448
34
+ });
35
+ }
36
+ function writeAtomic(path, content) {
37
+ const tmpPath = `${path}.tmp`;
38
+ fs.writeFileSync(tmpPath, content, "utf-8");
39
+ fs.renameSync(tmpPath, path);
40
+ fs.chmodSync(path, 384);
41
+ }
42
+ function loadCredentials() {
43
+ const path = join(getConfigDir(), "credentials.json");
44
+ try {
45
+ const raw = fs.readFileSync(path, "utf-8");
46
+ return JSON.parse(raw);
47
+ } catch {
48
+ return {};
49
+ }
50
+ }
51
+ function saveCredentials(creds) {
52
+ const dir = getConfigDir();
53
+ ensureDir(dir);
54
+ writeAtomic(join(dir, "credentials.json"), JSON.stringify(creds, null, 2));
55
+ }
56
+ function loadTokenCache() {
57
+ const path = join(getConfigDir(), "token_cache.json");
58
+ try {
59
+ const raw = fs.readFileSync(path, "utf-8");
60
+ return JSON.parse(raw);
61
+ } catch {
62
+ return {};
63
+ }
64
+ }
65
+ function saveTokenCache(cache) {
66
+ const dir = getConfigDir();
67
+ ensureDir(dir);
68
+ writeAtomic(join(dir, "token_cache.json"), JSON.stringify(cache, null, 2));
69
+ }
70
+ /**
71
+ * Resolves credentials for a profile. Priority:
72
+ * 1. RINE_CLIENT_ID + RINE_CLIENT_SECRET env vars (both required)
73
+ * 2. credentials.json for the given profile
74
+ * Returns undefined if no credentials found.
75
+ */
76
+ function getCredentialEntry(profile = "default") {
77
+ const envId = process.env.RINE_CLIENT_ID;
78
+ const envSecret = process.env.RINE_CLIENT_SECRET;
79
+ if (envId && envSecret) return {
80
+ client_id: envId,
81
+ client_secret: envSecret
82
+ };
83
+ return loadCredentials()[profile];
84
+ }
85
+ //#endregion
86
+ //#region src/http.ts
87
+ async function parseErrorDetail(res) {
88
+ try {
89
+ const body = await res.json();
90
+ if (typeof body.detail === "string") return body.detail;
91
+ if (Array.isArray(body.detail)) return body.detail.map((e) => e.msg).join("; ");
92
+ } catch {}
93
+ return res.statusText;
94
+ }
95
+ var HttpClient = class {
96
+ baseUrl;
97
+ tokenFn;
98
+ canRefresh;
99
+ constructor(opts) {
100
+ this.baseUrl = (opts.apiUrl ?? getApiUrl()).replace(/\/$/, "");
101
+ this.tokenFn = opts.tokenFn;
102
+ this.canRefresh = opts.canRefresh ?? true;
103
+ }
104
+ async request(method, path, body, params, extraHeaders) {
105
+ let url = this.baseUrl + path;
106
+ if (params) {
107
+ const qs = new URLSearchParams(Object.entries(params).filter(([, v]) => v !== void 0 && v !== null).map(([k, v]) => [k, String(v)])).toString();
108
+ if (qs) url += `?${qs}`;
109
+ }
110
+ const doFetch = async (force) => {
111
+ const headers = {
112
+ Authorization: `Bearer ${await this.tokenFn(force)}`,
113
+ ...extraHeaders
114
+ };
115
+ if (body !== void 0) headers["Content-Type"] = "application/json";
116
+ const init = {
117
+ method,
118
+ headers
119
+ };
120
+ if (body !== void 0) init.body = JSON.stringify(body);
121
+ return fetch(url, init);
122
+ };
123
+ let res = await doFetch();
124
+ if (res.status === 401 && this.canRefresh) res = await doFetch(true);
125
+ if (!res.ok) {
126
+ const detail = await parseErrorDetail(res);
127
+ throw new RineApiError(res.status, detail, res);
128
+ }
129
+ if (res.status === 204) return void 0;
130
+ return res.json();
131
+ }
132
+ get(path, params) {
133
+ return this.request("GET", path, void 0, params);
134
+ }
135
+ post(path, body, extraHeaders) {
136
+ return this.request("POST", path, body, void 0, extraHeaders);
137
+ }
138
+ put(path, body) {
139
+ return this.request("PUT", path, body);
140
+ }
141
+ patch(path, body) {
142
+ return this.request("PATCH", path, body);
143
+ }
144
+ delete(path) {
145
+ return this.request("DELETE", path);
146
+ }
147
+ };
148
+ /**
149
+ * Resolves a valid access token. Priority:
150
+ * 1. RINE_TOKEN env var — used directly, no cache/refresh
151
+ * 2. Token cache — returned if not expired (60s margin)
152
+ * 3. POST /oauth/token — fetches fresh token and caches it
153
+ */
154
+ async function getOrRefreshToken(entry, profileName, force = false) {
155
+ const envToken = process.env.RINE_TOKEN;
156
+ if (envToken) return envToken;
157
+ const now = Date.now() / 1e3;
158
+ if (!force) {
159
+ const cached = loadTokenCache()[profileName];
160
+ if (cached !== void 0 && cached.expires_at - now > 60) return cached.access_token;
161
+ }
162
+ if (!entry) throw new RineApiError(0, "No credentials found. Run `rine login` first.");
163
+ const apiUrl = getApiUrl();
164
+ const res = await fetch(`${apiUrl}/oauth/token`, {
165
+ method: "POST",
166
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
167
+ body: new URLSearchParams({
168
+ grant_type: "client_credentials",
169
+ client_id: entry.client_id,
170
+ client_secret: entry.client_secret
171
+ }).toString()
172
+ });
173
+ if (!res.ok) throw new RineApiError(res.status, "Token refresh failed");
174
+ const data = await res.json();
175
+ const freshNow = Date.now() / 1e3;
176
+ const cache = loadTokenCache();
177
+ cache[profileName] = {
178
+ access_token: data.access_token,
179
+ expires_at: freshNow + data.expires_in
180
+ };
181
+ saveTokenCache(cache);
182
+ return data.access_token;
183
+ }
184
+ async function createClient(profileFlag) {
185
+ const profileName = profileFlag ?? "default";
186
+ const entry = getCredentialEntry(profileName);
187
+ const canRefresh = !process.env.RINE_TOKEN && entry !== void 0;
188
+ const tokenFn = (force = false) => getOrRefreshToken(entry, profileName, force);
189
+ return {
190
+ client: new HttpClient({
191
+ tokenFn,
192
+ canRefresh
193
+ }),
194
+ profileName,
195
+ entry
196
+ };
197
+ }
198
+ //#endregion
199
+ //#region src/output.ts
200
+ function printSuccess(msg) {
201
+ console.log(`✓ ${msg}`);
202
+ }
203
+ /** Print mutation result: `{"ok":true}` when JSON mode, else human `✓ message`. */
204
+ function printMutationOk(msg, json) {
205
+ if (json) printJson({ ok: true });
206
+ else printSuccess(msg);
207
+ }
208
+ function printJson(data) {
209
+ console.log(JSON.stringify(data, null, 2));
210
+ }
211
+ function printTable(rows, keys) {
212
+ if (rows.length === 0) {
213
+ console.log("(no results)");
214
+ return;
215
+ }
216
+ const firstRow = rows[0];
217
+ const headers = keys ?? Object.keys(firstRow ?? {});
218
+ const allRows = [headers, ...rows.map((row) => headers.map((k) => String(row[k] ?? "")))];
219
+ const widths = headers.map((_, i) => Math.max(...allRows.map((r) => (r[i] ?? "").length)));
220
+ const sep = `+${widths.map((w) => "-".repeat(w + 2)).join("+")}+`;
221
+ const fmtRow = (row) => `| ${row.map((cell, i) => cell.padEnd(widths[i] ?? 0)).join(" | ")} |`;
222
+ const lines = [
223
+ sep,
224
+ fmtRow(headers),
225
+ sep
226
+ ];
227
+ for (const row of allRows.slice(1)) lines.push(fmtRow(row));
228
+ lines.push(sep);
229
+ console.log(lines.join("\n"));
230
+ }
231
+ function printText(msg) {
232
+ console.log(msg);
233
+ }
234
+ function printError(msg) {
235
+ console.error(msg);
236
+ }
237
+ function printResult(data, opts) {
238
+ if (opts.json) {
239
+ printJson(data);
240
+ return;
241
+ }
242
+ if (opts.table && Array.isArray(data)) {
243
+ printTable(data);
244
+ return;
245
+ }
246
+ if (Array.isArray(data)) for (const item of data) {
247
+ printObject(item);
248
+ console.log("---");
249
+ }
250
+ else printObject(data);
251
+ }
252
+ function printObject(data) {
253
+ if (data === null || data === void 0) return;
254
+ if (typeof data !== "object") {
255
+ console.log(String(data));
256
+ return;
257
+ }
258
+ for (const [k, v] of Object.entries(data)) {
259
+ const val = typeof v === "object" ? JSON.stringify(v) : String(v ?? "");
260
+ console.log(`${k}: ${val}`);
261
+ }
262
+ }
263
+ //#endregion
264
+ //#region src/commands/agent-profile.ts
265
+ const READ_ONLY_KEYS = [
266
+ "id",
267
+ "agent_id",
268
+ "created_at",
269
+ "updated_at",
270
+ "signatures"
271
+ ];
272
+ const VALID_PRICING = [
273
+ "free",
274
+ "per_request",
275
+ "subscription",
276
+ "negotiated"
277
+ ];
278
+ function stripReadOnly(card) {
279
+ const result = { ...card };
280
+ for (const key of READ_ONLY_KEYS) delete result[key];
281
+ return result;
282
+ }
283
+ function setRineField(card, key, value) {
284
+ const rine = { ...card.rine };
285
+ rine[key] = value;
286
+ return {
287
+ ...card,
288
+ rine
289
+ };
290
+ }
291
+ function toRow(card) {
292
+ const rine = card.rine ?? {};
293
+ const skillCount = Array.isArray(card.skills) ? card.skills.length : 0;
294
+ const join = (v) => Array.isArray(v) ? v.join(", ") : "—";
295
+ return {
296
+ ID: String(card.id ?? "—"),
297
+ "Agent ID": String(card.agent_id ?? "—"),
298
+ Name: String(card.name ?? "—"),
299
+ Description: card.description ? String(card.description).slice(0, 60) : "—",
300
+ Version: String(card.version ?? "—"),
301
+ Public: String(card.is_public ?? "—"),
302
+ Skills: String(skillCount),
303
+ Categories: join(rine.categories) || "—",
304
+ Languages: join(rine.languages) || "—",
305
+ Pricing: String(rine.pricing_model ?? "—"),
306
+ "Accept Types": join(rine.message_types_accepted) || "—",
307
+ Updated: String(card.updated_at ?? "—")
308
+ };
309
+ }
310
+ function getAgentCmd(program) {
311
+ const cmd = program.commands.find((c) => c.name() === "agent");
312
+ if (!cmd) throw new Error("registerAgent must be called before registerAgentProfile");
313
+ return cmd;
314
+ }
315
+ function registerAgentProfile(program) {
316
+ const agent = getAgentCmd(program);
317
+ agent.command("profile").description("Get agent card").argument("<agent-id>", "Agent ID").action(async (agentId) => {
318
+ try {
319
+ const gOpts = program.opts();
320
+ const { client } = await createClient(gOpts.profile);
321
+ const card = await client.get(`/agents/${agentId}/card`);
322
+ if (gOpts.json) printJson(card);
323
+ else printTable([toRow(card)]);
324
+ } catch (err) {
325
+ printError(formatError(err));
326
+ process.exitCode = 1;
327
+ }
328
+ });
329
+ agent.command("describe").description("Update agent description").argument("<agent-id>", "Agent ID").requiredOption("--description <desc>", "Agent description").action(async (agentId, opts) => {
330
+ try {
331
+ const gOpts = program.opts();
332
+ const { client } = await createClient(gOpts.profile);
333
+ const card = await client.get(`/agents/${agentId}/card`);
334
+ card.description = opts.description;
335
+ await client.put(`/agents/${agentId}/card`, stripReadOnly(card));
336
+ printMutationOk("Description updated", gOpts.json);
337
+ } catch (err) {
338
+ printError(formatError(err));
339
+ process.exitCode = 1;
340
+ }
341
+ });
342
+ agent.command("add-skill").description("Add a skill to the agent card").argument("<agent-id>", "Agent ID").requiredOption("--skill-id <id>", "Skill ID").requiredOption("--skill-name <name>", "Skill name").option("--skill-description <desc>", "Skill description").option("--tags <tags>", "Comma-separated tags").option("--examples <examples>", "Comma-separated examples").option("--input-modes <modes>", "Comma-separated input modes").option("--output-modes <modes>", "Comma-separated output modes").action(async (agentId, opts) => {
343
+ try {
344
+ const gOpts = program.opts();
345
+ const { client } = await createClient(gOpts.profile);
346
+ const card = await client.get(`/agents/${agentId}/card`);
347
+ const skill = {
348
+ id: opts.skillId,
349
+ name: opts.skillName
350
+ };
351
+ if (opts.skillDescription) skill.description = opts.skillDescription;
352
+ if (opts.tags) skill.tags = opts.tags.split(",").map((t) => t.trim());
353
+ if (opts.examples) skill.examples = opts.examples.split(",").map((e) => e.trim());
354
+ if (opts.inputModes) skill.inputModes = opts.inputModes.split(",").map((m) => m.trim());
355
+ if (opts.outputModes) skill.outputModes = opts.outputModes.split(",").map((m) => m.trim());
356
+ card.skills = [...Array.isArray(card.skills) ? card.skills : [], skill];
357
+ await client.put(`/agents/${agentId}/card`, stripReadOnly(card));
358
+ printMutationOk("Skill added", gOpts.json);
359
+ } catch (err) {
360
+ printError(formatError(err));
361
+ process.exitCode = 1;
362
+ }
363
+ });
364
+ agent.command("set-categories").description("Set agent categories").argument("<agent-id>", "Agent ID").requiredOption("--categories <cats>", "Comma-separated category names").action(async (agentId, opts) => {
365
+ try {
366
+ const gOpts = program.opts();
367
+ const { client } = await createClient(gOpts.profile);
368
+ const card = await client.get(`/agents/${agentId}/card`);
369
+ const cats = opts.categories.split(",").map((c) => c.trim());
370
+ await client.put(`/agents/${agentId}/card`, stripReadOnly(setRineField(card, "categories", cats)));
371
+ printMutationOk("Categories updated", gOpts.json);
372
+ } catch (err) {
373
+ printError(formatError(err));
374
+ process.exitCode = 1;
375
+ }
376
+ });
377
+ agent.command("set-languages").description("Set agent supported languages").argument("<agent-id>", "Agent ID").requiredOption("--languages <langs>", "Comma-separated language codes").action(async (agentId, opts) => {
378
+ try {
379
+ const gOpts = program.opts();
380
+ const { client } = await createClient(gOpts.profile);
381
+ const card = await client.get(`/agents/${agentId}/card`);
382
+ const langs = opts.languages.split(",").map((l) => l.trim());
383
+ await client.put(`/agents/${agentId}/card`, stripReadOnly(setRineField(card, "languages", langs)));
384
+ printMutationOk("Languages updated", gOpts.json);
385
+ } catch (err) {
386
+ printError(formatError(err));
387
+ process.exitCode = 1;
388
+ }
389
+ });
390
+ agent.command("set-pricing").description("Set agent pricing model").argument("<agent-id>", "Agent ID").requiredOption("--model <model>", `Pricing model (${VALID_PRICING.join("|")})`).action(async (agentId, opts) => {
391
+ try {
392
+ const gOpts = program.opts();
393
+ if (!VALID_PRICING.includes(opts.model)) {
394
+ printError(`Invalid pricing model '${opts.model}'. Valid: ${VALID_PRICING.join(", ")}`);
395
+ process.exitCode = 2;
396
+ return;
397
+ }
398
+ const { client } = await createClient(gOpts.profile);
399
+ const card = await client.get(`/agents/${agentId}/card`);
400
+ await client.put(`/agents/${agentId}/card`, stripReadOnly(setRineField(card, "pricing_model", opts.model)));
401
+ printMutationOk("Pricing updated", gOpts.json);
402
+ } catch (err) {
403
+ printError(formatError(err));
404
+ process.exitCode = 1;
405
+ }
406
+ });
407
+ agent.command("accept-types").description("Set accepted message types").argument("<agent-id>", "Agent ID").requiredOption("--types <types>", "Comma-separated message types").action(async (agentId, opts) => {
408
+ try {
409
+ const gOpts = program.opts();
410
+ const { client } = await createClient(gOpts.profile);
411
+ const card = await client.get(`/agents/${agentId}/card`);
412
+ const types = opts.types.split(",").map((t) => t.trim());
413
+ await client.put(`/agents/${agentId}/card`, stripReadOnly(setRineField(card, "message_types_accepted", types)));
414
+ printMutationOk("Accept types updated", gOpts.json);
415
+ } catch (err) {
416
+ printError(formatError(err));
417
+ process.exitCode = 1;
418
+ }
419
+ });
420
+ }
421
+ //#endregion
422
+ //#region src/prompt.ts
423
+ /** Prompt for a plain-text value on stdout/stdin. */
424
+ async function promptText(question) {
425
+ const rl = readline.createInterface({
426
+ input: process.stdin,
427
+ output: process.stdout
428
+ });
429
+ return new Promise((resolve) => {
430
+ rl.question(question, (answer) => {
431
+ rl.close();
432
+ resolve(answer.trim());
433
+ });
434
+ });
435
+ }
436
+ /** Prompt for a secret value with hidden input (raw mode). */
437
+ async function promptPassword(question) {
438
+ return new Promise((resolve) => {
439
+ process.stdout.write(question);
440
+ const isTTY = process.stdin.isTTY === true;
441
+ if (isTTY) process.stdin.setRawMode(true);
442
+ process.stdin.resume();
443
+ process.stdin.setEncoding("utf8");
444
+ let password = "";
445
+ const handler = (data) => {
446
+ if (data === "\r" || data === "\n" || data === "") {
447
+ if (isTTY) process.stdin.setRawMode(false);
448
+ process.stdin.pause();
449
+ process.stdin.removeListener("data", handler);
450
+ process.stdout.write("\n");
451
+ resolve(password);
452
+ } else if (data === "" || data === "\b") password = password.slice(0, -1);
453
+ else if (data === "") {
454
+ process.exitCode = 130;
455
+ process.exit();
456
+ } else password += data;
457
+ };
458
+ process.stdin.on("data", handler);
459
+ });
460
+ }
461
+ /** Prompt for yes/no confirmation (defaults to No). Returns true if user types y/Y. */
462
+ async function promptConfirm(question) {
463
+ return (await promptText(`${question} [y/N] `)).toLowerCase() === "y";
464
+ }
465
+ //#endregion
466
+ //#region src/commands/agent.ts
467
+ function toAgentRow(a, opts = {}) {
468
+ const row = {
469
+ ID: String(a.id ?? ""),
470
+ Name: String(a.name ?? ""),
471
+ Handle: String(a.handle ?? ""),
472
+ "Human Oversight": String(a.human_oversight ?? ""),
473
+ Created: String(a.created_at ?? "")
474
+ };
475
+ if (opts.includeVerificationWords && a.verification_words) row["Verification Words"] = String(a.verification_words);
476
+ if (opts.includeCard) row.Card = a.unlisted ? "unlisted" : "public";
477
+ if (opts.includeRevoked) row.Revoked = a.revoked_at ? String(a.revoked_at) : "—";
478
+ return row;
479
+ }
480
+ function registerAgent(program) {
481
+ const agent = program.command("agent").description("Agent management");
482
+ agent.command("create").description("Create a new agent").option("--name <name>", "Agent name").option("--human-oversight <bool>", "Require human oversight (true/false)").option("--unlisted", "Mark agent as unlisted (not in public directory)").action(async (opts) => {
483
+ try {
484
+ const gOpts = program.opts();
485
+ let name = opts.name;
486
+ if (!name) {
487
+ name = await promptText("Agent name: ");
488
+ if (!name) {
489
+ printError("Agent name is required");
490
+ process.exitCode = 2;
491
+ return;
492
+ }
493
+ }
494
+ const body = { name };
495
+ if (opts.humanOversight !== void 0) body.human_oversight = opts.humanOversight === "true";
496
+ if (opts.unlisted) body.unlisted = true;
497
+ const { client } = await createClient(gOpts.profile);
498
+ const data = await client.post("/agents", body);
499
+ if (gOpts.json) printJson(data);
500
+ else {
501
+ printTable([toAgentRow(data, {
502
+ includeCard: true,
503
+ includeVerificationWords: true
504
+ })]);
505
+ const handle = String(data.handle ?? "");
506
+ const name = String(data.name ?? "");
507
+ console.log(`
508
+ Agent '${name}' created — reachable at ${handle}`);
509
+ if (data.verification_words) console.log(`Verification words: ${data.verification_words}`);
510
+ }
511
+ } catch (err) {
512
+ printError(formatError(err));
513
+ process.exitCode = 1;
514
+ }
515
+ });
516
+ agent.command("list").description("List all agents").option("--include-revoked", "Include revoked agents").action(async (opts) => {
517
+ try {
518
+ const gOpts = program.opts();
519
+ const { client } = await createClient(gOpts.profile);
520
+ const params = opts.includeRevoked ? { include_revoked: true } : void 0;
521
+ const data = await client.get("/agents", params);
522
+ if (gOpts.json) printJson(data);
523
+ else printTable(data.items.map((a) => toAgentRow(a, { includeRevoked: opts.includeRevoked })));
524
+ } catch (err) {
525
+ printError(formatError(err));
526
+ process.exitCode = 1;
527
+ }
528
+ });
529
+ agent.command("get").description("Get agent details").argument("<agent-id>", "Agent ID").action(async (agentId) => {
530
+ try {
531
+ const gOpts = program.opts();
532
+ const { client } = await createClient(gOpts.profile);
533
+ const data = await client.get(`/agents/${agentId}`);
534
+ if (gOpts.json) printJson(data);
535
+ else printTable([toAgentRow(data, { includeRevoked: true })]);
536
+ } catch (err) {
537
+ printError(formatError(err));
538
+ process.exitCode = 1;
539
+ }
540
+ });
541
+ agent.command("update").description("Update agent details").argument("<agent-id>", "Agent ID").option("--name <name>", "Agent name").option("--human-oversight <bool>", "Require human oversight (true/false)").action(async (agentId, opts) => {
542
+ try {
543
+ const gOpts = program.opts();
544
+ const body = {};
545
+ if (opts.name) body.name = opts.name;
546
+ if (opts.humanOversight !== void 0) body.human_oversight = opts.humanOversight === "true";
547
+ if (Object.keys(body).length === 0) {
548
+ printError("At least one of --name, --human-oversight is required");
549
+ process.exitCode = 2;
550
+ return;
551
+ }
552
+ const { client } = await createClient(gOpts.profile);
553
+ await client.patch(`/agents/${agentId}`, body);
554
+ printMutationOk("Agent updated", gOpts.json);
555
+ } catch (err) {
556
+ printError(formatError(err));
557
+ process.exitCode = 1;
558
+ }
559
+ });
560
+ agent.command("revoke").description("Revoke an agent").argument("<agent-id>", "Agent ID").option("--yes", "Skip confirmation prompt").action(async (agentId, opts) => {
561
+ try {
562
+ const gOpts = program.opts();
563
+ if (!opts.yes && !gOpts.json) {
564
+ if (!await promptConfirm(`Revoke agent ${agentId}?`)) return;
565
+ }
566
+ const { client } = await createClient(gOpts.profile);
567
+ await client.delete(`/agents/${agentId}`);
568
+ printMutationOk("Agent revoked", gOpts.json);
569
+ } catch (err) {
570
+ if (err instanceof RineApiError && err.status === 409) printError("Agent already revoked");
571
+ else printError(formatError(err));
572
+ process.exitCode = 1;
573
+ }
574
+ });
575
+ }
576
+ //#endregion
577
+ //#region src/commands/auth.ts
578
+ function registerAuth(program) {
579
+ program.command("login").description("Log in with OAuth2 client credentials").option("--client-id <id>", "Client ID").option("--client-secret <secret>", "Client secret").action(async (opts) => {
580
+ try {
581
+ const profile = program.opts().profile ?? "default";
582
+ const clientId = opts.clientId ?? await promptText("Client ID: ");
583
+ const clientSecret = opts.clientSecret ?? await promptPassword("Client secret: ");
584
+ const apiUrl = getApiUrl();
585
+ const res = await fetch(`${apiUrl}/oauth/token`, {
586
+ method: "POST",
587
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
588
+ body: new URLSearchParams({
589
+ grant_type: "client_credentials",
590
+ client_id: clientId,
591
+ client_secret: clientSecret
592
+ }).toString()
593
+ });
594
+ if (!res.ok) {
595
+ printError(`Login failed: ${(await res.json().catch(() => ({}))).detail ?? res.statusText}`);
596
+ process.exitCode = 1;
597
+ return;
598
+ }
599
+ const tokenData = await res.json();
600
+ const creds = loadCredentials();
601
+ creds[profile] = {
602
+ client_id: clientId,
603
+ client_secret: clientSecret
604
+ };
605
+ saveCredentials(creds);
606
+ const cache = loadTokenCache();
607
+ cache[profile] = {
608
+ access_token: tokenData.access_token,
609
+ expires_at: Date.now() / 1e3 + tokenData.expires_in
610
+ };
611
+ saveTokenCache(cache);
612
+ printSuccess(`Logged in as ${clientId}`);
613
+ } catch (err) {
614
+ printError(formatError(err));
615
+ process.exitCode = 1;
616
+ }
617
+ });
618
+ program.command("logout").description("Clear token cache for the current profile").action(() => {
619
+ try {
620
+ const profile = program.opts().profile ?? "default";
621
+ const cache = loadTokenCache();
622
+ delete cache[profile];
623
+ saveTokenCache(cache);
624
+ printSuccess("Logged out (credentials preserved — use 'rine auth token' to re-authenticate)");
625
+ } catch (err) {
626
+ printError(formatError(err));
627
+ process.exitCode = 1;
628
+ }
629
+ });
630
+ program.command("status").description("Show org status").action(async () => {
631
+ await showOrgStatus(program);
632
+ });
633
+ const authGroup = program.command("auth").description("Auth commands");
634
+ authGroup.command("token").description("Get or refresh access token").option("--force", "Force token refresh (bypass cache)").action(async (opts) => {
635
+ try {
636
+ const gOpts = program.opts();
637
+ const profile = gOpts.profile ?? "default";
638
+ const force = opts.force === true;
639
+ const now = Date.now() / 1e3;
640
+ const envToken = process.env.RINE_TOKEN;
641
+ const cached = loadTokenCache()[profile];
642
+ const source = envToken !== void 0 || !force && cached !== void 0 && cached.expires_at - now > 60 ? "cache" : "server";
643
+ const token = await getOrRefreshToken(getCredentialEntry(profile), profile, force);
644
+ if (gOpts.json) printResult({
645
+ access_token: token,
646
+ source
647
+ }, gOpts);
648
+ else console.log(token);
649
+ } catch (err) {
650
+ printError(formatError(err));
651
+ process.exitCode = 1;
652
+ }
653
+ });
654
+ authGroup.command("status").description("Show org status").action(async () => {
655
+ await showOrgStatus(program);
656
+ });
657
+ }
658
+ async function showOrgStatus(program) {
659
+ try {
660
+ const gOpts = program.opts();
661
+ const { client } = await createClient(gOpts.profile);
662
+ const data = await client.get("/org");
663
+ if (gOpts.json) {
664
+ printResult(data, gOpts);
665
+ return;
666
+ }
667
+ printTable([
668
+ {
669
+ Field: "Name",
670
+ Value: String(data.name ?? "")
671
+ },
672
+ {
673
+ Field: "Slug",
674
+ Value: String(data.slug ?? "")
675
+ },
676
+ {
677
+ Field: "Trust Tier",
678
+ Value: String(data.trust_tier ?? "")
679
+ },
680
+ {
681
+ Field: "Agent Count",
682
+ Value: String(data.agent_count ?? "")
683
+ }
684
+ ]);
685
+ } catch (err) {
686
+ printError(formatError(err));
687
+ process.exitCode = 1;
688
+ }
689
+ }
690
+ //#endregion
691
+ //#region src/commands/discover.ts
692
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
693
+ async function directoryFetch(path, params) {
694
+ const base = getApiUrl();
695
+ const qs = params?.toString();
696
+ const url = qs ? `${base}${path}?${qs}` : `${base}${path}`;
697
+ const res = await fetch(url, { headers: { Accept: "application/json" } });
698
+ if (!res.ok) {
699
+ const body = await res.text().catch(() => res.statusText);
700
+ throw new RineApiError(res.status, body, res);
701
+ }
702
+ return res.json();
703
+ }
704
+ function trunc(s, len) {
705
+ const str = String(s ?? "");
706
+ return str.length > len ? `${str.slice(0, len - 1)}\u2026` : str;
707
+ }
708
+ function fmtList(v) {
709
+ return Array.isArray(v) ? v.join(", ") : String(v ?? "");
710
+ }
711
+ async function doAgentSearch(opts, gOpts) {
712
+ const params = new URLSearchParams();
713
+ if (opts.query) params.append("q", opts.query);
714
+ for (const c of opts.category) params.append("category", c);
715
+ for (const t of opts.tag) params.append("tag", t);
716
+ if (opts.jurisdiction) params.append("jurisdiction", opts.jurisdiction);
717
+ for (const l of opts.language) params.append("language", l);
718
+ if (opts.verified !== void 0) params.append("verified", String(opts.verified));
719
+ if (opts.pricingModel) params.append("pricing_model", opts.pricingModel);
720
+ if (opts.limit) params.append("limit", opts.limit);
721
+ if (opts.cursor) params.append("cursor", opts.cursor);
722
+ if (opts.sort) params.append("sort", opts.sort);
723
+ const data = await directoryFetch("/directory/agents", params);
724
+ if (gOpts.json) {
725
+ printJson(data);
726
+ return;
727
+ }
728
+ const result = data;
729
+ printTable((result.items ?? []).map((a) => ({
730
+ Name: trunc(a.name, 40),
731
+ Categories: trunc(fmtList(a.rine?.categories ?? a.categories), 40),
732
+ Tags: trunc(fmtList(a.rine?.tags ?? a.tags), 40),
733
+ Pricing: trunc(a.rine?.pricing_model ?? a.pricing_model, 40),
734
+ Handle: trunc(a.handle, 40)
735
+ })));
736
+ if (result.next_cursor) printText(`Next cursor: ${result.next_cursor}`);
737
+ }
738
+ const collect = (v, prev) => [...prev, v];
739
+ function addSearchOptions(cmd, requireQuery) {
740
+ if (requireQuery) cmd.requiredOption("-q, --query <query>", "Search query");
741
+ else cmd.option("-q, --query <query>", "Search query");
742
+ return cmd.option("--category <cat>", "Category filter (repeatable)", collect, []).option("--tag <tag>", "Tag filter (repeatable)", collect, []).option("--jurisdiction <j>", "Jurisdiction filter").option("--language <lang>", "Language filter (repeatable)", collect, []).option("--verified", "Only verified agents").option("--pricing-model <model>", "Pricing model filter").option("--limit <n>", "Max results").option("--cursor <cursor>", "Pagination cursor").option("--sort <sort>", "Sort order (relevance|name|created_at)");
743
+ }
744
+ function registerDiscover(program) {
745
+ const discover = program.command("discover").description("Discover agents in the directory");
746
+ addSearchOptions(discover.command("agents").description("List agents in the directory"), false).action(async (opts) => {
747
+ const gOpts = program.opts();
748
+ try {
749
+ await doAgentSearch(opts, gOpts);
750
+ } catch (err) {
751
+ printError(formatError(err));
752
+ process.exitCode = 1;
753
+ }
754
+ });
755
+ addSearchOptions(discover.command("search").description("Search agents (--query required)"), true).action(async (opts) => {
756
+ const gOpts = program.opts();
757
+ try {
758
+ await doAgentSearch(opts, gOpts);
759
+ } catch (err) {
760
+ printError(formatError(err));
761
+ process.exitCode = 1;
762
+ }
763
+ });
764
+ discover.command("categories").description("List directory categories with counts").action(async () => {
765
+ const gOpts = program.opts();
766
+ try {
767
+ const data = await directoryFetch("/directory/categories");
768
+ if (gOpts.json) {
769
+ printJson(data);
770
+ return;
771
+ }
772
+ printTable((data.items ?? []).sort((a, b) => a.name.localeCompare(b.name)).map((c) => ({
773
+ Category: c.name,
774
+ Count: c.count
775
+ })));
776
+ } catch (err) {
777
+ printError(formatError(err));
778
+ process.exitCode = 1;
779
+ }
780
+ });
781
+ discover.command("inspect").description("Inspect a specific agent in the directory").argument("<agent-id>", "Agent UUID or handle").action(async (agentId) => {
782
+ const gOpts = program.opts();
783
+ if (agentId.includes("@")) {
784
+ const match = ((await directoryFetch("/directory/agents", new URLSearchParams({
785
+ q: agentId,
786
+ limit: "5"
787
+ }))).items ?? []).find((i) => i.handle === agentId);
788
+ if (!match?.id) {
789
+ printError(`No agent found with handle: ${agentId}`);
790
+ process.exitCode = 1;
791
+ return;
792
+ }
793
+ agentId = match.id;
794
+ }
795
+ if (!UUID_RE.test(agentId)) {
796
+ printError("agent-id must be a valid UUID");
797
+ process.exitCode = 2;
798
+ return;
799
+ }
800
+ try {
801
+ const data = await directoryFetch(`/directory/agents/${agentId}`);
802
+ if (gOpts.json) {
803
+ printJson(data);
804
+ return;
805
+ }
806
+ const result = data;
807
+ const card = result.card ?? data;
808
+ const meta = result.directory_metadata ?? {};
809
+ printTable([{
810
+ Name: String(card.name ?? ""),
811
+ Description: trunc(card.description, 120),
812
+ Categories: fmtList(card.rine?.categories ?? card.categories),
813
+ Tags: fmtList(card.rine?.tags ?? card.tags),
814
+ Pricing: String(card.rine?.pricing_model ?? card.pricing_model ?? ""),
815
+ "Registered At": String(meta.registered_at ?? ""),
816
+ "Messages (30d)": String(meta.messages_processed_30d ?? ""),
817
+ "Avg Response (ms)": String(meta.avg_response_time_ms ?? "")
818
+ }]);
819
+ } catch (err) {
820
+ printError(formatError(err));
821
+ process.exitCode = 1;
822
+ }
823
+ });
824
+ }
825
+ //#endregion
826
+ //#region src/commands/resolve.ts
827
+ /**
828
+ * Resolve the first active agent for the authenticated org.
829
+ * Used by inbox and stream commands when no explicit --agent is given.
830
+ */
831
+ async function resolveFirstAgent(client) {
832
+ const agents = ((await client.get("/agents")).items ?? []).filter((a) => !a.revoked_at);
833
+ if (agents.length === 0) throw new Error("No active agents found. Create one with `rine agent create`.");
834
+ const first = agents[0];
835
+ if (agents.length > 1) process.stderr.write(`Warning: multiple agents found, using first: ${first.id}\n`);
836
+ return first.id;
837
+ }
838
+ //#endregion
839
+ //#region src/commands/messages.ts
840
+ function msgFrom(m) {
841
+ return String(m.sender_handle ?? m.from_agent_id ?? "");
842
+ }
843
+ function msgTo(m) {
844
+ return String(m.recipient_handle ?? m.to_agent_id ?? "");
845
+ }
846
+ function sendRow(m) {
847
+ return {
848
+ ID: m.id,
849
+ "Conversation ID": m.conversation_id,
850
+ From: msgFrom(m),
851
+ To: msgTo(m),
852
+ Type: m.type,
853
+ Created: m.created_at
854
+ };
855
+ }
856
+ function readRow(m) {
857
+ return {
858
+ ID: m.id,
859
+ "Conversation ID": m.conversation_id,
860
+ From: msgFrom(m),
861
+ To: msgTo(m),
862
+ Type: m.type,
863
+ Preview: m.payload_preview ? String(m.payload_preview).slice(0, 60) : "",
864
+ Payload: JSON.stringify(m.payload ?? {}),
865
+ Created: m.created_at
866
+ };
867
+ }
868
+ function inboxRow(m) {
869
+ return {
870
+ ID: m.id,
871
+ From: msgFrom(m),
872
+ Type: m.type,
873
+ Preview: m.payload_preview ? String(m.payload_preview).slice(0, 60) : "",
874
+ Created: m.created_at
875
+ };
876
+ }
877
+ function addMessageCommands(parent, program) {
878
+ parent.command("send").description("Send a message").requiredOption("--to <address>", "Recipient address (agent ID or handle with @)").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(async (opts) => {
879
+ try {
880
+ const gOpts = program.opts();
881
+ const { client } = await createClient(gOpts.profile);
882
+ let parsedPayload;
883
+ try {
884
+ parsedPayload = JSON.parse(opts.payload);
885
+ } catch {
886
+ printError("--payload must be valid JSON");
887
+ process.exitCode = 2;
888
+ return;
889
+ }
890
+ const body = {
891
+ type: opts.type,
892
+ payload: parsedPayload
893
+ };
894
+ if (opts.to.includes("@")) body.to_handle = opts.to;
895
+ else body.to_agent_id = opts.to;
896
+ if (opts.from !== void 0) if (opts.from.includes("@")) body.from_handle = opts.from;
897
+ else body.from_agent_id = opts.from;
898
+ const extraHeaders = {};
899
+ if (opts.idempotencyKey) extraHeaders["Idempotency-Key"] = opts.idempotencyKey;
900
+ const data = await client.post("/messages", body, Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0);
901
+ if (gOpts.json) printJson(data);
902
+ else printTable([sendRow(data)]);
903
+ } catch (err) {
904
+ printError(formatError(err));
905
+ process.exitCode = 1;
906
+ }
907
+ });
908
+ parent.command("read").description("Read a message by ID").argument("<message-id>", "Message ID").action(async (messageId) => {
909
+ try {
910
+ const gOpts = program.opts();
911
+ const { client } = await createClient(gOpts.profile);
912
+ const data = await client.get(`/messages/${messageId}`);
913
+ if (gOpts.json) printJson(data);
914
+ else printTable([readRow(data)]);
915
+ } catch (err) {
916
+ printError(formatError(err));
917
+ process.exitCode = 1;
918
+ }
919
+ });
920
+ parent.command("inbox").description("List inbox messages").option("--agent <id>", "Agent ID (auto-resolved if omitted)").option("--limit <n>", "Maximum number of messages to return").option("--cursor <cursor>", "Pagination cursor").action(async (opts) => {
921
+ try {
922
+ const gOpts = program.opts();
923
+ const { client } = await createClient(gOpts.profile);
924
+ let agentId = opts.agent;
925
+ if (!agentId) agentId = await resolveFirstAgent(client);
926
+ const params = {};
927
+ if (opts.limit) params.limit = opts.limit;
928
+ if (opts.cursor) params.cursor = opts.cursor;
929
+ const data = await client.get(`/agents/${agentId}/messages`, params);
930
+ if (gOpts.json) printJson(data);
931
+ else {
932
+ printTable(data.items.map(inboxRow));
933
+ if (data.next_cursor) printText(`Next cursor: ${data.next_cursor}`);
934
+ }
935
+ } catch (err) {
936
+ printError(formatError(err));
937
+ process.exitCode = 1;
938
+ }
939
+ });
940
+ parent.command("reply").description("Reply to a message").argument("<message-id>", "Message ID to reply to").requiredOption("--type <type>", "Message type").requiredOption("--payload <json>", "Reply payload as JSON string").action(async (messageId, opts) => {
941
+ try {
942
+ const gOpts = program.opts();
943
+ const { client } = await createClient(gOpts.profile);
944
+ let parsedPayload;
945
+ try {
946
+ parsedPayload = JSON.parse(opts.payload);
947
+ } catch {
948
+ printError("--payload must be valid JSON");
949
+ process.exitCode = 2;
950
+ return;
951
+ }
952
+ const data = await client.post(`/messages/${messageId}/reply`, {
953
+ type: opts.type,
954
+ payload: parsedPayload
955
+ });
956
+ if (gOpts.json) printJson(data);
957
+ else printTable([sendRow(data)]);
958
+ } catch (err) {
959
+ if (err instanceof RineApiError && err.status === 409) printError("Conversation is closed");
960
+ else printError(formatError(err));
961
+ process.exitCode = 1;
962
+ }
963
+ });
964
+ }
965
+ function registerMessages(program) {
966
+ addMessageCommands(program, program);
967
+ addMessageCommands(program.command("message").description("Message operations (aliases: send/read/inbox/reply)"), program);
968
+ }
969
+ //#endregion
970
+ //#region src/commands/org.ts
971
+ function renderOrg(data, opts) {
972
+ if (opts.json) {
973
+ printJson(data);
974
+ return;
975
+ }
976
+ printTable([{
977
+ ID: String(data.id ?? ""),
978
+ Name: String(data.name ?? ""),
979
+ "Contact Email": String(data.contact_email ?? ""),
980
+ Country: String(data.country_code ?? ""),
981
+ Created: String(data.created_at ?? "")
982
+ }]);
983
+ }
984
+ function registerOrg(program) {
985
+ const org = program.command("org").description("Organization management");
986
+ org.command("get").description("Get organization details").action(async () => {
987
+ try {
988
+ const gOpts = program.opts();
989
+ const { client } = await createClient(gOpts.profile);
990
+ renderOrg(await client.get("/org"), gOpts);
991
+ } catch (err) {
992
+ printError(formatError(err));
993
+ process.exitCode = 1;
994
+ }
995
+ });
996
+ org.command("update").description("Update organization details").option("--name <name>", "Organization name").option("--contact-email <email>", "Contact email address").option("--country-code <cc>", "ISO country code (e.g. DE)").action(async (opts) => {
997
+ try {
998
+ const gOpts = program.opts();
999
+ const body = {};
1000
+ if (opts.name) body.name = opts.name;
1001
+ if (opts.contactEmail) body.contact_email = opts.contactEmail;
1002
+ if (opts.countryCode) body.country_code = opts.countryCode;
1003
+ if (Object.keys(body).length === 0) {
1004
+ printError("At least one of --name, --contact-email, --country-code is required");
1005
+ process.exitCode = 2;
1006
+ return;
1007
+ }
1008
+ const { client } = await createClient(gOpts.profile);
1009
+ await client.patch("/org", body);
1010
+ printMutationOk("Org updated", gOpts.json);
1011
+ } catch (err) {
1012
+ printError(formatError(err));
1013
+ process.exitCode = 1;
1014
+ }
1015
+ });
1016
+ }
1017
+ //#endregion
1018
+ //#region src/timelock.ts
1019
+ async function solveTimeLockWithProgress(baseHex, modulusHex, T, onProgress) {
1020
+ const N = BigInt(`0x${modulusHex}`);
1021
+ let x = BigInt(`0x${baseHex}`) % N;
1022
+ const step = Math.max(1, Math.floor(T / 100));
1023
+ for (let i = 0; i < T; i++) {
1024
+ x = x * x % N;
1025
+ if (i % step === 0) {
1026
+ onProgress?.(Math.floor(i / T * 100));
1027
+ await new Promise((resolve) => setImmediate(resolve));
1028
+ }
1029
+ }
1030
+ return x.toString(16);
1031
+ }
1032
+ //#endregion
1033
+ //#region src/commands/register.ts
1034
+ function registerRegister(program) {
1035
+ program.command("register").description("Register a new organization (two-step PoW flow)").requiredOption("--email <email>", "Email address").requiredOption("--name <name>", "Organization name").requiredOption("--slug <slug>", "Organization slug").action(async (opts) => {
1036
+ try {
1037
+ const gOpts = program.opts();
1038
+ const profile = gOpts.profile ?? "default";
1039
+ const apiUrl = getApiUrl();
1040
+ const challengeRes = await fetch(`${apiUrl}/auth/register`, {
1041
+ method: "POST",
1042
+ headers: { "Content-Type": "application/json" },
1043
+ body: JSON.stringify({
1044
+ email: opts.email,
1045
+ org_slug: opts.slug
1046
+ })
1047
+ });
1048
+ if (challengeRes.status === 409) {
1049
+ printError("Email or slug already registered");
1050
+ process.exitCode = 1;
1051
+ return;
1052
+ }
1053
+ if (challengeRes.status === 429) {
1054
+ printError("Rate limited — please wait before retrying");
1055
+ process.exitCode = 1;
1056
+ return;
1057
+ }
1058
+ if (!challengeRes.ok) {
1059
+ printError(`Registration failed: ${(await challengeRes.json().catch(() => ({}))).detail ?? challengeRes.statusText}`);
1060
+ process.exitCode = 1;
1061
+ return;
1062
+ }
1063
+ const challenge = await challengeRes.json();
1064
+ if (challenge.algorithm !== "rsa-timelock-v1") {
1065
+ printError(`Unsupported algorithm: ${challenge.algorithm}. Please upgrade rine-cli.`);
1066
+ process.exitCode = 1;
1067
+ return;
1068
+ }
1069
+ process.stderr.write("By registering, you accept the rine terms of service and privacy policy:\n https://rine.network/terms.html\n https://rine.network/privacy.html\n");
1070
+ const nonce = await solveTimeLockWithProgress(challenge.prefix, challenge.modulus, challenge.difficulty, (pct) => {
1071
+ process.stdout.write(`\rSolving PoW challenge: ${pct}%`);
1072
+ });
1073
+ process.stdout.write("\rSolving PoW challenge: 100%\n");
1074
+ const solveRes = await fetch(`${apiUrl}/auth/register/solve`, {
1075
+ method: "POST",
1076
+ headers: { "Content-Type": "application/json" },
1077
+ body: JSON.stringify({
1078
+ challenge_id: challenge.challenge_id,
1079
+ nonce,
1080
+ org_name: opts.name,
1081
+ org_slug: opts.slug,
1082
+ consent: true
1083
+ })
1084
+ });
1085
+ if (solveRes.status === 409) {
1086
+ printError("Conflict: email or slug already registered");
1087
+ process.exitCode = 1;
1088
+ return;
1089
+ }
1090
+ if (solveRes.status === 410) {
1091
+ printError("Challenge expired — please register again");
1092
+ process.exitCode = 1;
1093
+ return;
1094
+ }
1095
+ if (!solveRes.ok) {
1096
+ printError(`Solve failed: ${(await solveRes.json().catch(() => ({}))).detail ?? solveRes.statusText}`);
1097
+ process.exitCode = 1;
1098
+ return;
1099
+ }
1100
+ const data = await solveRes.json();
1101
+ const creds = loadCredentials();
1102
+ creds[profile] = {
1103
+ client_id: data.client_id,
1104
+ client_secret: data.client_secret
1105
+ };
1106
+ saveCredentials(creds);
1107
+ try {
1108
+ const tokenRes = await fetch(`${apiUrl}/oauth/token`, {
1109
+ method: "POST",
1110
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1111
+ body: new URLSearchParams({
1112
+ grant_type: "client_credentials",
1113
+ client_id: data.client_id,
1114
+ client_secret: data.client_secret
1115
+ }).toString()
1116
+ });
1117
+ if (tokenRes.ok) {
1118
+ const tokenData = await tokenRes.json();
1119
+ const cache = loadTokenCache();
1120
+ cache[profile] = {
1121
+ access_token: tokenData.access_token,
1122
+ expires_at: Date.now() / 1e3 + tokenData.expires_in
1123
+ };
1124
+ saveTokenCache(cache);
1125
+ }
1126
+ } catch {
1127
+ process.stderr.write("Warning: initial token cache failed\n");
1128
+ }
1129
+ if (gOpts.json) printJson(data);
1130
+ else {
1131
+ printSuccess(`Registered '${opts.name}' (${opts.slug})`);
1132
+ console.log("Credentials saved to .rine/credentials.json");
1133
+ console.log("Next: create an agent with 'rine agent create --name <agent-name>'");
1134
+ }
1135
+ } catch (err) {
1136
+ printError(formatError(err));
1137
+ process.exitCode = 1;
1138
+ }
1139
+ });
1140
+ }
1141
+ //#endregion
1142
+ //#region src/commands/stream.ts
1143
+ function formatMessageLine(dataStr) {
1144
+ try {
1145
+ const data = JSON.parse(dataStr);
1146
+ return `[${(data.created_at ?? "").slice(0, 19) || "?"}] ${data.sender_handle ?? String(data.from_agent_id ?? "?")} \u2192 ${data.type ?? "?"}: ${data.payload ? JSON.stringify(data.payload).slice(0, 60) : ""}`;
1147
+ } catch {
1148
+ return `[message] ${dataStr.slice(0, 80)}`;
1149
+ }
1150
+ }
1151
+ function registerStream(program) {
1152
+ program.command("stream").description("Stream incoming messages via SSE").option("--agent <id>", "Agent ID (auto-resolved if omitted)").option("--verbose", "Show heartbeats and reconnect details").action(async (opts) => {
1153
+ try {
1154
+ const gOpts = program.opts();
1155
+ const { client, profileName, entry } = await createClient(gOpts.profile);
1156
+ let agentId = opts.agent;
1157
+ if (!agentId) agentId = await resolveFirstAgent(client);
1158
+ const url = `${getApiUrl()}/agents/${agentId}/stream`;
1159
+ let lastEventId;
1160
+ let backoff = 1;
1161
+ let stopped = false;
1162
+ process.on("SIGINT", () => {
1163
+ stopped = true;
1164
+ process.exitCode = 0;
1165
+ });
1166
+ while (!stopped) try {
1167
+ const headers = {
1168
+ Authorization: `Bearer ${await getOrRefreshToken(entry, profileName, false)}`,
1169
+ Accept: "text/event-stream"
1170
+ };
1171
+ if (lastEventId) headers["Last-Event-ID"] = lastEventId;
1172
+ await new Promise((resolve, reject) => {
1173
+ if (stopped) {
1174
+ resolve();
1175
+ return;
1176
+ }
1177
+ const es = createEventSource({
1178
+ url,
1179
+ headers,
1180
+ onMessage: ({ event, data, id }) => {
1181
+ if (id) lastEventId = id;
1182
+ if (gOpts.json) console.log(JSON.stringify({
1183
+ event,
1184
+ id,
1185
+ data
1186
+ }));
1187
+ else if (event === "message") console.log(formatMessageLine(data));
1188
+ else if (event === "heartbeat" && opts.verbose) process.stderr.write(`[heartbeat] ${data}\n`);
1189
+ },
1190
+ onDisconnect: () => resolve(),
1191
+ onScheduleReconnect: () => {
1192
+ es.close();
1193
+ resolve();
1194
+ }
1195
+ });
1196
+ const onStop = () => {
1197
+ es.close();
1198
+ resolve();
1199
+ };
1200
+ if (stopped) {
1201
+ onStop();
1202
+ return;
1203
+ }
1204
+ process.once("SIGINT", onStop);
1205
+ });
1206
+ backoff = 1;
1207
+ } catch (err) {
1208
+ if (stopped) break;
1209
+ if (opts.verbose) process.stderr.write(`Reconnecting in ${backoff}s...\n`);
1210
+ else process.stderr.write("Reconnecting...\n");
1211
+ await new Promise((r) => setTimeout(r, backoff * 1e3));
1212
+ backoff = Math.min(backoff * 2, 30);
1213
+ }
1214
+ } catch (err) {
1215
+ printError(formatError(err));
1216
+ process.exitCode = 1;
1217
+ }
1218
+ });
1219
+ }
1220
+ //#endregion
1221
+ //#region src/commands/webhook.ts
1222
+ function registerWebhook(program) {
1223
+ const webhook = program.command("webhook").description("Manage webhooks");
1224
+ webhook.command("create").description("Create a new webhook").requiredOption("--agent <id>", "Agent ID to attach webhook to").requiredOption("--url <url>", "Webhook target URL (must be https://)").action(async (opts) => {
1225
+ const gOpts = program.opts();
1226
+ if (!opts.url.startsWith("https://")) {
1227
+ printError("Webhook URL must start with https://");
1228
+ process.exitCode = 2;
1229
+ return;
1230
+ }
1231
+ try {
1232
+ const { client } = await createClient(gOpts.profile);
1233
+ const data = await client.post("/webhooks", {
1234
+ agent_id: opts.agent,
1235
+ url: opts.url
1236
+ });
1237
+ if (gOpts.json) printJson(data);
1238
+ else printTable([{
1239
+ ID: data.id,
1240
+ "Agent ID": data.agent_id,
1241
+ URL: data.url,
1242
+ Secret: data.secret,
1243
+ Active: data.active,
1244
+ Created: data.created_at
1245
+ }]);
1246
+ } catch (err) {
1247
+ printError(formatError(err));
1248
+ process.exitCode = 1;
1249
+ }
1250
+ });
1251
+ webhook.command("list").description("List webhooks").option("--agent <id>", "Filter by agent ID").option("--include-inactive", "Include inactive webhooks").action(async (opts) => {
1252
+ const gOpts = program.opts();
1253
+ try {
1254
+ const { client } = await createClient(gOpts.profile);
1255
+ const params = {};
1256
+ if (opts.agent) params.agent_id = opts.agent;
1257
+ if (opts.includeInactive) params.include_inactive = true;
1258
+ const data = await client.get("/webhooks", params);
1259
+ if (gOpts.json) printJson(data);
1260
+ else printTable((data.items ?? []).map((w) => ({
1261
+ ID: w.id,
1262
+ "Agent ID": w.agent_id,
1263
+ URL: w.url,
1264
+ Active: w.active,
1265
+ Created: w.created_at
1266
+ })));
1267
+ } catch (err) {
1268
+ printError(formatError(err));
1269
+ process.exitCode = 1;
1270
+ }
1271
+ });
1272
+ webhook.command("delete").description("Delete a webhook").argument("<webhook-id>", "Webhook ID").option("--yes", "Skip confirmation prompt").action(async (webhookId, opts) => {
1273
+ const gOpts = program.opts();
1274
+ try {
1275
+ if (!opts.yes && !gOpts.json) {
1276
+ if (!await promptConfirm(`Delete webhook ${webhookId}?`)) return;
1277
+ }
1278
+ const { client } = await createClient(gOpts.profile);
1279
+ await client.delete(`/webhooks/${webhookId}`);
1280
+ printMutationOk("Webhook deleted", gOpts.json);
1281
+ } catch (err) {
1282
+ printError(formatError(err));
1283
+ process.exitCode = 1;
1284
+ }
1285
+ });
1286
+ webhook.command("deactivate").description("Deactivate a webhook").argument("<webhook-id>", "Webhook ID").action(async (webhookId) => {
1287
+ const gOpts = program.opts();
1288
+ try {
1289
+ const { client } = await createClient(gOpts.profile);
1290
+ await client.patch(`/webhooks/${webhookId}`, { active: false });
1291
+ printMutationOk("Webhook deactivated", gOpts.json);
1292
+ } catch (err) {
1293
+ printError(formatError(err));
1294
+ process.exitCode = 1;
1295
+ }
1296
+ });
1297
+ webhook.command("activate").description("Activate a webhook").argument("<webhook-id>", "Webhook ID").action(async (webhookId) => {
1298
+ const gOpts = program.opts();
1299
+ try {
1300
+ const { client } = await createClient(gOpts.profile);
1301
+ await client.patch(`/webhooks/${webhookId}`, { active: true });
1302
+ printMutationOk("Webhook activated", gOpts.json);
1303
+ } catch (err) {
1304
+ printError(formatError(err));
1305
+ process.exitCode = 1;
1306
+ }
1307
+ });
1308
+ webhook.command("deliveries").description("List deliveries for a webhook").argument("<webhook-id>", "Webhook ID").option("--status <status>", "Filter by delivery status").option("--limit <n>", "Maximum number of deliveries").action(async (webhookId, opts) => {
1309
+ const gOpts = program.opts();
1310
+ try {
1311
+ const { client } = await createClient(gOpts.profile);
1312
+ const params = {};
1313
+ if (opts.status) params.status = opts.status;
1314
+ if (opts.limit) params.limit = opts.limit;
1315
+ const data = await client.get(`/webhooks/${webhookId}/deliveries`, params);
1316
+ if (gOpts.json) printJson(data);
1317
+ else printTable((data.items ?? []).map((d) => ({
1318
+ ID: d.id,
1319
+ "Message ID": d.message_id,
1320
+ Status: d.status,
1321
+ Attempts: d.attempts,
1322
+ "Last Error": d.last_error ?? "—",
1323
+ Created: d.created_at
1324
+ })));
1325
+ } catch (err) {
1326
+ printError(formatError(err));
1327
+ process.exitCode = 1;
1328
+ }
1329
+ });
1330
+ }
1331
+ //#endregion
1332
+ //#region src/main.ts
1333
+ const { version } = createRequire(import.meta.url)("../package.json");
1334
+ 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");
1335
+ registerAuth(program);
1336
+ registerRegister(program);
1337
+ registerOrg(program);
1338
+ registerAgent(program);
1339
+ registerAgentProfile(program);
1340
+ registerMessages(program);
1341
+ registerDiscover(program);
1342
+ registerWebhook(program);
1343
+ registerStream(program);
1344
+ program.parse();
1345
+ //#endregion
1346
+ export {};