@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/LICENSE +291 -0
- package/README.md +87 -0
- package/bin/rine.js +2 -0
- package/dist/main.js +1346 -0
- package/package.json +52 -0
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 {};
|