@fulldivevr/workcord-cli 2.17.0 → 3.0.1

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.
@@ -1,649 +0,0 @@
1
- import { Option } from "commander";
2
- import { randomUUID } from "node:crypto";
3
- import { readFile, stat, writeFile } from "node:fs/promises";
4
- import { Writable } from "node:stream";
5
- import { actingUserId, resolveHermesKey } from "../auth.js";
6
- import { hermesBinary, hermesCall, hermesStream, hermesUpload } from "../hermes-client.js";
7
- import { assertArchiveSize, flavorSetWarnings, guardOwnership, rolloutBody, } from "../hermes-guards.js";
8
- import { getOrCreateGroup } from "./_group.js";
9
- export function agentRef(opts) {
10
- const refs = [opts.agent, opts.roomAgent, opts.marketplaceInstall].filter(Boolean);
11
- if (refs.length !== 1) {
12
- throw new Error("Pass exactly one of --agent <id>, --room-agent <id>, or --marketplace-install <id>.");
13
- }
14
- if (opts.roomAgent)
15
- return `workcord-room-agent:${opts.roomAgent}`;
16
- if (opts.marketplaceInstall)
17
- return `marketplace-install:${opts.marketplaceInstall}`;
18
- return opts.agent;
19
- }
20
- function agentFlags(cmd) {
21
- return cmd
22
- .option("--agent <id>", "custom agent id")
23
- .option("--room-agent <id>", "room agent id (resolved as workcord-room-agent:<id>)")
24
- .option("--marketplace-install <id>", "marketplace flavor install id or its room agent id")
25
- .option("--refresh", "re-reveal the tenant key instead of using the cached one")
26
- .option("--no-cache", "do not read or write the local tenant-key cache");
27
- }
28
- function jsonOption(cmd, name, description) {
29
- return cmd.option(`--${name} <json>`, description);
30
- }
31
- function parseJson(raw, flag) {
32
- if (raw === undefined)
33
- return undefined;
34
- try {
35
- return JSON.parse(raw);
36
- }
37
- catch {
38
- throw new Error(`${flag} must be valid JSON`);
39
- }
40
- }
41
- function out(data) {
42
- process.stdout.write(JSON.stringify(data) + "\n");
43
- }
44
- export function hermesChatTextBody(text) {
45
- return { parts: [{ type: "text", text }] };
46
- }
47
- async function credentials(program, opts) {
48
- return resolveHermesKey(agentRef(opts), {
49
- refresh: opts.refresh,
50
- cache: opts.cache,
51
- asUserId: actingUserId(program),
52
- });
53
- }
54
- function tenantPath(creds, suffix) {
55
- if (!creds.tenantId) {
56
- throw new Error("No tenantId available for this agent. Re-run with --refresh, or set WORKCORD_HERMES_TENANT_ID alongside WORKCORD_HERMES_KEY.");
57
- }
58
- return `/v1/tenants/${encodeURIComponent(creds.tenantId)}${suffix}`;
59
- }
60
- function seg(value) {
61
- return encodeURIComponent(value);
62
- }
63
- function segments(value) {
64
- return value
65
- .split("/")
66
- .filter((part) => part.length > 0)
67
- .map(seg)
68
- .join("/");
69
- }
70
- function collect(value, previous = []) {
71
- return [...previous, value];
72
- }
73
- async function currentFlavor(creds) {
74
- const res = (await hermesCall({ ...creds, path: "/v1/tenant-api/flavors/current" }));
75
- return res?.flavor ?? null;
76
- }
77
- async function resolveFlavorId(creds, explicit) {
78
- if (explicit)
79
- return explicit;
80
- const current = await currentFlavor(creds);
81
- if (!current?.id) {
82
- throw new Error("This tenant has no current flavor. Pass --flavor <flavorId>.");
83
- }
84
- return current.id;
85
- }
86
- async function downloadToFile(creds, path, outPath, query) {
87
- const chunks = [];
88
- const sink = new Writable({
89
- write(chunk, _encoding, done) {
90
- chunks.push(Buffer.from(chunk));
91
- done();
92
- },
93
- });
94
- await hermesBinary({ ...creds, path, query, out: sink });
95
- const bytes = Buffer.concat(chunks);
96
- await writeFile(outPath, bytes);
97
- return bytes.byteLength;
98
- }
99
- async function readArchive(archivePath) {
100
- const info = await stat(archivePath);
101
- assertArchiveSize(info.size, archivePath);
102
- const buffer = await readFile(archivePath);
103
- return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
104
- }
105
- export function registerHermes(program) {
106
- const group = getOrCreateGroup(program, "hermes", "Advanced Hermes Hub diagnostics and recovery for an agent tenant");
107
- agentFlags(group.command("key").description("Reveal (and cache) the Hermes Hub tenant key for an agent.")).action(async (opts) => {
108
- const creds = await credentials(program, opts);
109
- out({ key: creds.key, tenantId: creds.tenantId, hubBaseUrl: creds.hubBaseUrl });
110
- });
111
- agentFlags(group.command("whoami").description("Show the Hermes Hub tenant this agent's key belongs to.")).action(async (opts) => {
112
- const creds = await credentials(program, opts);
113
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/tenant" }));
114
- });
115
- agentFlags(group.command("runtime").description("Show the tenant runtime.")).action(async (opts) => {
116
- const creds = await credentials(program, opts);
117
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/tenant/runtime" }));
118
- });
119
- const agent = group.command("agent").description("Tenant agent lifecycle");
120
- agentFlags(agent.command("status").description("Show the tenant agent.")).action(async (opts) => {
121
- const creds = await credentials(program, opts);
122
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/tenant/agent" }));
123
- });
124
- for (const verb of ["start", "stop", "restart"]) {
125
- agentFlags(agent.command(verb).description(`${verb[0].toUpperCase()}${verb.slice(1)} the tenant agent.`)).action(async (opts) => {
126
- const creds = await credentials(program, opts);
127
- out(await hermesCall({ ...creds, method: "POST", path: `/v1/tenant-api/tenant/agent/${verb}` }));
128
- });
129
- }
130
- agentFlags(agent.command("card").description("Show the tenant agent card.")).action(async (opts) => {
131
- const creds = await credentials(program, opts);
132
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/tenant/agent-card" }));
133
- });
134
- const chat = group.command("chat").description("Tenant chats and chat messages");
135
- jsonOption(agentFlags(chat.command("list").description("List chats.")), "query", "query parameters as JSON").action(async (opts) => {
136
- const creds = await credentials(program, opts);
137
- out(await hermesCall({ ...creds, path: tenantPath(creds, "/chats"), query: parseJson(opts.query, "--query") }));
138
- });
139
- jsonOption(agentFlags(chat.command("create").description("Create a chat.")), "body", "request body as JSON").action(async (opts) => {
140
- const creds = await credentials(program, opts);
141
- out(await hermesCall({ ...creds, method: "POST", path: tenantPath(creds, "/chats"), body: parseJson(opts.body, "--body") ?? {} }));
142
- });
143
- agentFlags(chat.command("get").description("Get one chat."))
144
- .requiredOption("--chat <chatId>", "chat id")
145
- .action(async (opts) => {
146
- const creds = await credentials(program, opts);
147
- out(await hermesCall({ ...creds, path: tenantPath(creds, `/chats/${encodeURIComponent(opts.chat)}`) }));
148
- });
149
- jsonOption(agentFlags(chat.command("messages").description("List messages in a chat.")), "query", "query parameters as JSON")
150
- .requiredOption("--chat <chatId>", "chat id")
151
- .action(async (opts) => {
152
- const creds = await credentials(program, opts);
153
- out(await hermesCall({
154
- ...creds,
155
- path: tenantPath(creds, `/chats/${encodeURIComponent(opts.chat)}/messages`),
156
- query: parseJson(opts.query, "--query"),
157
- }));
158
- });
159
- jsonOption(agentFlags(chat.command("send").description("Send a message to a chat.")), "body", "request body as JSON (overrides --text)")
160
- .requiredOption("--chat <chatId>", "chat id")
161
- .option("--text <text>", "message text")
162
- .option("--idempotency-key <key>", "Idempotency-Key header (generated when omitted)")
163
- .action(async (opts) => {
164
- const creds = await credentials(program, opts);
165
- const body = parseJson(opts.body, "--body") ?? (opts.text !== undefined ? hermesChatTextBody(opts.text) : undefined);
166
- if (body === undefined) {
167
- throw new Error("One of --text or --body is required.");
168
- }
169
- out(await hermesCall({
170
- ...creds,
171
- method: "POST",
172
- path: tenantPath(creds, `/chats/${encodeURIComponent(opts.chat)}/messages`),
173
- headers: { "idempotency-key": opts.idempotencyKey ?? randomUUID() },
174
- body,
175
- }));
176
- });
177
- agentFlags(chat.command("message").description("Get one chat message."))
178
- .requiredOption("--message <messageId>", "chat message id")
179
- .option("--after-revision <n>", "only return when the revision is newer than this")
180
- .option("--wait-seconds <n>", "long-poll for up to this many seconds")
181
- .action(async (opts) => {
182
- const creds = await credentials(program, opts);
183
- out(await hermesCall({
184
- ...creds,
185
- path: tenantPath(creds, `/chat-messages/${encodeURIComponent(opts.message)}`),
186
- query: { afterRevision: opts.afterRevision, waitSeconds: opts.waitSeconds },
187
- }));
188
- });
189
- agentFlags(chat.command("stream").description("Stream a chat message's events (SSE)."))
190
- .requiredOption("--message <messageId>", "chat message id")
191
- .option("--last-event-id <id>", "resume from this event id")
192
- .action(async (opts) => {
193
- const creds = await credentials(program, opts);
194
- await hermesStream({
195
- ...creds,
196
- path: tenantPath(creds, `/chat-messages/${encodeURIComponent(opts.message)}/events`),
197
- lastEventId: opts.lastEventId,
198
- });
199
- });
200
- agentFlags(chat.command("stop").description("Stop a chat message."))
201
- .requiredOption("--message <messageId>", "chat message id")
202
- .action(async (opts) => {
203
- const creds = await credentials(program, opts);
204
- out(await hermesCall({ ...creds, method: "POST", path: tenantPath(creds, `/chat-messages/${encodeURIComponent(opts.message)}/stop`) }));
205
- });
206
- jsonOption(agentFlags(chat.command("approve").description("Answer a chat message's approval request.")), "body", "request body as JSON")
207
- .requiredOption("--message <messageId>", "chat message id")
208
- .action(async (opts) => {
209
- const creds = await credentials(program, opts);
210
- out(await hermesCall({
211
- ...creds,
212
- method: "POST",
213
- path: tenantPath(creds, `/chat-messages/${encodeURIComponent(opts.message)}/approval`),
214
- body: parseJson(opts.body, "--body") ?? {},
215
- }));
216
- });
217
- const run = group.command("run").description("Tenant runs");
218
- jsonOption(agentFlags(run.command("create").description("Create a run.")), "body", "request body as JSON").action(async (opts) => {
219
- const creds = await credentials(program, opts);
220
- out(await hermesCall({ ...creds, method: "POST", path: tenantPath(creds, "/runs"), body: parseJson(opts.body, "--body") ?? {} }));
221
- });
222
- agentFlags(run.command("get").description("Get one run."))
223
- .requiredOption("--run <runId>", "run id")
224
- .action(async (opts) => {
225
- const creds = await credentials(program, opts);
226
- out(await hermesCall({ ...creds, path: tenantPath(creds, `/runs/${encodeURIComponent(opts.run)}`) }));
227
- });
228
- agentFlags(run.command("stream").description("Stream a run's events (SSE)."))
229
- .requiredOption("--run <runId>", "run id")
230
- .option("--last-event-id <id>", "resume from this event id")
231
- .action(async (opts) => {
232
- const creds = await credentials(program, opts);
233
- await hermesStream({ ...creds, path: tenantPath(creds, `/runs/${encodeURIComponent(opts.run)}/events`), lastEventId: opts.lastEventId });
234
- });
235
- agentFlags(run.command("stop").description("Stop a run."))
236
- .requiredOption("--run <runId>", "run id")
237
- .action(async (opts) => {
238
- const creds = await credentials(program, opts);
239
- out(await hermesCall({ ...creds, method: "POST", path: tenantPath(creds, `/runs/${encodeURIComponent(opts.run)}/stop`) }));
240
- });
241
- jsonOption(agentFlags(run.command("approve").description("Answer a run's approval request.")), "body", "request body as JSON")
242
- .requiredOption("--run <runId>", "run id")
243
- .action(async (opts) => {
244
- const creds = await credentials(program, opts);
245
- out(await hermesCall({
246
- ...creds,
247
- method: "POST",
248
- path: tenantPath(creds, `/runs/${encodeURIComponent(opts.run)}/approval`),
249
- body: parseJson(opts.body, "--body") ?? {},
250
- }));
251
- });
252
- const files = group.command("files").description("Tenant-visible files");
253
- agentFlags(files.command("ls").description("List files under the tenant's virtual roots."))
254
- .option("--path <path>", "directory path (empty means root)")
255
- .action(async (opts) => {
256
- const creds = await credentials(program, opts);
257
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/tenant/files", query: { path: opts.path } }));
258
- });
259
- agentFlags(files.command("cat").description("Read one tenant-visible file."))
260
- .requiredOption("--path <path>", "file path")
261
- .action(async (opts) => {
262
- const creds = await credentials(program, opts);
263
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/tenant/files/content", query: { path: opts.path } }));
264
- });
265
- agentFlags(files.command("download").description("Download one tenant-visible file (raw bytes)."))
266
- .requiredOption("--path <path>", "file path")
267
- .option("--out <file>", "write to this file instead of stdout")
268
- .action(async (opts) => {
269
- const creds = await credentials(program, opts);
270
- const path = "/v1/tenant-api/tenant/files/download";
271
- const query = { path: opts.path };
272
- if (opts.out) {
273
- out({ path: opts.out, bytes: await downloadToFile(creds, path, opts.out, query) });
274
- return;
275
- }
276
- await hermesBinary({ ...creds, path, query });
277
- });
278
- const share = group.command("share").description("Share-group filesystem");
279
- agentFlags(share.command("groups").description("List share groups visible to this tenant.")).action(async (opts) => {
280
- const creds = await credentials(program, opts);
281
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/tenant/shares" }));
282
- });
283
- const shareGroupFlags = (cmd) => agentFlags(cmd).requiredOption("--group <groupId>", "share group id");
284
- const sharePath = (groupId, action) => `/v1/tenant-api/tenant/shares/${seg(groupId)}/${action}`;
285
- shareGroupFlags(share.command("ls").description("List one share-group directory."))
286
- .option("--path <path>", "path relative to the share root")
287
- .action(async (opts) => {
288
- const creds = await credentials(program, opts);
289
- out(await hermesCall({ ...creds, path: sharePath(opts.group, "list"), query: { path: opts.path } }));
290
- });
291
- shareGroupFlags(share.command("read").description("Read one file from a share group."))
292
- .requiredOption("--path <path>", "path relative to the share root")
293
- .option("--offset <n>", "leading lines to skip")
294
- .option("--limit <n>", "maximum lines to return")
295
- .action(async (opts) => {
296
- const creds = await credentials(program, opts);
297
- out(await hermesCall({
298
- ...creds,
299
- path: sharePath(opts.group, "read"),
300
- query: { path: opts.path, offset: opts.offset, limit: opts.limit },
301
- }));
302
- });
303
- shareGroupFlags(share.command("stat").description("Stat one share-group path."))
304
- .requiredOption("--path <path>", "path relative to the share root")
305
- .action(async (opts) => {
306
- const creds = await credentials(program, opts);
307
- out(await hermesCall({ ...creds, path: sharePath(opts.group, "stat"), query: { path: opts.path } }));
308
- });
309
- shareGroupFlags(share.command("grep").description("Search share-group file contents."))
310
- .requiredOption("--pattern <regexp>", "Go regular expression")
311
- .option("--path <path>", "path relative to the share root")
312
- .option("--ignore-case", "case-insensitive match")
313
- .option("--max <n>", "maximum results")
314
- .action(async (opts) => {
315
- const creds = await credentials(program, opts);
316
- out(await hermesCall({
317
- ...creds,
318
- path: sharePath(opts.group, "grep"),
319
- query: { pattern: opts.pattern, path: opts.path, ignore_case: opts.ignoreCase, max: opts.max },
320
- }));
321
- });
322
- shareGroupFlags(share.command("find").description("Find share-group paths by base name."))
323
- .option("--name <glob>", "glob matched against each entry's base name")
324
- .option("--path <path>", "path relative to the share root")
325
- .option("--max <n>", "maximum results")
326
- .action(async (opts) => {
327
- const creds = await credentials(program, opts);
328
- out(await hermesCall({
329
- ...creds,
330
- path: sharePath(opts.group, "find"),
331
- query: { name: opts.name, path: opts.path, max: opts.max },
332
- }));
333
- });
334
- shareGroupFlags(share.command("write").description("Write one file into a share group."))
335
- .requiredOption("--path <path>", "path relative to the share root")
336
- .option("--content <text>", "file contents", "")
337
- .addOption(new Option("--mode <mode>", "write mode").choices(["overwrite", "append"]))
338
- .action(async (opts) => {
339
- const creds = await credentials(program, opts);
340
- out(await hermesCall({
341
- ...creds,
342
- method: "POST",
343
- path: sharePath(opts.group, "write"),
344
- body: { path: opts.path, content: opts.content, ...(opts.mode ? { mode: opts.mode } : {}) },
345
- }));
346
- });
347
- shareGroupFlags(share.command("mkdir").description("Create a share-group directory."))
348
- .requiredOption("--path <path>", "directory path relative to the share root")
349
- .action(async (opts) => {
350
- const creds = await credentials(program, opts);
351
- out(await hermesCall({ ...creds, method: "POST", path: sharePath(opts.group, "mkdir"), body: { path: opts.path } }));
352
- });
353
- shareGroupFlags(share.command("move").description("Move a share-group path."))
354
- .requiredOption("--from <path>", "source path relative to the share root")
355
- .requiredOption("--to <path>", "destination path relative to the share root")
356
- .action(async (opts) => {
357
- const creds = await credentials(program, opts);
358
- out(await hermesCall({ ...creds, method: "POST", path: sharePath(opts.group, "move"), body: { from: opts.from, to: opts.to } }));
359
- });
360
- shareGroupFlags(share.command("rm").description("Remove a share-group path."))
361
- .requiredOption("--path <path>", "path relative to the share root")
362
- .option("--recursive", "required to remove a non-empty directory")
363
- .action(async (opts) => {
364
- const creds = await credentials(program, opts);
365
- out(await hermesCall({
366
- ...creds,
367
- method: "POST",
368
- path: sharePath(opts.group, "remove"),
369
- body: { path: opts.path, recursive: Boolean(opts.recursive) },
370
- }));
371
- });
372
- const skills = group.command("skills").description("Skills installed on this tenant");
373
- agentFlags(skills.command("list").description("List this tenant's skills.")).action(async (opts) => {
374
- const creds = await credentials(program, opts);
375
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/tenant/skills" }));
376
- });
377
- for (const [verb, enabled] of [["enable", true], ["disable", false]]) {
378
- agentFlags(skills.command(verb).description(`${verb[0].toUpperCase()}${verb.slice(1)} one skill on this tenant.`))
379
- .requiredOption("--skill <name>", "skill name")
380
- .action(async (opts) => {
381
- const creds = await credentials(program, opts);
382
- out(await hermesCall({
383
- ...creds,
384
- method: "PUT",
385
- path: `/v1/tenant-api/tenant/skills/${seg(opts.skill)}/state`,
386
- body: { enabled },
387
- }));
388
- });
389
- }
390
- agentFlags(skills.command("files").description("List one skill's files."))
391
- .requiredOption("--category <category>", "skill category")
392
- .requiredOption("--skill <skill>", "skill name")
393
- .action(async (opts) => {
394
- const creds = await credentials(program, opts);
395
- out(await hermesCall({ ...creds, path: `/v1/tenant-api/tenant/skills/${seg(opts.category)}/${seg(opts.skill)}/files` }));
396
- });
397
- agentFlags(skills.command("cat").description("Read one skill file."))
398
- .requiredOption("--path <skillPath>", "slash-delimited path relative to $HERMES_HOME/skills")
399
- .action(async (opts) => {
400
- const creds = await credentials(program, opts);
401
- out(await hermesCall({ ...creds, path: `/v1/tenant-api/tenant/skill-files/${segments(opts.path)}` }));
402
- });
403
- const flavor = group.command("flavor").description("Flavor marketplace and this tenant's flavor pin");
404
- agentFlags(flavor.command("current").description("Show this tenant's current flavor pin.")).action(async (opts) => {
405
- const creds = await credentials(program, opts);
406
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/flavors/current" }));
407
- });
408
- agentFlags(flavor.command("catalog").description("List the flavor catalog.")).action(async (opts) => {
409
- const creds = await credentials(program, opts);
410
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/flavors" }));
411
- });
412
- agentFlags(flavor.command("get").description("Get one catalog flavor."))
413
- .requiredOption("--flavor <flavorId>", "flavor id")
414
- .action(async (opts) => {
415
- const creds = await credentials(program, opts);
416
- out(await hermesCall({ ...creds, path: `/v1/tenant-api/flavors/${seg(opts.flavor)}` }));
417
- });
418
- agentFlags(flavor.command("set").description("Re-pin this tenant to another version of its flavor."))
419
- .requiredOption("--flavor-version <version>", "flavor version")
420
- .option("--flavor <flavorId>", "flavor id (defaults to the current pin)")
421
- .option("--force", "proceed despite a downgrade or deprecated target")
422
- .action(async (opts) => {
423
- const creds = await credentials(program, opts);
424
- const current = await currentFlavor(creds);
425
- const flavorId = opts.flavor ?? current?.id;
426
- if (!flavorId) {
427
- throw new Error("This tenant has no current flavor. Pass --flavor <flavorId>.");
428
- }
429
- const entry = (await hermesCall({ ...creds, path: `/v1/tenant-api/flavors/${seg(flavorId)}` }));
430
- const warnings = flavorSetWarnings(flavorId === current?.id ? current?.version : undefined, opts.flavorVersion, entry?.versions ?? []);
431
- if (warnings.length > 0) {
432
- for (const warning of warnings) {
433
- process.stderr.write(`warning: ${warning}\n`);
434
- }
435
- if (!opts.force) {
436
- throw new Error(`Refusing to re-pin ${flavorId} to ${opts.flavorVersion}: ${warnings.join("; ")}. Re-run with --force to proceed.`);
437
- }
438
- }
439
- out(await hermesCall({
440
- ...creds,
441
- method: "PUT",
442
- path: "/v1/tenant-api/flavors/current",
443
- body: { flavorId, flavorVersion: opts.flavorVersion },
444
- }));
445
- });
446
- agentFlags(flavor.command("subscribe").description("Set this tenant's flavor update policy."))
447
- .addOption(new Option("--policy <policy>", "update policy").choices(["pinned", "stable", "canary", "auto_patch"]).makeOptionMandatory())
448
- .action(async (opts) => {
449
- const creds = await credentials(program, opts);
450
- out(await hermesCall({
451
- ...creds,
452
- method: "PUT",
453
- path: "/v1/tenant-api/flavors/current/subscription",
454
- body: { updatePolicy: opts.policy },
455
- }));
456
- });
457
- agentFlags(flavor.command("checkout").description("Download one flavor version's source tarball."))
458
- .requiredOption("--flavor-version <version>", "flavor version")
459
- .requiredOption("--out <file>", "write the gzipped tarball here")
460
- .option("--flavor <flavorId>", "flavor id (defaults to the current pin)")
461
- .action(async (opts) => {
462
- const creds = await credentials(program, opts);
463
- const flavorId = await resolveFlavorId(creds, opts.flavor);
464
- const bytes = await downloadToFile(creds, `/v1/tenant-api/flavors/${seg(flavorId)}/versions/${seg(opts.flavorVersion)}/source`, opts.out);
465
- out({ path: opts.out, bytes });
466
- });
467
- agentFlags(flavor.command("publish").description("Publish a new version of an owned flavor."))
468
- .requiredOption("--archive <file>", "gzipped flavor package built beforehand")
469
- .option("--flavor <flavorId>", "flavor id (defaults to the current pin)")
470
- .action(async (opts) => {
471
- const creds = await credentials(program, opts);
472
- const flavorId = await resolveFlavorId(creds, opts.flavor);
473
- const data = await readArchive(opts.archive);
474
- out(await guardOwnership("flavor", flavorId, creds.tenantId, () => hermesUpload({
475
- ...creds,
476
- path: `/v1/tenant-api/flavors/${seg(flavorId)}/versions`,
477
- contentType: "application/gzip",
478
- data,
479
- })));
480
- });
481
- agentFlags(flavor.command("rollout").description("Roll one flavor version out to tenants pinned to that flavor."))
482
- .requiredOption("--flavor-version <version>", "flavor version")
483
- .option("--flavor <flavorId>", "flavor id (defaults to the current pin)")
484
- .option("--all-matching", "target every tenant pinned to this flavor id, ignoring their update policy (requires --force)")
485
- .option("--canary", "restrict the automatic rollout to the canary cohort")
486
- .option("--tenant <tenantId>", "target one tenant (repeatable)", collect)
487
- .option("--force", "accept the blast radius of --all-matching")
488
- .action(async (opts) => {
489
- const body = rolloutBody(opts);
490
- const creds = await credentials(program, opts);
491
- const flavorId = await resolveFlavorId(creds, opts.flavor);
492
- const result = (await guardOwnership("flavor", flavorId, creds.tenantId, () => hermesCall({
493
- ...creds,
494
- method: "POST",
495
- path: `/v1/tenant-api/flavors/${seg(flavorId)}/versions/${seg(opts.flavorVersion)}/rollout`,
496
- body,
497
- })));
498
- process.stderr.write(`rollout affected ${String(result?.updatedCount ?? 0)} of ${String(result?.matchedCount ?? 0)} matched tenants\n`);
499
- out(result);
500
- });
501
- const skill = group.command("skill").description("Skill marketplace (owner-gated for publish)");
502
- agentFlags(skill.command("catalog").description("List the skill catalog.")).action(async (opts) => {
503
- const creds = await credentials(program, opts);
504
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/skills" }));
505
- });
506
- agentFlags(skill.command("get").description("Get one catalog skill."))
507
- .requiredOption("--skill <skillId>", "skill id")
508
- .action(async (opts) => {
509
- const creds = await credentials(program, opts);
510
- out(await hermesCall({ ...creds, path: `/v1/tenant-api/skills/${seg(opts.skill)}` }));
511
- });
512
- agentFlags(skill.command("checkout").description("Download one skill version's source tarball."))
513
- .requiredOption("--skill <skillId>", "skill id")
514
- .requiredOption("--skill-version <version>", "skill version")
515
- .requiredOption("--out <file>", "write the gzipped tarball here")
516
- .action(async (opts) => {
517
- const creds = await credentials(program, opts);
518
- const bytes = await downloadToFile(creds, `/v1/tenant-api/skills/${seg(opts.skill)}/versions/${seg(opts.skillVersion)}/source`, opts.out);
519
- out({ path: opts.out, bytes });
520
- });
521
- agentFlags(skill.command("publish").description("Publish a new version of an owned skill."))
522
- .requiredOption("--skill <skillId>", "skill id")
523
- .requiredOption("--archive <file>", "gzipped skill package built beforehand")
524
- .action(async (opts) => {
525
- const creds = await credentials(program, opts);
526
- const data = await readArchive(opts.archive);
527
- out(await guardOwnership("skill", opts.skill, creds.tenantId, () => hermesUpload({
528
- ...creds,
529
- path: `/v1/tenant-api/skills/${seg(opts.skill)}/versions`,
530
- contentType: "application/gzip",
531
- data,
532
- })));
533
- });
534
- const evals = group.command("eval").description("Flavor eval suites and runs");
535
- agentFlags(evals.command("suites").description("List eval suites for this tenant's flavor.")).action(async (opts) => {
536
- const creds = await credentials(program, opts);
537
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/tenant/flavor/eval-suites" }));
538
- });
539
- agentFlags(evals.command("run").description("Start an eval run."))
540
- .requiredOption("--suite <suiteId>", "eval suite id")
541
- .action(async (opts) => {
542
- const creds = await credentials(program, opts);
543
- out(await hermesCall({ ...creds, method: "POST", path: "/v1/tenant-api/tenant/flavor/eval-runs", body: { suiteId: opts.suite } }));
544
- });
545
- agentFlags(evals.command("list").description("List this tenant's eval runs."))
546
- .option("--suite <suiteId>", "filter by suite id")
547
- .option("--status <status>", "filter by status")
548
- .option("--limit <n>", "maximum runs to return")
549
- .action(async (opts) => {
550
- const creds = await credentials(program, opts);
551
- out(await hermesCall({
552
- ...creds,
553
- path: "/v1/tenant-api/tenant/eval-runs",
554
- query: { suite_id: opts.suite, status: opts.status, limit: opts.limit },
555
- }));
556
- });
557
- agentFlags(evals.command("get").description("Get one eval run."))
558
- .requiredOption("--run <evalRunId>", "eval run id")
559
- .action(async (opts) => {
560
- const creds = await credentials(program, opts);
561
- out(await hermesCall({ ...creds, path: `/v1/tenant-api/tenant/eval-runs/${seg(opts.run)}` }));
562
- });
563
- agentFlags(evals.command("cancel").description("Cancel one eval run."))
564
- .requiredOption("--run <evalRunId>", "eval run id")
565
- .action(async (opts) => {
566
- const creds = await credentials(program, opts);
567
- out(await hermesCall({ ...creds, method: "POST", path: `/v1/tenant-api/tenant/eval-runs/${seg(opts.run)}:cancel` }));
568
- });
569
- const peers = group.command("peers").description("A2A peers");
570
- agentFlags(peers.command("list").description("List this tenant's A2A peers.")).action(async (opts) => {
571
- const creds = await credentials(program, opts);
572
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/peers" }));
573
- });
574
- agentFlags(peers.command("get").description("Get one peer."))
575
- .requiredOption("--peer <peerId>", "peer id")
576
- .action(async (opts) => {
577
- const creds = await credentials(program, opts);
578
- out(await hermesCall({ ...creds, path: `/v1/tenant-api/peers/${seg(opts.peer)}` }));
579
- });
580
- jsonOption(agentFlags(peers.command("call").description("Send a message to a peer.")), "body", "request body as JSON (overrides the other flags)")
581
- .requiredOption("--peer <peerId>", "peer id")
582
- .option("--message <text>", "message text")
583
- .option("--context <contextId>", "conversation context id")
584
- .option("--async", "return before the peer replies")
585
- .option("--wait-seconds <n>", "seconds to wait for a reply")
586
- .option("--idempotency-key <key>", "idempotency key (generated when omitted)")
587
- .action(async (opts) => {
588
- const creds = await credentials(program, opts);
589
- const body = parseJson(opts.body, "--body") ??
590
- (opts.message !== undefined
591
- ? {
592
- message: opts.message,
593
- ...(opts.context ? { contextId: opts.context } : {}),
594
- ...(opts.async ? { async: true } : {}),
595
- ...(opts.waitSeconds ? { waitSeconds: Number(opts.waitSeconds) } : {}),
596
- idempotencyKey: opts.idempotencyKey ?? randomUUID(),
597
- }
598
- : undefined);
599
- if (body === undefined) {
600
- throw new Error("One of --message or --body is required.");
601
- }
602
- out(await hermesCall({ ...creds, method: "POST", path: `/v1/tenant-api/peers/${seg(opts.peer)}/messages`, body }));
603
- });
604
- jsonOption(agentFlags(peers.command("find").description("Find peers by query and capabilities.")), "body", "request body as JSON (overrides the other flags)")
605
- .option("--query <text>", "free-text query")
606
- .option("--required-capability <name>", "capability the peer must have (repeatable)", collect)
607
- .option("--exclude-capability <name>", "capability the peer must not have (repeatable)", collect)
608
- .option("--limit <n>", "maximum peers to return")
609
- .action(async (opts) => {
610
- const creds = await credentials(program, opts);
611
- const body = parseJson(opts.body, "--body") ?? {
612
- ...(opts.query ? { query: opts.query } : {}),
613
- ...(opts.requiredCapability?.length ? { requiredCapabilities: opts.requiredCapability } : {}),
614
- ...(opts.excludeCapability?.length ? { excludeCapabilities: opts.excludeCapability } : {}),
615
- ...(opts.limit ? { limit: Number(opts.limit) } : {}),
616
- };
617
- out(await hermesCall({ ...creds, method: "POST", path: "/v1/tenant-api/peers:find", body }));
618
- });
619
- const conversations = group.command("conversations").description("A2A conversations");
620
- agentFlags(conversations.command("list").description("List conversations.")).action(async (opts) => {
621
- const creds = await credentials(program, opts);
622
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/conversations" }));
623
- });
624
- agentFlags(conversations.command("get").description("Get one conversation."))
625
- .requiredOption("--context <contextId>", "conversation context id")
626
- .action(async (opts) => {
627
- const creds = await credentials(program, opts);
628
- out(await hermesCall({ ...creds, path: `/v1/tenant-api/conversations/${seg(opts.context)}` }));
629
- });
630
- agentFlags(conversations.command("messages").description("List one conversation's messages."))
631
- .requiredOption("--context <contextId>", "conversation context id")
632
- .action(async (opts) => {
633
- const creds = await credentials(program, opts);
634
- out(await hermesCall({ ...creds, path: `/v1/tenant-api/conversations/${seg(opts.context)}/messages` }));
635
- });
636
- const sessions = group.command("sessions").description("Tenant agent sessions");
637
- agentFlags(sessions.command("list").description("List this tenant's sessions."))
638
- .option("--limit <n>", "maximum sessions to return")
639
- .option("--source <source>", "filter by source")
640
- .action(async (opts) => {
641
- const creds = await credentials(program, opts);
642
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/tenant/sessions", query: { limit: opts.limit, source: opts.source } }));
643
- });
644
- agentFlags(group.command("artifacts").description("List this tenant's artifacts.")).action(async (opts) => {
645
- const creds = await credentials(program, opts);
646
- out(await hermesCall({ ...creds, path: "/v1/tenant-api/tenant/artifacts" }));
647
- });
648
- }
649
- //# sourceMappingURL=hermes.js.map